From 27ebf0450b8dc7a4db399760767cac5f8b95c0a8 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Thu, 20 Feb 2025 20:04:44 -0500 Subject: [PATCH 01/16] Before running with prettier --- eslint.config.mjs | 62 ++ package.json | 11 +- packages/core/cairo/package.json | 2 +- packages/core/cairo/src/build-generic.ts | 47 +- packages/core/cairo/src/erc1155.ts | 258 ++++--- packages/core/cairo/src/erc20.test.ts | 203 ++--- packages/core/cairo/src/erc20.ts | 203 ++--- packages/core/cairo/src/erc721.test.ts | 210 +++--- packages/core/cairo/src/generate/sources.ts | 132 ++-- packages/core/cairo/src/print.ts | 256 ++++--- .../cairo/src/scripts/update-scarb-project.ts | 48 +- packages/core/cairo/src/test.ts | 95 ++- .../cairo/src/utils/convert-strings.test.ts | 126 ++-- packages/core/cairo/src/vesting.ts | 171 +++-- packages/core/solidity/src/build-generic.ts | 47 +- packages/core/solidity/src/contract.ts | 82 +- packages/core/solidity/src/erc20.ts | 138 ++-- .../solidity/src/generate/alternatives.ts | 6 +- packages/core/solidity/src/governor.ts | 356 +++++---- packages/core/solidity/src/print.ts | 202 +++-- .../core/solidity/src/utils/map-values.ts | 3 +- packages/core/solidity/src/zip-hardhat.ts | 92 ++- packages/ui/api/ai.ts | 98 ++- packages/ui/src/cairo/highlightjs.ts | 6 +- packages/ui/src/cairo/inject-hyperlinks.ts | 31 +- packages/ui/src/common/error-tooltip.ts | 12 +- packages/ui/src/common/post-message.ts | 30 +- packages/ui/src/common/resize-to-fit.ts | 26 +- packages/ui/src/main.ts | 74 +- packages/ui/src/solidity/highlightjs.ts | 6 +- yarn.lock | 707 +++++++++++++++++- 31 files changed, 2462 insertions(+), 1278 deletions(-) create mode 100644 eslint.config.mjs diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..7a17e8293 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,62 @@ +// @ts-check + +import eslint from "@eslint/js"; +import prettierRecommended from "eslint-plugin-prettier/recommended"; +import unicornPlugin from "eslint-plugin-unicorn"; +import typescriptEslint from "typescript-eslint"; + +export default typescriptEslint.config( + eslint.configs.recommended, + typescriptEslint.configs.strict, + prettierRecommended, + { + plugins: { + unicorn: unicornPlugin, + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-non-null-assertion": "warn", + }, + }, + { + ignores: [ + "node_modules/", + "*.sol", + "packages/*/node_modules/", + "packages/**/dist/", + "packages/**/build/", + ], + }, + { + files: ["**/*.config.js"], + languageOptions: { + sourceType: "commonjs", + }, + }, + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + }, + rules: { + "@typescript-eslint/*": "off", + }, + }, + { + files: ["**/*.mjs", "**/*.js"], + languageOptions: { + sourceType: "commonjs", + globals: { + process: "readonly", + global: "readonly", + console: "readonly", + }, + }, + rules: { + "@typescript-eslint/no-require-imports": "off", + }, + } +); diff --git a/package.json b/package.json index c6c359d09..4b9b661e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "root", "private": true, "scripts": { - "prepare": "wsrun -m prepare" + "prepare": "wsrun -m prepare", + "lint": "eslint" }, "workspaces": { "packages": [ @@ -15,6 +16,14 @@ ] }, "devDependencies": { + "@eslint/js": "^9.20.0", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-unicorn": "^57.0.0", + "prettier": "^3.5.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", "wsrun": "^5.2.4" } } diff --git a/packages/core/cairo/package.json b/packages/core/cairo/package.json index 3e61b4fda..850e46d06 100644 --- a/packages/core/cairo/package.json +++ b/packages/core/cairo/package.json @@ -30,4 +30,4 @@ "typescript": "^5.0.0", "semver": "^7.6.0" } -} +} \ No newline at end of file diff --git a/packages/core/cairo/src/build-generic.ts b/packages/core/cairo/src/build-generic.ts index fc04c30dc..abfab3d16 100644 --- a/packages/core/cairo/src/build-generic.ts +++ b/packages/core/cairo/src/build-generic.ts @@ -1,48 +1,49 @@ -import { ERC20Options, buildERC20 } from './erc20'; -import { ERC721Options, buildERC721 } from './erc721'; -import { ERC1155Options, buildERC1155 } from './erc1155'; -import { CustomOptions, buildCustom } from './custom'; -import { AccountOptions, buildAccount } from './account'; -import { GovernorOptions, buildGovernor } from './governor'; -import { VestingOptions, buildVesting } from './vesting'; +import { ERC20Options, buildERC20 } from "./erc20"; +import { ERC721Options, buildERC721 } from "./erc721"; +import { ERC1155Options, buildERC1155 } from "./erc1155"; +import { CustomOptions, buildCustom } from "./custom"; +import { AccountOptions, buildAccount } from "./account"; +import { GovernorOptions, buildGovernor } from "./governor"; +import { VestingOptions, buildVesting } from "./vesting"; export interface KindedOptions { - ERC20: { kind: 'ERC20' } & ERC20Options; - ERC721: { kind: 'ERC721' } & ERC721Options; - ERC1155: { kind: 'ERC1155' } & ERC1155Options; - Account: { kind: 'Account' } & AccountOptions; - Governor: { kind: 'Governor' } & GovernorOptions; - Vesting: { kind: 'Vesting' } & VestingOptions; - Custom: { kind: 'Custom' } & CustomOptions; + ERC20: { kind: "ERC20" } & ERC20Options; + ERC721: { kind: "ERC721" } & ERC721Options; + ERC1155: { kind: "ERC1155" } & ERC1155Options; + Account: { kind: "Account" } & AccountOptions; + Governor: { kind: "Governor" } & GovernorOptions; + Vesting: { kind: "Vesting" } & VestingOptions; + Custom: { kind: "Custom" } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions) { switch (opts.kind) { - case 'ERC20': + case "ERC20": return buildERC20(opts); - case 'ERC721': + case "ERC721": return buildERC721(opts); - case 'ERC1155': + case "ERC1155": return buildERC1155(opts); - case 'Account': + case "Account": return buildAccount(opts); - case 'Governor': + case "Governor": return buildGovernor(opts); - case 'Vesting': + case "Vesting": return buildVesting(opts); - case 'Custom': + case "Custom": return buildCustom(opts); - default: + default: { const _: never = opts; - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); + } } } diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index d17cb8f3c..7d5eda31a 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -1,21 +1,33 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { addSRC5Component } from './common-components'; -import { externalTrait } from './external-trait'; -import { toByteArray } from './utils/convert-strings'; -import { RoyaltyInfoOptions, setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { addSRC5Component } from "./common-components"; +import { externalTrait } from "./external-trait"; +import { toByteArray } from "./utils/convert-strings"; +import { + RoyaltyInfoOptions, + setRoyaltyInfo, + defaults as royaltyInfoDefaults, +} from "./set-royalty-info"; export const defaults: Required = { - name: 'MyToken', - baseUri: '', + name: "MyToken", + baseUri: "", burnable: false, pausable: false, mintable: false, @@ -23,7 +35,7 @@ export const defaults: Required = { royaltyInfo: royaltyInfoDefaults, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC1155(opts: ERC1155Options = defaults): string { @@ -52,8 +64,16 @@ function withDefaults(opts: ERC1155Options): Required { }; } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.updatableUri !== false || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; +export function isAccessControlRequired( + opts: Partial +): boolean { + return ( + opts.mintable === true || + opts.pausable === true || + opts.updatableUri !== false || + opts.upgradeable === true || + opts.royaltyInfo?.enabled === true + ); } export function buildERC1155(opts: ERC1155Options): Contract { @@ -84,7 +104,7 @@ export function buildERC1155(opts: ERC1155Options): Contract { setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); - + addHooks(c, allOpts); return c; @@ -94,92 +114,112 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable; if (usesCustomHooks) { const hooksTrait = { - name: 'ERC1155HooksImpl', - of: 'ERC1155Component::ERC1155HooksTrait', + name: "ERC1155HooksImpl", + of: "ERC1155Component::ERC1155HooksTrait", tags: [], priority: 1, }; c.addImplementedTrait(hooksTrait); - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); c.addFunction(hooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: `ERC1155Component::ComponentState` }, - { name: 'from', type: 'ContractAddress' }, - { name: 'to', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, + { + name: "ref self", + type: `ERC1155Component::ComponentState`, + }, + { name: "from", type: "ContractAddress" }, + { name: "to", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, ], code: [ - 'let contract_state = self.get_contract()', - 'contract_state.pausable.assert_not_paused()', + "let contract_state = self.get_contract()", + "contract_state.pausable.assert_not_paused()", ], }); } else { - c.addUseClause('openzeppelin::token::erc1155', 'ERC1155HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc1155", "ERC1155HooksEmptyImpl"); } } function addERC1155Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC1155Component, { - name: 'ERC1155MixinImpl', - value: 'ERC1155Component::ERC1155MixinImpl', + name: "ERC1155MixinImpl", + value: "ERC1155Component::ERC1155MixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } function addBase(c: ContractBuilder, baseUri: string) { - c.addComponent( - components.ERC1155Component, - [ - baseUri, - ], - true, - ); + c.addComponent(components.ERC1155Component, [baseUri], true); } function addBurnable(c: ContractBuilder) { - c.addUseClause('starknet', 'ContractAddress'); - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("starknet", "ContractAddress"); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); c.addFunction(externalTrait, functions.batch_burn); c.addFunction(externalTrait, functions.batchBurn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); - requireAccessControl(c, externalTrait, functions.batch_mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.mint, + access, + "MINTER", + "minter" + ); + requireAccessControl( + c, + externalTrait, + functions.batch_mint, + access, + "MINTER", + "minter" + ); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. c.addFunction(externalTrait, functions.batchMint); } function addSetBaseUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, externalTrait, functions.set_base_uri, access, 'URI_SETTER', 'uri_setter'); + requireAccessControl( + c, + externalTrait, + functions.set_base_uri, + access, + "URI_SETTER", + "uri_setter" + ); // Camel case version of set_base_uri. Access control is already set on set_base_uri. c.addFunction(externalTrait, functions.setBaseUri); } -const components = defineComponents( { +const components = defineComponents({ ERC1155Component: { - path: 'openzeppelin::token::erc1155', + path: "openzeppelin::token::erc1155", substorage: { - name: 'erc1155', - type: 'ERC1155Component::Storage', + name: "erc1155", + type: "ERC1155Component::Storage", }, event: { - name: 'ERC1155Event', - type: 'ERC1155Component::Event', + name: "ERC1155Event", + type: "ERC1155Component::Event", }, - impls: [{ - name: 'ERC1155InternalImpl', - embed: false, - value: 'ERC1155Component::InternalImpl', - }], + impls: [ + { + name: "ERC1155InternalImpl", + embed: false, + value: "ERC1155Component::InternalImpl", + }, + ], }, }); @@ -187,96 +227,82 @@ const functions = defineFunctions({ burn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'value', type: 'u256' }, + { name: "account", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "value", type: "u256" }, ], code: [ - 'let caller = get_caller_address();', - 'if account != caller {', - ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', - '}', - 'self.erc1155.burn(account, token_id, value);' - ] + "let caller = get_caller_address();", + "if account != caller {", + " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", + "}", + "self.erc1155.burn(account, token_id, value);", + ], }, batch_burn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, ], code: [ - 'let caller = get_caller_address();', - 'if account != caller {', - ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', - '}', - 'self.erc1155.batch_burn(account, token_ids, values);' - ] + "let caller = get_caller_address();", + "if account != caller {", + " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", + "}", + "self.erc1155.batch_burn(account, token_ids, values);", + ], }, batchBurn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'tokenIds', type: 'Span' }, - { name: 'values', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "tokenIds", type: "Span" }, + { name: "values", type: "Span" }, ], - code: [ - 'self.batch_burn(account, tokenIds, values);' - ] + code: ["self.batch_burn(account, tokenIds, values);"], }, mint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'value', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "value", type: "u256" }, + { name: "data", type: "Span" }, ], code: [ - 'self.erc1155.mint_with_acceptance_check(account, token_id, value, data);', - ] + "self.erc1155.mint_with_acceptance_check(account, token_id, value, data);", + ], }, batch_mint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, + { name: "data", type: "Span" }, ], code: [ - 'self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);', - ] + "self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);", + ], }, batchMint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'tokenIds', type: 'Span' }, - { name: 'values', type: 'Span' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "tokenIds", type: "Span" }, + { name: "values", type: "Span" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.batch_mint(account, tokenIds, values, data);', - ] + code: ["self.batch_mint(account, tokenIds, values, data);"], }, set_base_uri: { - args: [ - getSelfArg(), - { name: 'base_uri', type: 'ByteArray' }, - ], - code: [ - 'self.erc1155._set_base_uri(base_uri);' - ] + args: [getSelfArg(), { name: "base_uri", type: "ByteArray" }], + code: ["self.erc1155._set_base_uri(base_uri);"], }, setBaseUri: { - args: [ - getSelfArg(), - { name: 'baseUri', type: 'ByteArray' }, - ], - code: [ - 'self.set_base_uri(baseUri);' - ] + args: [getSelfArg(), { name: "baseUri", type: "ByteArray" }], + code: ["self.set_base_uri(baseUri);"], }, }); diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 69f4cdd3d..479c598f4 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; +import test from "ava"; -import { buildERC20, ERC20Options, getInitialSupply } from './erc20'; -import { printContract } from './print'; +import { buildERC20, ERC20Options, getInitialSupply } from "./erc20"; +import { printContract } from "./print"; -import { erc20, OptionsError } from '.'; +import { erc20, OptionsError } from "."; function testERC20(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC20({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -20,170 +20,185 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, t => { - t.is(erc20.print(opts), printContract(buildERC20({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); + test(title, (t) => { + t.is( + erc20.print(opts), + printContract( + buildERC20({ + name: "MyToken", + symbol: "MTK", + ...opts, + }) + ) + ); }); } -testERC20('basic erc20, non-upgradeable', { +testERC20("basic erc20, non-upgradeable", { upgradeable: false, }); -testERC20('basic erc20', {}); +testERC20("basic erc20", {}); -testERC20('erc20 burnable', { +testERC20("erc20 burnable", { burnable: true, }); -testERC20('erc20 pausable', { +testERC20("erc20 pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 pausable with roles', { +testERC20("erc20 pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 burnable pausable', { +testERC20("erc20 burnable pausable", { burnable: true, pausable: true, }); -testERC20('erc20 preminted', { - premint: '1000', +testERC20("erc20 preminted", { + premint: "1000", }); -testERC20('erc20 premint of 0', { - premint: '0', +testERC20("erc20 premint of 0", { + premint: "0", }); -testERC20('erc20 mintable', { +testERC20("erc20 mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 mintable with roles', { +testERC20("erc20 mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 votes', { +testERC20("erc20 votes", { votes: true, - appName: 'MY_DAPP_NAME', + appName: "MY_DAPP_NAME", }); -testERC20('erc20 votes, version', { +testERC20("erc20 votes, version", { votes: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', -}); - -test('erc20 votes, no name', async t => { - let error = t.throws(() => buildERC20({ - name: 'MyToken', - symbol: 'MTK', - votes: true, - })); - t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); -}); - -test('erc20 votes, empty version', async t => { - let error = t.throws(() => buildERC20({ - name: 'MyToken', - symbol: 'MTK', - votes: true, - appName: 'MY_DAPP_NAME', - appVersion: '', // avoids default value of v1 - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -testERC20('erc20 votes, non-upgradeable', { + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", +}); + +test("erc20 votes, no name", async (t) => { + const error = t.throws(() => + buildERC20({ + name: "MyToken", + symbol: "MTK", + votes: true, + }) + ); + t.is( + (error as OptionsError).messages.appName, + "Application Name is required when Votes are enabled" + ); +}); + +test("erc20 votes, empty version", async (t) => { + const error = t.throws(() => + buildERC20({ + name: "MyToken", + symbol: "MTK", + votes: true, + appName: "MY_DAPP_NAME", + appVersion: "", // avoids default value of v1 + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +testERC20("erc20 votes, non-upgradeable", { votes: true, - appName: 'MY_DAPP_NAME', + appName: "MY_DAPP_NAME", upgradeable: false, }); -testERC20('erc20 full, non-upgradeable', { - premint: '2000', - access: 'ownable', +testERC20("erc20 full, non-upgradeable", { + premint: "2000", + access: "ownable", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: false, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testERC20('erc20 full upgradeable', { - premint: '2000', - access: 'ownable', +testERC20("erc20 full upgradeable", { + premint: "2000", + access: "ownable", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testERC20('erc20 full upgradeable with roles', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable with roles", { + premint: "2000", + access: "roles", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testAPIEquivalence('erc20 API default'); +testAPIEquivalence("erc20 API default"); -testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('erc20 API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', - premint: '2000', - access: 'roles', +testAPIEquivalence("erc20 API full upgradeable", { + name: "CustomToken", + symbol: "CTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -test('erc20 API assert defaults', async t => { +test("erc20 API assert defaults", async (t) => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test('erc20 API isAccessControlRequired', async t => { +test("erc20 API isAccessControlRequired", async (t) => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); t.is(erc20.isAccessControlRequired({ upgradeable: true }), true); }); -test('erc20 getInitialSupply', async t => { - t.is(getInitialSupply('1000', 18), '1000000000000000000000'); - t.is(getInitialSupply('1000.1', 18), '1000100000000000000000'); - t.is(getInitialSupply('.1', 18), '100000000000000000'); - t.is(getInitialSupply('.01', 2), '1'); +test("erc20 getInitialSupply", async (t) => { + t.is(getInitialSupply("1000", 18), "1000000000000000000000"); + t.is(getInitialSupply("1000.1", 18), "1000100000000000000000"); + t.is(getInitialSupply(".1", 18), "100000000000000000"); + t.is(getInitialSupply(".01", 2), "1"); - let error = t.throws(() => getInitialSupply('.01', 1)); + let error = t.throws(() => getInitialSupply(".01", 1)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, 'Too many decimals'); + t.is((error as OptionsError).messages.premint, "Too many decimals"); - error = t.throws(() => getInitialSupply('1.1.1', 18)); + error = t.throws(() => getInitialSupply("1.1.1", 18)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, 'Not a valid number'); -}); \ No newline at end of file + t.is((error as OptionsError).messages.premint, "Not a valid number"); +}); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 57cfe8d86..8dbe4cd14 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -1,32 +1,39 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { OptionsError } from './error'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { externalTrait } from './external-trait'; -import { toByteArray, toFelt252, toUint } from './utils/convert-strings'; -import { addVotesComponent } from './common-components'; - +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { OptionsError } from "./error"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { externalTrait } from "./external-trait"; +import { toByteArray, toFelt252, toUint } from "./utils/convert-strings"; +import { addVotesComponent } from "./common-components"; export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", burnable: false, pausable: false, - premint: '0', + premint: "0", mintable: false, votes: false, - appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: 'v1', + appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: "v1", access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC20(opts: ERC20Options = defaults): string { @@ -55,12 +62,16 @@ function withDefaults(opts: ERC20Options): Required { mintable: opts.mintable ?? defaults.mintable, votes: opts.votes ?? defaults.votes, appName: opts.appName ?? defaults.appName, - appVersion: opts.appVersion ?? defaults.appVersion + appVersion: opts.appVersion ?? defaults.appVersion, }; } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; + return ( + opts.mintable === true || + opts.pausable === true || + opts.upgradeable === true + ); } export function buildERC20(opts: ERC20Options): Contract { @@ -100,8 +111,8 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable || allOpts.votes; if (usesCustomHooks) { const hooksTrait = { - name: 'ERC20HooksImpl', - of: 'ERC20Component::ERC20HooksTrait', + name: "ERC20HooksImpl", + of: "ERC20Component::ERC20HooksTrait", tags: [], priority: 1, }; @@ -109,99 +120,103 @@ function addHooks(c: ContractBuilder, allOpts: Required) { if (allOpts.pausable) { const beforeUpdateFn = c.addFunction(hooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: 'ERC20Component::ComponentState' }, - { name: 'from', type: 'ContractAddress' }, - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' }, + { + name: "ref self", + type: "ERC20Component::ComponentState", + }, + { name: "from", type: "ContractAddress" }, + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], code: [], }); beforeUpdateFn.code.push( - 'let contract_state = self.get_contract();', - 'contract_state.pausable.assert_not_paused();', + "let contract_state = self.get_contract();", + "contract_state.pausable.assert_not_paused();" ); } if (allOpts.votes) { if (!allOpts.appName) { throw new OptionsError({ - appName: 'Application Name is required when Votes are enabled', + appName: "Application Name is required when Votes are enabled", }); } if (!allOpts.appVersion) { throw new OptionsError({ - appVersion: 'Application Version is required when Votes are enabled', + appVersion: "Application Version is required when Votes are enabled", }); } addVotesComponent( c, - toFelt252(allOpts.appName, 'appName'), - toFelt252(allOpts.appVersion, 'appVersion'), - 'SNIP12 Metadata', + toFelt252(allOpts.appName, "appName"), + toFelt252(allOpts.appVersion, "appVersion"), + "SNIP12 Metadata" ); const afterUpdateFn = c.addFunction(hooksTrait, { - name: 'after_update', + name: "after_update", args: [ - { name: 'ref self', type: 'ERC20Component::ComponentState' }, - { name: 'from', type: 'ContractAddress' }, - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' }, + { + name: "ref self", + type: "ERC20Component::ComponentState", + }, + { name: "from", type: "ContractAddress" }, + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], code: [], }); afterUpdateFn.code.push( - 'let mut contract_state = self.get_contract_mut();', - 'contract_state.votes.transfer_voting_units(from, recipient, amount);', + "let mut contract_state = self.get_contract_mut();", + "contract_state.votes.transfer_voting_units(from, recipient, amount);" ); } } else { - c.addUseClause('openzeppelin::token::erc20', 'ERC20HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc20", "ERC20HooksEmptyImpl"); } } function addERC20Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC20Component, { - name: 'ERC20MixinImpl', - value: 'ERC20Component::ERC20MixinImpl', + name: "ERC20MixinImpl", + value: "ERC20Component::ERC20MixinImpl", }); } function addBase(c: ContractBuilder, name: string, symbol: string) { - c.addComponent( - components.ERC20Component, - [ - name, symbol - ], - true, - ); + c.addComponent(components.ERC20Component, [name, symbol], true); } function addBurnable(c: ContractBuilder) { - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); } export const premintPattern = /^(\d*\.?\d*)$/; function addPremint(c: ContractBuilder, amount: string) { - if (amount !== undefined && amount !== '0') { + if (amount !== undefined && amount !== "0") { if (!premintPattern.test(amount)) { throw new OptionsError({ - premint: 'Not a valid number', + premint: "Not a valid number", }); } - const premintAbsolute = toUint(getInitialSupply(amount, 18), 'premint', 'u256'); + const premintAbsolute = toUint( + getInitialSupply(amount, 18), + "premint", + "u256" + ); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name:'recipient', type:'ContractAddress' }); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: "recipient", type: "ContractAddress" }); c.addConstructorCode(`self.erc20.mint(recipient, ${premintAbsolute})`); } } @@ -219,76 +234,78 @@ export function getInitialSupply(premint: string, decimals: number): string { const premintSegments = premint.split("."); if (premintSegments.length > 2) { throw new OptionsError({ - premint: 'Not a valid number', + premint: "Not a valid number", }); } else { - let firstSegment = premintSegments[0] ?? ''; - let lastSegment = premintSegments[1] ?? ''; + const firstSegment = premintSegments[0] ?? ""; + let lastSegment = premintSegments[1] ?? ""; if (decimals > lastSegment.length) { try { lastSegment += "0".repeat(decimals - lastSegment.length); - } catch (e) { + } catch { // .repeat gives an error if decimals number is too large throw new OptionsError({ - premint: 'Decimals number too large', + premint: "Decimals number too large", }); } } else if (decimals < lastSegment.length) { throw new OptionsError({ - premint: 'Too many decimals', + premint: "Too many decimals", }); } // concat segments without leading zeros - result = firstSegment.concat(lastSegment).replace(/^0+/, ''); + result = firstSegment.concat(lastSegment).replace(/^0+/, ""); } if (result.length === 0) { - result = '0'; + result = "0"; } return result; } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.mint, + access, + "MINTER", + "minter" + ); } -const components = defineComponents( { +const components = defineComponents({ ERC20Component: { - path: 'openzeppelin::token::erc20', + path: "openzeppelin::token::erc20", substorage: { - name: 'erc20', - type: 'ERC20Component::Storage', + name: "erc20", + type: "ERC20Component::Storage", }, event: { - name: 'ERC20Event', - type: 'ERC20Component::Event', + name: "ERC20Event", + type: "ERC20Component::Event", }, - impls: [{ - name: 'ERC20InternalImpl', - embed: false, - value: 'ERC20Component::InternalImpl', - }], + impls: [ + { + name: "ERC20InternalImpl", + embed: false, + value: "ERC20Component::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ burn: { - args: [ - getSelfArg(), - { name: 'value', type: 'u256' } - ], - code: [ - 'self.erc20.burn(get_caller_address(), value);' - ] + args: [getSelfArg(), { name: "value", type: "u256" }], + code: ["self.erc20.burn(get_caller_address(), value);"], }, mint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' } + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], - code: [ - 'self.erc20.mint(recipient, amount);' - ] + code: ["self.erc20.mint(recipient, amount);"], }, }); diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index 6608368fa..c2819b953 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -1,18 +1,19 @@ -import test from 'ava'; +import test from "ava"; -import { buildERC721, ERC721Options } from './erc721'; -import { printContract } from './print'; -import { royaltyInfoOptions } from './set-royalty-info'; +import { buildERC721, ERC721Options } from "./erc721"; +import { printContract } from "./print"; +import { royaltyInfoOptions } from "./set-royalty-info"; -import { erc721, OptionsError } from '.'; +import { erc721, OptionsError } from "."; -const NAME = 'MyToken'; -const CUSTOM_NAME = 'CustomToken'; -const SYMBOL = 'MTK'; -const CUSTOM_SYMBOL = 'CTK'; -const APP_NAME = 'MY_DAPP_NAME'; -const APP_VERSION = 'MY_DAPP_VERSION'; -const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; +const NAME = "MyToken"; +const CUSTOM_NAME = "CustomToken"; +const SYMBOL = "MTK"; +const CUSTOM_SYMBOL = "CTK"; +const APP_NAME = "MY_DAPP_NAME"; +const APP_VERSION = "MY_DAPP_VERSION"; +const BASE_URI = + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; const allFeaturesON: Partial = { mintable: true, @@ -23,11 +24,11 @@ const allFeaturesON: Partial = { votes: true, appName: APP_NAME, appVersion: APP_VERSION, - upgradeable: true + upgradeable: true, } as const; function testERC721(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC721({ name: NAME, symbol: SYMBOL, @@ -40,152 +41,187 @@ function testERC721(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, t => { - t.is(erc721.print(opts), printContract(buildERC721({ - name: NAME, - symbol: SYMBOL, - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC721Options) { + test(title, (t) => { + t.is( + erc721.print(opts), + printContract( + buildERC721({ + name: NAME, + symbol: SYMBOL, + ...opts, + }) + ) + ); }); } -testERC721('basic non-upgradeable', { +testERC721("basic non-upgradeable", { upgradeable: false, }); -testERC721('basic', {}); +testERC721("basic", {}); -testERC721('base uri', { +testERC721("base uri", { baseUri: BASE_URI, }); -testERC721('burnable', { +testERC721("burnable", { burnable: true, }); -testERC721('pausable', { +testERC721("pausable", { pausable: true, }); -testERC721('mintable', { +testERC721("mintable", { mintable: true, }); -testERC721('enumerable', { +testERC721("enumerable", { enumerable: true, }); -testERC721('pausable + enumerable', { +testERC721("pausable + enumerable", { pausable: true, enumerable: true, }); -testERC721('mintable + roles', { +testERC721("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC721('royalty info disabled', { - royaltyInfo: royaltyInfoOptions.disabled +testERC721("royalty info disabled", { + royaltyInfo: royaltyInfoOptions.disabled, }); -testERC721('royalty info enabled default + ownable', { +testERC721("royalty info enabled default + ownable", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'ownable' + access: "ownable", }); -testERC721('royalty info enabled default + roles', { +testERC721("royalty info enabled default + roles", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'roles' + access: "roles", }); -testERC721('royalty info enabled custom + ownable', { +testERC721("royalty info enabled custom + ownable", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'ownable' + access: "ownable", }); -testERC721('royalty info enabled custom + roles', { +testERC721("royalty info enabled custom + roles", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'roles' + access: "roles", }); -testERC721('full non-upgradeable', { +testERC721("full non-upgradeable", { ...allFeaturesON, upgradeable: false, }); -testERC721('erc721 votes', { +testERC721("erc721 votes", { votes: true, appName: APP_NAME, }); -testERC721('erc721 votes, version', { +testERC721("erc721 votes, version", { votes: true, appName: APP_NAME, appVersion: APP_VERSION, }); -test('erc721 votes, no name', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - })); - t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); -}); - -test('erc721 votes, no version', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - appName: APP_NAME, - appVersion: '' - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -test('erc721 votes, empty version', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - appName: APP_NAME, - appVersion: '', // avoids default value of v1 - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -testERC721('erc721 votes, non-upgradeable', { +test("erc721 votes, no name", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + }) + ); + t.is( + (error as OptionsError).messages.appName, + "Application Name is required when Votes are enabled" + ); +}); + +test("erc721 votes, no version", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + appName: APP_NAME, + appVersion: "", + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +test("erc721 votes, empty version", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + appName: APP_NAME, + appVersion: "", // avoids default value of v1 + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +testERC721("erc721 votes, non-upgradeable", { votes: true, appName: APP_NAME, upgradeable: false, }); -testERC721('full upgradeable', allFeaturesON); +testERC721("full upgradeable", allFeaturesON); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); +testAPIEquivalence("API basic", { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); -testAPIEquivalence('API full upgradeable', { +testAPIEquivalence("API full upgradeable", { ...allFeaturesON, name: CUSTOM_NAME, - symbol: CUSTOM_SYMBOL + symbol: CUSTOM_SYMBOL, }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); t.is(erc721.isAccessControlRequired({ upgradeable: true }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.disabled }), false); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledDefault, + }), + true + ); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledCustom, + }), + true + ); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.disabled, + }), + false + ); t.is(erc721.isAccessControlRequired({ burnable: true }), false); t.is(erc721.isAccessControlRequired({ enumerable: true }), false); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index 5be28487c..67867b287 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -1,64 +1,64 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import crypto from 'crypto'; - -import { generateERC20Options } from './erc20'; -import { generateERC721Options } from './erc721'; -import { generateERC1155Options } from './erc1155'; -import { generateAccountOptions } from './account'; -import { generateCustomOptions } from './custom'; -import { generateGovernorOptions } from './governor'; -import { generateVestingOptions } from './vesting'; -import { buildGeneric, GenericOptions, KindedOptions } from '../build-generic'; -import { printContract } from '../print'; -import { OptionsError } from '../error'; -import { findCover } from '../utils/find-cover'; -import type { Contract } from '../contract'; - -type Subset = 'all' | 'minimal-cover'; +import { promises as fs } from "fs"; +import path from "path"; +import crypto from "crypto"; + +import { generateERC20Options } from "./erc20"; +import { generateERC721Options } from "./erc721"; +import { generateERC1155Options } from "./erc1155"; +import { generateAccountOptions } from "./account"; +import { generateCustomOptions } from "./custom"; +import { generateGovernorOptions } from "./governor"; +import { generateVestingOptions } from "./vesting"; +import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; +import { printContract } from "../print"; +import { OptionsError } from "../error"; +import { findCover } from "../utils/find-cover"; +import type { Contract } from "../contract"; + +type Subset = "all" | "minimal-cover"; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === 'ERC20') { + if (!kind || kind === "ERC20") { for (const kindOpts of generateERC20Options()) { - yield { kind: 'ERC20', ...kindOpts }; + yield { kind: "ERC20", ...kindOpts }; } } - if (!kind || kind === 'ERC721') { + if (!kind || kind === "ERC721") { for (const kindOpts of generateERC721Options()) { - yield { kind: 'ERC721', ...kindOpts }; + yield { kind: "ERC721", ...kindOpts }; } } - if (!kind || kind === 'ERC1155') { + if (!kind || kind === "ERC1155") { for (const kindOpts of generateERC1155Options()) { - yield { kind: 'ERC1155', ...kindOpts }; + yield { kind: "ERC1155", ...kindOpts }; } } - if (!kind || kind === 'Account') { + if (!kind || kind === "Account") { for (const kindOpts of generateAccountOptions()) { - yield { kind: 'Account', ...kindOpts }; + yield { kind: "Account", ...kindOpts }; } } - if (!kind || kind === 'Custom') { + if (!kind || kind === "Custom") { for (const kindOpts of generateCustomOptions()) { - yield { kind: 'Custom', ...kindOpts }; + yield { kind: "Custom", ...kindOpts }; } } - if (!kind || kind === 'Governor') { + if (!kind || kind === "Governor") { for (const kindOpts of generateGovernorOptions()) { - yield { kind: 'Governor', ...kindOpts }; + yield { kind: "Governor", ...kindOpts }; } } - if (!kind || kind === 'Vesting') { + if (!kind || kind === "Vesting") { for (const kindOpts of generateVestingOptions()) { - yield { kind: 'Vesting', ...kindOpts }; + yield { kind: "Vesting", ...kindOpts }; } } } @@ -73,15 +73,18 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { +function generateContractSubset( + subset: Subset, + kind?: Kind +): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { const id = crypto - .createHash('sha1') + .createHash("sha1") .update(JSON.stringify(options)) .digest() - .toString('hex'); + .toString("hex"); try { const contract = buildGeneric(options); @@ -95,36 +98,48 @@ function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[ } } - if (subset === 'all') { + if (subset === "all") { return contracts; } else { - const getParents = (c: GeneratedContract) => c.contract.components.map(p => p.path); + const getParents = (c: GeneratedContract) => + c.contract.components.map((p) => p.path); function filterByUpgradeableSetTo(isUpgradeable: boolean) { return (c: GeneratedContract) => { switch (c.options.kind) { - case 'Vesting': + case "Vesting": return isUpgradeable === false; - case 'Account': - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Governor': - case 'Custom': + case "Account": + case "ERC20": + case "ERC721": + case "ERC1155": + case "Governor": + case "Custom": return c.options.upgradeable === isUpgradeable; - default: + default: { const _: never = c.options; - throw new Error('Unknown kind'); + throw new Error("Unknown kind"); + } } - } + }; } return [ - ...findCover(contracts.filter(filterByUpgradeableSetTo(true)), getParents), - ...findCover(contracts.filter(filterByUpgradeableSetTo(false)), getParents), + ...findCover( + contracts.filter(filterByUpgradeableSetTo(true)), + getParents + ), + ...findCover( + contracts.filter(filterByUpgradeableSetTo(false)), + getParents + ), ]; } } -export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Kind): Generator { +export function* generateSources( + subset: Subset, + uniqueName?: boolean, + kind?: Kind +): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { if (uniqueName) { @@ -135,13 +150,22 @@ export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Ki } } -export async function writeGeneratedSources(dir: string, subset: Subset, uniqueName?: boolean, kind?: Kind): Promise { +export async function writeGeneratedSources( + dir: string, + subset: Subset, + uniqueName?: boolean, + kind?: Kind +): Promise { await fs.mkdir(dir, { recursive: true }); - let contractNames = []; + const contractNames = []; - for (const { id, contract, source } of generateSources(subset, uniqueName, kind)) { + for (const { id, contract, source } of generateSources( + subset, + uniqueName, + kind + )) { const name = uniqueName ? contract.name : id; - await fs.writeFile(path.format({ dir, name, ext: '.cairo' }), source); + await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); contractNames.push(name); } diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index d946c1a26..71f5dec38 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -1,16 +1,27 @@ -import type { Contract, Component, Argument, Value, Impl, ContractFunction, ImplementedTrait, UseClause, } from './contract'; - -import { formatLines, spaceBetween, Lines } from './utils/format-lines'; -import { getSelfArg } from './common-options'; -import { compatibleContractsSemver } from './utils/version'; - -const DEFAULT_SECTION = '1. with no section'; -const STANDALONE_IMPORTS_GROUP = 'Standalone Imports'; +import type { + Contract, + Component, + Argument, + Value, + Impl, + ContractFunction, + ImplementedTrait, + UseClause, +} from "./contract"; + +import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; +import { getSelfArg } from "./common-options"; +import { compatibleContractsSemver } from "./utils/version"; + +const DEFAULT_SECTION = "1. with no section"; +const STANDALONE_IMPORTS_GROUP = "Standalone Imports"; const MAX_USE_CLAUSE_LINE_LENGTH = 90; const TAB = "\t"; export function printContract(contract: Contract): string { - const contractAttribute = contract.account ? '#[starknet::contract(account)]' : '#[starknet::contract]' + const contractAttribute = contract.account + ? "#[starknet::contract(account)]" + : "#[starknet::contract]"; return formatLines( ...spaceBetween( [ @@ -29,20 +40,24 @@ export function printContract(contract: Contract): string { printStorage(contract), printEvents(contract), printConstructor(contract), - printImplementedTraits(contract), + printImplementedTraits(contract) ), `}`, - ], - ), + ] + ) ); } function withSemicolons(lines: string[]): string[] { - return lines.map(line => line.endsWith(';') ? line : line + ';'); + return lines.map((line) => (line.endsWith(";") ? line : line + ";")); } function printSuperVariables(contract: Contract): string[] { - return withSemicolons(contract.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); + return withSemicolons( + contract.superVariables.map( + (v) => `const ${v.name}: ${v.type} = ${v.value}` + ) + ); } function printUseClauses(contract: Contract): Lines[] { @@ -50,30 +65,46 @@ function printUseClauses(contract: Contract): Lines[] { // group by containerPath const grouped = useClauses.reduce( - (result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => { + ( + result: { [containerPath: string]: UseClause[] }, + useClause: UseClause + ) => { if (useClause.groupable) { - (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause); + (result[useClause.containerPath] = + result[useClause.containerPath] || []).push(useClause); } else { - (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); + (result[STANDALONE_IMPORTS_GROUP] = + result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); } return result; - }, {}); + }, + {} + ); - const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName)); - return lines.flatMap(line => splitLongUseClauseLine(line.toString())); + const lines = Object.entries(grouped).flatMap(([groupName, group]) => + getLinesFromUseClausesGroup(group, groupName) + ); + return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); } -function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] { +function getLinesFromUseClausesGroup( + group: UseClause[], + groupName: string +): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { - lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`); + lines.push( + `use ${useClause.containerPath}::${nameWithAlias(useClause)};` + ); } } else { if (group.length == 1) { lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`); } else if (group.length > 1) { - let names = group.map((useClause) => nameWithAlias(useClause)).join(', '); + const names = group + .map((useClause) => nameWithAlias(useClause)) + .join(", "); lines.push(`use ${groupName}::{${names}};`); } } @@ -81,18 +112,20 @@ function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lin } function nameWithAlias(useClause: UseClause): string { - return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name; + return useClause.alias + ? `${useClause.name} as ${useClause.alias}` + : useClause.name; } // TODO: remove this when we can use a formatting js library function splitLongUseClauseLine(line: string): Lines[] { const lines = []; - const containsBraces = line.indexOf('{') !== -1; + const containsBraces = line.indexOf("{") !== -1; if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) { // split at the first brace - lines.push(line.slice(0, line.indexOf('{') + 1)); - lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2))); + lines.push(line.slice(0, line.indexOf("{") + 1)); + lines.push(...splitLongLineInner(line.slice(line.indexOf("{") + 1, -2))); lines.push("};"); } else { lines.push(line); @@ -104,7 +137,7 @@ function splitLongLineInner(line: string): Lines[] { const lines = []; if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) { const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH); - const lastCommaIndex = max_accessible_string.lastIndexOf(','); + const lastCommaIndex = max_accessible_string.lastIndexOf(","); if (lastCommaIndex !== -1) { lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1)); lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2))); @@ -134,11 +167,17 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value};` + ); } else if (commented) { - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}` + ); } else { - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value};` + ); } } return lines; @@ -147,35 +186,39 @@ function printConstants(contract: Contract): Lines[] { function printComponentDeclarations(contract: Contract): Lines[] { const lines = []; for (const component of contract.components) { - lines.push(`component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});`); + lines.push( + `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});` + ); } return lines; } function printImpls(contract: Contract): Lines[] { - const impls = contract.components.flatMap(c => c.impls); + const impls = contract.components.flatMap((c) => c.impls); // group by section const grouped = impls.reduce( - (result: { [section: string]: Impl[] }, current:Impl) => { + (result: { [section: string]: Impl[] }, current: Impl) => { // default section depends on embed // embed defaults to true const embed = current.embed ?? true; - const section = current.section ?? (embed ? 'External' : 'Internal'); + const section = current.section ?? (embed ? "External" : "Internal"); (result[section] = result[section] || []).push(current); return result; - }, {}); - - const sections = Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])).map( - ([section, impls]) => printSection(section, impls as Impl[]), + }, + {} ); + + const sections = Object.entries(grouped) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([section, impls]) => printSection(section, impls as Impl[])); return spaceBetween(...sections); } function printSection(section: string, impls: Impl[]): Lines[] { const lines = []; lines.push(`// ${section}`); - impls.map(impl => lines.push(...printImpl(impl))); + impls.map((impl) => lines.push(...printImpl(impl))); return lines; } @@ -183,7 +226,7 @@ function printImpl(impl: Impl): Lines[] { const lines = []; // embed is optional, default to true if (impl.embed ?? true) { - lines.push('#[abi(embed_v0)]'); + lines.push("#[abi(embed_v0)]"); } lines.push(`impl ${impl.name} = ${impl.value};`); return lines; @@ -192,31 +235,33 @@ function printImpl(impl: Impl): Lines[] { function printStorage(contract: Contract): (string | string[])[] { const lines = []; // storage is required regardless of whether there are components - lines.push('#[storage]'); - lines.push('struct Storage {'); + lines.push("#[storage]"); + lines.push("struct Storage {"); const storageLines = []; for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); - storageLines.push(`${component.substorage.name}: ${component.substorage.type},`); + storageLines.push( + `${component.substorage.name}: ${component.substorage.type},` + ); } lines.push(storageLines); - lines.push('}'); + lines.push("}"); return lines; } function printEvents(contract: Contract): (string | string[])[] { const lines = []; if (contract.components.length > 0) { - lines.push('#[event]'); - lines.push('#[derive(Drop, starknet::Event)]'); - lines.push('enum Event {') + lines.push("#[event]"); + lines.push("#[derive(Drop, starknet::Event)]"); + lines.push("enum Event {"); const eventLines = []; for (const component of contract.components) { - eventLines.push('#[flat]'); + eventLines.push("#[flat]"); eventLines.push(`${component.event.name}: ${component.event.type},`); } lines.push(eventLines); - lines.push('}'); + lines.push("}"); } return lines; } @@ -235,31 +280,41 @@ function printImplementedTraits(contract: Contract): Lines[] { // group by section const grouped = sortedTraits.reduce( - (result: { [section: string]: ImplementedTrait[] }, current:ImplementedTrait) => { + ( + result: { [section: string]: ImplementedTrait[] }, + current: ImplementedTrait + ) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); return result; - }, {}); - - const sections = Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])).map( - ([section, impls]) => printImplementedTraitsSection(section, impls as ImplementedTrait[]), + }, + {} ); + const sections = Object.entries(grouped) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([section, impls]) => + printImplementedTraitsSection(section, impls as ImplementedTrait[]) + ); + return spaceBetween(...sections); } -function printImplementedTraitsSection(section: string, impls: ImplementedTrait[]): Lines[] { +function printImplementedTraitsSection( + section: string, + impls: ImplementedTrait[] +): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; if (!isDefaultSection) { - lines.push('//'); + lines.push("//"); lines.push(`// ${section}`); - lines.push('//'); + lines.push("//"); } impls.forEach((trait, index) => { if (index > 0 || !isDefaultSection) { - lines.push(''); + lines.push(""); } lines.push(...printImplementedTrait(trait)); }); @@ -268,32 +323,36 @@ function printImplementedTraitsSection(section: string, impls: ImplementedTrait[ function printImplementedTrait(trait: ImplementedTrait): Lines[] { const implLines = []; - implLines.push(...trait.tags.map(t => `#[${t}]`)); + implLines.push(...trait.tags.map((t) => `#[${t}]`)); implLines.push(`impl ${trait.name} of ${trait.of} {`); const superVars = withSemicolons( - trait.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`) + trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`) ); implLines.push(superVars); - const fns = trait.functions.map(fn => printFunction(fn)); + const fns = trait.functions.map((fn) => printFunction(fn)); implLines.push(spaceBetween(...fns)); - implLines.push('}'); + implLines.push("}"); return implLines; } function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; - const args = fn.args.map(a => printArgument(a)); + const args = fn.args.map((a) => printArgument(a)); const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code; for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; - const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; + const shouldEndWithSemicolon = + i < codeLines.length - 1 || fn.returns === undefined; if (line !== undefined && line.length > 0) { - if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { - codeLines[i] += ';'; - } else if (!shouldEndWithSemicolon && line.endsWith(';')) { + if ( + shouldEndWithSemicolon && + !["{", "}", ";"].includes(line.charAt(line.length - 1)) + ) { + codeLines[i] += ";"; + } else if (!shouldEndWithSemicolon && line.endsWith(";")) { codeLines[i] = line.slice(0, line.length - 1); } } @@ -303,28 +362,30 @@ function printFunction(fn: ContractFunction): Lines[] { } function printConstructor(contract: Contract): Lines[] { - const hasInitializers = contract.components.some(p => p.initializer !== undefined); + const hasInitializers = contract.components.some( + (p) => p.initializer !== undefined + ); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { const parents = contract.components .filter(hasInitializer) - .flatMap(p => printParentConstructor(p)); - const tag = 'constructor'; - const head = 'fn constructor'; - const args = [ getSelfArg(), ...contract.constructorArgs ]; + .flatMap((p) => printParentConstructor(p)); + const tag = "constructor"; + const head = "fn constructor"; + const args = [getSelfArg(), ...contract.constructorArgs]; const body = spaceBetween( - withSemicolons(parents), - withSemicolons(contract.constructorCode), - ); + withSemicolons(parents), + withSemicolons(contract.constructorCode) + ); const constructor = printFunction2( head, - args.map(a => printArgument(a)), + args.map((a) => printArgument(a)), tag, undefined, undefined, - body, + body ); return constructor; } else { @@ -333,37 +394,40 @@ function printConstructor(contract: Contract): Lines[] { } function hasInitializer(parent: Component): boolean { - return parent.initializer !== undefined && parent.substorage?.name !== undefined; + return ( + parent.initializer !== undefined && parent.substorage?.name !== undefined + ); } -function printParentConstructor({ substorage, initializer }: Component): [] | [string] { +function printParentConstructor({ + substorage, + initializer, +}: Component): [] | [string] { if (initializer === undefined || substorage?.name === undefined) { return []; } const fn = `self.${substorage.name}.initializer`; - return [ - fn + '(' + initializer.params.map(printValue).join(', ') + ')', - ]; + return [fn + "(" + initializer.params.map(printValue).join(", ") + ")"]; } export function printValue(value: Value): string { - if (typeof value === 'object') { - if ('lit' in value) { + if (typeof value === "object") { + if ("lit" in value) { return value.lit; - } else if ('note' in value) { + } else if ("note" in value) { // TODO: add /* ${value.note} */ after lsp is fixed return `${printValue(value.value)}`; } else { - throw Error('Unknown value type'); + throw Error("Unknown value type"); } - } else if (typeof value === 'number') { + } else if (typeof value === "number") { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { throw new Error(`Number not representable (${value})`); } - } else if (typeof value === 'bigint') { - return `${value}` + } else if (typeof value === "bigint") { + return `${value}`; } else { return `"${value}"`; } @@ -388,20 +452,20 @@ function printFunction2( let accum = `${kindedName}(`; if (args.length > 0) { - const formattedArgs = args.join(', '); + const formattedArgs = args.join(", "); if (formattedArgs.length > 80) { fn.push(accum); - accum = ''; + accum = ""; // print each arg in a separate line - fn.push(args.map(arg => `${arg},`)); + fn.push(args.map((arg) => `${arg},`)); } else { accum += `${formattedArgs}`; } } - accum += ')'; + accum += ")"; if (returns === undefined) { - accum += ' {'; + accum += " {"; } else { accum += ` -> ${returns} {`; } @@ -411,7 +475,7 @@ function printFunction2( if (returnLine !== undefined) { fn.push([returnLine]); } - fn.push('}'); + fn.push("}"); return fn; } diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index 022e74759..53a626d05 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -1,15 +1,24 @@ -import { promises as fs } from 'fs'; -import path from 'path'; +import { promises as fs } from "fs"; +import path from "path"; -import { writeGeneratedSources } from '../generate/sources'; -import { contractsVersion, edition, cairoVersion, scarbVersion } from '../utils/version'; +import { writeGeneratedSources } from "../generate/sources"; +import { + contractsVersion, + edition, + cairoVersion, + scarbVersion, +} from "../utils/version"; export async function updateScarbProject() { - const generatedSourcesPath = path.join('test_project', 'src'); + const generatedSourcesPath = path.join("test_project", "src"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); // Generate the contracts source code - const contractNames = await writeGeneratedSources(generatedSourcesPath, 'all', true); + const contractNames = await writeGeneratedSources( + generatedSourcesPath, + "all", + true + ); // Generate lib.cairo file writeLibCairo(contractNames); @@ -19,23 +28,32 @@ export async function updateScarbProject() { } async function writeLibCairo(contractNames: string[]) { - const libCairoPath = path.join('test_project/src', 'lib.cairo'); - const libCairo = contractNames.map(name => `pub mod ${name};\n`).join(''); + const libCairoPath = path.join("test_project/src", "lib.cairo"); + const libCairo = contractNames.map((name) => `pub mod ${name};\n`).join(""); await fs.writeFile(libCairoPath, libCairo); } async function updateScarbToml() { - const scarbTomlPath = path.join('test_project', 'Scarb.toml'); + const scarbTomlPath = path.join("test_project", "Scarb.toml"); - let currentContent = await fs.readFile(scarbTomlPath, 'utf8'); - let updatedContent = currentContent + const currentContent = await fs.readFile(scarbTomlPath, "utf8"); + const updatedContent = currentContent .replace(/edition = "\w+"/, `edition = "${edition}"`) - .replace(/cairo-version = "\d+\.\d+\.\d+"/, `cairo-version = "${cairoVersion}"`) - .replace(/scarb-version = "\d+\.\d+\.\d+"/, `scarb-version = "${scarbVersion}"`) + .replace( + /cairo-version = "\d+\.\d+\.\d+"/, + `cairo-version = "${cairoVersion}"` + ) + .replace( + /scarb-version = "\d+\.\d+\.\d+"/, + `scarb-version = "${scarbVersion}"` + ) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) - .replace(/openzeppelin = "\d+\.\d+\.\d+"/, `openzeppelin = "${contractsVersion}"`); + .replace( + /openzeppelin = "\d+\.\d+\.\d+"/, + `openzeppelin = "${contractsVersion}"` + ); - await fs.writeFile(scarbTomlPath, updatedContent, 'utf8'); + await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); } updateScarbProject(); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index 967e17ece..e34e31247 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -1,88 +1,101 @@ -import { promises as fs } from 'fs'; -import os from 'os'; -import _test, { TestFn, ExecutionContext } from 'ava'; -import path from 'path'; +import { promises as fs } from "fs"; +import os from "os"; +import _test, { TestFn, ExecutionContext } from "ava"; +import path from "path"; -import { generateSources, writeGeneratedSources } from './generate/sources'; -import type { GenericOptions, KindedOptions } from './build-generic'; -import { custom, erc20, erc721, erc1155 } from './api'; +import { generateSources, writeGeneratedSources } from "./generate/sources"; +import type { GenericOptions, KindedOptions } from "./build-generic"; +import { custom, erc20, erc721, erc1155 } from "./api"; interface Context { - generatedSourcesPath: string + generatedSourcesPath: string; } const test = _test as TestFn; -test.serial('erc20 result generated', async t => { - await testGenerate(t, 'ERC20'); +test.serial("erc20 result generated", async (t) => { + await testGenerate(t, "ERC20"); }); -test.serial('erc721 result generated', async t => { - await testGenerate(t, 'ERC721'); +test.serial("erc721 result generated", async (t) => { + await testGenerate(t, "ERC721"); }); -test.serial('erc1155 result generated', async t => { - await testGenerate(t, 'ERC1155'); +test.serial("erc1155 result generated", async (t) => { + await testGenerate(t, "ERC1155"); }); -test.serial('account result generated', async t => { - await testGenerate(t, 'Account'); +test.serial("account result generated", async (t) => { + await testGenerate(t, "Account"); }); -test.serial('custom result generated', async t => { - await testGenerate(t, 'Custom'); +test.serial("custom result generated", async (t) => { + await testGenerate(t, "Custom"); }); -async function testGenerate(t: ExecutionContext, kind: keyof KindedOptions) { - const generatedSourcesPath = path.join(os.tmpdir(), 'oz-wizard-cairo'); +async function testGenerate( + t: ExecutionContext, + kind: keyof KindedOptions +) { + const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, 'all', true, kind); + await writeGeneratedSources(generatedSourcesPath, "all", true, kind); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { - switch(opts.kind) { - case 'ERC20': + switch (opts.kind) { + case "ERC20": return erc20.isAccessControlRequired(opts); - case 'ERC721': + case "ERC721": return erc721.isAccessControlRequired(opts); - case 'ERC1155': + case "ERC1155": return erc1155.isAccessControlRequired(opts); - case 'Account': + case "Account": throw new Error("Not applicable for accounts"); - case 'Custom': + case "Custom": return custom.isAccessControlRequired(opts); default: throw new Error("No such kind"); } } -test('is access control required', async t => { - for (const contract of generateSources('all')) { - const regexOwnable = /(use openzeppelin::access::ownable::OwnableComponent)/gm; +test("is access control required", async (t) => { + for (const contract of generateSources("all")) { + const regexOwnable = + /(use openzeppelin::access::ownable::OwnableComponent)/gm; switch (contract.options.kind) { - case 'Account': - case 'Governor': - case 'Vesting': + case "Account": + case "Governor": + case "Vesting": // These contracts have no access control option break; - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Custom': + case "ERC20": + case "ERC721": + case "ERC1155": + case "Custom": if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.regex( + contract.source, + regexOwnable, + JSON.stringify(contract.options) + ); } else { - t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.notRegex( + contract.source, + regexOwnable, + JSON.stringify(contract.options) + ); } } break; - default: + default: { const _: never = contract.options; - throw new Error('Unknown kind'); + throw new Error("Unknown kind"); + } } } }); diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index 8a1421343..2bab75c9d 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -1,99 +1,119 @@ -import test from 'ava'; +import test from "ava"; -import { toIdentifier, toByteArray, toFelt252 } from './convert-strings'; -import { OptionsError } from '../error'; +import { toIdentifier, toByteArray, toFelt252 } from "./convert-strings"; +import { OptionsError } from "../error"; -test('identifier - unmodified', t => { - t.is(toIdentifier('abc'), 'abc'); +test("identifier - unmodified", (t) => { + t.is(toIdentifier("abc"), "abc"); }); -test('identifier - remove leading specials', t => { - t.is(toIdentifier('--abc'), 'abc'); +test("identifier - remove leading specials", (t) => { + t.is(toIdentifier("--abc"), "abc"); }); -test('identifier - remove specials and upcase next char', t => { - t.is(toIdentifier('abc-def'), 'abcDef'); - t.is(toIdentifier('abc--def'), 'abcDef'); +test("identifier - remove specials and upcase next char", (t) => { + t.is(toIdentifier("abc-def"), "abcDef"); + t.is(toIdentifier("abc--def"), "abcDef"); }); -test('identifier - capitalize', t => { - t.is(toIdentifier('abc', true), 'Abc'); +test("identifier - capitalize", (t) => { + t.is(toIdentifier("abc", true), "Abc"); }); -test('identifier - remove accents', t => { - t.is(toIdentifier('ábc'), 'abc'); +test("identifier - remove accents", (t) => { + t.is(toIdentifier("ábc"), "abc"); }); -test('identifier - underscores', t => { - t.is(toIdentifier('_abc_'), '_abc_'); +test("identifier - underscores", (t) => { + t.is(toIdentifier("_abc_"), "_abc_"); }); -test('identifier - remove starting numbers', t => { - t.is(toIdentifier('123abc456'), 'abc456'); +test("identifier - remove starting numbers", (t) => { + t.is(toIdentifier("123abc456"), "abc456"); }); -test('identifier - empty string', t => { - let error = t.throws(() => toIdentifier(''), { instanceOf: OptionsError }); - t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); +test("identifier - empty string", (t) => { + const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); + t.is( + error.messages.name, + "Identifier is empty or does not have valid characters" + ); }); -test('identifier - no valid chars', t => { - let error = t.throws(() => toIdentifier('123'), { instanceOf: OptionsError }); - t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); +test("identifier - no valid chars", (t) => { + const error = t.throws(() => toIdentifier("123"), { + instanceOf: OptionsError, + }); + t.is( + error.messages.name, + "Identifier is empty or does not have valid characters" + ); }); -test('toByteArray - unmodified', t => { - t.is(toByteArray('abc'), 'abc'); +test("toByteArray - unmodified", (t) => { + t.is(toByteArray("abc"), "abc"); }); -test('toByteArray - remove accents', t => { - t.is(toByteArray('ábc'), 'abc'); +test("toByteArray - remove accents", (t) => { + t.is(toByteArray("ábc"), "abc"); }); -test('toByteArray - remove non-ascii-printable characters', t => { - t.is(toByteArray('abc😀'), 'abc'); +test("toByteArray - remove non-ascii-printable characters", (t) => { + t.is(toByteArray("abc😀"), "abc"); }); -test('toByteArray - escape double quote', t => { - t.is(toByteArray("abc\"def"), "abc\\\"def"); +test("toByteArray - escape double quote", (t) => { + t.is(toByteArray('abc"def'), 'abc\\"def'); }); -test('toByteArray - does not escape single quote', t => { +test("toByteArray - does not escape single quote", (t) => { t.is(toByteArray("abc'def"), "abc'def"); }); -test('toByteArray - escape backslash', t => { - t.is(toByteArray('abc\\def'), 'abc\\\\def'); +test("toByteArray - escape backslash", (t) => { + t.is(toByteArray("abc\\def"), "abc\\\\def"); }); -test('more than 31 characters', t => { - t.is(toByteArray('A234567890123456789012345678901'), 'A234567890123456789012345678901'); - t.is(toByteArray('A2345678901234567890123456789012'), 'A2345678901234567890123456789012'); +test("more than 31 characters", (t) => { + t.is( + toByteArray("A234567890123456789012345678901"), + "A234567890123456789012345678901" + ); + t.is( + toByteArray("A2345678901234567890123456789012"), + "A2345678901234567890123456789012" + ); }); -test('toFelt252 - unmodified', t => { - t.is(toFelt252('abc', 'foo'), 'abc'); +test("toFelt252 - unmodified", (t) => { + t.is(toFelt252("abc", "foo"), "abc"); }); -test('toFelt252 - remove accents', t => { - t.is(toFelt252('ábc', 'foo'), 'abc'); +test("toFelt252 - remove accents", (t) => { + t.is(toFelt252("ábc", "foo"), "abc"); }); -test('toFelt252 - remove non-ascii-printable characters', t => { - t.is(toFelt252('abc😀', 'foo'), 'abc'); +test("toFelt252 - remove non-ascii-printable characters", (t) => { + t.is(toFelt252("abc😀", "foo"), "abc"); }); -test('toFelt252 - escape single quote', t => { - t.is(toFelt252("abc'def", 'foo'), "abc\\'def"); +test("toFelt252 - escape single quote", (t) => { + t.is(toFelt252("abc'def", "foo"), "abc\\'def"); }); -test('toFelt252 - escape backslash', t => { - t.is(toFelt252('abc\\def', 'foo'), 'abc\\\\def'); +test("toFelt252 - escape backslash", (t) => { + t.is(toFelt252("abc\\def", "foo"), "abc\\\\def"); }); -test('toFelt252 - max 31 characters', t => { - t.is(toFelt252('A234567890123456789012345678901', 'foo'), 'A234567890123456789012345678901'); +test("toFelt252 - max 31 characters", (t) => { + t.is( + toFelt252("A234567890123456789012345678901", "foo"), + "A234567890123456789012345678901" + ); - let error = t.throws(() => toFelt252('A2345678901234567890123456789012', 'foo'), { instanceOf: OptionsError }); - t.is(error.messages.foo, 'String is longer than 31 characters'); -}); \ No newline at end of file + const error = t.throws( + () => toFelt252("A2345678901234567890123456789012", "foo"), + { instanceOf: OptionsError } + ); + t.is(error.messages.foo, "String is longer than 31 characters"); +}); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 51c6512f2..372fde3b2 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -1,23 +1,23 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { contractDefaults as commonDefaults } from './common-options'; -import { setAccessControl } from './set-access-control'; -import { setUpgradeable } from './set-upgradeable'; -import { Info, setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { printContract } from './print'; -import { OptionsError } from './error'; -import { durationToTimestamp } from './utils/duration'; -import { toUint, validateUint } from './utils/convert-strings'; - -export type VestingSchedule = 'linear' | 'custom'; +import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { setAccessControl } from "./set-access-control"; +import { setUpgradeable } from "./set-upgradeable"; +import { Info, setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { printContract } from "./print"; +import { OptionsError } from "./error"; +import { durationToTimestamp } from "./utils/duration"; +import { toUint, validateUint } from "./utils/convert-strings"; + +export type VestingSchedule = "linear" | "custom"; export const defaults: Required = { - name: 'VestingWallet', - startDate: '', - duration: '0 day', - cliffDuration: '0 day', - schedule: 'custom', - info: commonDefaults.info + name: "VestingWallet", + startDate: "", + duration: "0 day", + cliffDuration: "0 day", + schedule: "custom", + info: commonDefaults.info, } as const; export function printVesting(opts: VestingOptions = defaults): string { @@ -40,7 +40,7 @@ function withDefaults(opts: VestingOptions): Required { duration: opts.duration ?? defaults.duration, cliffDuration: opts.cliffDuration ?? defaults.cliffDuration, schedule: opts.schedule ?? defaults.schedule, - info: opts.info ?? defaults.info + info: opts.info ?? defaults.info, }; } @@ -53,7 +53,7 @@ export function buildVesting(opts: VestingOptions): Contract { setInfo(c, allOpts.info); // Vesting component depends on Ownable component - const access = 'ownable'; + const access = "ownable"; setAccessControl(c, access); // Must be non-upgradable to guarantee vesting according to the schedule @@ -63,97 +63,107 @@ export function buildVesting(opts: VestingOptions): Contract { } function addBase(c: ContractBuilder, opts: VestingOptions) { - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); const startDate = getVestingStart(opts); const totalDuration = getVestingDuration(opts); const cliffDuration = getCliffDuration(opts); validateDurations(totalDuration, cliffDuration); if (startDate !== undefined) { c.addConstant({ - name: 'START', - type: 'u64', + name: "START", + type: "u64", value: startDate.timestampInSec.toString(), comment: startDate.formattedDate, - inlineComment: true + inlineComment: true, }); } else { c.addConstant({ - name: 'START', - type: 'u64', - value: '0' + name: "START", + type: "u64", + value: "0", }); } c.addConstant({ - name: 'DURATION', - type: 'u64', + name: "DURATION", + type: "u64", value: totalDuration.toString(), comment: opts.duration, - inlineComment: true + inlineComment: true, }); c.addConstant({ - name: 'CLIFF_DURATION', - type: 'u64', + name: "CLIFF_DURATION", + type: "u64", value: cliffDuration.toString(), comment: opts.cliffDuration, - inlineComment: true + inlineComment: true, }); - const initParams = [{ lit: 'START' }, { lit: 'DURATION' }, { lit: 'CLIFF_DURATION' }]; + const initParams = [ + { lit: "START" }, + { lit: "DURATION" }, + { lit: "CLIFF_DURATION" }, + ]; c.addComponent(components.VestingComponent, initParams, true); } function addSchedule(c: ContractBuilder, opts: VestingOptions) { switch (opts.schedule) { - case 'linear': - c.addUseClause('openzeppelin::finance::vesting', 'LinearVestingSchedule'); + case "linear": + c.addUseClause("openzeppelin::finance::vesting", "LinearVestingSchedule"); return; - case 'custom': + case "custom": { const scheduleTrait: BaseImplementedTrait = { name: `VestingSchedule`, - of: 'VestingComponent::VestingScheduleTrait', + of: "VestingComponent::VestingScheduleTrait", tags: [], priority: 0, }; c.addImplementedTrait(scheduleTrait); c.addFunction(scheduleTrait, { - name: 'calculate_vested_amount', - returns: 'u256', + name: "calculate_vested_amount", + returns: "u256", args: [ - { name: 'self', type: `@VestingComponent::ComponentState` }, - { name: 'token', type: 'ContractAddress' }, - { name: 'total_allocation', type: 'u256' }, - { name: 'timestamp', type: 'u64' }, - { name: 'start', type: 'u64' }, - { name: 'duration', type: 'u64' }, - { name: 'cliff', type: 'u64' } + { + name: "self", + type: `@VestingComponent::ComponentState`, + }, + { name: "token", type: "ContractAddress" }, + { name: "total_allocation", type: "u256" }, + { name: "timestamp", type: "u64" }, + { name: "start", type: "u64" }, + { name: "duration", type: "u64" }, + { name: "cliff", type: "u64" }, ], code: [ - '// TODO: Must be implemented according to the desired vesting schedule', - '0', + "// TODO: Must be implemented according to the desired vesting schedule", + "0", ], }); return; + } } } -function getVestingStart(opts: VestingOptions): { timestampInSec: bigint, formattedDate: string } | undefined { - if (opts.startDate === '' || opts.startDate === 'NaN') { +function getVestingStart( + opts: VestingOptions +): { timestampInSec: bigint; formattedDate: string } | undefined { + if (opts.startDate === "" || opts.startDate === "NaN") { return undefined; } const startDate = new Date(`${opts.startDate}Z`); const timestampInMillis = startDate.getTime(); const timestampInSec = toUint( Math.floor(timestampInMillis / 1000), - 'startDate', - 'u64' + "startDate", + "u64" ); - const formattedDate = startDate.toLocaleString('en-GB', { - day: '2-digit', - month: 'short', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', + const formattedDate = startDate.toLocaleString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", hour12: false, - timeZone: 'UTC' + timeZone: "UTC", }); return { timestampInSec, formattedDate: `${formattedDate} (UTC)` }; } @@ -164,7 +174,7 @@ function getVestingDuration(opts: VestingOptions): number { } catch (e) { if (e instanceof Error) { throw new OptionsError({ - duration: e.message + duration: e.message, }); } else { throw e; @@ -178,7 +188,7 @@ function getCliffDuration(opts: VestingOptions): number { } catch (e) { if (e instanceof Error) { throw new OptionsError({ - cliffDuration: e.message + cliffDuration: e.message, }); } else { throw e; @@ -187,33 +197,36 @@ function getCliffDuration(opts: VestingOptions): number { } function validateDurations(duration: number, cliffDuration: number): void { - validateUint(duration, 'duration', 'u64'); - validateUint(cliffDuration, 'cliffDuration', 'u64'); + validateUint(duration, "duration", "u64"); + validateUint(cliffDuration, "cliffDuration", "u64"); if (cliffDuration > duration) { throw new OptionsError({ - cliffDuration: `Cliff duration must be less than or equal to the total duration` + cliffDuration: `Cliff duration must be less than or equal to the total duration`, }); } } const components = defineComponents({ VestingComponent: { - path: 'openzeppelin::finance::vesting', + path: "openzeppelin::finance::vesting", substorage: { - name: 'vesting', - type: 'VestingComponent::Storage', + name: "vesting", + type: "VestingComponent::Storage", }, event: { - name: 'VestingEvent', - type: 'VestingComponent::Event', + name: "VestingEvent", + type: "VestingComponent::Event", }, - impls: [{ - name: 'VestingImpl', - value: 'VestingComponent::VestingImpl' - }, { - name: 'VestingInternalImpl', - embed: false, - value: 'VestingComponent::InternalImpl', - }], - } + impls: [ + { + name: "VestingImpl", + value: "VestingComponent::VestingImpl", + }, + { + name: "VestingInternalImpl", + embed: false, + value: "VestingComponent::InternalImpl", + }, + ], + }, }); diff --git a/packages/core/solidity/src/build-generic.ts b/packages/core/solidity/src/build-generic.ts index e0b9084da..8487be2e3 100644 --- a/packages/core/solidity/src/build-generic.ts +++ b/packages/core/solidity/src/build-generic.ts @@ -1,48 +1,49 @@ -import { CustomOptions, buildCustom } from './custom'; -import { ERC20Options, buildERC20 } from './erc20'; -import { ERC721Options, buildERC721 } from './erc721'; -import { ERC1155Options, buildERC1155 } from './erc1155'; -import { StablecoinOptions, buildStablecoin } from './stablecoin'; -import { GovernorOptions, buildGovernor } from './governor'; -import { Contract } from './contract'; +import { CustomOptions, buildCustom } from "./custom"; +import { ERC20Options, buildERC20 } from "./erc20"; +import { ERC721Options, buildERC721 } from "./erc721"; +import { ERC1155Options, buildERC1155 } from "./erc1155"; +import { StablecoinOptions, buildStablecoin } from "./stablecoin"; +import { GovernorOptions, buildGovernor } from "./governor"; +import { Contract } from "./contract"; export interface KindedOptions { - ERC20: { kind: 'ERC20' } & ERC20Options; - ERC721: { kind: 'ERC721' } & ERC721Options; - ERC1155: { kind: 'ERC1155' } & ERC1155Options; - Stablecoin: { kind: 'Stablecoin' } & StablecoinOptions; - RealWorldAsset: { kind: 'RealWorldAsset' } & StablecoinOptions; - Governor: { kind: 'Governor' } & GovernorOptions; - Custom: { kind: 'Custom' } & CustomOptions; + ERC20: { kind: "ERC20" } & ERC20Options; + ERC721: { kind: "ERC721" } & ERC721Options; + ERC1155: { kind: "ERC1155" } & ERC1155Options; + Stablecoin: { kind: "Stablecoin" } & StablecoinOptions; + RealWorldAsset: { kind: "RealWorldAsset" } & StablecoinOptions; + Governor: { kind: "Governor" } & GovernorOptions; + Custom: { kind: "Custom" } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions): Contract { switch (opts.kind) { - case 'ERC20': + case "ERC20": return buildERC20(opts); - case 'ERC721': + case "ERC721": return buildERC721(opts); - case 'ERC1155': + case "ERC1155": return buildERC1155(opts); - case 'Stablecoin': + case "Stablecoin": return buildStablecoin(opts); - case 'RealWorldAsset': + case "RealWorldAsset": return buildStablecoin(opts); - case 'Governor': + case "Governor": return buildGovernor(opts); - case 'Custom': + case "Custom": return buildCustom(opts); - default: + default: { const _: never = opts; - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); + } } } diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index e7142d54f..7ac998d20 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from './utils/to-identifier'; +import { toIdentifier } from "./utils/to-identifier"; export interface Contract { name: string; @@ -13,7 +13,11 @@ export interface Contract { upgradeable: boolean; } -export type Value = string | number | { lit: string } | { note: string, value: Value }; +export type Value = + | string + | number + | { lit: string } + | { note: string; value: Value }; export interface Parent { contract: ImportContract; @@ -52,13 +56,16 @@ export interface ContractFunction extends BaseFunction { comments: string[]; } -export type FunctionKind = 'internal' | 'public'; -export type FunctionMutability = typeof mutabilityRank[number]; +export type FunctionKind = "internal" | "public"; +export type FunctionMutability = (typeof mutabilityRank)[number]; // Order is important -const mutabilityRank = ['pure', 'view', 'nonpayable', 'payable'] as const; +const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; -function maxMutability(a: FunctionMutability, b: FunctionMutability): FunctionMutability { +function maxMutability( + a: FunctionMutability, + b: FunctionMutability +): FunctionMutability { return mutabilityRank[ Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) ]!; @@ -76,7 +83,7 @@ export interface NatspecTag { export class ContractBuilder implements Contract { readonly name: string; - license: string = 'MIT'; + license: string = "MIT"; upgradeable = false; readonly using: Using[] = []; @@ -94,21 +101,23 @@ export class ContractBuilder implements Contract { } get parents(): Parent[] { - return [...this.parentMap.values()].filter(p => !p.importOnly).sort((a, b) => { - if (a.contract.name === 'Initializable') { - return -1; - } else if (b.contract.name === 'Initializable') { - return 1; - } else { - return 0; - } - }); + return [...this.parentMap.values()] + .filter((p) => !p.importOnly) + .sort((a, b) => { + if (a.contract.name === "Initializable") { + return -1; + } else if (b.contract.name === "Initializable") { + return 1; + } else { + return 0; + } + }); } get imports(): ImportContract[] { return [ - ...[...this.parentMap.values()].map(p => p.contract), - ...this.using.map(u => u.library), + ...[...this.parentMap.values()].map((p) => p.contract), + ...this.using.map((u) => u.library), ]; } @@ -128,11 +137,19 @@ export class ContractBuilder implements Contract { addImportOnly(contract: ImportContract): boolean { const present = this.parentMap.has(contract.name); - this.parentMap.set(contract.name, { contract, params: [], importOnly: true }); + this.parentMap.set(contract.name, { + contract, + params: [], + importOnly: true, + }); return !present; } - addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) { + addOverride( + parent: ReferencedContract, + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); fn.override.add(parent); if (mutability) { @@ -146,12 +163,19 @@ export class ContractBuilder implements Contract { } addNatspecTag(key: string, value: string) { - if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) throw new Error(`Invalid natspec key: ${key}`); + // eslint-disable-next-line no-useless-escape + if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) + throw new Error(`Invalid natspec key: ${key}`); this.natspecTags.push({ key, value }); } private addFunction(baseFn: BaseFunction): ContractFunction { - const signature = [baseFn.name, '(', ...baseFn.args.map(a => a.name), ')'].join(''); + const signature = [ + baseFn.name, + "(", + ...baseFn.args.map((a) => a.name), + ")", + ].join(""); const got = this.functionMap.get(signature); if (got !== undefined) { return got; @@ -160,7 +184,7 @@ export class ContractBuilder implements Contract { override: new Set(), modifiers: [], code: [], - mutability: 'nonpayable', + mutability: "nonpayable", final: false, comments: [], ...baseFn, @@ -178,7 +202,11 @@ export class ContractBuilder implements Contract { this.constructorCode.push(code); } - addFunctionCode(code: string, baseFn: BaseFunction, mutability?: FunctionMutability) { + addFunctionCode( + code: string, + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); if (fn.final) { throw new Error(`Function ${baseFn.name} is already finalized`); @@ -189,7 +217,11 @@ export class ContractBuilder implements Contract { } } - setFunctionBody(code: string[], baseFn: BaseFunction, mutability?: FunctionMutability) { + setFunctionBody( + code: string[], + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { throw new Error(`Function ${baseFn.name} has additional code`); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 09847179b..ce0b9e49c 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -1,12 +1,20 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; -import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode'; +import { ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; +import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; export interface ERC20Options extends CommonOptions { name: string; @@ -25,11 +33,11 @@ export interface ERC20Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", burnable: false, pausable: false, - premint: '0', + premint: "0", mintable: false, permit: true, votes: false, @@ -58,7 +66,7 @@ export function printERC20(opts: ERC20Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; + return opts.mintable || opts.pausable || opts.upgradeable === "uups"; } export function buildERC20(opts: ERC20Options): ContractBuilder { @@ -109,13 +117,10 @@ export function buildERC20(opts: ERC20Options): ContractBuilder { function addBase(c: ContractBuilder, name: string, symbol: string) { const ERC20 = { - name: 'ERC20', - path: '@openzeppelin/contracts/token/ERC20/ERC20.sol', + name: "ERC20", + path: "@openzeppelin/contracts/token/ERC20/ERC20.sol", }; - c.addParent( - ERC20, - [name, symbol], - ); + c.addParent(ERC20, [name, symbol]); c.addOverride(ERC20, functions._update); c.addOverride(ERC20, functions._approve); // allows override from stablecoin @@ -123,8 +128,8 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC20Pausable = { - name: 'ERC20Pausable', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', + name: "ERC20Pausable", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol", }; c.addParent(ERC20Pausable); c.addOverride(ERC20Pausable, functions._update); @@ -134,8 +139,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC20Burnable', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', + name: "ERC20Burnable", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol", }); } @@ -144,115 +149,116 @@ export const premintPattern = /^(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addPremint(c: ContractBuilder, amount: string) { const m = amount.match(premintPattern); if (m) { - const integer = m[1]?.replace(/^0+/, '') ?? ''; - const decimals = m[2]?.replace(/0+$/, '') ?? ''; + const integer = m[1]?.replace(/^0+/, "") ?? ""; + const decimals = m[2]?.replace(/0+$/, "") ?? ""; const exponent = Number(m[3] ?? 0); if (Number(integer + decimals) > 0) { const decimalPlace = decimals.length - exponent; - const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); + const zeroes = new Array(Math.max(0, -decimalPlace)).fill("0").join(""); const units = integer + decimals + zeroes; - const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; - c.addConstructorArgument({type: 'address', name: 'recipient'}); + const exp = + decimalPlace <= 0 ? "decimals()" : `(decimals() - ${decimalPlace})`; + c.addConstructorArgument({ type: "address", name: "recipient" }); c.addConstructorCode(`_mint(recipient, ${units} * 10 ** ${exp});`); } } } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); - c.addFunctionCode('_mint(to, amount);', functions.mint); + requireAccessControl(c, functions.mint, access, "MINTER", "minter"); + c.addFunctionCode("_mint(to, amount);", functions.mint); } function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { - name: 'ERC20Permit', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol', + name: "ERC20Permit", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol", }; c.addParent(ERC20Permit, [name]); c.addOverride(ERC20Permit, functions.nonces); - } function addVotes(c: ContractBuilder, clockMode: ClockMode) { - if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) { - throw new Error('Missing ERC20Permit requirement for ERC20Votes'); + if (!c.parents.some((p) => p.contract.name === "ERC20Permit")) { + throw new Error("Missing ERC20Permit requirement for ERC20Votes"); } const ERC20Votes = { - name: 'ERC20Votes', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol', + name: "ERC20Votes", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol", }; c.addParent(ERC20Votes); c.addOverride(ERC20Votes, functions._update); c.addImportOnly({ - name: 'Nonces', - path: '@openzeppelin/contracts/utils/Nonces.sol', + name: "Nonces", + path: "@openzeppelin/contracts/utils/Nonces.sol", }); - c.addOverride({ - name: 'Nonces', - }, functions.nonces); + c.addOverride( + { + name: "Nonces", + }, + functions.nonces + ); setClockMode(c, ERC20Votes, clockMode); } function addFlashMint(c: ContractBuilder) { c.addParent({ - name: 'ERC20FlashMint', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol', + name: "ERC20FlashMint", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol", }); } export const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, ], }, _approve: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'emitEvent', type: 'bool' }, + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "emitEvent", type: "bool" }, ], }, mint: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }, pause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, unpause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, snapshot: { - kind: 'public' as const, + kind: "public" as const, args: [], }, nonces: { - kind: 'public' as const, - args: [ - { name: 'owner', type: 'address' }, - ], - returns: ['uint256'], - mutability: 'view' as const, - } + kind: "public" as const, + args: [{ name: "owner", type: "address" }], + returns: ["uint256"], + mutability: "view" as const, + }, }); diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index e26603539..e37ac9562 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -1,5 +1,3 @@ -import { mapValues } from "../utils/map-values"; - type Blueprint = Record; type Alternatives = { @@ -7,7 +5,7 @@ type Alternatives = { }; export function* generateAlternatives( - blueprint: B, + blueprint: B ): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, @@ -18,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map(e => [e.key, e.values[e.current % e.limit]]), + entries.map((e) => [e.key, e.values[e.current % e.limit]]) ) as Alternatives; } diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 4591cdfdb..5f61a21e2 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -1,42 +1,46 @@ import { supportsInterface } from "./common-functions"; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from "./common-options"; -import { ContractBuilder, Contract, Value } from "./contract"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { ContractBuilder, Contract } from "./contract"; import { OptionsError } from "./error"; import { setAccessControl } from "./set-access-control"; import { printContract } from "./print"; import { setInfo } from "./set-info"; import { setUpgradeable } from "./set-upgradeable"; -import { defineFunctions } from './utils/define-functions'; +import { defineFunctions } from "./utils/define-functions"; import { durationToBlocks, durationToTimestamp } from "./utils/duration"; import { clockModeDefault, type ClockMode } from "./set-clock-mode"; export const defaults: Required = { - name: 'MyGovernor', - delay: '1 day', - period: '1 week', + name: "MyGovernor", + delay: "1 day", + period: "1 week", - votes: 'erc20votes', + votes: "erc20votes", clockMode: clockModeDefault, - timelock: 'openzeppelin', + timelock: "openzeppelin", blockTime: 12, decimals: 18, - proposalThreshold: '0', - quorumMode: 'percent', + proposalThreshold: "0", + quorumMode: "percent", quorumPercent: 4, - quorumAbsolute: '', + quorumAbsolute: "", storage: false, settings: true, - + access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; -export const votesOptions = ['erc20votes', 'erc721votes'] as const; -export type VotesOptions = typeof votesOptions[number]; +export const votesOptions = ["erc20votes", "erc721votes"] as const; +export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, 'openzeppelin', 'compound'] as const; -export type TimelockOptions = typeof timelockOptions[number]; +export const timelockOptions = [false, "openzeppelin", "compound"] as const; +export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { return printContract(buildGovernor(opts)); @@ -49,7 +53,7 @@ export interface GovernorOptions extends CommonOptions { blockTime?: number; proposalThreshold?: string; decimals?: number; - quorumMode?: 'percent' | 'absolute'; + quorumMode?: "percent" | "absolute"; quorumPercent?: number; quorumAbsolute?: string; votes?: VotesOptions; @@ -59,8 +63,10 @@ export interface GovernorOptions extends CommonOptions { settings?: boolean; } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial +): boolean { + return opts.upgradeable === "uups"; } function withDefaults(opts: GovernorOptions): Required { @@ -77,7 +83,7 @@ function withDefaults(opts: GovernorOptions): Required { quorumMode: opts.quorumMode ?? defaults.quorumMode, votes: opts.votes ?? defaults.votes, clockMode: opts.clockMode ?? defaults.clockMode, - timelock: opts.timelock ?? defaults.timelock + timelock: opts.timelock ?? defaults.timelock, }; } @@ -105,8 +111,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { function addBase(c: ContractBuilder, { name }: GovernorOptions) { const Governor = { - name: 'Governor', - path: '@openzeppelin/contracts/governance/Governor.sol', + name: "Governor", + path: "@openzeppelin/contracts/governance/Governor.sol", }; c.addParent(Governor, [name]); c.addOverride(Governor, functions.votingDelay); @@ -127,36 +133,35 @@ function addBase(c: ContractBuilder, { name }: GovernorOptions) { function addSettings(c: ContractBuilder, allOpts: Required) { if (allOpts.settings) { const GovernorSettings = { - name: 'GovernorSettings', - path: '@openzeppelin/contracts/governance/extensions/GovernorSettings.sol', + name: "GovernorSettings", + path: "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol", }; - c.addParent( - GovernorSettings, - [ - getVotingDelay(allOpts), - getVotingPeriod(allOpts), - { lit: getProposalThreshold(allOpts) }, - ], - ); - c.addOverride(GovernorSettings, functions.votingDelay, 'view'); - c.addOverride(GovernorSettings, functions.votingPeriod, 'view'); - c.addOverride(GovernorSettings, functions.proposalThreshold, 'view'); + c.addParent(GovernorSettings, [ + getVotingDelay(allOpts), + getVotingPeriod(allOpts), + { lit: getProposalThreshold(allOpts) }, + ]); + c.addOverride(GovernorSettings, functions.votingDelay, "view"); + c.addOverride(GovernorSettings, functions.votingPeriod, "view"); + c.addOverride(GovernorSettings, functions.proposalThreshold, "view"); } else { setVotingParameters(c, allOpts); setProposalThreshold(c, allOpts); } } -function getVotingDelay(opts: Required): { lit: string } | { note: string, value: number } { +function getVotingDelay( + opts: Required +): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === 'timestamp') { + if (opts.clockMode === "timestamp") { return { lit: durationToTimestamp(opts.delay), }; } else { return { value: durationToBlocks(opts.delay, opts.blockTime), - note: opts.delay + note: opts.delay, }; } } catch (e) { @@ -170,16 +175,18 @@ function getVotingDelay(opts: Required): { lit: string } | { no } } -function getVotingPeriod(opts: Required): { lit: string } | { note: string, value: number } { +function getVotingPeriod( + opts: Required +): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === 'timestamp') { + if (opts.clockMode === "timestamp") { return { lit: durationToTimestamp(opts.period), }; } else { return { value: durationToBlocks(opts.period, opts.blockTime), - note: opts.period + note: opts.period, }; } } catch (e) { @@ -196,42 +203,62 @@ function getVotingPeriod(opts: Required): { lit: string } | { n function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: 'Not a valid number', + decimals: "Not a valid number", }); } } -function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): string { +function getProposalThreshold({ + proposalThreshold, + decimals, + votes, +}: Required): string { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: 'Not a valid number', + proposalThreshold: "Not a valid number", }); } - if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { + if ( + /^0+$/.test(proposalThreshold) || + decimals === 0 || + votes === "erc721votes" + ) { return proposalThreshold; } else { return `${proposalThreshold}e${decimals}`; } } -function setVotingParameters(c: ContractBuilder, opts: Required) { +function setVotingParameters( + c: ContractBuilder, + opts: Required +) { const delayBlocks = getVotingDelay(opts); - if ('lit' in delayBlocks) { + if ("lit" in delayBlocks) { c.setFunctionBody([`return ${delayBlocks.lit};`], functions.votingDelay); } else { - c.setFunctionBody([`return ${delayBlocks.value}; // ${delayBlocks.note}`], functions.votingDelay); + c.setFunctionBody( + [`return ${delayBlocks.value}; // ${delayBlocks.note}`], + functions.votingDelay + ); } const periodBlocks = getVotingPeriod(opts); - if ('lit' in periodBlocks) { + if ("lit" in periodBlocks) { c.setFunctionBody([`return ${periodBlocks.lit};`], functions.votingPeriod); } else { - c.setFunctionBody([`return ${periodBlocks.value}; // ${periodBlocks.note}`], functions.votingPeriod); + c.setFunctionBody( + [`return ${periodBlocks.value}; // ${periodBlocks.note}`], + functions.votingPeriod + ); } } -function setProposalThreshold(c: ContractBuilder, opts: Required) { +function setProposalThreshold( + c: ContractBuilder, + opts: Required +) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -242,128 +269,143 @@ function setProposalThreshold(c: ContractBuilder, opts: Required) { - if (opts.quorumMode === 'percent') { + if (opts.quorumMode === "percent") { if (opts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); } - let { quorumFractionNumerator, quorumFractionDenominator } = getQuorumFractionComponents(opts.quorumPercent); + const { quorumFractionNumerator, quorumFractionDenominator } = + getQuorumFractionComponents(opts.quorumPercent); const GovernorVotesQuorumFraction = { - name: 'GovernorVotesQuorumFraction', - path: '@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol', + name: "GovernorVotesQuorumFraction", + path: "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol", }; if (quorumFractionDenominator !== undefined) { c.addOverride(GovernorVotesQuorumFraction, functions.quorumDenominator); - c.setFunctionBody([ - `return ${quorumFractionDenominator};` - ], functions.quorumDenominator, 'pure'); + c.setFunctionBody( + [`return ${quorumFractionDenominator};`], + functions.quorumDenominator, + "pure" + ); } c.addParent(GovernorVotesQuorumFraction, [quorumFractionNumerator]); c.addOverride(GovernorVotesQuorumFraction, functions.quorum); - } - - else if (opts.quorumMode === 'absolute') { + } else if (opts.quorumMode === "absolute") { if (!numberPattern.test(opts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: 'Not a valid number', + quorumAbsolute: "Not a valid number", }); } - let returnStatement = (opts.decimals === 0 || opts.votes === 'erc721votes') ? - `return ${opts.quorumAbsolute};` : - `return ${opts.quorumAbsolute}e${opts.decimals};`; + const returnStatement = + opts.decimals === 0 || opts.votes === "erc721votes" + ? `return ${opts.quorumAbsolute};` + : `return ${opts.quorumAbsolute}e${opts.decimals};`; - c.setFunctionBody([ - returnStatement, - ], functions.quorum, 'pure'); + c.setFunctionBody([returnStatement], functions.quorum, "pure"); } } const timelockModules = { openzeppelin: { timelockType: { - name: 'TimelockController', + name: "TimelockController", path: `@openzeppelin/contracts/governance/TimelockController.sol`, }, timelockParent: { - name: 'GovernorTimelockControl', + name: "GovernorTimelockControl", path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol`, - } + }, }, compound: { timelockType: { - name: 'ICompoundTimelock', + name: "ICompoundTimelock", path: `@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol`, transpiled: false, }, timelockParent: { - name: 'GovernorTimelockCompound', + name: "GovernorTimelockCompound", path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol`, - } + }, }, } as const; -function getQuorumFractionComponents(quorumPercent: number): {quorumFractionNumerator: number, quorumFractionDenominator: string | undefined} { +function getQuorumFractionComponents(quorumPercent: number): { + quorumFractionNumerator: number; + quorumFractionDenominator: string | undefined; +} { let quorumFractionNumerator = quorumPercent; let quorumFractionDenominator = undefined; const quorumPercentSegments = quorumPercent.toString().split("."); if (quorumPercentSegments.length > 2) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); - } else if (quorumPercentSegments.length == 2 && quorumPercentSegments[0] !== undefined && quorumPercentSegments[1] !== undefined) { - quorumFractionNumerator = parseInt(quorumPercentSegments[0].concat(quorumPercentSegments[1])); + } else if ( + quorumPercentSegments.length == 2 && + quorumPercentSegments[0] !== undefined && + quorumPercentSegments[1] !== undefined + ) { + quorumFractionNumerator = parseInt( + quorumPercentSegments[0].concat(quorumPercentSegments[1]) + ); const decimals = quorumPercentSegments[1].length; - quorumFractionDenominator = '100'; + quorumFractionDenominator = "100"; while (quorumFractionDenominator.length < decimals + 3) { - quorumFractionDenominator += '0'; + quorumFractionDenominator += "0"; } } return { quorumFractionNumerator, quorumFractionDenominator }; } -function addTimelock(c: ContractBuilder, { timelock }: Required) { +function addTimelock( + c: ContractBuilder, + { timelock }: Required +) { if (timelock === false) { return; } - const timelockArg = '_timelock'; + const timelockArg = "_timelock"; const { timelockType, timelockParent } = timelockModules[timelock]; c.addImportOnly(timelockType); @@ -384,8 +426,8 @@ function addTimelock(c: ContractBuilder, { timelock }: Required function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { if (storage) { const GovernorStorage = { - name: 'GovernorStorage', - path: '@openzeppelin/contracts/governance/extensions/GovernorStorage.sol', + name: "GovernorStorage", + path: "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol", }; c.addParent(GovernorStorage); c.addOverride(GovernorStorage, functions._propose); @@ -395,108 +437,102 @@ function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { const functions = defineFunctions({ votingDelay: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, votingPeriod: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, proposalThreshold: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, proposalNeedsQueuing: { - args: [ - { name: 'proposalId', type: 'uint256' }, - ], - returns: ['bool'], - kind: 'public', - mutability: 'view', + args: [{ name: "proposalId", type: "uint256" }], + returns: ["bool"], + kind: "public", + mutability: "view", }, quorum: { - args: [ - { name: 'blockNumber', type: 'uint256' }, - ], - returns: ['uint256'], - kind: 'public', - mutability: 'view', + args: [{ name: "blockNumber", type: "uint256" }], + returns: ["uint256"], + kind: "public", + mutability: "view", }, quorumDenominator: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'view', + returns: ["uint256"], + kind: "public", + mutability: "view", }, propose: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'description', type: 'string memory' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "description", type: "string memory" }, ], - returns: ['uint256'], - kind: 'public', + returns: ["uint256"], + kind: "public", }, _propose: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'description', type: 'string memory' }, - { name: 'proposer', type: 'address' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "description", type: "string memory" }, + { name: "proposer", type: "address" }, ], - returns: ['uint256'], - kind: 'internal', + returns: ["uint256"], + kind: "internal", }, _queueOperations: { args: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "proposalId", type: "uint256" }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - kind: 'internal', - returns: ['uint48'], + kind: "internal", + returns: ["uint48"], }, _executeOperations: { args: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "proposalId", type: "uint256" }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - kind: 'internal', + kind: "internal", }, _cancel: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - returns: ['uint256'], - kind: 'internal', + returns: ["uint256"], + kind: "internal", }, state: { - args: [ - { name: 'proposalId', type: 'uint256' }, - ], - returns: ['ProposalState'], - kind: 'public', - mutability: 'view', + args: [{ name: "proposalId", type: "uint256" }], + returns: ["ProposalState"], + kind: "public", + mutability: "view", }, _executor: { args: [], - returns: ['address'], - kind: 'internal', - mutability: 'view', + returns: ["address"], + kind: "internal", + mutability: "view", }, }); diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index 65156a973..9f30967fe 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -1,21 +1,28 @@ -import type { Contract, Parent, ContractFunction, FunctionArgument, Value, NatspecTag, ImportContract } from './contract'; -import { Options, Helpers, withHelpers } from './options'; - -import { formatLines, spaceBetween, Lines } from './utils/format-lines'; -import { mapValues } from './utils/map-values'; -import SOLIDITY_VERSION from './solidity-version.json'; -import { inferTranspiled } from './infer-transpiled'; -import { compatibleContractsSemver } from './utils/version'; +import type { + Contract, + Parent, + ContractFunction, + FunctionArgument, + Value, + NatspecTag, + ImportContract, +} from "./contract"; +import { Options, Helpers, withHelpers } from "./options"; + +import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; +import { mapValues } from "./utils/map-values"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import { inferTranspiled } from "./infer-transpiled"; +import { compatibleContractsSemver } from "./utils/version"; export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); - const fns = mapValues( - sortedFunctions(contract), - fns => fns.map(fn => printFunction(fn, helpers)), + const fns = mapValues(sortedFunctions(contract), (fns) => + fns.map((fn) => printFunction(fn, helpers)) ); - const hasOverrides = fns.override.some(l => l.length > 0); + const hasOverrides = fns.override.some((l) => l.length > 0); return formatLines( ...spaceBetween( @@ -29,61 +36,68 @@ export function printContract(contract: Contract, opts?: Options): string { [ ...printNatspecTags(contract.natspecTags), - [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '), + [ + `contract ${contract.name}`, + ...printInheritance(contract, helpers), + "{", + ].join(" "), spaceBetween( contract.variables, printConstructor(contract, helpers), ...fns.code, ...fns.modifiers, - hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], - ...fns.override, + hasOverrides + ? [`// The following functions are overrides required by Solidity.`] + : [], + ...fns.override ), `}`, - ], - ), + ] + ) ); } -function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] { +function printInheritance( + contract: Contract, + { transformName }: Helpers +): [] | [string] { if (contract.parents.length > 0) { - return ['is ' + contract.parents.map(p => transformName(p.contract)).join(', ')]; + return [ + "is " + contract.parents.map((p) => transformName(p.contract)).join(", "), + ]; } else { return []; } } function printConstructor(contract: Contract, helpers: Helpers): Lines[] { - const hasParentParams = contract.parents.some(p => p.params.length > 0); + const hasParentParams = contract.parents.some((p) => p.params.length > 0); const hasConstructorCode = contract.constructorCode.length > 0; const parentsWithInitializers = contract.parents.filter(hasInitializer); - if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) { - const parents = parentsWithInitializers - .flatMap(p => printParentConstructor(p, helpers)); - const modifiers = helpers.upgradeable ? ['initializer public'] : parents; - const args = contract.constructorArgs.map(a => printArgument(a, helpers)); + if ( + hasParentParams || + hasConstructorCode || + (helpers.upgradeable && parentsWithInitializers.length > 0) + ) { + const parents = parentsWithInitializers.flatMap((p) => + printParentConstructor(p, helpers) + ); + const modifiers = helpers.upgradeable ? ["initializer public"] : parents; + const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); const body = helpers.upgradeable ? spaceBetween( - parents.map(p => p + ';'), - contract.constructorCode, - ) + parents.map((p) => p + ";"), + contract.constructorCode + ) : contract.constructorCode; - const head = helpers.upgradeable ? 'function initialize' : 'constructor'; - const constructor = printFunction2( - [], - head, - args, - modifiers, - body, - ); + const head = helpers.upgradeable ? "function initialize" : "constructor"; + const constructor = printFunction2([], head, args, modifiers, body); if (!helpers.upgradeable) { return constructor; } else { - return spaceBetween( - DISABLE_INITIALIZERS, - constructor, - ); + return spaceBetween(DISABLE_INITIALIZERS, constructor); } } else if (!helpers.upgradeable) { return []; @@ -92,24 +106,24 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { } } -const DISABLE_INITIALIZERS = -[ - '/// @custom:oz-upgrades-unsafe-allow constructor', - 'constructor() {', - [ - '_disableInitializers();' - ], - '}' +const DISABLE_INITIALIZERS = [ + "/// @custom:oz-upgrades-unsafe-allow constructor", + "constructor() {", + ["_disableInitializers();"], + "}", ]; function hasInitializer(parent: Parent) { // CAUTION // This list is validated by compilation of SafetyCheck.sol. // Always keep this list and that file in sync. - return !['Initializable'].includes(parent.contract.name); + return !["Initializable"].includes(parent.contract.name); } -type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>; +type SortedFunctions = Record< + "code" | "modifiers" | "override", + ContractFunction[] +>; // Functions with code first, then those with modifiers, then the rest function sortedFunctions(contract: Contract): SortedFunctions { @@ -128,28 +142,29 @@ function sortedFunctions(contract: Contract): SortedFunctions { return fns; } -function printParentConstructor({ contract, params }: Parent, helpers: Helpers): [] | [string] { +function printParentConstructor( + { contract, params }: Parent, + helpers: Helpers +): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; if (useTranspiled || params.length > 0) { - return [ - fn + '(' + params.map(printValue).join(', ') + ')', - ]; + return [fn + "(" + params.map(printValue).join(", ") + ")"]; } else { return []; } } export function printValue(value: Value): string { - if (typeof value === 'object') { - if ('lit' in value) { + if (typeof value === "object") { + if ("lit" in value) { return value.lit; - } else if ('note' in value) { + } else if ("note" in value) { return `${printValue(value.value)} /* ${value.note} */`; } else { - throw Error('Unknown value type'); + throw Error("Unknown value type"); } - } else if (typeof value === 'number') { + } else if (typeof value === "number") { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { @@ -163,40 +178,49 @@ export function printValue(value: Value): string { function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { const { transformName } = helpers; - if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) { - return [] + if ( + fn.override.size <= 1 && + fn.modifiers.length === 0 && + fn.code.length === 0 && + !fn.final + ) { + return []; } const modifiers: string[] = [fn.kind, ...fn.modifiers]; - if (fn.mutability !== 'nonpayable') { + if (fn.mutability !== "nonpayable") { modifiers.splice(1, 0, fn.mutability); } if (fn.override.size === 1) { modifiers.push(`override`); } else if (fn.override.size > 1) { - modifiers.push(`override(${[...fn.override].map(transformName).join(', ')})`); + modifiers.push( + `override(${[...fn.override].map(transformName).join(", ")})` + ); } if (fn.returns?.length) { - modifiers.push(`returns (${fn.returns.join(', ')})`); + modifiers.push(`returns (${fn.returns.join(", ")})`); } const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { - const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; - code.push(fn.returns?.length ? 'return ' + superCall : superCall); + const superCall = `super.${fn.name}(${fn.args + .map((a) => a.name) + .join(", ")});`; + code.push(fn.returns?.length ? "return " + superCall : superCall); } if (modifiers.length + fn.code.length > 1) { return printFunction2( fn.comments, - 'function ' + fn.name, - fn.args.map(a => printArgument(a, helpers)), + "function " + fn.name, + fn.args.map((a) => printArgument(a, helpers)), modifiers, - code, + code ); } else { return []; @@ -205,32 +229,44 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { // generic for functions and constructors // kindedName = 'function foo' or 'constructor' -function printFunction2(comments: string[], kindedName: string, args: string[], modifiers: string[], code: Lines[]): Lines[] { - const fn: Lines[] = [ ...comments ]; +function printFunction2( + comments: string[], + kindedName: string, + args: string[], + modifiers: string[], + code: Lines[] +): Lines[] { + const fn: Lines[] = [...comments]; const headingLength = [kindedName, ...args, ...modifiers] - .map(s => s.length) + .map((s) => s.length) .reduce((a, b) => a + b); - const braces = code.length > 0 ? '{' : '{}'; + const braces = code.length > 0 ? "{" : "{}"; if (headingLength <= 72) { - fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' ')); + fn.push( + [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" ") + ); } else { - fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces); + fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); } if (code.length > 0) { - fn.push(code, '}'); + fn.push(code, "}"); } return fn; } -function printArgument(arg: FunctionArgument, { transformName }: Helpers): string { +function printArgument( + arg: FunctionArgument, + { transformName }: Helpers +): string { let type: string; - if (typeof arg.type === 'string') { + if (typeof arg.type === "string") { if (/^[A-Z]/.test(arg.type)) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions `Type ${arg.type} is not a primitive type. Define it as a ContractReference`; } type = arg.type; @@ -238,7 +274,7 @@ function printArgument(arg: FunctionArgument, { transformName }: Helpers): strin type = transformName(arg.type); } - return [type, arg.name].join(' '); + return [type, arg.name].join(" "); } function printNatspecTags(tags: NatspecTag[]): string[] { @@ -254,10 +290,12 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { }); const lines: string[] = []; - imports.map(p => { + imports.map((p) => { const importContract = helpers.transformImport(p); - lines.push(`import {${importContract.name}} from "${importContract.path}";`); + lines.push( + `import {${importContract.name}} from "${importContract.path}";` + ); }); return lines; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index 56d598de3..1d3a359b2 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,6 +1,7 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function mapValues( obj: Record, - fn: (val: V) => W, + fn: (val: V) => W ): Record { const res = {} as Record; for (const key in obj) { diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index 195322586..8f09757d5 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -2,13 +2,17 @@ import JSZip from "jszip"; import type { GenericOptions } from "./build-generic"; import type { Contract } from "./contract"; import { printContract } from "./print"; -import SOLIDITY_VERSION from './solidity-version.json'; -import { formatLinesWithSpaces, Lines, spaceBetween } from "./utils/format-lines"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import { + formatLinesWithSpaces, + Lines, + spaceBetween, +} from "./utils/format-lines"; const hardhatConfig = (upgradeable: boolean) => `\ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; -${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ''} +${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ""} const config: HardhatUserConfig = { solidity: { @@ -54,10 +58,7 @@ artifacts const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getImports(c), - getTestCase(c), - ), + ...spaceBetween(getImports(c), getTestCase(c)) ); function getTestCase(c: Contract) { @@ -73,39 +74,39 @@ const test = (c: Contract, opts?: GenericOptions) => { getAddressVariables(args), [ `const instance = await ${getDeploymentCall(c, args)};`, - 'await instance.waitForDeployment();' + "await instance.waitForDeployment();", ], - getExpects(), + getExpects() ), - '});' + "});", ], - '});', + "});", ]; } function getImports(c: Contract) { return [ 'import { expect } from "chai";', - `import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";`, + `import { ${getHardhatPlugins(c).join(", ")} } from "hardhat";`, ]; } function getExpects(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case 'ERC20': - case 'ERC721': + case "ERC20": + case "ERC721": return [`expect(await instance.name()).to.equal("${opts.name}");`]; - case 'ERC1155': + case "ERC1155": return [`expect(await instance.uri(0)).to.equal("${opts.uri}");`]; - case 'Governor': - case 'Custom': + case "Governor": + case "Custom": break; default: - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); } } return []; @@ -114,7 +115,9 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressVariables(args: string[]): Lines[] { const vars = []; for (let i = 0; i < args.length; i++) { - vars.push(`const ${args[i]} = (await ethers.getSigners())[${i}].address;`); + vars.push( + `const ${args[i]} = (await ethers.getSigners())[${i}].address;` + ); } return vars; } @@ -123,7 +126,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === 'address') { + if (constructorArg.type === "address") { args.push(constructorArg.name); } } @@ -131,22 +134,30 @@ function getAddressArgs(c: Contract): string[] { } function getDeploymentCall(c: Contract, args: string[]): string { - return c.upgradeable ? `upgrades.deployProxy(ContractFactory, [${args.join(', ')}])` : `ContractFactory.deploy(${args.join(', ')})`; + return c.upgradeable + ? `upgrades.deployProxy(ContractFactory, [${args.join(", ")}])` + : `ContractFactory.deploy(${args.join(", ")})`; } const script = (c: Contract) => { const args = getAddressArgs(c); return `\ -import { ${getHardhatPlugins(c).join(', ')} } from "hardhat"; +import { ${getHardhatPlugins(c).join(", ")} } from "hardhat"; async function main() { const ContractFactory = await ethers.getContractFactory("${c.name}"); - ${args.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''} + ${ + args.length > 0 + ? "// TODO: Set addresses for the contract arguments below" + : "" + } const instance = await ${getDeploymentCall(c, args)}; await instance.waitForDeployment(); - console.log(\`${c.upgradeable ? 'Proxy' : 'Contract'} deployed to \${await instance.getAddress()}\`); + console.log(\`${ + c.upgradeable ? "Proxy" : "Contract" + } deployed to \${await instance.getAddress()}\`); } // We recommend this pattern to be able to use async/await everywhere @@ -155,7 +166,8 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); -`}; +`; +}; const readme = `\ # Sample Hardhat Project @@ -184,9 +196,9 @@ npx hardhat run --network scripts/deploy.ts `; function getHardhatPlugins(c: Contract) { - let plugins = ['ethers']; + const plugins = ["ethers"]; if (c.upgradeable) { - plugins.push('upgrades'); + plugins.push("upgrades"); } return plugins; } @@ -194,21 +206,25 @@ function getHardhatPlugins(c: Contract) { export async function zipHardhat(c: Contract, opts?: GenericOptions) { const zip = new JSZip(); - const { default: packageJson } = c.upgradeable ? await import("./environments/hardhat/upgradeable/package.json") : await import("./environments/hardhat/package.json"); + const { default: packageJson } = c.upgradeable + ? await import("./environments/hardhat/upgradeable/package.json") + : await import("./environments/hardhat/package.json"); packageJson.license = c.license; - const { default: packageLock } = c.upgradeable ? await import("./environments/hardhat/upgradeable/package-lock.json") : await import("./environments/hardhat/package-lock.json"); - packageLock.packages[''].license = c.license; + const { default: packageLock } = c.upgradeable + ? await import("./environments/hardhat/upgradeable/package-lock.json") + : await import("./environments/hardhat/package-lock.json"); + packageLock.packages[""].license = c.license; zip.file(`contracts/${c.name}.sol`, printContract(c)); - zip.file('test/test.ts', test(c, opts)); - zip.file('scripts/deploy.ts', script(c)); - zip.file('.gitignore', gitIgnore); - zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable)); - zip.file('package.json', JSON.stringify(packageJson, null, 2)); + zip.file("test/test.ts", test(c, opts)); + zip.file("scripts/deploy.ts", script(c)); + zip.file(".gitignore", gitIgnore); + zip.file("hardhat.config.ts", hardhatConfig(c.upgradeable)); + zip.file("package.json", JSON.stringify(packageJson, null, 2)); zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2)); - zip.file('README.md', readme); - zip.file('tsconfig.json', tsConfig); + zip.file("README.md", readme); + zip.file("tsconfig.json", tsConfig); return zip; -} \ No newline at end of file +} diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index 90fff1255..fd010e506 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -1,54 +1,75 @@ -import OpenAI from 'https://esm.sh/openai@4.11.0' -import { OpenAIStream, StreamingTextResponse } from 'https://esm.sh/ai@2.2.16' -import { erc20Function, erc721Function, erc1155Function, stablecoinFunction, realWorldAssetFunction, governorFunction, customFunction } from '../src/solidity/wiz-functions.ts' -import { Redis } from 'https://esm.sh/@upstash/redis@1.25.1' +import OpenAI from "https://esm.sh/openai@4.11.0"; +import { OpenAIStream, StreamingTextResponse } from "https://esm.sh/ai@2.2.16"; +import { + erc20Function, + erc721Function, + erc1155Function, + stablecoinFunction, + realWorldAssetFunction, + governorFunction, + customFunction, +} from "../src/solidity/wiz-functions.ts"; +import { Redis } from "https://esm.sh/@upstash/redis@1.25.1"; export default async (req: Request) => { try { - const data = await req.json() - const apiKey = Deno.env.get('OPENAI_API_KEY') + const data = await req.json(); + const apiKey = Deno.env.get("OPENAI_API_KEY"); - const redisUrl = Deno.env.get('REDIS_URL') - const redisToken = Deno.env.get('REDIS_TOKEN') + const redisUrl = Deno.env.get("REDIS_URL"); + const redisToken = Deno.env.get("REDIS_TOKEN"); - if (!redisUrl || !redisToken) { throw new Error('missing redis credentials') } + if (!redisUrl || !redisToken) { + throw new Error("missing redis credentials"); + } const redis = new Redis({ - url: redisUrl, + url: redisUrl, token: redisToken, - }) + }); const openai = new OpenAI({ - apiKey: apiKey - }) + apiKey: apiKey, + }); - const validatedMessages = data.messages.filter((message: { role: string, content: string }) => { - return message.content.length < 500 - }) + const validatedMessages = data.messages.filter( + (message: { role: string; content: string }) => { + return message.content.length < 500; + } + ); - const messages = [{ - role: 'system', - content: ` + const messages = [ + { + role: "system", + content: ` You are a smart contract assistant built by OpenZeppelin to help users using OpenZeppelin Contracts Wizard. The current options are ${JSON.stringify(data.currentOpts)}. Please be kind and concise. Keep responses to <100 words. - `.trim() - }, ...validatedMessages] + `.trim(), + }, + ...validatedMessages, + ]; const response = await openai.chat.completions.create({ - model: 'gpt-4-1106-preview', + model: "gpt-4-1106-preview", messages, functions: [ - erc20Function, erc721Function, erc1155Function, stablecoinFunction, realWorldAssetFunction, governorFunction, customFunction + erc20Function, + erc721Function, + erc1155Function, + stablecoinFunction, + realWorldAssetFunction, + governorFunction, + customFunction, ], temperature: 0.7, - stream: true - }) + stream: true, + }); const stream = OpenAIStream(response, { async onCompletion(completion) { - const id = data.chatId - const updatedAt = Date.now() + const id = data.chatId; + const updatedAt = Date.now(); const payload = { id, updatedAt, @@ -56,24 +77,23 @@ export default async (req: Request) => { ...messages, { content: completion, - role: 'assistant' - } - ] - } - const exists = await redis.exists(`chat:${id}`) + role: "assistant", + }, + ], + }; + const exists = await redis.exists(`chat:${id}`); if (!exists) { - // @ts-ignore redis types seem to require [key: string] - payload.createdAt = updatedAt + // @ts-expect-error redis types seem to require [key: string] + payload.createdAt = updatedAt; } - await redis.hset(`chat:${id}`, payload) - } + await redis.hset(`chat:${id}`, payload); + }, }); return new StreamingTextResponse(stream); - } catch (e) { console.error("Could not retrieve results:", e); return Response.json({ - error: 'Could not retrieve results.' + error: "Could not retrieve results.", }); } -} +}; diff --git a/packages/ui/src/cairo/highlightjs.ts b/packages/ui/src/cairo/highlightjs.ts index c94c01084..cfc81aa15 100644 --- a/packages/ui/src/cairo/highlightjs.ts +++ b/packages/ui/src/cairo/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from 'highlight.js/lib/core'; +import hljs from "highlight.js/lib/core"; -// @ts-ignore -import hljsDefineCairo from 'highlightjs-cairo'; +// @ts-expect-error missing type declaration +import hljsDefineCairo from "highlightjs-cairo"; hljsDefineCairo(hljs); export default hljs; diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index d48acfdd3..0db04cc5d 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -1,29 +1,40 @@ +/* eslint-disable no-useless-escape */ import { contractsVersionTag } from "@openzeppelin/wizard-cairo/src"; export function injectHyperlinks(code: string) { - const importRegex = /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g + const importRegex = + /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; let result = code; let match = importRegex.exec(code); while (match != null) { const [line, libraryPrefix, libraryPath, suffix] = match; - if (line !== undefined && libraryPrefix !== undefined && libraryPath !== undefined && suffix !== undefined) { + if ( + line !== undefined && + libraryPrefix !== undefined && + libraryPath !== undefined && + suffix !== undefined + ) { const githubPrefix = `https://github.com/OpenZeppelin/cairo-contracts/blob/${contractsVersionTag}/packages/`; - let libraryPathSegments = libraryPath.split('::'); - libraryPathSegments.splice(1, 0, 'src'); + const libraryPathSegments = libraryPath.split("::"); + libraryPathSegments.splice(1, 0, "src"); if (libraryPathSegments !== undefined && libraryPathSegments.length > 0) { let replacement; - if (suffix === '::{') { + if (suffix === "::{") { // Multiple components are imported, so remove components and link to the parent .cairo file - replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link + replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported // If a mapping exists, link to the mapped file, otherwise remove the component and link to the parent .cairo file const componentName = suffix.substring(2, suffix.length); const mapping = componentMappings[componentName]; - const urlSuffix = mapping ? `/${mapping}.cairo` : '.cairo'; - replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link + const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; + replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } result = result.replace(line, replacement); @@ -35,6 +46,6 @@ export function injectHyperlinks(code: string) { } const componentMappings: { [key: string]: string } = { - 'AccountComponent': 'account', - 'UpgradeableComponent': 'upgradeable', + AccountComponent: "account", + UpgradeableComponent: "upgradeable", } as const; diff --git a/packages/ui/src/common/error-tooltip.ts b/packages/ui/src/common/error-tooltip.ts index 79b546a88..cd6b96741 100644 --- a/packages/ui/src/common/error-tooltip.ts +++ b/packages/ui/src/common/error-tooltip.ts @@ -1,15 +1,17 @@ -import tippy, { Props } from 'tippy.js'; +import tippy from "tippy.js"; -const klass = 'has-error'; +const klass = "has-error"; export function error(node: HTMLElement, content?: string) { let shown = false; const t = tippy(node, { - placement: 'right', - theme: 'light-red border', + placement: "right", + theme: "light-red border", showOnCreate: false, - onShow: () => { shown = true; }, + onShow: () => { + shown = true; + }, }); t.disable(); diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index d621a7053..6f9a3a3f0 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -1,42 +1,48 @@ -import type { SolcInputSources } from '@openzeppelin/wizard/get-imports'; +import type { SolcInputSources } from "@openzeppelin/wizard/get-imports"; -export type Message = ResizeMessage | TabChangeMessage | UnsupportedVersionMessage | DefenderDeployMessage; +export type Message = + | ResizeMessage + | TabChangeMessage + | UnsupportedVersionMessage + | DefenderDeployMessage; export interface ResizeMessage { - kind: 'oz-wizard-resize'; + kind: "oz-wizard-resize"; height: number; } export interface TabChangeMessage { - kind: 'oz-wizard-tab-change'; + kind: "oz-wizard-tab-change"; tab: string; } export interface UnsupportedVersionMessage { - kind: 'oz-wizard-unsupported-version'; + kind: "oz-wizard-unsupported-version"; } export interface DefenderDeployMessage { - kind: 'oz-wizard-defender-deploy'; + kind: "oz-wizard-defender-deploy"; sources: SolcInputSources; } export function postMessage(msg: Message) { if (parent) { - parent.postMessage(msg, '*'); + parent.postMessage(msg, "*"); } } -export function postMessageToIframe(id: 'defender-deploy', msg: Message) { - var iframe: HTMLIFrameElement | null = document.getElementById(id) as HTMLIFrameElement; +export function postMessageToIframe(id: "defender-deploy", msg: Message) { + const iframe: HTMLIFrameElement | null = document.getElementById( + id + ) as HTMLIFrameElement; if (iframe) { - iframe.contentWindow?.postMessage(msg, '*'); + iframe.contentWindow?.postMessage(msg, "*"); // in case the iframe is still loading, waits // a second to fully load and tries again iframe.onload = () => { setTimeout(() => { - iframe?.contentWindow?.postMessage(msg, '*'); + iframe?.contentWindow?.postMessage(msg, "*"); }, 1000); - } + }; } } diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index f24613e88..6e8e7ce8e 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -1,25 +1,33 @@ export function resizeToFit(node: HTMLInputElement) { const resize = () => { - if (node.value === '') { + if (node.value === "") { return; } const style = window.getComputedStyle(node); - const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); const textWidth = measureTextWidth(node.value, style); const minWidth = measureTextWidth(node.placeholder, style); - const padding = ['padding-left', 'padding-right', 'border-left-width', 'border-right-width'].map(p => style.getPropertyValue(p)); - const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join(' + ')})`; - node.style.setProperty('width', result); + const padding = [ + "padding-left", + "padding-right", + "border-left-width", + "border-right-width", + ].map((p) => style.getPropertyValue(p)); + const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( + " + " + )})`; + node.style.setProperty("width", result); }; resize(); - node.addEventListener('input', resize); + node.addEventListener("input", resize); } function measureTextWidth(text: string, style: CSSStyleDeclaration): string { - const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); - const ctx = document.createElement('canvas').getContext('2d')!; + const font = ["font-size", "font-family"] + .map((p) => style.getPropertyValue(p)) + .join(" "); + const ctx = document.createElement("canvas").getContext("2d")!; ctx.font = font; - return ctx.measureText(text).width + 'px'; + return ctx.measureText(text).width + "px"; } diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index a85ab8a81..e8afa5c09 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -1,18 +1,18 @@ -import './common/styles/global.css'; - -import type {} from 'svelte'; -import SolidityApp from './solidity/App.svelte'; -import CairoApp from './cairo/App.svelte'; -import { postMessage } from './common/post-message'; -import UnsupportedVersion from './common/UnsupportedVersion.svelte'; -import semver from 'semver'; -import { compatibleContractsSemver as compatibleSolidityContractsSemver } from '@openzeppelin/wizard'; -import { compatibleContractsSemver as compatibleCairoContractsSemver } from '@openzeppelin/wizard-cairo'; -import type { InitialOptions } from './common/initial-options.ts'; +import "./common/styles/global.css"; + +import type {} from "svelte"; +import SolidityApp from "./solidity/App.svelte"; +import CairoApp from "./cairo/App.svelte"; +import { postMessage } from "./common/post-message"; +import UnsupportedVersion from "./common/UnsupportedVersion.svelte"; +import semver from "semver"; +import { compatibleContractsSemver as compatibleSolidityContractsSemver } from "@openzeppelin/wizard"; +import { compatibleContractsSemver as compatibleCairoContractsSemver } from "@openzeppelin/wizard-cairo"; +import type { InitialOptions } from "./common/initial-options.ts"; function postResize() { const { height } = document.documentElement.getBoundingClientRect(); - postMessage({ kind: 'oz-wizard-resize', height }); + postMessage({ kind: "oz-wizard-resize", height }); } window.onload = postResize; @@ -22,37 +22,51 @@ resizeObserver.observe(document.body); const params = new URLSearchParams(window.location.search); -const initialTab = params.get('tab') ?? undefined; -const lang = params.get('lang') ?? undefined; -const requestedVersion = params.get('version') ?? undefined; +const initialTab = params.get("tab") ?? undefined; +const lang = params.get("lang") ?? undefined; +const requestedVersion = params.get("version") ?? undefined; const initialOpts: InitialOptions = { - name: params.get('name') ?? undefined, - symbol: params.get('symbol') ?? undefined, - premint: params.get('premint') ?? undefined, -} + name: params.get("name") ?? undefined, + symbol: params.get("symbol") ?? undefined, + premint: params.get("premint") ?? undefined, +}; -let compatibleVersionSemver = lang === 'cairo' ? compatibleCairoContractsSemver : compatibleSolidityContractsSemver; +const compatibleVersionSemver = + lang === "cairo" + ? compatibleCairoContractsSemver + : compatibleSolidityContractsSemver; let app; -if (requestedVersion && !semver.satisfies(requestedVersion, compatibleVersionSemver)) { - postMessage({ kind: 'oz-wizard-unsupported-version' }); - app = new UnsupportedVersion({ target: document.body, props: { requestedVersion, compatibleVersionSemver }}); +if ( + requestedVersion && + !semver.satisfies(requestedVersion, compatibleVersionSemver) +) { + postMessage({ kind: "oz-wizard-unsupported-version" }); + app = new UnsupportedVersion({ + target: document.body, + props: { requestedVersion, compatibleVersionSemver }, + }); } else { switch (lang) { - case 'cairo': - app = new CairoApp({ target: document.body, props: { initialTab, initialOpts } }); + case "cairo": + app = new CairoApp({ + target: document.body, + props: { initialTab, initialOpts }, + }); break; - case 'solidity': + case "solidity": default: - app = new SolidityApp({ target: document.body, props: { initialTab, initialOpts } }); + app = new SolidityApp({ + target: document.body, + props: { initialTab, initialOpts }, + }); break; } } -app.$on('tab-change', (e: CustomEvent) => { - postMessage({ kind: 'oz-wizard-tab-change', tab: e.detail.toLowerCase() }); +app.$on("tab-change", (e: CustomEvent) => { + postMessage({ kind: "oz-wizard-tab-change", tab: e.detail.toLowerCase() }); }); export default app; - diff --git a/packages/ui/src/solidity/highlightjs.ts b/packages/ui/src/solidity/highlightjs.ts index bbe81e6f6..b387baaa6 100644 --- a/packages/ui/src/solidity/highlightjs.ts +++ b/packages/ui/src/solidity/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from 'highlight.js/lib/core'; +import hljs from "highlight.js/lib/core"; -// @ts-ignore -import hljsDefineSolidity from 'highlightjs-solidity'; +// @ts-expect-error missing type declaration file +import hljsDefineSolidity from "highlightjs-solidity"; hljsDefineSolidity(hljs); export default hljs; diff --git a/yarn.lock b/yarn.lock index 0c990aae4..437ec947e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -28,6 +28,67 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.20.0", "@eslint/js@^9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz#a30084164a4ced1efb6ec31d3d04f581cb8929c0" + integrity sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA== + dependencies: + "@eslint/core" "^0.11.0" + levn "^0.4.1" + "@ethersproject/abi@^5.1.2": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -210,6 +271,34 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" + integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -484,6 +573,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" @@ -794,7 +888,7 @@ dependencies: cssnano "*" -"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -824,6 +918,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/lru-cache@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" @@ -843,6 +942,11 @@ dependencies: undici-types "~5.26.4" +"@types/normalize-package-data@^2.4.3": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -894,6 +998,87 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz#d104c2a6212304c649105b18af2c110b4a1dd4ae" + integrity sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/type-utils" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.1.tgz#67965c2d2ddd7eadb2f094c395695db8334ef9a2" + integrity sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ== + dependencies: + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/typescript-estree" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz#1e1e76ec4560aa85077ab36deb9b2bead4ae124e" + integrity sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q== + dependencies: + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + +"@typescript-eslint/type-utils@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz#99113e1df63d1571309d87eef68967344c78dd65" + integrity sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw== + dependencies: + "@typescript-eslint/typescript-estree" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.1.tgz#8777a024f3afc4ace5e48f9a804309c6dd38f95a" + integrity sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A== + +"@typescript-eslint/typescript-estree@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz#3bb479401f8bd471b3c6dd3db89e7256977c54db" + integrity sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg== + dependencies: + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.1.tgz#08d14eac33cfb3456feeee5a275b8ad3349e52ed" + integrity sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/typescript-estree" "8.24.1" + +"@typescript-eslint/visitor-keys@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz#8bdfe47a89195344b34eb21ef61251562148202b" + integrity sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg== + dependencies: + "@typescript-eslint/types" "8.24.1" + eslint-visitor-keys "^4.2.0" + "@vercel/nft@^0.27.5": version "0.27.6" resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.27.6.tgz#4fffb7ad4ffdb70698213cfd67596be5e9cdfb6c" @@ -922,6 +1107,11 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.1.1, acorn-walk@^8.3.4: version "8.3.4" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" @@ -929,7 +1119,7 @@ acorn-walk@^8.1.1, acorn-walk@^8.3.4: dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.13.0, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.8.2: +acorn@^8.11.0, acorn@^8.13.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.8.2: version "8.14.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== @@ -954,6 +1144,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-align@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" @@ -1262,6 +1462,16 @@ browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.3: node-releases "^2.0.18" update-browserslist-db "^1.1.1" +browserslist@^4.24.3: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -1293,6 +1503,11 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== +builtin-modules@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-4.0.0.tgz#348db54ec0e6b197494423d26845f1674025ee46" + integrity sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -1349,6 +1564,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== +caniuse-lite@^1.0.30001688: + version "1.0.30001700" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6" + integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ== + cbor@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.2.tgz#536b4f2d544411e70ec2b19a2453f10f83cd9fdb" @@ -1365,7 +1585,7 @@ chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1415,7 +1635,7 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^4.0.0: +ci-info@^4.0.0, ci-info@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== @@ -1433,6 +1653,13 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.4" safe-buffer "^5.2.1" +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -1593,6 +1820,13 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +core-js-compat@^3.40.0: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38" + integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ== + dependencies: + browserslist "^4.24.3" + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -1648,7 +1882,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1856,6 +2090,13 @@ debug@4, debug@^4.1.1, debug@^4.3.5, debug@^4.3.7: dependencies: ms "^2.1.3" +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1871,6 +2112,11 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -1990,6 +2236,11 @@ electron-to-chromium@^1.5.41: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz#8289468414b0b0b3e9180ef619a763555debe612" integrity sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg== +electron-to-chromium@^1.5.73: + version "1.5.102" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f" + integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q== + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -2120,17 +2371,138 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== +eslint-config-prettier@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz#fbb03bfc8db0651df9ce4e8b7150d11c5fe3addf" + integrity sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw== + +eslint-plugin-prettier@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz#c4af01691a6fa9905207f0fbba0d7bea0902cce5" + integrity sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-plugin-unicorn@^57.0.0: + version "57.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-57.0.0.tgz#4ae27a31e65b1a0307c09cb957f5de36b1773575" + integrity sha512-zUYYa6zfNdTeG9BISWDlcLmz16c+2Ck2o5ZDHh0UzXJz3DEP7xjmlVDTzbyV0W+XksgZ0q37WEWzN2D2Ze+g9Q== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + "@eslint-community/eslint-utils" "^4.4.1" + ci-info "^4.1.0" + clean-regexp "^1.0.0" + core-js-compat "^3.40.0" + esquery "^1.6.0" + globals "^15.15.0" + indent-string "^5.0.0" + is-builtin-module "^4.0.0" + jsesc "^3.1.0" + pluralize "^8.0.0" + read-package-up "^11.0.0" + regexp-tree "^0.1.27" + regjsparser "^0.12.0" + semver "^7.7.1" + strip-indent "^4.0.0" + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.20.1: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.5.0, esquery@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + estree-walker@2.0.2, estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== -esutils@^2.0.3: +esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -2221,7 +2593,12 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -fast-diff@^1.2.0: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2, fast-diff@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== @@ -2237,6 +2614,16 @@ fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2256,6 +2643,13 @@ figures@^6.1.0: dependencies: is-unicode-supported "^2.0.0" +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + file-saver@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" @@ -2298,11 +2692,24 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + follow-redirects@^1.12.1: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" @@ -2475,6 +2882,16 @@ glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.15.0: + version "15.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== + globby@^14.0.2: version "14.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.2.tgz#06554a54ccfe9264e5a9ff8eded46aa1e306482f" @@ -2499,6 +2916,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + hardhat@^2.1.1: version "2.22.16" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.16.tgz#6cf3413f63b14770f863f35452da891ac2bd50cb" @@ -2641,6 +3063,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" + integrity sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w== + dependencies: + lru-cache "^10.0.1" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -2677,7 +3106,7 @@ ignore-by-default@^2.1.0: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-2.1.0.tgz#c0e0de1a99b6065bdc93315a6f728867981464db" integrity sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw== -ignore@^5.2.4: +ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -2715,6 +3144,11 @@ indent-string@^5.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== +index-to-position@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-0.1.2.tgz#e11bfe995ca4d8eddb1ec43274488f3c201a7f09" + integrity sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2760,6 +3194,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-builtin-module@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-4.0.0.tgz#dfbf2080dad42d28af2bde71df7e4bc3f1dee6c0" + integrity sha512-rWP3AMAalQSesXO8gleROyL2iKU73SX5Er66losQn9rWOWL4Gef0a/xOEOVqjWGMuR2vHG3FJ8UUmT700O8oFg== + dependencies: + builtin-modules "^4.0.0" + is-callable@^1.1.3: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -2799,7 +3240,7 @@ is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2937,11 +3378,36 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsesc@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json-stream-stringify@^3.1.4: version "3.1.6" resolved "https://registry.yarnpkg.com/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz#ebe32193876fb99d4ec9f612389a8d8e2b5d54d4" @@ -2982,11 +3448,26 @@ keccak@^3.0.0, keccak@^3.0.2: node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kleur@^4.1.4: version "4.1.5" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -3054,6 +3535,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -3072,7 +3558,7 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -lru-cache@^10.2.0: +lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -3186,7 +3672,7 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== -min-indent@^1.0.0: +min-indent@^1.0.0, min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== @@ -3201,7 +3687,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3326,6 +3812,11 @@ nanoid@^3.3.7: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -3358,6 +3849,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + nofilter@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" @@ -3370,6 +3866,15 @@ nopt@^5.0.0: dependencies: abbrev "1" +normalize-package-data@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.2.tgz#a7bc22167fe24025412bcff0a9651eb768b03506" + integrity sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g== + dependencies: + hosted-git-info "^7.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -3431,6 +3936,18 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + "opts@>= 1.2.0": version "2.0.2" resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.2.tgz#a17e189fbbfee171da559edd8a42423bc5993ce1" @@ -3541,6 +4058,15 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-json@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.1.0.tgz#91cdc7728004e955af9cb734de5684733b24a717" + integrity sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA== + dependencies: + "@babel/code-frame" "^7.22.13" + index-to-position "^0.1.2" + type-fest "^4.7.1" + parse-ms@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-4.0.0.tgz#c0c058edd47c2a590151a718990533fd62803df4" @@ -3642,6 +4168,11 @@ plur@^5.1.0: dependencies: irregular-plurals "^3.3.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -4139,6 +4670,23 @@ postcss@^8.2.8, postcss@^8.4.47, postcss@^8.4.5: picocolors "^1.1.1" source-map-js "^1.2.1" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7" + integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw== + pretty-ms@^9.1.0: version "9.2.0" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-9.2.0.tgz#e14c0aad6493b69ed63114442a84133d7e560ef0" @@ -4159,6 +4707,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + query-string@^7.1.0: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -4198,6 +4751,26 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +read-package-up@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/read-package-up/-/read-package-up-11.0.0.tgz#71fb879fdaac0e16891e6e666df22de24a48d5ba" + integrity sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ== + dependencies: + find-up-simple "^1.0.0" + read-pkg "^9.0.0" + type-fest "^4.6.0" + +read-pkg@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-9.0.1.tgz#b1b81fb15104f5dbb121b6bbdee9bbc9739f569b" + integrity sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA== + dependencies: + "@types/normalize-package-data" "^2.4.3" + normalize-package-data "^6.0.0" + parse-json "^8.0.0" + type-fest "^4.6.0" + unicorn-magic "^0.1.0" + readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -4232,6 +4805,18 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4470,6 +5055,11 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -4642,6 +5232,32 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.21" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz#6d6e980c9df2b6fc905343a3b2d702a6239536c3" + integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -4794,6 +5410,13 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853" + integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA== + dependencies: + min-indent "^1.0.1" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -4918,6 +5541,14 @@ svgo@^3.3.2: csso "^5.0.5" picocolors "^1.0.0" +synckit@^0.9.1: + version "0.9.2" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62" + integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + tailwindcss@^3.0.15: version "3.4.15" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.15.tgz#04808bf4bf1424b105047d19e7d4bfab368044a9" @@ -5051,6 +5682,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" @@ -5080,7 +5716,7 @@ tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.3.1, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -5100,6 +5736,13 @@ tweetnacl@^1.0.3: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" @@ -5120,7 +5763,21 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -typescript@^5.0.0: +type-fest@^4.6.0, type-fest@^4.7.1: + version "4.35.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.35.0.tgz#007ed74d65c2ca0fb3b564b3dc8170d5c872d665" + integrity sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A== + +typescript-eslint@^8.24.1: + version "8.24.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.24.1.tgz#ce85d791392608a2a9f80c4b2530a214d16a2a47" + integrity sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.24.1" + "@typescript-eslint/parser" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + +typescript@^5.0.0, typescript@^5.7.3: version "5.7.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== @@ -5175,6 +5832,13 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -5201,6 +5865,14 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -5263,6 +5935,11 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + workerpool@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" From 7c7828ed7805fcc83cd0e9eaef4d2b91181ff994 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Thu, 20 Feb 2025 20:05:13 -0500 Subject: [PATCH 02/16] After running with prettier --- eslint.config.mjs | 2 +- packages/core/cairo/ava.config.js | 12 +- packages/core/cairo/src/account.test.ts | 145 ++--- packages/core/cairo/src/account.ts | 160 +++--- packages/core/cairo/src/add-pausable.ts | 75 +-- packages/core/cairo/src/api.ts | 84 ++- packages/core/cairo/src/common-components.ts | 102 ++-- packages/core/cairo/src/common-options.ts | 18 +- packages/core/cairo/src/contract.test.ts | 139 +++-- packages/core/cairo/src/contract.ts | 73 ++- packages/core/cairo/src/custom.test.ts | 63 +- packages/core/cairo/src/custom.ts | 24 +- packages/core/cairo/src/erc1155.test.ts | 129 +++-- packages/core/cairo/src/erc1155.ts | 8 +- packages/core/cairo/src/erc20.test.ts | 12 +- packages/core/cairo/src/erc20.ts | 10 +- packages/core/cairo/src/erc721.test.ts | 22 +- packages/core/cairo/src/erc721.ts | 251 ++++---- packages/core/cairo/src/external-trait.ts | 13 +- packages/core/cairo/src/generate/account.ts | 10 +- .../core/cairo/src/generate/alternatives.ts | 2 +- packages/core/cairo/src/generate/custom.ts | 12 +- packages/core/cairo/src/generate/erc1155.ts | 22 +- packages/core/cairo/src/generate/erc20.ts | 22 +- packages/core/cairo/src/generate/erc721.ts | 28 +- packages/core/cairo/src/generate/governor.ts | 33 +- packages/core/cairo/src/generate/sources.ts | 12 +- packages/core/cairo/src/generate/vesting.ts | 18 +- packages/core/cairo/src/governor.test.ts | 207 +++---- packages/core/cairo/src/governor.ts | 539 ++++++++++-------- packages/core/cairo/src/index.ts | 68 ++- packages/core/cairo/src/kind.ts | 25 +- packages/core/cairo/src/print.ts | 50 +- .../cairo/src/scripts/update-scarb-project.ts | 8 +- packages/core/cairo/src/set-access-control.ts | 155 +++-- packages/core/cairo/src/set-info.ts | 6 +- packages/core/cairo/src/set-royalty-info.ts | 124 ++-- packages/core/cairo/src/set-upgradeable.ts | 119 ++-- packages/core/cairo/src/test.ts | 6 +- .../cairo/src/utils/convert-strings.test.ts | 12 +- .../core/cairo/src/utils/convert-strings.ts | 63 +- .../core/cairo/src/utils/define-components.ts | 4 +- .../core/cairo/src/utils/define-functions.ts | 4 +- packages/core/cairo/src/utils/duration.ts | 18 +- packages/core/cairo/src/utils/find-cover.ts | 11 +- packages/core/cairo/src/utils/format-lines.ts | 12 +- packages/core/cairo/src/utils/version.test.ts | 14 +- packages/core/cairo/src/utils/version.ts | 10 +- packages/core/cairo/src/vesting.test.ts | 110 ++-- packages/core/cairo/src/vesting.ts | 4 +- packages/core/solidity/ava.config.js | 12 +- packages/core/solidity/get-imports.d.ts | 2 +- packages/core/solidity/get-imports.js | 2 +- packages/core/solidity/hardhat.config.js | 43 +- packages/core/solidity/print-versioned.js | 2 +- packages/core/solidity/print-versioned.ts | 2 +- packages/core/solidity/src/add-pausable.ts | 30 +- packages/core/solidity/src/api.ts | 82 ++- .../core/solidity/src/common-functions.ts | 14 +- packages/core/solidity/src/common-options.ts | 4 +- packages/core/solidity/src/contract.test.ts | 165 +++--- packages/core/solidity/src/contract.ts | 8 +- packages/core/solidity/src/custom.test.ts | 89 +-- packages/core/solidity/src/custom.ts | 23 +- packages/core/solidity/src/erc1155.test.ts | 122 ++-- packages/core/solidity/src/erc1155.ts | 107 ++-- packages/core/solidity/src/erc20.test.ts | 131 +++-- packages/core/solidity/src/erc20.ts | 2 +- packages/core/solidity/src/erc721.test.ts | 112 ++-- packages/core/solidity/src/erc721.ts | 127 +++-- .../solidity/src/generate/alternatives.ts | 4 +- packages/core/solidity/src/generate/custom.ts | 12 +- .../core/solidity/src/generate/erc1155.ts | 14 +- packages/core/solidity/src/generate/erc20.ts | 20 +- packages/core/solidity/src/generate/erc721.ts | 20 +- .../core/solidity/src/generate/governor.ts | 33 +- .../core/solidity/src/generate/sources.ts | 99 ++-- .../core/solidity/src/generate/stablecoin.ts | 26 +- .../core/solidity/src/get-imports.test.ts | 129 +++-- packages/core/solidity/src/get-imports.ts | 22 +- packages/core/solidity/src/governor.test.ts | 166 +++--- packages/core/solidity/src/governor.ts | 22 +- packages/core/solidity/src/index.ts | 40 +- .../solidity/src/infer-transpiled.test.ts | 20 +- .../core/solidity/src/infer-transpiled.ts | 2 +- packages/core/solidity/src/kind.ts | 25 +- packages/core/solidity/src/options.ts | 29 +- packages/core/solidity/src/print-versioned.ts | 15 +- packages/core/solidity/src/print.ts | 28 +- packages/core/solidity/src/scripts/prepare.ts | 59 +- .../core/solidity/src/set-access-control.ts | 74 +-- packages/core/solidity/src/set-clock-mode.ts | 37 +- packages/core/solidity/src/set-info.ts | 11 +- packages/core/solidity/src/set-upgradeable.ts | 45 +- packages/core/solidity/src/stablecoin.test.ts | 122 ++-- packages/core/solidity/src/stablecoin.ts | 133 +++-- packages/core/solidity/src/test.ts | 83 +-- .../solidity/src/utils/define-functions.ts | 4 +- packages/core/solidity/src/utils/duration.ts | 31 +- .../core/solidity/src/utils/find-cover.ts | 11 +- .../core/solidity/src/utils/format-lines.ts | 17 +- .../core/solidity/src/utils/map-values.ts | 2 +- .../solidity/src/utils/to-identifier.test.ts | 22 +- .../core/solidity/src/utils/to-identifier.ts | 7 +- .../solidity/src/utils/transitive-closure.ts | 4 +- .../core/solidity/src/utils/version.test.ts | 16 +- packages/core/solidity/src/utils/version.ts | 2 +- .../core/solidity/src/zip-foundry.test.ts | 160 ++++-- packages/core/solidity/src/zip-foundry.ts | 184 +++--- .../core/solidity/src/zip-hardhat.test.ts | 144 +++-- packages/core/solidity/src/zip-hardhat.ts | 6 +- packages/core/solidity/zip-env-foundry.js | 2 +- packages/core/solidity/zip-env-foundry.ts | 2 +- packages/core/solidity/zip-env-hardhat.js | 2 +- packages/core/solidity/zip-env-hardhat.ts | 2 +- packages/ui/api/ai.ts | 2 +- packages/ui/postcss.config.js | 12 +- packages/ui/rollup.config.mjs | 92 +-- packages/ui/rollup.server.mjs | 18 +- packages/ui/src/cairo/inject-hyperlinks.ts | 4 +- packages/ui/src/common/initial-options.ts | 6 +- packages/ui/src/common/post-config.ts | 29 +- packages/ui/src/common/post-message.ts | 2 +- packages/ui/src/common/resize-to-fit.ts | 2 +- packages/ui/src/embed.ts | 69 ++- packages/ui/src/solidity/inject-hyperlinks.ts | 18 +- packages/ui/src/solidity/remix.ts | 6 +- packages/ui/src/solidity/wiz-functions.ts | 313 +++++++--- packages/ui/src/standalone.js | 2 +- packages/ui/svelte.config.js | 2 +- packages/ui/tailwind.config.js | 43 +- scripts/bump-changelog.js | 19 +- 132 files changed, 3810 insertions(+), 2886 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 7a17e8293..e5715f264 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -58,5 +58,5 @@ export default typescriptEslint.config( rules: { "@typescript-eslint/no-require-imports": "off", }, - } + }, ); diff --git a/packages/core/cairo/ava.config.js b/packages/core/cairo/ava.config.js index 77a9117a9..a39075959 100644 --- a/packages/core/cairo/ava.config.js +++ b/packages/core/cairo/ava.config.js @@ -1,13 +1,9 @@ module.exports = { - extensions: ['ts'], - require: ['ts-node/register'], + extensions: ["ts"], + require: ["ts-node/register"], watchmode: { - ignoreChanges: [ - 'contracts', - 'artifacts', - 'cache', - ], + ignoreChanges: ["contracts", "artifacts", "cache"], }, - timeout: '10m', + timeout: "10m", workerThreads: false, }; diff --git a/packages/core/cairo/src/account.test.ts b/packages/core/cairo/src/account.test.ts index 217e1d813..4b4097e5c 100644 --- a/packages/core/cairo/src/account.test.ts +++ b/packages/core/cairo/src/account.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; +import test from "ava"; -import { buildAccount, AccountOptions } from './account'; -import { printContract } from './print'; +import { buildAccount, AccountOptions } from "./account"; +import { printContract } from "./print"; -import { account } from '.'; +import { account } from "."; function testAccount(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildAccount({ - name: 'MyAccount', - type: 'stark', + name: "MyAccount", + type: "stark", ...opts, }); t.snapshot(printContract(c)); @@ -17,10 +17,10 @@ function testAccount(title: string, opts: Partial) { } function testEthAccount(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildAccount({ - name: 'MyAccount', - type: 'eth', + name: "MyAccount", + type: "eth", ...opts, }); t.snapshot(printContract(c)); @@ -31,184 +31,189 @@ function testEthAccount(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: AccountOptions) { - test(title, t => { - t.is(account.print(opts), printContract(buildAccount({ - name: 'MyAccount', - type: 'stark', - declare: true, - deploy: true, - pubkey: true, - outsideExecution: true, - ...opts, - }))); + test(title, (t) => { + t.is( + account.print(opts), + printContract( + buildAccount({ + name: "MyAccount", + type: "stark", + declare: true, + deploy: true, + pubkey: true, + outsideExecution: true, + ...opts, + }), + ), + ); }); } -testAccount('default full account, mixin + upgradeable', {}); +testAccount("default full account, mixin + upgradeable", {}); -testAccount('default full account, mixin + non-upgradeable', { - upgradeable: false +testAccount("default full account, mixin + non-upgradeable", { + upgradeable: false, }); -testAccount('explicit full account, mixin + upgradeable', { - name: 'MyAccount', - type: 'stark', +testAccount("explicit full account, mixin + upgradeable", { + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: true + upgradeable: true, }); -testAccount('explicit full account, mixin + non-upgradeable', { - name: 'MyAccount', - type: 'stark', +testAccount("explicit full account, mixin + non-upgradeable", { + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: false + upgradeable: false, }); -testAccount('basic account, upgradeable', { +testAccount("basic account, upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testAccount('basic account, non-upgradeable', { +testAccount("basic account, non-upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, - upgradeable: false + upgradeable: false, }); -testAccount('account outside execution', { +testAccount("account outside execution", { deploy: false, pubkey: false, declare: false, }); -testAccount('account declarer', { +testAccount("account declarer", { deploy: false, pubkey: false, outsideExecution: false, }); -testAccount('account deployable', { +testAccount("account deployable", { declare: false, pubkey: false, outsideExecution: false, }); -testAccount('account public key', { +testAccount("account public key", { declare: false, deploy: false, outsideExecution: false, }); -testAccount('account declarer, deployable', { +testAccount("account declarer, deployable", { pubkey: false, outsideExecution: false, }); -testAccount('account declarer, public key', { +testAccount("account declarer, public key", { deploy: false, outsideExecution: false, }); -testAccount('account deployable, public key', { +testAccount("account deployable, public key", { declare: false, outsideExecution: false, }); -testEthAccount('default full ethAccount, mixin + upgradeable', {}); +testEthAccount("default full ethAccount, mixin + upgradeable", {}); -testEthAccount('default full ethAccount, mixin + non-upgradeable', { - upgradeable: false +testEthAccount("default full ethAccount, mixin + non-upgradeable", { + upgradeable: false, }); -testEthAccount('explicit full ethAccount, mixin + upgradeable', { - name: 'MyAccount', - type: 'eth', +testEthAccount("explicit full ethAccount, mixin + upgradeable", { + name: "MyAccount", + type: "eth", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: true + upgradeable: true, }); -testEthAccount('explicit full ethAccount, mixin + non-upgradeable', { - name: 'MyAccount', - type: 'eth', +testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { + name: "MyAccount", + type: "eth", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: false + upgradeable: false, }); -testEthAccount('basic ethAccount, upgradeable', { +testEthAccount("basic ethAccount, upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount('basic ethAccount, non-upgradeable', { +testEthAccount("basic ethAccount, non-upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, - upgradeable: false + upgradeable: false, }); -testEthAccount('ethAccount outside execution', { +testEthAccount("ethAccount outside execution", { deploy: false, pubkey: false, declare: false, }); -testEthAccount('ethAccount declarer', { +testEthAccount("ethAccount declarer", { deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount deployable', { +testEthAccount("ethAccount deployable", { declare: false, pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount public key', { +testEthAccount("ethAccount public key", { declare: false, deploy: false, outsideExecution: false, }); -testEthAccount('ethAccount declarer, deployable', { +testEthAccount("ethAccount declarer, deployable", { pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount declarer, public key', { +testEthAccount("ethAccount declarer, public key", { deploy: false, outsideExecution: false, }); -testEthAccount('ethAccount deployable, public key', { +testEthAccount("ethAccount deployable, public key", { declare: false, outsideExecution: false, }); -testAPIEquivalence('account API default'); +testAPIEquivalence("account API default"); -testAPIEquivalence('account API basic', { - name: 'CustomAccount', - type: 'stark', +testAPIEquivalence("account API basic", { + name: "CustomAccount", + type: "stark", declare: false, deploy: false, pubkey: false, @@ -216,9 +221,9 @@ testAPIEquivalence('account API basic', { upgradeable: false, }); -testAPIEquivalence('account API full upgradeable', { - name: 'CustomAccount', - type: 'stark', +testAPIEquivalence("account API full upgradeable", { + name: "CustomAccount", + type: "stark", declare: true, deploy: true, pubkey: true, @@ -226,6 +231,6 @@ testAPIEquivalence('account API full upgradeable', { upgradeable: true, }); -test('account API assert defaults', async t => { +test("account API assert defaults", async (t) => { t.is(account.print(account.defaults), account.print()); }); diff --git a/packages/core/cairo/src/account.ts b/packages/core/cairo/src/account.ts index 68914729e..5f1659de9 100644 --- a/packages/core/cairo/src/account.ts +++ b/packages/core/cairo/src/account.ts @@ -1,25 +1,24 @@ -import { Contract, ContractBuilder } from './contract'; -import { CommonOptions, withCommonDefaults } from './common-options'; -import { defaults as commonDefaults } from './common-options'; -import { setAccountUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { printContract } from './print'; -import { addSRC5Component } from './common-components'; - - -export const accountTypes = ['stark', 'eth'] as const; -export type Account = typeof accountTypes[number]; +import { Contract, ContractBuilder } from "./contract"; +import { CommonOptions, withCommonDefaults } from "./common-options"; +import { defaults as commonDefaults } from "./common-options"; +import { setAccountUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { printContract } from "./print"; +import { addSRC5Component } from "./common-components"; + +export const accountTypes = ["stark", "eth"] as const; +export type Account = (typeof accountTypes)[number]; export const defaults: Required = { - name: 'MyAccount', - type: 'stark', + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printAccount(opts: AccountOptions = defaults): string { @@ -42,8 +41,8 @@ function withDefaults(opts: AccountOptions): Required { declare: opts.declare ?? defaults.declare, deploy: opts.deploy ?? defaults.deploy, pubkey: opts.pubkey ?? defaults.pubkey, - outsideExecution: opts.outsideExecution ?? defaults.outsideExecution - } + outsideExecution: opts.outsideExecution ?? defaults.outsideExecution, + }; } export function buildAccount(opts: AccountOptions): Contract { @@ -53,14 +52,22 @@ export function buildAccount(opts: AccountOptions): Contract { const allOpts = withDefaults(opts); switch (allOpts.type) { - case 'stark': - c.addConstructorArgument({ name: 'public_key', type: 'felt252' }); - c.addComponent(components.AccountComponent, [{ lit: 'public_key' }], true); + case "stark": + c.addConstructorArgument({ name: "public_key", type: "felt252" }); + c.addComponent( + components.AccountComponent, + [{ lit: "public_key" }], + true, + ); break; - case 'eth': - c.addUseClause('openzeppelin::account::interface', 'EthPublicKey'); - c.addConstructorArgument({ name: 'public_key', type: 'EthPublicKey' }); - c.addComponent(components.EthAccountComponent, [{ lit: 'public_key' }], true); + case "eth": + c.addUseClause("openzeppelin::account::interface", "EthPublicKey"); + c.addConstructorArgument({ name: "public_key", type: "EthPublicKey" }); + c.addComponent( + components.EthAccountComponent, + [{ lit: "public_key" }], + true, + ); break; } @@ -96,11 +103,11 @@ function addSRC6(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'SRC6Impl', + name: "SRC6Impl", value: `${baseComponent}::SRC6Impl`, }); c.addImplToComponent(componentType, { - name: 'SRC6CamelOnlyImpl', + name: "SRC6CamelOnlyImpl", value: `${baseComponent}::SRC6CamelOnlyImpl`, }); @@ -111,7 +118,7 @@ function addDeclarer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'DeclarerImpl', + name: "DeclarerImpl", value: `${baseComponent}::DeclarerImpl`, }); } @@ -120,7 +127,7 @@ function addDeployer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'DeployableImpl', + name: "DeployableImpl", value: `${baseComponent}::DeployableImpl`, }); } @@ -129,22 +136,23 @@ function addPublicKey(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'PublicKeyImpl', + name: "PublicKeyImpl", value: `${baseComponent}::PublicKeyImpl`, }); c.addImplToComponent(componentType, { - name: 'PublicKeyCamelImpl', + name: "PublicKeyCamelImpl", value: `${baseComponent}::PublicKeyCamelImpl`, }); } function addOutsideExecution(c: ContractBuilder) { - c.addUseClause('openzeppelin::account::extensions', 'SRC9Component'); + c.addUseClause("openzeppelin::account::extensions", "SRC9Component"); c.addComponent(components.SRC9Component, [], true); } function addAccountMixin(c: ContractBuilder, accountType: Account) { - const accountMixinImpl = accountType === 'stark' ? 'AccountMixinImpl' : 'EthAccountMixinImpl'; + const accountMixinImpl = + accountType === "stark" ? "AccountMixinImpl" : "EthAccountMixinImpl"; const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { @@ -152,65 +160,77 @@ function addAccountMixin(c: ContractBuilder, accountType: Account) { value: `${baseComponent}::${accountMixinImpl}`, }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } -function getBaseCompAndCompType(accountType: Account): [string, typeof componentType] { - const [baseComponent, componentType] = accountType === 'stark' ? ['AccountComponent', components.AccountComponent] : ['EthAccountComponent', components.EthAccountComponent]; +function getBaseCompAndCompType( + accountType: Account, +): [string, typeof componentType] { + const [baseComponent, componentType] = + accountType === "stark" + ? ["AccountComponent", components.AccountComponent] + : ["EthAccountComponent", components.EthAccountComponent]; return [baseComponent, componentType]; } -const components = defineComponents( { +const components = defineComponents({ AccountComponent: { - path: 'openzeppelin::account', + path: "openzeppelin::account", substorage: { - name: 'account', - type: 'AccountComponent::Storage', + name: "account", + type: "AccountComponent::Storage", }, event: { - name: 'AccountEvent', - type: 'AccountComponent::Event', + name: "AccountEvent", + type: "AccountComponent::Event", }, - impls: [{ - name: 'AccountInternalImpl', - embed: false, - value: 'AccountComponent::InternalImpl', - }], + impls: [ + { + name: "AccountInternalImpl", + embed: false, + value: "AccountComponent::InternalImpl", + }, + ], }, EthAccountComponent: { - path: 'openzeppelin::account::eth_account', + path: "openzeppelin::account::eth_account", substorage: { - name: 'eth_account', - type: 'EthAccountComponent::Storage', + name: "eth_account", + type: "EthAccountComponent::Storage", }, event: { - name: 'EthAccountEvent', - type: 'EthAccountComponent::Event', + name: "EthAccountEvent", + type: "EthAccountComponent::Event", }, - impls: [{ - name: 'EthAccountInternalImpl', - embed: false, - value: 'EthAccountComponent::InternalImpl', - }] + impls: [ + { + name: "EthAccountInternalImpl", + embed: false, + value: "EthAccountComponent::InternalImpl", + }, + ], }, SRC9Component: { - path: 'openzeppelin::account::extensions', + path: "openzeppelin::account::extensions", substorage: { - name: 'src9', - type: 'SRC9Component::Storage', + name: "src9", + type: "SRC9Component::Storage", }, event: { - name: 'SRC9Event', - type: 'SRC9Component::Event', + name: "SRC9Event", + type: "SRC9Component::Event", }, - impls: [{ - name: 'OutsideExecutionV2Impl', - value: 'SRC9Component::OutsideExecutionV2Impl', - }, { - name: 'OutsideExecutionInternalImpl', - embed: false, - value: 'SRC9Component::InternalImpl', - }] - } + impls: [ + { + name: "OutsideExecutionV2Impl", + value: "SRC9Component::OutsideExecutionV2Impl", + }, + { + name: "OutsideExecutionInternalImpl", + embed: false, + value: "SRC9Component::InternalImpl", + }, + ], + }, }); diff --git a/packages/core/cairo/src/add-pausable.ts b/packages/core/cairo/src/add-pausable.ts index 1e950f98e..4b33dd23b 100644 --- a/packages/core/cairo/src/add-pausable.ts +++ b/packages/core/cairo/src/add-pausable.ts @@ -1,58 +1,65 @@ -import { getSelfArg } from './common-options'; -import type { ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; -import { defineComponents } from './utils/define-components'; -import { externalTrait } from './external-trait'; +import { getSelfArg } from "./common-options"; +import type { ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; +import { defineComponents } from "./utils/define-components"; +import { externalTrait } from "./external-trait"; export function addPausable(c: ContractBuilder, access: Access) { c.addComponent(components.PausableComponent, [], false); c.addFunction(externalTrait, functions.pause); c.addFunction(externalTrait, functions.unpause); - requireAccessControl(c, externalTrait, functions.pause, access, 'PAUSER', 'pauser'); - requireAccessControl(c, externalTrait, functions.unpause, access, 'PAUSER', 'pauser'); + requireAccessControl( + c, + externalTrait, + functions.pause, + access, + "PAUSER", + "pauser", + ); + requireAccessControl( + c, + externalTrait, + functions.unpause, + access, + "PAUSER", + "pauser", + ); } -const components = defineComponents( { +const components = defineComponents({ PausableComponent: { - path: 'openzeppelin::security::pausable', + path: "openzeppelin::security::pausable", substorage: { - name: 'pausable', - type: 'PausableComponent::Storage', + name: "pausable", + type: "PausableComponent::Storage", }, event: { - name: 'PausableEvent', - type: 'PausableComponent::Event', + name: "PausableEvent", + type: "PausableComponent::Event", }, - impls: [{ - name: 'PausableImpl', - value: 'PausableComponent::PausableImpl', - }, { - name: 'PausableInternalImpl', + impls: [ + { + name: "PausableImpl", + value: "PausableComponent::PausableImpl", + }, + { + name: "PausableInternalImpl", embed: false, - value: 'PausableComponent::InternalImpl', - } + value: "PausableComponent::InternalImpl", + }, ], }, }); const functions = defineFunctions({ pause: { - args: [ - getSelfArg(), - ], - code: [ - 'self.pausable.pause()' - ] + args: [getSelfArg()], + code: ["self.pausable.pause()"], }, unpause: { - args: [ - getSelfArg(), - ], - code: [ - 'self.pausable.unpause()' - ] + args: [getSelfArg()], + code: ["self.pausable.unpause()"], }, }); - diff --git a/packages/core/cairo/src/api.ts b/packages/core/cairo/src/api.ts index a70fc497d..67bca280b 100644 --- a/packages/core/cairo/src/api.ts +++ b/packages/core/cairo/src/api.ts @@ -1,13 +1,45 @@ -import type { CommonOptions, CommonContractOptions } from './common-options'; -import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; -import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; -import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; -import { printAccount, defaults as accountDefaults, AccountOptions } from './account'; -import { printGovernor, defaults as governorDefaults, GovernorOptions } from './governor'; -import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; -import { printVesting, defaults as vestingDefaults, VestingOptions } from './vesting'; +import type { CommonOptions, CommonContractOptions } from "./common-options"; +import { + printERC20, + defaults as erc20defaults, + isAccessControlRequired as erc20IsAccessControlRequired, + ERC20Options, +} from "./erc20"; +import { + printERC721, + defaults as erc721defaults, + isAccessControlRequired as erc721IsAccessControlRequired, + ERC721Options, +} from "./erc721"; +import { + printERC1155, + defaults as erc1155defaults, + isAccessControlRequired as erc1155IsAccessControlRequired, + ERC1155Options, +} from "./erc1155"; +import { + printAccount, + defaults as accountDefaults, + AccountOptions, +} from "./account"; +import { + printGovernor, + defaults as governorDefaults, + GovernorOptions, +} from "./governor"; +import { + printCustom, + defaults as customDefaults, + isAccessControlRequired as customIsAccessControlRequired, + CustomOptions, +} from "./custom"; +import { + printVesting, + defaults as vestingDefaults, + VestingOptions, +} from "./vesting"; -export interface WizardAccountAPI{ +export interface WizardAccountAPI { /** * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. */ @@ -33,49 +65,53 @@ export interface WizardContractAPI { export interface AccessControlAPI { /** - * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the + * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. */ isAccessControlRequired: (opts: Partial) => boolean; } -export type ERC20 = WizardContractAPI & AccessControlAPI; -export type ERC721 = WizardContractAPI & AccessControlAPI; -export type ERC1155 = WizardContractAPI & AccessControlAPI; +export type ERC20 = WizardContractAPI & + AccessControlAPI; +export type ERC721 = WizardContractAPI & + AccessControlAPI; +export type ERC1155 = WizardContractAPI & + AccessControlAPI; export type Account = WizardAccountAPI; export type Governor = WizardContractAPI; export type Vesting = WizardContractAPI; -export type Custom = WizardContractAPI & AccessControlAPI; +export type Custom = WizardContractAPI & + AccessControlAPI; export const erc20: ERC20 = { print: printERC20, defaults: erc20defaults, isAccessControlRequired: erc20IsAccessControlRequired, -} +}; export const erc721: ERC721 = { print: printERC721, defaults: erc721defaults, - isAccessControlRequired: erc721IsAccessControlRequired -} + isAccessControlRequired: erc721IsAccessControlRequired, +}; export const erc1155: ERC1155 = { print: printERC1155, defaults: erc1155defaults, - isAccessControlRequired: erc1155IsAccessControlRequired -} + isAccessControlRequired: erc1155IsAccessControlRequired, +}; export const account: Account = { print: printAccount, defaults: accountDefaults, -} +}; export const governor: Governor = { print: printGovernor, defaults: governorDefaults, -} +}; export const vesting: Vesting = { print: printVesting, defaults: vestingDefaults, -} +}; export const custom: Custom = { print: printCustom, defaults: customDefaults, - isAccessControlRequired: customIsAccessControlRequired -} + isAccessControlRequired: customIsAccessControlRequired, +}; diff --git a/packages/core/cairo/src/common-components.ts b/packages/core/cairo/src/common-components.ts index a05776fdd..90615a056 100644 --- a/packages/core/cairo/src/common-components.ts +++ b/packages/core/cairo/src/common-components.ts @@ -1,88 +1,100 @@ -import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; import { defineComponents } from "./utils/define-components"; -export const tokenTypes = ['ERC20', 'ERC721'] as const; -export type Token = typeof tokenTypes[number]; +export const tokenTypes = ["ERC20", "ERC721"] as const; +export type Token = (typeof tokenTypes)[number]; -const components = defineComponents( { +const components = defineComponents({ SRC5Component: { - path: 'openzeppelin::introspection::src5', + path: "openzeppelin::introspection::src5", substorage: { - name: 'src5', - type: 'SRC5Component::Storage', + name: "src5", + type: "SRC5Component::Storage", }, event: { - name: 'SRC5Event', - type: 'SRC5Component::Event', + name: "SRC5Event", + type: "SRC5Component::Event", }, impls: [], }, VotesComponent: { - path: 'openzeppelin::governance::votes', + path: "openzeppelin::governance::votes", substorage: { - name: 'votes', - type: 'VotesComponent::Storage', + name: "votes", + type: "VotesComponent::Storage", }, event: { - name: 'VotesEvent', - type: 'VotesComponent::Event', + name: "VotesEvent", + type: "VotesComponent::Event", }, - impls: [{ - name: 'VotesInternalImpl', - embed: false, - value: 'VotesComponent::InternalImpl', - }], + impls: [ + { + name: "VotesInternalImpl", + embed: false, + value: "VotesComponent::InternalImpl", + }, + ], }, NoncesComponent: { - path: 'openzeppelin::utils::cryptography::nonces', + path: "openzeppelin::utils::cryptography::nonces", substorage: { - name: 'nonces', - type: 'NoncesComponent::Storage', + name: "nonces", + type: "NoncesComponent::Storage", }, event: { - name: 'NoncesEvent', - type: 'NoncesComponent::Event', + name: "NoncesEvent", + type: "NoncesComponent::Event", }, impls: [ { - name: 'NoncesImpl', - value: 'NoncesComponent::NoncesImpl', + name: "NoncesImpl", + value: "NoncesComponent::NoncesImpl", }, ], }, -}) +}); export function addSRC5Component(c: ContractBuilder, section?: string) { c.addComponent(components.SRC5Component, [], false); - if (!c.interfaceFlags.has('ISRC5')) { + if (!c.interfaceFlags.has("ISRC5")) { c.addImplToComponent(components.SRC5Component, { - name: 'SRC5Impl', - value: 'SRC5Component::SRC5Impl', + name: "SRC5Impl", + value: "SRC5Component::SRC5Impl", section, }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); } } -export function addVotesComponent(c: ContractBuilder, name: string, version: string, section?: string) { +export function addVotesComponent( + c: ContractBuilder, + name: string, + version: string, + section?: string, +) { addSNIP12Metadata(c, name, version, section); c.addComponent(components.NoncesComponent, [], false); c.addComponent(components.VotesComponent, [], false); c.addImplToComponent(components.VotesComponent, { - name: 'VotesImpl', + name: "VotesImpl", value: `VotesComponent::VotesImpl`, }); } -export function addSNIP12Metadata(c: ContractBuilder, name: string, version: string, section?: string) { - c.addUseClause('openzeppelin::utils::cryptography::snip12', 'SNIP12Metadata'); +export function addSNIP12Metadata( + c: ContractBuilder, + name: string, + version: string, + section?: string, +) { + c.addUseClause("openzeppelin::utils::cryptography::snip12", "SNIP12Metadata"); const SNIP12Metadata: BaseImplementedTrait = { - name: 'SNIP12MetadataImpl', - of: 'SNIP12Metadata', + name: "SNIP12MetadataImpl", + of: "SNIP12Metadata", tags: [], priority: 0, section, @@ -90,20 +102,16 @@ export function addSNIP12Metadata(c: ContractBuilder, name: string, version: str c.addImplementedTrait(SNIP12Metadata); c.addFunction(SNIP12Metadata, { - name: 'name', + name: "name", args: [], - returns: 'felt252', - code: [ - `'${name}'`, - ], + returns: "felt252", + code: [`'${name}'`], }); c.addFunction(SNIP12Metadata, { - name: 'version', + name: "version", args: [], - returns: 'felt252', - code: [ - `'${version}'`, - ], + returns: "felt252", + code: [`'${version}'`], }); } diff --git a/packages/core/cairo/src/common-options.ts b/packages/core/cairo/src/common-options.ts index 067ea317a..de54d727c 100644 --- a/packages/core/cairo/src/common-options.ts +++ b/packages/core/cairo/src/common-options.ts @@ -23,24 +23,28 @@ export interface CommonContractOptions extends CommonOptions { access?: Access; } -export function withCommonDefaults(opts: CommonOptions): Required { +export function withCommonDefaults( + opts: CommonOptions, +): Required { return { upgradeable: opts.upgradeable ?? defaults.upgradeable, info: opts.info ?? defaults.info, }; } -export function withCommonContractDefaults(opts: CommonContractOptions): Required { +export function withCommonContractDefaults( + opts: CommonContractOptions, +): Required { return { ...withCommonDefaults(opts), access: opts.access ?? contractDefaults.access, }; } -export function getSelfArg(scope: 'external' | 'view' = 'external'): Argument { - if (scope === 'view') { - return { name: 'self', type: '@ContractState' }; +export function getSelfArg(scope: "external" | "view" = "external"): Argument { + if (scope === "view") { + return { name: "self", type: "@ContractState" }; } else { - return { name: 'ref self', type: 'ContractState' }; + return { name: "ref self", type: "ContractState" }; } -} \ No newline at end of file +} diff --git a/packages/core/cairo/src/contract.test.ts b/packages/core/cairo/src/contract.test.ts index 2dcaae2aa..c6351dcc2 100644 --- a/packages/core/cairo/src/contract.test.ts +++ b/packages/core/cairo/src/contract.test.ts @@ -1,123 +1,112 @@ -import test from 'ava'; +import test from "ava"; -import { ContractBuilder, BaseFunction, BaseImplementedTrait, Component } from './contract'; -import { printContract } from './print'; +import { + ContractBuilder, + BaseFunction, + BaseImplementedTrait, + Component, +} from "./contract"; +import { printContract } from "./print"; const FOO_COMPONENT: Component = { - name: 'FooComponent', - path: 'some::path', + name: "FooComponent", + path: "some::path", substorage: { - name: 'foo', - type: 'FooComponent::Storage', + name: "foo", + type: "FooComponent::Storage", }, event: { - name: 'FooEvent', - type: 'FooComponent::Event', + name: "FooEvent", + type: "FooComponent::Event", }, - impls: [{ - name: 'FooImpl', - value: 'FooComponent::FooImpl', - }, { - name: 'FooInternalImpl', + impls: [ + { + name: "FooImpl", + value: "FooComponent::FooImpl", + }, + { + name: "FooInternalImpl", embed: false, - value: 'FooComponent::InternalImpl', - } + value: "FooComponent::InternalImpl", + }, ], }; -test('contract basics', t => { - const Foo = new ContractBuilder('Foo'); +test("contract basics", (t) => { + const Foo = new ContractBuilder("Foo"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('someFunction()'); +test("contract with constructor code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("someFunction()"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code with semicolon', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('someFunction();'); +test("contract with constructor code with semicolon", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("someFunction();"); t.snapshot(printContract(Foo)); }); -test('contract with function code before', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with function code before", (t) => { + const Foo = new ContractBuilder("Foo"); const trait: BaseImplementedTrait = { - name: 'External', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', + name: "External", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: 'someFunction', + name: "someFunction", args: [], - code: [ - 'someFunction()' - ] + code: ["someFunction()"], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, 'before()'); + Foo.addFunctionCodeBefore(trait, fn, "before()"); t.snapshot(printContract(Foo)); }); -test('contract with function code before with semicolons', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with function code before with semicolons", (t) => { + const Foo = new ContractBuilder("Foo"); const trait: BaseImplementedTrait = { - name: 'External', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', + name: "External", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: 'someFunction', + name: "someFunction", args: [], - code: [ - 'someFunction();' - ] + code: ["someFunction();"], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, 'before();'); + Foo.addFunctionCodeBefore(trait, fn, "before();"); t.snapshot(printContract(Foo)); }); -test('contract with initializer params', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with initializer params", (t) => { + const Foo = new ContractBuilder("Foo"); - Foo.addComponent( - FOO_COMPONENT, - ['param1'], - true - ); + Foo.addComponent(FOO_COMPONENT, ["param1"], true); t.snapshot(printContract(Foo)); }); -test('contract with standalone import', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addComponent( - FOO_COMPONENT, - ); - Foo.addUseClause('some::library', 'SomeLibrary'); +test("contract with standalone import", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addComponent(FOO_COMPONENT); + Foo.addUseClause("some::library", "SomeLibrary"); t.snapshot(printContract(Foo)); }); -test('contract with sorted use clauses', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addComponent( - FOO_COMPONENT, - ); - Foo.addUseClause('some::library', 'SomeLibrary'); - Foo.addUseClause('another::library', 'AnotherLibrary'); - Foo.addUseClause('another::library', 'Foo', { alias: 'Custom2' }); - Foo.addUseClause('another::library', 'Foo', { alias: 'Custom1' }); +test("contract with sorted use clauses", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addComponent(FOO_COMPONENT); + Foo.addUseClause("some::library", "SomeLibrary"); + Foo.addUseClause("another::library", "AnotherLibrary"); + Foo.addUseClause("another::library", "Foo", { alias: "Custom2" }); + Foo.addUseClause("another::library", "Foo", { alias: "Custom1" }); t.snapshot(printContract(Foo)); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/contract.ts b/packages/core/cairo/src/contract.ts index b7f77d6a6..b876ee2ee 100644 --- a/packages/core/cairo/src/contract.ts +++ b/packages/core/cairo/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from './utils/convert-strings'; +import { toIdentifier } from "./utils/convert-strings"; export interface Contract { license: string; @@ -14,7 +14,12 @@ export interface Contract { superVariables: Variable[]; } -export type Value = string | number | bigint | { lit: string } | { note: string, value: Value }; +export type Value = + | string + | number + | bigint + | { lit: string } + | { note: string; value: Value }; export interface UseClause { containerPath: string; @@ -100,7 +105,7 @@ export interface Argument { export class ContractBuilder implements Contract { readonly name: string; readonly account: boolean; - license = 'MIT'; + license = "MIT"; upgradeable = false; readonly constructorArgs: Argument[] = []; @@ -145,24 +150,41 @@ export class ContractBuilder implements Contract { return this.interfaceFlagsSet; } - addUseClause(containerPath: string, name: string, options?: { groupable?: boolean, alias?: string }): void { + addUseClause( + containerPath: string, + name: string, + options?: { groupable?: boolean; alias?: string }, + ): void { // groupable defaults to true const groupable = options?.groupable ?? true; - const alias = options?.alias ?? ''; + const alias = options?.alias ?? ""; const uniqueName = alias.length > 0 ? alias : name; const present = this.useClausesMap.has(uniqueName); if (!present) { - this.useClausesMap.set(uniqueName, { containerPath, name, groupable, alias }); + this.useClausesMap.set(uniqueName, { + containerPath, + name, + groupable, + alias, + }); } } - addComponent(component: Component, params: Value[] = [], initializable: boolean = true): boolean { + addComponent( + component: Component, + params: Value[] = [], + initializable: boolean = true, + ): boolean { this.addUseClause(component.path, component.name); const key = component.name; const present = this.componentsMap.has(key); if (!present) { const initializer = initializable ? { params } : undefined; - const cp: Component = { initializer, ...component, impls: [ ...component.impls ] }; // spread impls to deep copy from original component + const cp: Component = { + initializer, + ...component, + impls: [...component.impls], + }; // spread impls to deep copy from original component this.componentsMap.set(key, cp); } return !present; @@ -175,7 +197,7 @@ export class ContractBuilder implements Contract { throw new Error(`Component ${component.name} has not been added yet`); } - if (!c.impls.some(i => i.name === impl.name)) { + if (!c.impls.some((i) => i.name === impl.name)) { c.impls.push(impl); } } @@ -194,7 +216,7 @@ export class ContractBuilder implements Contract { return false; } else { this.superVariablesMap.set(variable.name, variable); - this.addUseClause('super', variable.name); + this.addUseClause("super", variable.name); return true; } } @@ -208,7 +230,7 @@ export class ContractBuilder implements Contract { const t: ImplementedTrait = { name: baseTrait.name, of: baseTrait.of, - tags: [ ...baseTrait.tags ], + tags: [...baseTrait.tags], superVariables: [], functions: [], section: baseTrait.section, @@ -219,18 +241,21 @@ export class ContractBuilder implements Contract { } } - addSuperVariableToTrait(baseTrait: BaseImplementedTrait, newVar: Variable): boolean { + addSuperVariableToTrait( + baseTrait: BaseImplementedTrait, + newVar: Variable, + ): boolean { const trait = this.addImplementedTrait(baseTrait); for (const existingVar of trait.superVariables) { if (existingVar.name === newVar.name) { if (existingVar.type !== newVar.type) { throw new Error( - `Tried to add duplicate super var ${newVar.name} with different type: ${newVar.type} instead of ${existingVar.type}.` + `Tried to add duplicate super var ${newVar.name} with different type: ${newVar.type} instead of ${existingVar.type}.`, ); } if (existingVar.value !== newVar.value) { throw new Error( - `Tried to add duplicate super var ${newVar.name} with different value: ${newVar.value} instead of ${existingVar.value}.` + `Tried to add duplicate super var ${newVar.name} with different value: ${newVar.value} instead of ${existingVar.value}.`, ); } return false; // No need to add, already exists @@ -240,7 +265,10 @@ export class ContractBuilder implements Contract { return true; } - addFunction(baseTrait: BaseImplementedTrait, fn: BaseFunction): ContractFunction { + addFunction( + baseTrait: BaseImplementedTrait, + fn: BaseFunction, + ): ContractFunction { const t = this.addImplementedTrait(baseTrait); const signature = this.getFunctionSignature(fn); @@ -248,7 +276,10 @@ export class ContractBuilder implements Contract { // Look for the existing function with the same signature and return it if found for (let i = 0; i < t.functions.length; i++) { const existingFn = t.functions[i]; - if (existingFn !== undefined && this.getFunctionSignature(existingFn) === signature) { + if ( + existingFn !== undefined && + this.getFunctionSignature(existingFn) === signature + ) { return existingFn; } } @@ -264,13 +295,17 @@ export class ContractBuilder implements Contract { } private getFunctionSignature(fn: BaseFunction): string { - return [fn.name, '(', ...fn.args.map(a => a.name), ')'].join(''); + return [fn.name, "(", ...fn.args.map((a) => a.name), ")"].join(""); } - addFunctionCodeBefore(baseTrait: BaseImplementedTrait, fn: BaseFunction, codeBefore: string): void { + addFunctionCodeBefore( + baseTrait: BaseImplementedTrait, + fn: BaseFunction, + codeBefore: string, + ): void { this.addImplementedTrait(baseTrait); const existingFn = this.addFunction(baseTrait, fn); - existingFn.codeBefore = [ ...existingFn.codeBefore ?? [], codeBefore ]; + existingFn.codeBefore = [...(existingFn.codeBefore ?? []), codeBefore]; } addConstructorArgument(arg: Argument): void { diff --git a/packages/core/cairo/src/custom.test.ts b/packages/core/cairo/src/custom.test.ts index ae5b28cf0..85b3fb9be 100644 --- a/packages/core/cairo/src/custom.test.ts +++ b/packages/core/cairo/src/custom.test.ts @@ -1,13 +1,13 @@ -import test from 'ava'; -import { custom } from '.'; +import test from "ava"; +import { custom } from "."; -import { buildCustom, CustomOptions } from './custom'; -import { printContract } from './print'; +import { buildCustom, CustomOptions } from "./custom"; +import { printContract } from "./print"; function testCustom(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildCustom({ - name: 'MyContract', + name: "MyContract", ...opts, }); t.snapshot(printContract(c)); @@ -17,63 +17,68 @@ function testCustom(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, t => { - t.is(custom.print(opts), printContract(buildCustom({ - name: 'MyContract', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: CustomOptions) { + test(title, (t) => { + t.is( + custom.print(opts), + printContract( + buildCustom({ + name: "MyContract", + ...opts, + }), + ), + ); }); } -testCustom('custom non-upgradeable', { +testCustom("custom non-upgradeable", { upgradeable: false, }); -testCustom('custom defaults', {}); +testCustom("custom defaults", {}); -testCustom('pausable', { +testCustom("pausable", { pausable: true, }); -testCustom('upgradeable', { +testCustom("upgradeable", { upgradeable: true, }); -testCustom('access control disabled', { +testCustom("access control disabled", { upgradeable: false, access: false, }); -testCustom('access control ownable', { - access: 'ownable', +testCustom("access control ownable", { + access: "ownable", }); -testCustom('access control roles', { - access: 'roles', +testCustom("access control roles", { + access: "roles", }); -testCustom('pausable with access control disabled', { +testCustom("pausable with access control disabled", { // API should override access to true since it is required for pausable access: false, pausable: true, upgradeable: false, }); -testAPIEquivalence('custom API default'); +testAPIEquivalence("custom API default"); -testAPIEquivalence('custom API full upgradeable', { - name: 'CustomContract', - access: 'roles', +testAPIEquivalence("custom API full upgradeable", { + name: "CustomContract", + access: "roles", pausable: true, upgradeable: true, }); -test('custom API assert defaults', async t => { +test("custom API assert defaults", async (t) => { t.is(custom.print(custom.defaults), custom.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(custom.isAccessControlRequired({ pausable: true }), true); t.is(custom.isAccessControlRequired({ upgradeable: true }), true); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/custom.ts b/packages/core/cairo/src/custom.ts index 7f361415f..a97ea2c7a 100644 --- a/packages/core/cairo/src/custom.ts +++ b/packages/core/cairo/src/custom.ts @@ -1,18 +1,21 @@ -import { Contract, ContractBuilder } from './contract'; -import { setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { CommonContractOptions, withCommonContractDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { setAccessControl } from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { + CommonContractOptions, + withCommonContractDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; export const defaults: Required = { - name: 'MyContract', + name: "MyContract", pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printCustom(opts: CustomOptions = defaults): string { @@ -51,4 +54,3 @@ export function buildCustom(opts: CustomOptions): Contract { return c; } - diff --git a/packages/core/cairo/src/erc1155.test.ts b/packages/core/cairo/src/erc1155.test.ts index 9923de2d5..55f9b240c 100644 --- a/packages/core/cairo/src/erc1155.test.ts +++ b/packages/core/cairo/src/erc1155.test.ts @@ -1,24 +1,25 @@ -import test from 'ava'; -import { erc1155 } from '.'; +import test from "ava"; +import { erc1155 } from "."; -import { buildERC1155, ERC1155Options } from './erc1155'; -import { printContract } from './print'; -import { royaltyInfoOptions } from './set-royalty-info'; +import { buildERC1155, ERC1155Options } from "./erc1155"; +import { printContract } from "./print"; +import { royaltyInfoOptions } from "./set-royalty-info"; -const NAME = 'MyToken'; -const CUSTOM_NAME = 'CustomToken'; -const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; +const NAME = "MyToken"; +const CUSTOM_NAME = "CustomToken"; +const BASE_URI = + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; const allFeaturesON: Partial = { mintable: true, burnable: true, pausable: true, royaltyInfo: royaltyInfoOptions.enabledDefault, - upgradeable: true + upgradeable: true, } as const; function testERC1155(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC1155({ name: NAME, baseUri: BASE_URI, @@ -32,105 +33,129 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, t => { - t.is(erc1155.print(opts), printContract(buildERC1155({ - name: NAME, - baseUri: '', - ...opts, - }))); + test(title, (t) => { + t.is( + erc1155.print(opts), + printContract( + buildERC1155({ + name: NAME, + baseUri: "", + ...opts, + }), + ), + ); }); } -testERC1155('basic non-upgradeable', { +testERC1155("basic non-upgradeable", { upgradeable: false, }); -testERC1155('basic', {}); +testERC1155("basic", {}); -testERC1155('basic + roles', { - access: 'roles', +testERC1155("basic + roles", { + access: "roles", }); -testERC1155('no updatable uri', { +testERC1155("no updatable uri", { updatableUri: false, }); -testERC1155('burnable', { +testERC1155("burnable", { burnable: true, }); -testERC1155('pausable', { +testERC1155("pausable", { pausable: true, }); -testERC1155('mintable', { +testERC1155("mintable", { mintable: true, }); -testERC1155('mintable + roles', { +testERC1155("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC1155('royalty info disabled', { - royaltyInfo: royaltyInfoOptions.disabled +testERC1155("royalty info disabled", { + royaltyInfo: royaltyInfoOptions.disabled, }); -testERC1155('royalty info enabled default + ownable', { +testERC1155("royalty info enabled default + ownable", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'ownable' + access: "ownable", }); -testERC1155('royalty info enabled default + roles', { +testERC1155("royalty info enabled default + roles", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'roles' + access: "roles", }); -testERC1155('royalty info enabled custom + ownable', { +testERC1155("royalty info enabled custom + ownable", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'ownable' + access: "ownable", }); -testERC1155('royalty info enabled custom + roles', { +testERC1155("royalty info enabled custom + roles", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'roles' + access: "roles", }); -testERC1155('full non-upgradeable', { +testERC1155("full non-upgradeable", { ...allFeaturesON, - access: 'roles', + access: "roles", upgradeable: false, }); -testERC1155('full upgradeable', { +testERC1155("full upgradeable", { ...allFeaturesON, - access: 'roles', + access: "roles", upgradeable: true, }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: CUSTOM_NAME, baseUri: BASE_URI }); +testAPIEquivalence("API basic", { name: CUSTOM_NAME, baseUri: BASE_URI }); -testAPIEquivalence('API full upgradeable', { +testAPIEquivalence("API full upgradeable", { ...allFeaturesON, name: CUSTOM_NAME, baseUri: BASE_URI, - access: 'roles', + access: "roles", upgradeable: true, }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test('API isAccessControlRequired', async t => { - t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), true); +test("API isAccessControlRequired", async (t) => { + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); - t.is(erc1155.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); - t.is(erc1155.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); + t.is( + erc1155.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledDefault, + }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledCustom, + }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: false }), false); t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index 7d5eda31a..59baee0c8 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -65,7 +65,7 @@ function withDefaults(opts: ERC1155Options): Required { } export function isAccessControlRequired( - opts: Partial + opts: Partial, ): boolean { return ( opts.mintable === true || @@ -173,7 +173,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.mint, access, "MINTER", - "minter" + "minter", ); requireAccessControl( c, @@ -181,7 +181,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.batch_mint, access, "MINTER", - "minter" + "minter", ); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. @@ -195,7 +195,7 @@ function addSetBaseUri(c: ContractBuilder, access: Access) { functions.set_base_uri, access, "URI_SETTER", - "uri_setter" + "uri_setter", ); // Camel case version of set_base_uri. Access control is already set on set_base_uri. diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 479c598f4..798659d7b 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -28,8 +28,8 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { name: "MyToken", symbol: "MTK", ...opts, - }) - ) + }), + ), ); }); } @@ -94,11 +94,11 @@ test("erc20 votes, no name", async (t) => { name: "MyToken", symbol: "MTK", votes: true, - }) + }), ); t.is( (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled" + "Application Name is required when Votes are enabled", ); }); @@ -110,11 +110,11 @@ test("erc20 votes, empty version", async (t) => { votes: true, appName: "MY_DAPP_NAME", appVersion: "", // avoids default value of v1 - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 8dbe4cd14..3334cd78b 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -135,7 +135,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { beforeUpdateFn.code.push( "let contract_state = self.get_contract();", - "contract_state.pausable.assert_not_paused();" + "contract_state.pausable.assert_not_paused();", ); } @@ -156,7 +156,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { c, toFelt252(allOpts.appName, "appName"), toFelt252(allOpts.appVersion, "appVersion"), - "SNIP12 Metadata" + "SNIP12 Metadata", ); const afterUpdateFn = c.addFunction(hooksTrait, { @@ -175,7 +175,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { afterUpdateFn.code.push( "let mut contract_state = self.get_contract_mut();", - "contract_state.votes.transfer_voting_units(from, recipient, amount);" + "contract_state.votes.transfer_voting_units(from, recipient, amount);", ); } } else { @@ -212,7 +212,7 @@ function addPremint(c: ContractBuilder, amount: string) { const premintAbsolute = toUint( getInitialSupply(amount, 18), "premint", - "u256" + "u256", ); c.addUseClause("starknet", "ContractAddress"); @@ -270,7 +270,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.mint, access, "MINTER", - "minter" + "minter", ); } diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index c2819b953..7f260e482 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -50,8 +50,8 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { name: NAME, symbol: SYMBOL, ...opts, - }) - ) + }), + ), ); }); } @@ -138,11 +138,11 @@ test("erc721 votes, no name", async (t) => { name: NAME, symbol: SYMBOL, votes: true, - }) + }), ); t.is( (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled" + "Application Name is required when Votes are enabled", ); }); @@ -154,11 +154,11 @@ test("erc721 votes, no version", async (t) => { votes: true, appName: APP_NAME, appVersion: "", - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); @@ -170,11 +170,11 @@ test("erc721 votes, empty version", async (t) => { votes: true, appName: APP_NAME, appVersion: "", // avoids default value of v1 - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); @@ -208,19 +208,19 @@ test("API isAccessControlRequired", async (t) => { erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault, }), - true + true, ); t.is( erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom, }), - true + true, ); t.is( erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.disabled, }), - false + false, ); t.is(erc721.isAccessControlRequired({ burnable: true }), false); t.is(erc721.isAccessControlRequired({ enumerable: true }), false); diff --git a/packages/core/cairo/src/erc721.ts b/packages/core/cairo/src/erc721.ts index 6b23e1c28..12f7ecd34 100644 --- a/packages/core/cairo/src/erc721.ts +++ b/packages/core/cairo/src/erc721.ts @@ -1,34 +1,46 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { addSRC5Component, addVotesComponent } from './common-components'; -import { externalTrait } from './external-trait'; -import { toByteArray, toFelt252 } from './utils/convert-strings'; -import { OptionsError } from './error'; -import { RoyaltyInfoOptions, setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; +import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { addSRC5Component, addVotesComponent } from "./common-components"; +import { externalTrait } from "./external-trait"; +import { toByteArray, toFelt252 } from "./utils/convert-strings"; +import { OptionsError } from "./error"; +import { + RoyaltyInfoOptions, + setRoyaltyInfo, + defaults as royaltyInfoDefaults, +} from "./set-royalty-info"; export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', - baseUri: '', + name: "MyToken", + symbol: "MTK", + baseUri: "", burnable: false, pausable: false, mintable: false, enumerable: false, votes: false, royaltyInfo: royaltyInfoDefaults, - appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: 'v1', + appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: "v1", access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC721(opts: ERC721Options = defaults): string { @@ -61,12 +73,17 @@ function withDefaults(opts: ERC721Options): Required { royaltyInfo: opts.royaltyInfo ?? defaults.royaltyInfo, votes: opts.votes ?? defaults.votes, appName: opts.appName ?? defaults.appName, - appVersion: opts.appVersion ?? defaults.appVersion + appVersion: opts.appVersion ?? defaults.appVersion, }; } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; + return ( + opts.mintable === true || + opts.pausable === true || + opts.upgradeable === true || + opts.royaltyInfo?.enabled === true + ); } export function buildERC721(opts: ERC721Options): Contract { @@ -74,7 +91,12 @@ export function buildERC721(opts: ERC721Options): Contract { const allOpts = withDefaults(opts); - addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.baseUri)); + addBase( + c, + toByteArray(allOpts.name), + toByteArray(allOpts.symbol), + toByteArray(allOpts.baseUri), + ); addERC721Mixin(c); if (allOpts.pausable) { @@ -97,7 +119,7 @@ export function buildERC721(opts: ERC721Options): Contract { setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); - + addHooks(c, allOpts); return c; @@ -108,78 +130,84 @@ function addHooks(c: ContractBuilder, opts: Required) { if (usesCustomHooks) { const ERC721HooksTrait: BaseImplementedTrait = { name: `ERC721HooksImpl`, - of: 'ERC721Component::ERC721HooksTrait', + of: "ERC721Component::ERC721HooksTrait", tags: [], priority: 0, }; c.addImplementedTrait(ERC721HooksTrait); - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); const requiresMutState = opts.enumerable || opts.votes; const initStateLine = requiresMutState - ? 'let mut contract_state = self.get_contract_mut()' - : 'let contract_state = self.get_contract()'; + ? "let mut contract_state = self.get_contract_mut()" + : "let contract_state = self.get_contract()"; const beforeUpdateCode = [initStateLine]; if (opts.pausable) { - beforeUpdateCode.push('contract_state.pausable.assert_not_paused()'); + beforeUpdateCode.push("contract_state.pausable.assert_not_paused()"); } if (opts.enumerable) { - beforeUpdateCode.push('contract_state.erc721_enumerable.before_update(to, token_id)'); + beforeUpdateCode.push( + "contract_state.erc721_enumerable.before_update(to, token_id)", + ); } if (opts.votes) { - if (!opts.appName) { - throw new OptionsError({ - appName: 'Application Name is required when Votes are enabled', - }); - } + if (!opts.appName) { + throw new OptionsError({ + appName: "Application Name is required when Votes are enabled", + }); + } - if (!opts.appVersion) { - throw new OptionsError({ - appVersion: 'Application Version is required when Votes are enabled', - }); - } + if (!opts.appVersion) { + throw new OptionsError({ + appVersion: "Application Version is required when Votes are enabled", + }); + } addVotesComponent( c, - toFelt252(opts.appName, 'appName'), - toFelt252(opts.appVersion, 'appVersion'), - 'SNIP12 Metadata', + toFelt252(opts.appName, "appName"), + toFelt252(opts.appVersion, "appVersion"), + "SNIP12 Metadata", + ); + beforeUpdateCode.push("let previous_owner = self._owner_of(token_id);"); + beforeUpdateCode.push( + "contract_state.votes.transfer_voting_units(previous_owner, to, 1);", ); - beforeUpdateCode.push('let previous_owner = self._owner_of(token_id);'); - beforeUpdateCode.push('contract_state.votes.transfer_voting_units(previous_owner, to, 1);'); } c.addFunction(ERC721HooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: `ERC721Component::ComponentState` }, - { name: 'to', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'auth', type: 'ContractAddress' }, + { + name: "ref self", + type: `ERC721Component::ComponentState`, + }, + { name: "to", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "auth", type: "ContractAddress" }, ], code: beforeUpdateCode, }); } else { - c.addUseClause('openzeppelin::token::erc721', 'ERC721HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc721", "ERC721HooksEmptyImpl"); } } function addERC721Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC721Component, { - name: 'ERC721MixinImpl', - value: 'ERC721Component::ERC721MixinImpl', + name: "ERC721MixinImpl", + value: "ERC721Component::ERC721MixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } -function addBase(c: ContractBuilder, name: string, symbol: string, baseUri: string) { - c.addComponent( - components.ERC721Component, - [ - name, symbol, baseUri, - ], - true, - ); +function addBase( + c: ContractBuilder, + name: string, + symbol: string, + baseUri: string, +) { + c.addComponent(components.ERC721Component, [name, symbol, baseUri], true); } function addEnumerable(c: ContractBuilder) { @@ -187,88 +215,91 @@ function addEnumerable(c: ContractBuilder) { } function addBurnable(c: ContractBuilder) { - c.addUseClause('core::num::traits', 'Zero'); - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("core::num::traits", "Zero"); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.safe_mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.safe_mint, + access, + "MINTER", + "minter", + ); // Camel case version of safe_mint. Access control and pausable are already set on safe_mint. c.addFunction(externalTrait, functions.safeMint); } -const components = defineComponents( { +const components = defineComponents({ ERC721Component: { - path: 'openzeppelin::token::erc721', + path: "openzeppelin::token::erc721", substorage: { - name: 'erc721', - type: 'ERC721Component::Storage', + name: "erc721", + type: "ERC721Component::Storage", }, event: { - name: 'ERC721Event', - type: 'ERC721Component::Event', + name: "ERC721Event", + type: "ERC721Component::Event", }, - impls: [{ - name: 'ERC721InternalImpl', - embed: false, - value: 'ERC721Component::InternalImpl', - }], + impls: [ + { + name: "ERC721InternalImpl", + embed: false, + value: "ERC721Component::InternalImpl", + }, + ], }, ERC721EnumerableComponent: { - path: 'openzeppelin::token::erc721::extensions', + path: "openzeppelin::token::erc721::extensions", substorage: { - name: 'erc721_enumerable', - type: 'ERC721EnumerableComponent::Storage', + name: "erc721_enumerable", + type: "ERC721EnumerableComponent::Storage", }, event: { - name: 'ERC721EnumerableEvent', - type: 'ERC721EnumerableComponent::Event', + name: "ERC721EnumerableEvent", + type: "ERC721EnumerableComponent::Event", }, - impls: [{ - name: 'ERC721EnumerableImpl', - value: 'ERC721EnumerableComponent::ERC721EnumerableImpl', - }, { - name: 'ERC721EnumerableInternalImpl', - embed: false, - value: 'ERC721EnumerableComponent::InternalImpl', - }], + impls: [ + { + name: "ERC721EnumerableImpl", + value: "ERC721EnumerableComponent::ERC721EnumerableImpl", + }, + { + name: "ERC721EnumerableInternalImpl", + embed: false, + value: "ERC721EnumerableComponent::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ burn: { - args: [ - getSelfArg(), - { name: 'token_id', type: 'u256' } - ], - code: [ - 'self.erc721.update(Zero::zero(), token_id, get_caller_address());', - ] + args: [getSelfArg(), { name: "token_id", type: "u256" }], + code: ["self.erc721.update(Zero::zero(), token_id, get_caller_address());"], }, safe_mint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "recipient", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.erc721.safe_mint(recipient, token_id, data);', - ] + code: ["self.erc721.safe_mint(recipient, token_id, data);"], }, safeMint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'tokenId', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "recipient", type: "ContractAddress" }, + { name: "tokenId", type: "u256" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.safe_mint(recipient, tokenId, data);', - ] + code: ["self.safe_mint(recipient, tokenId, data);"], }, }); diff --git a/packages/core/cairo/src/external-trait.ts b/packages/core/cairo/src/external-trait.ts index ed7e99e45..04e671d6d 100644 --- a/packages/core/cairo/src/external-trait.ts +++ b/packages/core/cairo/src/external-trait.ts @@ -1,11 +1,8 @@ import type { BaseImplementedTrait } from "./contract"; export const externalTrait: BaseImplementedTrait = { - name: 'ExternalImpl', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', -} + name: "ExternalImpl", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", +}; diff --git a/packages/core/cairo/src/generate/account.ts b/packages/core/cairo/src/generate/account.ts index 175ba8df9..b61bf2056 100644 --- a/packages/core/cairo/src/generate/account.ts +++ b/packages/core/cairo/src/generate/account.ts @@ -1,12 +1,12 @@ -import { accountTypes, type AccountOptions } from '../account'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { accountTypes, type AccountOptions } from "../account"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyAccount'], + name: ["MyAccount"], type: accountTypes, declare: booleans, deploy: booleans, diff --git a/packages/core/cairo/src/generate/alternatives.ts b/packages/core/cairo/src/generate/alternatives.ts index afd89ab67..c4b282064 100644 --- a/packages/core/cairo/src/generate/alternatives.ts +++ b/packages/core/cairo/src/generate/alternatives.ts @@ -16,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map(e => [e.key, e.values[e.current % e.limit]]), + entries.map((e) => [e.key, e.values[e.current % e.limit]]), ) as Alternatives; } diff --git a/packages/core/cairo/src/generate/custom.ts b/packages/core/cairo/src/generate/custom.ts index 40207666a..884032068 100644 --- a/packages/core/cairo/src/generate/custom.ts +++ b/packages/core/cairo/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from '../custom'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { CustomOptions } from "../custom"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyContract'], + name: ["MyContract"], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/cairo/src/generate/erc1155.ts b/packages/core/cairo/src/generate/erc1155.ts index 07170fcbd..9fc09c9ca 100644 --- a/packages/core/cairo/src/generate/erc1155.ts +++ b/packages/core/cairo/src/generate/erc1155.ts @@ -1,21 +1,25 @@ -import type { ERC1155Options } from '../erc1155'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { royaltyInfoOptions } from '../set-royalty-info'; -import { generateAlternatives } from './alternatives'; +import type { ERC1155Options } from "../erc1155"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { royaltyInfoOptions } from "../set-royalty-info"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + baseUri: ["https://example.com/"], burnable: booleans, pausable: booleans, mintable: booleans, updatableUri: booleans, upgradeable: upgradeableOptions, - royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], + royaltyInfo: [ + royaltyInfoOptions.disabled, + royaltyInfoOptions.enabledDefault, + royaltyInfoOptions.enabledCustom, + ], access: accessOptions, info: infoOptions, }; diff --git a/packages/core/cairo/src/generate/erc20.ts b/packages/core/cairo/src/generate/erc20.ts index 941505c7e..2f8235af1 100644 --- a/packages/core/cairo/src/generate/erc20.ts +++ b/packages/core/cairo/src/generate/erc20.ts @@ -1,24 +1,24 @@ -import type { ERC20Options } from '../erc20'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC20Options } from "../erc20"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], + name: ["MyToken"], + symbol: ["MTK"], burnable: booleans, pausable: booleans, mintable: booleans, - premint: ['1'], + premint: ["1"], votes: booleans, - appName: ['MyApp'], - appVersion: ['v1'], + appName: ["MyApp"], + appVersion: ["v1"], access: accessOptions, upgradeable: upgradeableOptions, - info: infoOptions + info: infoOptions, }; export function* generateERC20Options(): Generator> { diff --git a/packages/core/cairo/src/generate/erc721.ts b/packages/core/cairo/src/generate/erc721.ts index e78a94f15..f3003120a 100644 --- a/packages/core/cairo/src/generate/erc721.ts +++ b/packages/core/cairo/src/generate/erc721.ts @@ -1,24 +1,28 @@ -import type { ERC721Options } from '../erc721'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { royaltyInfoOptions } from '../set-royalty-info'; -import { generateAlternatives } from './alternatives'; +import type { ERC721Options } from "../erc721"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { royaltyInfoOptions } from "../set-royalty-info"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + symbol: ["MTK"], + baseUri: ["https://example.com/"], burnable: booleans, enumerable: booleans, votes: booleans, - appName: ['MyApp'], - appVersion: ['v1'], + appName: ["MyApp"], + appVersion: ["v1"], pausable: booleans, mintable: booleans, - royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], + royaltyInfo: [ + royaltyInfoOptions.disabled, + royaltyInfoOptions.enabledDefault, + royaltyInfoOptions.enabledCustom, + ], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/governor.ts b/packages/core/cairo/src/generate/governor.ts index 8ffee9aef..6d55cb762 100644 --- a/packages/core/cairo/src/generate/governor.ts +++ b/packages/core/cairo/src/generate/governor.ts @@ -1,30 +1,37 @@ -import { clockModeOptions, GovernorOptions, quorumModeOptions, timelockOptions, votesOptions } from '../governor'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { + clockModeOptions, + GovernorOptions, + quorumModeOptions, + timelockOptions, + votesOptions, +} from "../governor"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyGovernor'], - delay: ['1 day'], - period: ['1 week'], - proposalThreshold: ['1'], + name: ["MyGovernor"], + delay: ["1 day"], + period: ["1 week"], + proposalThreshold: ["1"], decimals: [18], quorumMode: quorumModeOptions, quorumPercent: [10], - quorumAbsolute: ['10'], + quorumAbsolute: ["10"], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, settings: booleans, - appName: ['Openzeppelin Governor'], - appVersion: ['v1'], + appName: ["Openzeppelin Governor"], + appVersion: ["v1"], upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateGovernorOptions(): Generator> { +export function* generateGovernorOptions(): Generator< + Required +> { yield* generateAlternatives(blueprint); } - diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index 67867b287..ab6ebd08d 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -75,7 +75,7 @@ interface GeneratedSource extends GeneratedContract { function generateContractSubset( subset: Subset, - kind?: Kind + kind?: Kind, ): GeneratedContract[] { const contracts = []; @@ -125,11 +125,11 @@ function generateContractSubset( return [ ...findCover( contracts.filter(filterByUpgradeableSetTo(true)), - getParents + getParents, ), ...findCover( contracts.filter(filterByUpgradeableSetTo(false)), - getParents + getParents, ), ]; } @@ -138,7 +138,7 @@ function generateContractSubset( export function* generateSources( subset: Subset, uniqueName?: boolean, - kind?: Kind + kind?: Kind, ): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { @@ -154,7 +154,7 @@ export async function writeGeneratedSources( dir: string, subset: Subset, uniqueName?: boolean, - kind?: Kind + kind?: Kind, ): Promise { await fs.mkdir(dir, { recursive: true }); const contractNames = []; @@ -162,7 +162,7 @@ export async function writeGeneratedSources( for (const { id, contract, source } of generateSources( subset, uniqueName, - kind + kind, )) { const name = uniqueName ? contract.name : id; await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); diff --git a/packages/core/cairo/src/generate/vesting.ts b/packages/core/cairo/src/generate/vesting.ts index 45de06143..49a34f72e 100644 --- a/packages/core/cairo/src/generate/vesting.ts +++ b/packages/core/cairo/src/generate/vesting.ts @@ -1,14 +1,14 @@ -import { infoOptions } from '../set-info'; -import type { VestingOptions } from '../vesting'; -import { generateAlternatives } from './alternatives'; +import { infoOptions } from "../set-info"; +import type { VestingOptions } from "../vesting"; +import { generateAlternatives } from "./alternatives"; const blueprint = { - name: ['MyVesting'], - startDate: ['2024-12-31T23:59'], - duration: ['90 days', '1 year'], - cliffDuration: ['0 seconds', '30 day'], - schedule: ['linear', 'custom'] as const, - info: infoOptions + name: ["MyVesting"], + startDate: ["2024-12-31T23:59"], + duration: ["90 days", "1 year"], + cliffDuration: ["0 seconds", "30 day"], + schedule: ["linear", "custom"] as const, + info: infoOptions, }; export function* generateVestingOptions(): Generator> { diff --git a/packages/core/cairo/src/governor.test.ts b/packages/core/cairo/src/governor.test.ts index e3b3f0c22..561e4dc08 100644 --- a/packages/core/cairo/src/governor.test.ts +++ b/packages/core/cairo/src/governor.test.ts @@ -1,17 +1,17 @@ -import test from 'ava'; -import { governor } from '.'; +import test from "ava"; +import { governor } from "."; -import { buildGovernor, GovernorOptions } from './governor'; -import { printContract } from './print'; +import { buildGovernor, GovernorOptions } from "./governor"; +import { printContract } from "./print"; -const NAME = 'MyGovernor'; +const NAME = "MyGovernor"; function testGovernor(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildGovernor({ name: NAME, - delay: '1 day', - period: '1 week', + delay: "1 day", + period: "1 week", ...opts, }); t.snapshot(printContract(c)); @@ -22,160 +22,165 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, t => { - t.is(governor.print(opts), printContract(buildGovernor({ - name: NAME, - delay: '1 day', - period: '1 week', - ...opts, - }))); + test(title, (t) => { + t.is( + governor.print(opts), + printContract( + buildGovernor({ + name: NAME, + delay: "1 day", + period: "1 week", + ...opts, + }), + ), + ); }); } -testGovernor('basic + upgradeable', { - upgradeable: true +testGovernor("basic + upgradeable", { + upgradeable: true, }); -testGovernor('basic non-upgradeable', { - upgradeable: false +testGovernor("basic non-upgradeable", { + upgradeable: false, }); -testGovernor('erc20 votes + timelock', { - votes: 'erc20votes', - timelock: 'openzeppelin', +testGovernor("erc20 votes + timelock", { + votes: "erc20votes", + timelock: "openzeppelin", }); -testGovernor('erc721 votes + timelock', { - votes: 'erc721votes', - timelock: 'openzeppelin', +testGovernor("erc721 votes + timelock", { + votes: "erc721votes", + timelock: "openzeppelin", }); -testGovernor('custom name', { - name: 'CustomGovernor', +testGovernor("custom name", { + name: "CustomGovernor", }); -testGovernor('custom settings', { - delay: '2 hours', - period: '1 year', - proposalThreshold: '300', +testGovernor("custom settings", { + delay: "2 hours", + period: "1 year", + proposalThreshold: "300", settings: true, }); -testGovernor('quorum mode absolute', { - quorumMode: 'absolute', - quorumAbsolute: '200', +testGovernor("quorum mode absolute", { + quorumMode: "absolute", + quorumAbsolute: "200", }); -testGovernor('quorum mode percent', { - quorumMode: 'percent', +testGovernor("quorum mode percent", { + quorumMode: "percent", quorumPercent: 40, }); -testGovernor('custom snip12 metadata', { - appName: 'Governor', - appVersion: 'v3', +testGovernor("custom snip12 metadata", { + appName: "Governor", + appVersion: "v3", }); -testGovernor('all options', { +testGovernor("all options", { name: NAME, - delay: '4 day', - period: '4 week', - proposalThreshold: '500', + delay: "4 day", + period: "4 week", + proposalThreshold: "500", decimals: 10, - quorumMode: 'absolute', + quorumMode: "absolute", quorumPercent: 50, - quorumAbsolute: '200', - votes: 'erc721votes', - clockMode: 'timestamp', - timelock: 'openzeppelin', + quorumAbsolute: "200", + votes: "erc721votes", + clockMode: "timestamp", + timelock: "openzeppelin", settings: true, - appName: 'MyApp2', - appVersion: 'v5', + appName: "MyApp2", + appVersion: "v5", upgradeable: true, }); -testAPIEquivalence('API basic + upgradeable', { +testAPIEquivalence("API basic + upgradeable", { name: NAME, - delay: '1 day', - period: '1 week', - upgradeable: true + delay: "1 day", + period: "1 week", + upgradeable: true, }); -testAPIEquivalence('API basic non-upgradeable', { +testAPIEquivalence("API basic non-upgradeable", { name: NAME, - delay: '1 day', - period: '1 week', - upgradeable: false + delay: "1 day", + period: "1 week", + upgradeable: false, }); -testAPIEquivalence('API erc20 votes + timelock', { +testAPIEquivalence("API erc20 votes + timelock", { name: NAME, - delay: '1 day', - period: '1 week', - votes: 'erc20votes', - timelock: 'openzeppelin', + delay: "1 day", + period: "1 week", + votes: "erc20votes", + timelock: "openzeppelin", }); -testAPIEquivalence('API erc721 votes + timelock', { +testAPIEquivalence("API erc721 votes + timelock", { name: NAME, - delay: '1 day', - period: '1 week', - votes: 'erc721votes', - timelock: 'openzeppelin', + delay: "1 day", + period: "1 week", + votes: "erc721votes", + timelock: "openzeppelin", }); -testAPIEquivalence('API custom name', { - name: 'CustomGovernor', - delay: '1 day', - period: '1 week', +testAPIEquivalence("API custom name", { + name: "CustomGovernor", + delay: "1 day", + period: "1 week", }); -testAPIEquivalence('API custom settings', { +testAPIEquivalence("API custom settings", { name: NAME, - delay: '2 hours', - period: '1 year', - proposalThreshold: '300', + delay: "2 hours", + period: "1 year", + proposalThreshold: "300", settings: true, }); -testAPIEquivalence('API quorum mode absolute', { +testAPIEquivalence("API quorum mode absolute", { name: NAME, - delay: '1 day', - period: '1 week', - quorumMode: 'absolute', - quorumAbsolute: '200', + delay: "1 day", + period: "1 week", + quorumMode: "absolute", + quorumAbsolute: "200", }); -testAPIEquivalence('API quorum mode percent', { +testAPIEquivalence("API quorum mode percent", { name: NAME, - delay: '1 day', - period: '1 week', - quorumMode: 'percent', + delay: "1 day", + period: "1 week", + quorumMode: "percent", quorumPercent: 40, }); -testAPIEquivalence('API custom snip12 metadata', { +testAPIEquivalence("API custom snip12 metadata", { name: NAME, - delay: '1 day', - period: '1 week', - appName: 'Governor', - appVersion: 'v3', + delay: "1 day", + period: "1 week", + appName: "Governor", + appVersion: "v3", }); -testAPIEquivalence('API all options', { +testAPIEquivalence("API all options", { name: NAME, - delay: '4 day', - period: '4 week', - proposalThreshold: '500', + delay: "4 day", + period: "4 week", + proposalThreshold: "500", decimals: 10, - quorumMode: 'absolute', + quorumMode: "absolute", quorumPercent: 50, - quorumAbsolute: '200', - votes: 'erc721votes', - clockMode: 'timestamp', - timelock: 'openzeppelin', + quorumAbsolute: "200", + votes: "erc721votes", + clockMode: "timestamp", + timelock: "openzeppelin", settings: true, - appName: 'MyApp2', - appVersion: 'v5', + appName: "MyApp2", + appVersion: "v5", upgradeable: true, }); diff --git a/packages/core/cairo/src/governor.ts b/packages/core/cairo/src/governor.ts index fac953e62..50b6c65ef 100644 --- a/packages/core/cairo/src/governor.ts +++ b/packages/core/cairo/src/governor.ts @@ -1,49 +1,52 @@ -import { contractDefaults as commonDefaults, withCommonDefaults } from './common-options'; -import type { CommonOptions } from './common-options'; +import { + contractDefaults as commonDefaults, + withCommonDefaults, +} from "./common-options"; +import type { CommonOptions } from "./common-options"; import { ContractBuilder, Contract } from "./contract"; import { OptionsError } from "./error"; import { printContract } from "./print"; import { setInfo } from "./set-info"; import { setUpgradeableGovernor } from "./set-upgradeable"; -import { defineComponents } from './utils/define-components'; -import { durationToTimestamp } from './utils/duration'; -import { addSNIP12Metadata, addSRC5Component } from './common-components'; -import { toUint } from './utils/convert-strings'; -export const clockModeOptions = ['timestamp'] as const; -export const clockModeDefault = 'timestamp' as const; -export type ClockMode = typeof clockModeOptions[number]; - -const extensionPath = 'openzeppelin::governance::governor::extensions'; -const extensionExternalSection = 'Extensions (external)'; -const extensionInternalSection = 'Extensions (internal)'; +import { defineComponents } from "./utils/define-components"; +import { durationToTimestamp } from "./utils/duration"; +import { addSNIP12Metadata, addSRC5Component } from "./common-components"; +import { toUint } from "./utils/convert-strings"; +export const clockModeOptions = ["timestamp"] as const; +export const clockModeDefault = "timestamp" as const; +export type ClockMode = (typeof clockModeOptions)[number]; + +const extensionPath = "openzeppelin::governance::governor::extensions"; +const extensionExternalSection = "Extensions (external)"; +const extensionInternalSection = "Extensions (internal)"; export const defaults: Required = { - name: 'MyGovernor', - delay: '1 day', - period: '1 week', - votes: 'erc20votes', + name: "MyGovernor", + delay: "1 day", + period: "1 week", + votes: "erc20votes", clockMode: clockModeDefault, - timelock: 'openzeppelin', + timelock: "openzeppelin", decimals: 18, - proposalThreshold: '0', - quorumMode: 'percent', + proposalThreshold: "0", + quorumMode: "percent", quorumPercent: 4, - quorumAbsolute: '', + quorumAbsolute: "", settings: true, upgradeable: commonDefaults.upgradeable, - appName: 'OpenZeppelin Governor', - appVersion: 'v1', - info: commonDefaults.info + appName: "OpenZeppelin Governor", + appVersion: "v1", + info: commonDefaults.info, } as const; -export const quorumModeOptions = ['percent', 'absolute'] as const; -export type QuorumMode = typeof quorumModeOptions[number]; +export const quorumModeOptions = ["percent", "absolute"] as const; +export type QuorumMode = (typeof quorumModeOptions)[number]; -export const votesOptions = ['erc20votes', 'erc721votes'] as const; -export type VotesOptions = typeof votesOptions[number]; +export const votesOptions = ["erc20votes", "erc721votes"] as const; +export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, 'openzeppelin'] as const; -export type TimelockOptions = typeof timelockOptions[number]; +export const timelockOptions = [false, "openzeppelin"] as const; +export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { return printContract(buildGovernor(opts)); @@ -93,8 +96,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { validateDecimals(allOpts.decimals); addBase(c, allOpts); - addSRC5Component(c, 'SRC5'); - addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, 'SNIP12 Metadata'); + addSRC5Component(c, "SRC5"); + addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, "SNIP12 Metadata"); addCounting(c, allOpts); addQuorumAndVotes(c, allOpts); addSettings(c, allOpts); @@ -105,185 +108,224 @@ export function buildGovernor(opts: GovernorOptions): Contract { return c; } -const components = defineComponents( { +const components = defineComponents({ GovernorComponent: { - path: 'openzeppelin::governance::governor', + path: "openzeppelin::governance::governor", substorage: { - name: 'governor', - type: 'GovernorComponent::Storage', + name: "governor", + type: "GovernorComponent::Storage", }, event: { - name: 'GovernorEvent', - type: 'GovernorComponent::Event', + name: "GovernorEvent", + type: "GovernorComponent::Event", }, - impls: [{ - name: 'GovernorImpl', - value: 'GovernorComponent::GovernorImpl', - section: 'Governor Core', - }], + impls: [ + { + name: "GovernorImpl", + value: "GovernorComponent::GovernorImpl", + section: "Governor Core", + }, + ], }, GovernorSettingsComponent: { path: extensionPath, substorage: { - name: 'governor_settings', - type: 'GovernorSettingsComponent::Storage', + name: "governor_settings", + type: "GovernorSettingsComponent::Storage", }, event: { - name: 'GovernorSettingsEvent', - type: 'GovernorSettingsComponent::Event', + name: "GovernorSettingsEvent", + type: "GovernorSettingsComponent::Event", }, - impls: [{ - name: 'GovernorSettingsAdminImpl', - value: 'GovernorSettingsComponent::GovernorSettingsAdminImpl', - section: extensionExternalSection, - }, { - name: 'GovernorSettingsImpl', - value: 'GovernorSettingsComponent::GovernorSettings', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorSettingsAdminImpl", + value: + "GovernorSettingsComponent::GovernorSettingsAdminImpl", + section: extensionExternalSection, + }, + { + name: "GovernorSettingsImpl", + value: "GovernorSettingsComponent::GovernorSettings", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorVotesComponent: { path: extensionPath, substorage: { - name: 'governor_votes', - type: 'GovernorVotesComponent::Storage', + name: "governor_votes", + type: "GovernorVotesComponent::Storage", }, event: { - name: 'GovernorVotesEvent', - type: 'GovernorVotesComponent::Event', + name: "GovernorVotesEvent", + type: "GovernorVotesComponent::Event", }, - impls: [{ - name: 'VotesTokenImpl', - value: 'GovernorVotesComponent::VotesTokenImpl', - section: extensionExternalSection, - }, { - name: 'GovernorVotesImpl', - value: 'GovernorVotesComponent::GovernorVotes', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "VotesTokenImpl", + value: "GovernorVotesComponent::VotesTokenImpl", + section: extensionExternalSection, + }, + { + name: "GovernorVotesImpl", + value: "GovernorVotesComponent::GovernorVotes", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorVotesQuorumFractionComponent: { path: extensionPath, substorage: { - name: 'governor_votes', - type: 'GovernorVotesQuorumFractionComponent::Storage', + name: "governor_votes", + type: "GovernorVotesQuorumFractionComponent::Storage", }, event: { - name: 'GovernorVotesEvent', - type: 'GovernorVotesQuorumFractionComponent::Event', + name: "GovernorVotesEvent", + type: "GovernorVotesQuorumFractionComponent::Event", }, - impls: [{ - name: 'GovernorQuorumImpl', - value: 'GovernorVotesQuorumFractionComponent::GovernorQuorum', - embed: false, - section: extensionInternalSection, - }, { - name: 'GovernorVotesImpl', - value: 'GovernorVotesQuorumFractionComponent::GovernorVotes', - embed: false, - section: extensionInternalSection, - }, { - name: 'QuorumFractionImpl', - value: 'GovernorVotesQuorumFractionComponent::QuorumFractionImpl', - section: extensionExternalSection, - }], + impls: [ + { + name: "GovernorQuorumImpl", + value: + "GovernorVotesQuorumFractionComponent::GovernorQuorum", + embed: false, + section: extensionInternalSection, + }, + { + name: "GovernorVotesImpl", + value: + "GovernorVotesQuorumFractionComponent::GovernorVotes", + embed: false, + section: extensionInternalSection, + }, + { + name: "QuorumFractionImpl", + value: + "GovernorVotesQuorumFractionComponent::QuorumFractionImpl", + section: extensionExternalSection, + }, + ], }, GovernorCountingSimpleComponent: { path: extensionPath, substorage: { - name: 'governor_counting', - type: 'GovernorCountingSimpleComponent::Storage', + name: "governor_counting", + type: "GovernorCountingSimpleComponent::Storage", }, event: { - name: 'GovernorCountingSimpleEvent', - type: 'GovernorCountingSimpleComponent::Event', + name: "GovernorCountingSimpleEvent", + type: "GovernorCountingSimpleComponent::Event", }, - impls: [{ - name: 'GovernorCountingSimpleImpl', - value: 'GovernorCountingSimpleComponent::GovernorCounting', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorCountingSimpleImpl", + value: + "GovernorCountingSimpleComponent::GovernorCounting", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorCoreExecutionComponent: { path: extensionPath, substorage: { - name: 'governor_execution', - type: 'GovernorCoreExecutionComponent::Storage', + name: "governor_execution", + type: "GovernorCoreExecutionComponent::Storage", }, event: { - name: 'GovernorCoreExecutionEvent', - type: 'GovernorCoreExecutionComponent::Event', + name: "GovernorCoreExecutionEvent", + type: "GovernorCoreExecutionComponent::Event", }, - impls: [{ - name: 'GovernorCoreExecutionImpl', - value: 'GovernorCoreExecutionComponent::GovernorExecution', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorCoreExecutionImpl", + value: + "GovernorCoreExecutionComponent::GovernorExecution", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorTimelockExecutionComponent: { path: extensionPath, substorage: { - name: 'governor_timelock_execution', - type: 'GovernorTimelockExecutionComponent::Storage', + name: "governor_timelock_execution", + type: "GovernorTimelockExecutionComponent::Storage", }, event: { - name: 'GovernorTimelockExecutionEvent', - type: 'GovernorTimelockExecutionComponent::Event', + name: "GovernorTimelockExecutionEvent", + type: "GovernorTimelockExecutionComponent::Event", }, - impls: [{ - name: 'TimelockedImpl', - value: 'GovernorTimelockExecutionComponent::TimelockedImpl', - section: extensionExternalSection, - }, { - name: 'GovernorTimelockExecutionImpl', - value: 'GovernorTimelockExecutionComponent::GovernorExecution', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "TimelockedImpl", + value: + "GovernorTimelockExecutionComponent::TimelockedImpl", + section: extensionExternalSection, + }, + { + name: "GovernorTimelockExecutionImpl", + value: + "GovernorTimelockExecutionComponent::GovernorExecution", + embed: false, + section: extensionInternalSection, + }, + ], }, }); function addBase(c: ContractBuilder, _: GovernorOptions) { - c.addUseClause('starknet', 'ContractAddress'); - c.addUseClause('openzeppelin::governance::governor', 'DefaultConfig'); - c.addConstructorArgument({ name: 'votes_token', type: 'ContractAddress' }); - c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalTrait', { alias: 'GovernorInternalTrait' }); + c.addUseClause("starknet", "ContractAddress"); + c.addUseClause("openzeppelin::governance::governor", "DefaultConfig"); + c.addConstructorArgument({ name: "votes_token", type: "ContractAddress" }); + c.addUseClause( + "openzeppelin::governance::governor::GovernorComponent", + "InternalTrait", + { alias: "GovernorInternalTrait" }, + ); c.addComponent(components.GovernorComponent, [], true); } function addSettings(c: ContractBuilder, allOpts: Required) { c.addConstant({ - name: 'VOTING_DELAY', - type: 'u64', + name: "VOTING_DELAY", + type: "u64", value: getVotingDelay(allOpts).toString(), comment: allOpts.delay, inlineComment: true, }); c.addConstant({ - name: 'VOTING_PERIOD', - type: 'u64', + name: "VOTING_PERIOD", + type: "u64", value: getVotingPeriod(allOpts).toString(), comment: allOpts.period, inlineComment: true, }); c.addConstant({ - name: 'PROPOSAL_THRESHOLD', - type: 'u256', + name: "PROPOSAL_THRESHOLD", + type: "u256", ...getProposalThreshold(allOpts), inlineComment: true, }); if (allOpts.settings) { - c.addUseClause(`${extensionPath}::GovernorSettingsComponent`, 'InternalTrait', { alias: 'GovernorSettingsInternalTrait' }); - c.addComponent(components.GovernorSettingsComponent, [ - { lit: 'VOTING_DELAY' }, - { lit: 'VOTING_PERIOD' }, - { lit: 'PROPOSAL_THRESHOLD' }, - ], true); + c.addUseClause( + `${extensionPath}::GovernorSettingsComponent`, + "InternalTrait", + { alias: "GovernorSettingsInternalTrait" }, + ); + c.addComponent( + components.GovernorSettingsComponent, + [ + { lit: "VOTING_DELAY" }, + { lit: "VOTING_PERIOD" }, + { lit: "PROPOSAL_THRESHOLD" }, + ], + true, + ); } else { addSettingsLocalImpl(c, allOpts); } @@ -291,10 +333,10 @@ function addSettings(c: ContractBuilder, allOpts: Required) { function getVotingDelay(opts: Required): number { try { - if (opts.clockMode === 'timestamp') { - return durationToTimestamp(opts.delay); + if (opts.clockMode === "timestamp") { + return durationToTimestamp(opts.delay); } else { - throw new Error('Invalid clock mode'); + throw new Error("Invalid clock mode"); } } catch (e) { if (e instanceof Error) { @@ -309,10 +351,10 @@ function getVotingDelay(opts: Required): number { function getVotingPeriod(opts: Required): number { try { - if (opts.clockMode === 'timestamp') { - return durationToTimestamp(opts.period); + if (opts.clockMode === "timestamp") { + return durationToTimestamp(opts.period); } else { - throw new Error('Invalid clock mode'); + throw new Error("Invalid clock mode"); } } catch (e) { if (e instanceof Error) { @@ -328,92 +370,114 @@ function getVotingPeriod(opts: Required): number { function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: 'Not a valid number', + decimals: "Not a valid number", }); } } -function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): {value: string, comment?: string} { +function getProposalThreshold({ + proposalThreshold, + decimals, + votes, +}: Required): { value: string; comment?: string } { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: 'Not a valid number', + proposalThreshold: "Not a valid number", }); } - if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { + if ( + /^0+$/.test(proposalThreshold) || + decimals === 0 || + votes === "erc721votes" + ) { return { value: proposalThreshold }; } else { - let value = `${BigInt(proposalThreshold)*BigInt(10)**BigInt(decimals)}`; - value = toUint(value, 'proposalThreshold', 'u256').toString(); - return { value: `${value}`, comment: `${proposalThreshold} * pow!(10, ${decimals})` }; + let value = `${BigInt(proposalThreshold) * BigInt(10) ** BigInt(decimals)}`; + value = toUint(value, "proposalThreshold", "u256").toString(); + return { + value: `${value}`, + comment: `${proposalThreshold} * pow!(10, ${decimals})`, + }; } } -function addSettingsLocalImpl(c: ContractBuilder, _: Required) { +function addSettingsLocalImpl( + c: ContractBuilder, + _: Required, +) { const settingsTrait = { - name: 'GovernorSettings', - of: 'GovernorComponent::GovernorSettingsTrait', + name: "GovernorSettings", + of: "GovernorComponent::GovernorSettingsTrait", tags: [], - section: 'Locally implemented extensions', + section: "Locally implemented extensions", priority: 2, }; c.addImplementedTrait(settingsTrait); c.addFunction(settingsTrait, { - name: 'voting_delay', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u64', - code: ['VOTING_DELAY'], + name: "voting_delay", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u64", + code: ["VOTING_DELAY"], }); c.addFunction(settingsTrait, { - name: 'voting_period', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u64', - code: ['VOTING_PERIOD'], + name: "voting_period", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u64", + code: ["VOTING_PERIOD"], }); c.addFunction(settingsTrait, { - name: 'proposal_threshold', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u256', - code: ['PROPOSAL_THRESHOLD'], + name: "proposal_threshold", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u256", + code: ["PROPOSAL_THRESHOLD"], }); } -function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { - if (allOpts.quorumMode === 'percent') { +function addQuorumAndVotes( + c: ContractBuilder, + allOpts: Required, +) { + if (allOpts.quorumMode === "percent") { if (allOpts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); } addVotesQuorumFractionComponent(c, allOpts.quorumPercent); - } - else if (allOpts.quorumMode === 'absolute') { + } else if (allOpts.quorumMode === "absolute") { if (!numberPattern.test(allOpts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: 'Not a valid number', + quorumAbsolute: "Not a valid number", }); } let quorum: string; - let comment = ''; - if (allOpts.decimals === 0 || allOpts.votes === 'erc721votes') { + let comment = ""; + if (allOpts.decimals === 0 || allOpts.votes === "erc721votes") { quorum = `${allOpts.quorumAbsolute}`; } else { - quorum = `${BigInt(allOpts.quorumAbsolute)*BigInt(10)**BigInt(allOpts.decimals)}`; - quorum = toUint(quorum, 'quorumAbsolute', 'u256').toString(); + quorum = `${BigInt(allOpts.quorumAbsolute) * BigInt(10) ** BigInt(allOpts.decimals)}`; + quorum = toUint(quorum, "quorumAbsolute", "u256").toString(); comment = `${allOpts.quorumAbsolute} * pow!(10, ${allOpts.decimals})`; } @@ -422,56 +486,75 @@ function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { - c.addUseClause(`${extensionPath}::GovernorVotesComponent`, 'InternalTrait', { alias: 'GovernorVotesInternalTrait' }); - c.addComponent(components.GovernorVotesComponent, [ - { lit: 'votes_token' }, - ], true); + c.addUseClause(`${extensionPath}::GovernorVotesComponent`, "InternalTrait", { + alias: "GovernorVotesInternalTrait", + }); + c.addComponent( + components.GovernorVotesComponent, + [{ lit: "votes_token" }], + true, + ); } -function addQuorumLocalImpl(c: ContractBuilder, quorum: string, comment: string) { +function addQuorumLocalImpl( + c: ContractBuilder, + quorum: string, + comment: string, +) { c.addConstant({ - name: 'QUORUM', - type: 'u256', + name: "QUORUM", + type: "u256", value: quorum, comment, inlineComment: true, }); const quorumTrait = { - name: 'GovernorQuorum', - of: 'GovernorComponent::GovernorQuorumTrait', + name: "GovernorQuorum", + of: "GovernorComponent::GovernorQuorumTrait", tags: [], - section: 'Locally implemented extensions', + section: "Locally implemented extensions", priority: 1, }; c.addImplementedTrait(quorumTrait); c.addFunction(quorumTrait, { - name: 'quorum', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }, { - name: 'timepoint', - type: 'u64', - }], - returns: 'u256', - code: ['QUORUM'], + name: "quorum", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + { + name: "timepoint", + type: "u64", + }, + ], + returns: "u256", + code: ["QUORUM"], }); } @@ -479,15 +562,27 @@ function addCounting(c: ContractBuilder, _: Required) { c.addComponent(components.GovernorCountingSimpleComponent, [], false); } -function addExecution(c: ContractBuilder, { timelock }: Required) { +function addExecution( + c: ContractBuilder, + { timelock }: Required, +) { if (timelock === false) { c.addComponent(components.GovernorCoreExecutionComponent, [], false); } else { - c.addConstructorArgument({ name: 'timelock_controller', type: 'ContractAddress' }); - c.addUseClause(`${extensionPath}::GovernorTimelockExecutionComponent`, 'InternalTrait', { alias: 'GovernorTimelockExecutionInternalTrait' }); - c.addComponent(components.GovernorTimelockExecutionComponent, [ - { lit: 'timelock_controller' }, - ], true); + c.addConstructorArgument({ + name: "timelock_controller", + type: "ContractAddress", + }); + c.addUseClause( + `${extensionPath}::GovernorTimelockExecutionComponent`, + "InternalTrait", + { alias: "GovernorTimelockExecutionInternalTrait" }, + ); + c.addComponent( + components.GovernorTimelockExecutionComponent, + [{ lit: "timelock_controller" }], + true, + ); } } diff --git a/packages/core/cairo/src/index.ts b/packages/core/cairo/src/index.ts index 5f102fabb..461ec1823 100644 --- a/packages/core/cairo/src/index.ts +++ b/packages/core/cairo/src/index.ts @@ -1,28 +1,40 @@ -export type { GenericOptions, KindedOptions } from './build-generic'; -export { buildGeneric } from './build-generic'; - -export type { Contract } from './contract'; -export { ContractBuilder } from './contract'; - -export { printContract } from './print'; - -export type { Access } from './set-access-control'; -export type { Account } from './account'; -export type { Upgradeable } from './set-upgradeable'; -export type { Info } from './set-info'; -export type { RoyaltyInfoOptions } from './set-royalty-info'; - -export { premintPattern } from './erc20'; - -export { defaults as infoDefaults } from './set-info'; -export { defaults as royaltyInfoDefaults } from './set-royalty-info'; - -export type { OptionsErrorMessages } from './error'; -export { OptionsError } from './error'; - -export type { Kind } from './kind'; -export { sanitizeKind } from './kind'; - -export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; - -export { erc20, erc721, erc1155, account, governor, vesting, custom } from './api'; +export type { GenericOptions, KindedOptions } from "./build-generic"; +export { buildGeneric } from "./build-generic"; + +export type { Contract } from "./contract"; +export { ContractBuilder } from "./contract"; + +export { printContract } from "./print"; + +export type { Access } from "./set-access-control"; +export type { Account } from "./account"; +export type { Upgradeable } from "./set-upgradeable"; +export type { Info } from "./set-info"; +export type { RoyaltyInfoOptions } from "./set-royalty-info"; + +export { premintPattern } from "./erc20"; + +export { defaults as infoDefaults } from "./set-info"; +export { defaults as royaltyInfoDefaults } from "./set-royalty-info"; + +export type { OptionsErrorMessages } from "./error"; +export { OptionsError } from "./error"; + +export type { Kind } from "./kind"; +export { sanitizeKind } from "./kind"; + +export { + contractsVersion, + contractsVersionTag, + compatibleContractsSemver, +} from "./utils/version"; + +export { + erc20, + erc721, + erc1155, + account, + governor, + vesting, + custom, +} from "./api"; diff --git a/packages/core/cairo/src/kind.ts b/packages/core/cairo/src/kind.ts index d61180216..c205c7715 100644 --- a/packages/core/cairo/src/kind.ts +++ b/packages/core/cairo/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from './build-generic'; +import type { GenericOptions } from "./build-generic"; -export type Kind = GenericOptions['kind']; +export type Kind = GenericOptions["kind"]; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === 'string') { - const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); + if (typeof kind === "string") { + const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return 'ERC20'; + return "ERC20"; } function isKind(value: Kind | T): value is Kind { switch (value) { - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Account': - case 'Governor': - case 'Vesting': - case 'Custom': + case "ERC20": + case "ERC721": + case "ERC1155": + case "Account": + case "Governor": + case "Vesting": + case "Custom": return true; default: { @@ -30,4 +30,3 @@ function isKind(value: Kind | T): value is Kind { } } } - diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index 71f5dec38..2c55c403c 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -40,11 +40,11 @@ export function printContract(contract: Contract): string { printStorage(contract), printEvents(contract), printConstructor(contract), - printImplementedTraits(contract) + printImplementedTraits(contract), ), `}`, - ] - ) + ], + ), ); } @@ -55,8 +55,8 @@ function withSemicolons(lines: string[]): string[] { function printSuperVariables(contract: Contract): string[] { return withSemicolons( contract.superVariables.map( - (v) => `const ${v.name}: ${v.type} = ${v.value}` - ) + (v) => `const ${v.name}: ${v.type} = ${v.value}`, + ), ); } @@ -67,7 +67,7 @@ function printUseClauses(contract: Contract): Lines[] { const grouped = useClauses.reduce( ( result: { [containerPath: string]: UseClause[] }, - useClause: UseClause + useClause: UseClause, ) => { if (useClause.groupable) { (result[useClause.containerPath] = @@ -78,24 +78,24 @@ function printUseClauses(contract: Contract): Lines[] { } return result; }, - {} + {}, ); const lines = Object.entries(grouped).flatMap(([groupName, group]) => - getLinesFromUseClausesGroup(group, groupName) + getLinesFromUseClausesGroup(group, groupName), ); return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); } function getLinesFromUseClausesGroup( group: UseClause[], - groupName: string + groupName: string, ): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { lines.push( - `use ${useClause.containerPath}::${nameWithAlias(useClause)};` + `use ${useClause.containerPath}::${nameWithAlias(useClause)};`, ); } } else { @@ -168,15 +168,15 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};` + `const ${constant.name}: ${constant.type} = ${constant.value};`, ); } else if (commented) { lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}` + `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`, ); } else { lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};` + `const ${constant.name}: ${constant.type} = ${constant.value};`, ); } } @@ -187,7 +187,7 @@ function printComponentDeclarations(contract: Contract): Lines[] { const lines = []; for (const component of contract.components) { lines.push( - `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});` + `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});`, ); } return lines; @@ -206,7 +206,7 @@ function printImpls(contract: Contract): Lines[] { (result[section] = result[section] || []).push(current); return result; }, - {} + {}, ); const sections = Object.entries(grouped) @@ -241,7 +241,7 @@ function printStorage(contract: Contract): (string | string[])[] { for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); storageLines.push( - `${component.substorage.name}: ${component.substorage.type},` + `${component.substorage.name}: ${component.substorage.type},`, ); } lines.push(storageLines); @@ -282,20 +282,20 @@ function printImplementedTraits(contract: Contract): Lines[] { const grouped = sortedTraits.reduce( ( result: { [section: string]: ImplementedTrait[] }, - current: ImplementedTrait + current: ImplementedTrait, ) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); return result; }, - {} + {}, ); const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) .map(([section, impls]) => - printImplementedTraitsSection(section, impls as ImplementedTrait[]) + printImplementedTraitsSection(section, impls as ImplementedTrait[]), ); return spaceBetween(...sections); @@ -303,7 +303,7 @@ function printImplementedTraits(contract: Contract): Lines[] { function printImplementedTraitsSection( section: string, - impls: ImplementedTrait[] + impls: ImplementedTrait[], ): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; @@ -327,7 +327,7 @@ function printImplementedTrait(trait: ImplementedTrait): Lines[] { implLines.push(`impl ${trait.name} of ${trait.of} {`); const superVars = withSemicolons( - trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`) + trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`), ); implLines.push(superVars); @@ -363,7 +363,7 @@ function printFunction(fn: ContractFunction): Lines[] { function printConstructor(contract: Contract): Lines[] { const hasInitializers = contract.components.some( - (p) => p.initializer !== undefined + (p) => p.initializer !== undefined, ); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { @@ -376,7 +376,7 @@ function printConstructor(contract: Contract): Lines[] { const body = spaceBetween( withSemicolons(parents), - withSemicolons(contract.constructorCode) + withSemicolons(contract.constructorCode), ); const constructor = printFunction2( @@ -385,7 +385,7 @@ function printConstructor(contract: Contract): Lines[] { tag, undefined, undefined, - body + body, ); return constructor; } else { @@ -441,7 +441,7 @@ function printFunction2( tag: string | undefined, returns: string | undefined, returnLine: string | undefined, - code: Lines[] + code: Lines[], ): Lines[] { const fn = []; diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index 53a626d05..ee5f24897 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -17,7 +17,7 @@ export async function updateScarbProject() { const contractNames = await writeGeneratedSources( generatedSourcesPath, "all", - true + true, ); // Generate lib.cairo file @@ -41,16 +41,16 @@ async function updateScarbToml() { .replace(/edition = "\w+"/, `edition = "${edition}"`) .replace( /cairo-version = "\d+\.\d+\.\d+"/, - `cairo-version = "${cairoVersion}"` + `cairo-version = "${cairoVersion}"`, ) .replace( /scarb-version = "\d+\.\d+\.\d+"/, - `scarb-version = "${scarbVersion}"` + `scarb-version = "${scarbVersion}"`, ) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) .replace( /openzeppelin = "\d+\.\d+\.\d+"/, - `openzeppelin = "${contractsVersion}"` + `openzeppelin = "${contractsVersion}"`, ); await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); diff --git a/packages/core/cairo/src/set-access-control.ts b/packages/core/cairo/src/set-access-control.ts index 7bd3c2218..fe9d0c65a 100644 --- a/packages/core/cairo/src/set-access-control.ts +++ b/packages/core/cairo/src/set-access-control.ts @@ -1,50 +1,64 @@ -import type { BaseFunction, BaseImplementedTrait, ContractBuilder } from './contract'; -import { defineComponents } from './utils/define-components'; -import { addSRC5Component } from './common-components'; +import type { + BaseFunction, + BaseImplementedTrait, + ContractBuilder, +} from "./contract"; +import { defineComponents } from "./utils/define-components"; +import { addSRC5Component } from "./common-components"; -export const accessOptions = [false, 'ownable', 'roles'] as const; -export const DEFAULT_ACCESS_CONTROL = 'ownable'; +export const accessOptions = [false, "ownable", "roles"] as const; +export const DEFAULT_ACCESS_CONTROL = "ownable"; -export type Access = typeof accessOptions[number]; +export type Access = (typeof accessOptions)[number]; /** * Sets access control for the contract by adding inheritance. */ - export function setAccessControl(c: ContractBuilder, access: Access): void { +export function setAccessControl(c: ContractBuilder, access: Access): void { switch (access) { - case 'ownable': { - c.addComponent(components.OwnableComponent, [{ lit: 'owner' }], true); + case "ownable": { + c.addComponent(components.OwnableComponent, [{ lit: "owner" }], true); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'owner', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: "owner", type: "ContractAddress" }); break; } - case 'roles': { + case "roles": { if (c.addComponent(components.AccessControlComponent, [], true)) { - if (c.interfaceFlags.has('ISRC5')) { + if (c.interfaceFlags.has("ISRC5")) { c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlImpl', - value: 'AccessControlComponent::AccessControlImpl', + name: "AccessControlImpl", + value: "AccessControlComponent::AccessControlImpl", }); c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlCamelImpl', - value: 'AccessControlComponent::AccessControlCamelImpl', + name: "AccessControlCamelImpl", + value: + "AccessControlComponent::AccessControlCamelImpl", }); } else { c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlMixinImpl', - value: 'AccessControlComponent::AccessControlMixinImpl', + name: "AccessControlMixinImpl", + value: + "AccessControlComponent::AccessControlMixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); } addSRC5Component(c); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'default_admin', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ + name: "default_admin", + type: "ContractAddress", + }); - c.addUseClause('openzeppelin::access::accesscontrol', 'DEFAULT_ADMIN_ROLE'); - c.addConstructorCode('self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)'); + c.addUseClause( + "openzeppelin::access::accesscontrol", + "DEFAULT_ADMIN_ROLE", + ); + c.addConstructorCode( + "self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)", + ); } break; } @@ -55,12 +69,12 @@ export type Access = typeof accessOptions[number]; * Enables access control for the contract and restricts the given function with access control. */ export function requireAccessControl( - c: ContractBuilder, - trait: BaseImplementedTrait, - fn: BaseFunction, - access: Access, - roleIdPrefix: string, - roleOwner: string | undefined + c: ContractBuilder, + trait: BaseImplementedTrait, + fn: BaseFunction, + access: Access, + roleIdPrefix: string, + roleOwner: string | undefined, ): void { if (access === false) { access = DEFAULT_ACCESS_CONTROL; @@ -68,62 +82,77 @@ export function requireAccessControl( setAccessControl(c, access); switch (access) { - case 'ownable': { - c.addFunctionCodeBefore(trait, fn, 'self.ownable.assert_only_owner()'); + case "ownable": { + c.addFunctionCodeBefore(trait, fn, "self.ownable.assert_only_owner()"); break; } - case 'roles': { - const roleId = roleIdPrefix + '_ROLE'; - const addedSuper = c.addSuperVariable({ name: roleId, type: 'felt252', value: `selector!("${roleId}")` }) + case "roles": { + const roleId = roleIdPrefix + "_ROLE"; + const addedSuper = c.addSuperVariable({ + name: roleId, + type: "felt252", + value: `selector!("${roleId}")`, + }); if (roleOwner !== undefined) { - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: roleOwner, type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: roleOwner, type: "ContractAddress" }); if (addedSuper) { - c.addConstructorCode(`self.accesscontrol._grant_role(${roleId}, ${roleOwner})`); + c.addConstructorCode( + `self.accesscontrol._grant_role(${roleId}, ${roleOwner})`, + ); } } - c.addFunctionCodeBefore(trait, fn, `self.accesscontrol.assert_only_role(${roleId})`); + c.addFunctionCodeBefore( + trait, + fn, + `self.accesscontrol.assert_only_role(${roleId})`, + ); break; } } } -const components = defineComponents( { +const components = defineComponents({ OwnableComponent: { - path: 'openzeppelin::access::ownable', + path: "openzeppelin::access::ownable", substorage: { - name: 'ownable', - type: 'OwnableComponent::Storage', + name: "ownable", + type: "OwnableComponent::Storage", }, event: { - name: 'OwnableEvent', - type: 'OwnableComponent::Event', + name: "OwnableEvent", + type: "OwnableComponent::Event", }, - impls: [{ - name: 'OwnableMixinImpl', - value: 'OwnableComponent::OwnableMixinImpl', - }, { - name: 'OwnableInternalImpl', - embed: false, - value: 'OwnableComponent::InternalImpl', - }], + impls: [ + { + name: "OwnableMixinImpl", + value: "OwnableComponent::OwnableMixinImpl", + }, + { + name: "OwnableInternalImpl", + embed: false, + value: "OwnableComponent::InternalImpl", + }, + ], }, AccessControlComponent: { - path: 'openzeppelin::access::accesscontrol', + path: "openzeppelin::access::accesscontrol", substorage: { - name: 'accesscontrol', - type: 'AccessControlComponent::Storage', + name: "accesscontrol", + type: "AccessControlComponent::Storage", }, event: { - name: 'AccessControlEvent', - type: 'AccessControlComponent::Event', + name: "AccessControlEvent", + type: "AccessControlComponent::Event", }, - impls: [{ - name: 'AccessControlInternalImpl', - embed: false, - value: 'AccessControlComponent::InternalImpl', - }], + impls: [ + { + name: "AccessControlInternalImpl", + embed: false, + value: "AccessControlComponent::InternalImpl", + }, + ], }, }); diff --git a/packages/core/cairo/src/set-info.ts b/packages/core/cairo/src/set-info.ts index 421a15029..30a110db5 100644 --- a/packages/core/cairo/src/set-info.ts +++ b/packages/core/cairo/src/set-info.ts @@ -1,12 +1,12 @@ import type { ContractBuilder } from "./contract"; -export const infoOptions = [{}, { license: 'WTFPL' }] as const; +export const infoOptions = [{}, { license: "WTFPL" }] as const; -export const defaults: Info = { license: 'MIT' }; +export const defaults: Info = { license: "MIT" }; export type Info = { license?: string; -} +}; export function setInfo(c: ContractBuilder, info: Info): void { const { license } = info; diff --git a/packages/core/cairo/src/set-royalty-info.ts b/packages/core/cairo/src/set-royalty-info.ts index 5464be105..fb27f0dfe 100644 --- a/packages/core/cairo/src/set-royalty-info.ts +++ b/packages/core/cairo/src/set-royalty-info.ts @@ -1,38 +1,46 @@ -import type { BaseImplementedTrait, ContractBuilder } from './contract'; -import { defineComponents } from './utils/define-components'; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; +import { defineComponents } from "./utils/define-components"; import { OptionsError } from "./error"; -import { toUint } from './utils/convert-strings'; -import { Access, setAccessControl, DEFAULT_ACCESS_CONTROL } from './set-access-control'; +import { toUint } from "./utils/convert-strings"; +import { + Access, + setAccessControl, + DEFAULT_ACCESS_CONTROL, +} from "./set-access-control"; const DEFAULT_FEE_DENOMINATOR = BigInt(10_000); export const defaults: RoyaltyInfoOptions = { enabled: false, - defaultRoyaltyFraction: '0', - feeDenominator: DEFAULT_FEE_DENOMINATOR.toString() + defaultRoyaltyFraction: "0", + feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }; export const royaltyInfoOptions = { disabled: defaults, enabledDefault: { enabled: true, - defaultRoyaltyFraction: '500', + defaultRoyaltyFraction: "500", feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }, enabledCustom: { enabled: true, - defaultRoyaltyFraction: '15125', - feeDenominator: '100000', - } -} + defaultRoyaltyFraction: "15125", + feeDenominator: "100000", + }, +}; export type RoyaltyInfoOptions = { - enabled: boolean, - defaultRoyaltyFraction: string, - feeDenominator: string, + enabled: boolean; + defaultRoyaltyFraction: string; + feeDenominator: string; }; -export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, access: Access): void { +export function setRoyaltyInfo( + c: ContractBuilder, + options: RoyaltyInfoOptions, + access: Access, +): void { if (!options.enabled) { return; } @@ -41,61 +49,77 @@ export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, } setAccessControl(c, access); - const { defaultRoyaltyFraction, feeDenominator } = getRoyaltyParameters(options); + const { defaultRoyaltyFraction, feeDenominator } = + getRoyaltyParameters(options); const initParams = [ - { lit: 'default_royalty_receiver' }, - defaultRoyaltyFraction + { lit: "default_royalty_receiver" }, + defaultRoyaltyFraction, ]; c.addComponent(components.ERC2981Component, initParams, true); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'default_royalty_receiver', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ + name: "default_royalty_receiver", + type: "ContractAddress", + }); switch (access) { - case 'ownable': + case "ownable": c.addImplToComponent(components.ERC2981Component, { - name: 'ERC2981AdminOwnableImpl', + name: "ERC2981AdminOwnableImpl", value: `ERC2981Component::ERC2981AdminOwnableImpl`, }); break; - case 'roles': + case "roles": c.addImplToComponent(components.ERC2981Component, { - name: 'ERC2981AdminAccessControlImpl', + name: "ERC2981AdminAccessControlImpl", value: `ERC2981Component::ERC2981AdminAccessControlImpl`, }); - c.addConstructorArgument({ name: 'royalty_admin', type: 'ContractAddress'}); - c.addConstructorCode('self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)'); + c.addConstructorArgument({ + name: "royalty_admin", + type: "ContractAddress", + }); + c.addConstructorCode( + "self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)", + ); break; } if (feeDenominator === DEFAULT_FEE_DENOMINATOR) { - c.addUseClause('openzeppelin::token::common::erc2981', 'DefaultConfig'); + c.addUseClause("openzeppelin::token::common::erc2981", "DefaultConfig"); } else { const trait: BaseImplementedTrait = { - name: 'ERC2981ImmutableConfig', - of: 'ERC2981Component::ImmutableConfig', + name: "ERC2981ImmutableConfig", + of: "ERC2981Component::ImmutableConfig", tags: [], }; c.addImplementedTrait(trait); c.addSuperVariableToTrait(trait, { - name: 'FEE_DENOMINATOR', - type: 'u128', - value: feeDenominator.toString() + name: "FEE_DENOMINATOR", + type: "u128", + value: feeDenominator.toString(), }); } } -function getRoyaltyParameters(opts: Required): { defaultRoyaltyFraction: bigint, feeDenominator: bigint } { - const feeDenominator = toUint(opts.feeDenominator, 'feeDenominator', 'u128'); +function getRoyaltyParameters(opts: Required): { + defaultRoyaltyFraction: bigint; + feeDenominator: bigint; +} { + const feeDenominator = toUint(opts.feeDenominator, "feeDenominator", "u128"); if (feeDenominator === BigInt(0)) { throw new OptionsError({ - feeDenominator: 'Must be greater than 0' + feeDenominator: "Must be greater than 0", }); } - const defaultRoyaltyFraction = toUint(opts.defaultRoyaltyFraction, 'defaultRoyaltyFraction', 'u128'); + const defaultRoyaltyFraction = toUint( + opts.defaultRoyaltyFraction, + "defaultRoyaltyFraction", + "u128", + ); if (defaultRoyaltyFraction > feeDenominator) { throw new OptionsError({ - defaultRoyaltyFraction: 'Cannot be greater than fee denominator' + defaultRoyaltyFraction: "Cannot be greater than fee denominator", }); } return { defaultRoyaltyFraction, feeDenominator }; @@ -103,29 +127,29 @@ function getRoyaltyParameters(opts: Required): { defaultRoya const components = defineComponents({ ERC2981Component: { - path: 'openzeppelin::token::common::erc2981', + path: "openzeppelin::token::common::erc2981", substorage: { - name: 'erc2981', - type: 'ERC2981Component::Storage', + name: "erc2981", + type: "ERC2981Component::Storage", }, event: { - name: 'ERC2981Event', - type: 'ERC2981Component::Event', + name: "ERC2981Event", + type: "ERC2981Component::Event", }, impls: [ { - name: 'ERC2981Impl', - value: 'ERC2981Component::ERC2981Impl', + name: "ERC2981Impl", + value: "ERC2981Component::ERC2981Impl", }, { - name: 'ERC2981InfoImpl', - value: 'ERC2981Component::ERC2981InfoImpl', + name: "ERC2981InfoImpl", + value: "ERC2981Component::ERC2981InfoImpl", }, { - name: 'ERC2981InternalImpl', - value: 'ERC2981Component::InternalImpl', - embed: false - } + name: "ERC2981InternalImpl", + value: "ERC2981Component::InternalImpl", + embed: false, + }, ], }, }); diff --git a/packages/core/cairo/src/set-upgradeable.ts b/packages/core/cairo/src/set-upgradeable.ts index 65a944128..fdb955e54 100644 --- a/packages/core/cairo/src/set-upgradeable.ts +++ b/packages/core/cairo/src/set-upgradeable.ts @@ -1,15 +1,18 @@ -import { getSelfArg } from './common-options'; -import type { BaseImplementedTrait, ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineComponents } from './utils/define-components'; -import { defineFunctions } from './utils/define-functions'; -import type { Account } from './account'; +import { getSelfArg } from "./common-options"; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineComponents } from "./utils/define-components"; +import { defineFunctions } from "./utils/define-functions"; +import type { Account } from "./account"; export const upgradeableOptions = [false, true] as const; -export type Upgradeable = typeof upgradeableOptions[number]; +export type Upgradeable = (typeof upgradeableOptions)[number]; -function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseImplementedTrait | undefined { +function setUpgradeableBase( + c: ContractBuilder, + upgradeable: Upgradeable, +): BaseImplementedTrait | undefined { if (upgradeable === false) { return undefined; } @@ -18,78 +21,106 @@ function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseI c.addComponent(components.UpgradeableComponent, [], false); - c.addUseClause('openzeppelin::upgrades::interface', 'IUpgradeable'); - c.addUseClause('starknet', 'ClassHash'); + c.addUseClause("openzeppelin::upgrades::interface", "IUpgradeable"); + c.addUseClause("starknet", "ClassHash"); const t: BaseImplementedTrait = { - name: 'UpgradeableImpl', - of: 'IUpgradeable', - section: 'Upgradeable', - tags: [ - 'abi(embed_v0)' - ], + name: "UpgradeableImpl", + of: "IUpgradeable", + section: "Upgradeable", + tags: ["abi(embed_v0)"], }; c.addImplementedTrait(t); return t; } -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access): void { +export function setUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + access: Access, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - requireAccessControl(c, trait, functions.upgrade, access, 'UPGRADER', 'upgrader'); + requireAccessControl( + c, + trait, + functions.upgrade, + access, + "UPGRADER", + "upgrader", + ); } } -export function setUpgradeableGovernor(c: ContractBuilder, upgradeable: Upgradeable): void { +export function setUpgradeableGovernor( + c: ContractBuilder, + upgradeable: Upgradeable, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalExtendedImpl'); - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.governor.assert_only_governance()'); + c.addUseClause( + "openzeppelin::governance::governor::GovernorComponent", + "InternalExtendedImpl", + ); + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.governor.assert_only_governance()", + ); } } -export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account): void { +export function setAccountUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + type: Account, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { switch (type) { - case 'stark': - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.account.assert_only_self()'); + case "stark": + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.account.assert_only_self()", + ); break; - case 'eth': - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.eth_account.assert_only_self()'); + case "eth": + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.eth_account.assert_only_self()", + ); break; } } } -const components = defineComponents( { +const components = defineComponents({ UpgradeableComponent: { - path: 'openzeppelin::upgrades', + path: "openzeppelin::upgrades", substorage: { - name: 'upgradeable', - type: 'UpgradeableComponent::Storage', + name: "upgradeable", + type: "UpgradeableComponent::Storage", }, event: { - name: 'UpgradeableEvent', - type: 'UpgradeableComponent::Event', + name: "UpgradeableEvent", + type: "UpgradeableComponent::Event", }, - impls: [{ - name: 'UpgradeableInternalImpl', - embed: false, - value: 'UpgradeableComponent::InternalImpl', - }], + impls: [ + { + name: "UpgradeableInternalImpl", + embed: false, + value: "UpgradeableComponent::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ upgrade: { - args: [ - getSelfArg(), - { name: 'new_class_hash', type: 'ClassHash' }, - ], - code: [ - 'self.upgradeable.upgrade(new_class_hash)' - ] + args: [getSelfArg(), { name: "new_class_hash", type: "ClassHash" }], + code: ["self.upgradeable.upgrade(new_class_hash)"], }, }); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index e34e31247..e5974c16a 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -35,7 +35,7 @@ test.serial("custom result generated", async (t) => { async function testGenerate( t: ExecutionContext, - kind: keyof KindedOptions + kind: keyof KindedOptions, ) { const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); @@ -81,13 +81,13 @@ test("is access control required", async (t) => { t.regex( contract.source, regexOwnable, - JSON.stringify(contract.options) + JSON.stringify(contract.options), ); } else { t.notRegex( contract.source, regexOwnable, - JSON.stringify(contract.options) + JSON.stringify(contract.options), ); } } diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index 2bab75c9d..f4f9eca87 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -36,7 +36,7 @@ test("identifier - empty string", (t) => { const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); t.is( error.messages.name, - "Identifier is empty or does not have valid characters" + "Identifier is empty or does not have valid characters", ); }); @@ -46,7 +46,7 @@ test("identifier - no valid chars", (t) => { }); t.is( error.messages.name, - "Identifier is empty or does not have valid characters" + "Identifier is empty or does not have valid characters", ); }); @@ -77,11 +77,11 @@ test("toByteArray - escape backslash", (t) => { test("more than 31 characters", (t) => { t.is( toByteArray("A234567890123456789012345678901"), - "A234567890123456789012345678901" + "A234567890123456789012345678901", ); t.is( toByteArray("A2345678901234567890123456789012"), - "A2345678901234567890123456789012" + "A2345678901234567890123456789012", ); }); @@ -108,12 +108,12 @@ test("toFelt252 - escape backslash", (t) => { test("toFelt252 - max 31 characters", (t) => { t.is( toFelt252("A234567890123456789012345678901", "foo"), - "A234567890123456789012345678901" + "A234567890123456789012345678901", ); const error = t.throws( () => toFelt252("A2345678901234567890123456789012", "foo"), - { instanceOf: OptionsError } + { instanceOf: OptionsError }, ); t.is(error.messages.foo, "String is longer than 31 characters"); }); diff --git a/packages/core/cairo/src/utils/convert-strings.ts b/packages/core/cairo/src/utils/convert-strings.ts index d1681ec09..da8d0fe57 100644 --- a/packages/core/cairo/src/utils/convert-strings.ts +++ b/packages/core/cairo/src/utils/convert-strings.ts @@ -5,14 +5,15 @@ import { OptionsError } from "../error"; */ export function toIdentifier(str: string, capitalize = false): string { const result = str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/^[^a-zA-Z_]+/, '') - .replace(/^(.)/, c => capitalize ? c.toUpperCase() : c) + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/^[^a-zA-Z_]+/, "") + .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w]+(.?)/g, (_, c) => c.toUpperCase()); if (result.length === 0) { throw new OptionsError({ - name: 'Identifier is empty or does not have valid characters', + name: "Identifier is empty or does not have valid characters", }); } else { return result; @@ -24,9 +25,10 @@ export function toIdentifier(str: string, capitalize = false): string { */ export function toByteArray(str: string): string { return str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters - .replace(/(\\|")/g, (_, c) => '\\' + c); // escape backslash or double quotes + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters + .replace(/(\\|")/g, (_, c) => "\\" + c); // escape backslash or double quotes } /** @@ -34,13 +36,14 @@ export function toByteArray(str: string): string { */ export function toFelt252(str: string, field: string): string { const result = str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters - .replace(/(\\|')/g, (_, c) => '\\' + c); // escape backslash or single quote + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters + .replace(/(\\|')/g, (_, c) => "\\" + c); // escape backslash or single quote if (result.length > 31) { throw new OptionsError({ - [field]: 'String is longer than 31 characters', + [field]: "String is longer than 31 characters", }); } else { return result; @@ -49,22 +52,24 @@ export function toFelt252(str: string, field: string): string { function maxValueOfUint(bits: number): bigint { if (bits <= 0) { - throw new Error(`Number of bits must be positive (actual '${bits}').`) + throw new Error(`Number of bits must be positive (actual '${bits}').`); } if (bits % 8 !== 0) { - throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`) + throw new Error( + `The number of bits must be a multiple of 8 (actual '${bits}').`, + ); } const bytes = bits / 8; - return BigInt('0x' + 'ff'.repeat(bytes)) + return BigInt("0x" + "ff".repeat(bytes)); } const UINT_MAX_VALUES = { - 'u8': maxValueOfUint(8), - 'u16': maxValueOfUint(16), - 'u32': maxValueOfUint(32), - 'u64': maxValueOfUint(64), - 'u128': maxValueOfUint(128), - 'u256': maxValueOfUint(256) + u8: maxValueOfUint(8), + u16: maxValueOfUint(16), + u32: maxValueOfUint(32), + u64: maxValueOfUint(64), + u128: maxValueOfUint(128), + u256: maxValueOfUint(256), } as const; export type UintType = keyof typeof UINT_MAX_VALUES; @@ -72,18 +77,22 @@ export type UintType = keyof typeof UINT_MAX_VALUES; /** * Checks that a string/number value is a valid `uint` value and converts it to bigint */ -export function toUint(value: number | string, field: string, type: UintType): bigint { - const valueAsStr = value.toString(); +export function toUint( + value: number | string, + field: string, + type: UintType, +): bigint { + const valueAsStr = value.toString(); const isValidNumber = /^\d+$/.test(valueAsStr); if (!isValidNumber) { throw new OptionsError({ - [field]: 'Not a valid number' + [field]: "Not a valid number", }); } const numValue = BigInt(valueAsStr); if (numValue > UINT_MAX_VALUES[type]) { throw new OptionsError({ - [field]: `Value is greater than ${type} max value` + [field]: `Value is greater than ${type} max value`, }); } return numValue; @@ -92,6 +101,10 @@ export function toUint(value: number | string, field: string, type: UintType): b /** * Checks that a string/number value is a valid `uint` value */ -export function validateUint(value: number | string, field: string, type: UintType): void { +export function validateUint( + value: number | string, + field: string, + type: UintType, +): void { const _ = toUint(value, field, type); } diff --git a/packages/core/cairo/src/utils/define-components.ts b/packages/core/cairo/src/utils/define-components.ts index 62ba3ca6e..f929db64f 100644 --- a/packages/core/cairo/src/utils/define-components.ts +++ b/packages/core/cairo/src/utils/define-components.ts @@ -1,6 +1,6 @@ -import type { Component } from '../contract'; +import type { Component } from "../contract"; -type ImplicitNameComponent = Omit; +type ImplicitNameComponent = Omit; export function defineComponents( fns: Record, diff --git a/packages/core/cairo/src/utils/define-functions.ts b/packages/core/cairo/src/utils/define-functions.ts index c1f664e7e..c05316bce 100644 --- a/packages/core/cairo/src/utils/define-functions.ts +++ b/packages/core/cairo/src/utils/define-functions.ts @@ -1,6 +1,6 @@ -import type { BaseFunction } from '../contract'; +import type { BaseFunction } from "../contract"; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; export function defineFunctions( fns: Record, diff --git a/packages/core/cairo/src/utils/duration.ts b/packages/core/cairo/src/utils/duration.ts index ffc2fe5b5..224de5dfa 100644 --- a/packages/core/cairo/src/utils/duration.ts +++ b/packages/core/cairo/src/utils/duration.ts @@ -1,6 +1,16 @@ -const durationUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; -type DurationUnit = typeof durationUnits[number]; -export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); +const durationUnits = [ + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", +] as const; +type DurationUnit = (typeof durationUnits)[number]; +export const durationPattern = new RegExp( + `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, +); const second = 1; const minute = 60 * second; @@ -15,7 +25,7 @@ export function durationToTimestamp(duration: string): number { const match = duration.trim().match(durationPattern); if (!match || match.length < 2) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = parseFloat(match[1]!); diff --git a/packages/core/cairo/src/utils/find-cover.ts b/packages/core/cairo/src/utils/find-cover.ts index 939ed9240..0cc7f0bb6 100644 --- a/packages/core/cairo/src/utils/find-cover.ts +++ b/packages/core/cairo/src/utils/find-cover.ts @@ -1,11 +1,14 @@ -import { sortedBy } from './sorted-by'; +import { sortedBy } from "./sorted-by"; // Greedy approximation of minimum set cover. -export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { +export function findCover( + sets: T[], + getElements: (set: T) => unknown[], +): T[] { const sortedSets = sortedBy( - sets.map(set => ({ set, elems: getElements(set) })), - s => -s.elems.length, + sets.map((set) => ({ set, elems: getElements(set) })), + (s) => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/cairo/src/utils/format-lines.ts b/packages/core/cairo/src/utils/format-lines.ts index 5e18a0c4a..66a39941e 100644 --- a/packages/core/cairo/src/utils/format-lines.ts +++ b/packages/core/cairo/src/utils/format-lines.ts @@ -1,9 +1,9 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol('whitespace'); +const whitespace = Symbol("whitespace"); export function formatLines(...lines: Lines[]): string { - return [...indentEach(0, lines)].join('\n') + '\n'; + return [...indentEach(0, lines)].join("\n") + "\n"; } function* indentEach( @@ -12,18 +12,18 @@ function* indentEach( ): Generator { for (const line of lines) { if (line === whitespace) { - yield ''; + yield ""; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line); } else { - yield ' '.repeat(indent) + line; + yield " ".repeat(indent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter(l => l.length > 0) - .flatMap(l => [whitespace, ...l]) + .filter((l) => l.length > 0) + .flatMap((l) => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/cairo/src/utils/version.test.ts b/packages/core/cairo/src/utils/version.test.ts index 5b580626c..a612a88e8 100644 --- a/packages/core/cairo/src/utils/version.test.ts +++ b/packages/core/cairo/src/utils/version.test.ts @@ -1,11 +1,13 @@ -import test from 'ava'; +import test from "ava"; -import semver from 'semver'; +import semver from "semver"; -import { contractsVersion, compatibleContractsSemver } from './version'; +import { contractsVersion, compatibleContractsSemver } from "./version"; -test('latest target contracts satisfies compatible range', t => { - t.true(semver.satisfies(contractsVersion, compatibleContractsSemver), +test("latest target contracts satisfies compatible range", (t) => { + t.true( + semver.satisfies(contractsVersion, compatibleContractsSemver), `Latest target contracts version ${contractsVersion} does not satisfy compatible range ${compatibleContractsSemver}. -Check whether the compatible range is up to date.`); +Check whether the compatible range is up to date.`, + ); }); diff --git a/packages/core/cairo/src/utils/version.ts b/packages/core/cairo/src/utils/version.ts index ccba00acc..89eaca1a3 100644 --- a/packages/core/cairo/src/utils/version.ts +++ b/packages/core/cairo/src/utils/version.ts @@ -1,17 +1,17 @@ /** * The actual latest version to use in links. */ -export const contractsVersion = '0.20.0'; +export const contractsVersion = "0.20.0"; export const contractsVersionTag = `v${contractsVersion}`; /** * Cairo compiler versions. */ -export const edition = '2024_07'; -export const cairoVersion = '2.9.1'; -export const scarbVersion = '2.9.1'; +export const edition = "2024_07"; +export const cairoVersion = "2.9.1"; +export const scarbVersion = "2.9.1"; /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = '^0.20.0'; +export const compatibleContractsSemver = "^0.20.0"; diff --git a/packages/core/cairo/src/vesting.test.ts b/packages/core/cairo/src/vesting.test.ts index 3f9ead374..cf0fa48fc 100644 --- a/packages/core/cairo/src/vesting.test.ts +++ b/packages/core/cairo/src/vesting.test.ts @@ -1,41 +1,46 @@ -import test from 'ava'; -import { OptionsError, vesting } from '.'; -import { buildVesting, VestingOptions } from './vesting'; -import { printContract } from './print'; +import test from "ava"; +import { OptionsError, vesting } from "."; +import { buildVesting, VestingOptions } from "./vesting"; +import { printContract } from "./print"; const defaults: VestingOptions = { - name: 'MyVesting', - startDate: '', - duration: '0 day', - cliffDuration: '0 day', - schedule: 'linear' + name: "MyVesting", + startDate: "", + duration: "0 day", + cliffDuration: "0 day", + schedule: "linear", }; -const CUSTOM_NAME = 'CustomVesting'; -const CUSTOM_DATE = '2024-12-31T23:59'; -const CUSTOM_DURATION = '36 months'; -const CUSTOM_CLIFF = '90 days'; +const CUSTOM_NAME = "CustomVesting"; +const CUSTOM_DATE = "2024-12-31T23:59"; +const CUSTOM_DURATION = "36 months"; +const CUSTOM_CLIFF = "90 days"; // // Test helpers // function testVesting(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildVesting({ ...defaults, - ...opts + ...opts, }); t.snapshot(printContract(c)); }); } function testAPIEquivalence(title: string, opts?: VestingOptions) { - test(title, t => { - t.is(vesting.print(opts), printContract(buildVesting({ - ...defaults, - ...opts - }))); + test(title, (t) => { + t.is( + vesting.print(opts), + printContract( + buildVesting({ + ...defaults, + ...opts, + }), + ), + ); }); } @@ -43,77 +48,82 @@ function testAPIEquivalence(title: string, opts?: VestingOptions) { // Snapshot tests // -testVesting('custom name', { +testVesting("custom name", { name: CUSTOM_NAME, }); -testVesting('custom start date', { - startDate: CUSTOM_DATE +testVesting("custom start date", { + startDate: CUSTOM_DATE, }); -testVesting('custom duration', { - duration: CUSTOM_DURATION +testVesting("custom duration", { + duration: CUSTOM_DURATION, }); -testVesting('custom cliff', { +testVesting("custom cliff", { duration: CUSTOM_DURATION, - cliffDuration: CUSTOM_CLIFF + cliffDuration: CUSTOM_CLIFF, }); -testVesting('custom schedule', { - schedule: 'custom' +testVesting("custom schedule", { + schedule: "custom", }); -testVesting('all custom settings', { +testVesting("all custom settings", { startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: 'custom' + schedule: "custom", }); // // API tests // -testAPIEquivalence('API custom name', { +testAPIEquivalence("API custom name", { ...defaults, - name: CUSTOM_NAME + name: CUSTOM_NAME, }); -testAPIEquivalence('API custom start date', { +testAPIEquivalence("API custom start date", { ...defaults, - startDate: CUSTOM_DATE + startDate: CUSTOM_DATE, }); -testAPIEquivalence('API custom duration', { +testAPIEquivalence("API custom duration", { ...defaults, - duration: CUSTOM_DURATION + duration: CUSTOM_DURATION, }); -testAPIEquivalence('API custom cliff', { +testAPIEquivalence("API custom cliff", { ...defaults, duration: CUSTOM_DURATION, - cliffDuration: CUSTOM_CLIFF + cliffDuration: CUSTOM_CLIFF, }); -testAPIEquivalence('API custom schedule', { +testAPIEquivalence("API custom schedule", { ...defaults, - schedule: 'custom' + schedule: "custom", }); -testAPIEquivalence('API all custom settings', { +testAPIEquivalence("API all custom settings", { ...defaults, startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: 'custom' + schedule: "custom", }); -test('cliff too high', async t => { - const error = t.throws(() => buildVesting({ - ...defaults, - duration: '20 days', - cliffDuration: '21 days' - })); - t.is((error as OptionsError).messages.cliffDuration, 'Cliff duration must be less than or equal to the total duration'); +test("cliff too high", async (t) => { + const error = t.throws(() => + buildVesting({ + ...defaults, + duration: "20 days", + cliffDuration: "21 days", + }), + ); + t.is( + (error as OptionsError).messages.cliffDuration, + "Cliff duration must be less than or equal to the total duration", + ); }); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 372fde3b2..398a509a9 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -144,7 +144,7 @@ function addSchedule(c: ContractBuilder, opts: VestingOptions) { } function getVestingStart( - opts: VestingOptions + opts: VestingOptions, ): { timestampInSec: bigint; formattedDate: string } | undefined { if (opts.startDate === "" || opts.startDate === "NaN") { return undefined; @@ -154,7 +154,7 @@ function getVestingStart( const timestampInSec = toUint( Math.floor(timestampInMillis / 1000), "startDate", - "u64" + "u64", ); const formattedDate = startDate.toLocaleString("en-GB", { day: "2-digit", diff --git a/packages/core/solidity/ava.config.js b/packages/core/solidity/ava.config.js index 77a9117a9..a39075959 100644 --- a/packages/core/solidity/ava.config.js +++ b/packages/core/solidity/ava.config.js @@ -1,13 +1,9 @@ module.exports = { - extensions: ['ts'], - require: ['ts-node/register'], + extensions: ["ts"], + require: ["ts-node/register"], watchmode: { - ignoreChanges: [ - 'contracts', - 'artifacts', - 'cache', - ], + ignoreChanges: ["contracts", "artifacts", "cache"], }, - timeout: '10m', + timeout: "10m", workerThreads: false, }; diff --git a/packages/core/solidity/get-imports.d.ts b/packages/core/solidity/get-imports.d.ts index 4c0a44175..d89032e9e 100644 --- a/packages/core/solidity/get-imports.d.ts +++ b/packages/core/solidity/get-imports.d.ts @@ -1 +1 @@ -export * from './src/get-imports'; +export * from "./src/get-imports"; diff --git a/packages/core/solidity/get-imports.js b/packages/core/solidity/get-imports.js index c93672cc4..52dd2841a 100644 --- a/packages/core/solidity/get-imports.js +++ b/packages/core/solidity/get-imports.js @@ -1 +1 @@ -module.exports = require('./dist/get-imports'); \ No newline at end of file +module.exports = require("./dist/get-imports"); diff --git a/packages/core/solidity/hardhat.config.js b/packages/core/solidity/hardhat.config.js index 680ecea41..01edf1775 100644 --- a/packages/core/solidity/hardhat.config.js +++ b/packages/core/solidity/hardhat.config.js @@ -1,21 +1,24 @@ -const { task } = require('hardhat/config'); -const { HardhatError } = require('hardhat/internal/core/errors'); -const { ERRORS } = require('hardhat/internal/core/errors-list'); +const { task } = require("hardhat/config"); +const { HardhatError } = require("hardhat/internal/core/errors"); +const { ERRORS } = require("hardhat/internal/core/errors-list"); const { TASK_COMPILE_SOLIDITY_CHECK_ERRORS, TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, -} = require('hardhat/builtin-tasks/task-names'); -const SOLIDITY_VERSION = require('./src/solidity-version.json'); +} = require("hardhat/builtin-tasks/task-names"); +const SOLIDITY_VERSION = require("./src/solidity-version.json"); // Unused parameter warnings are caused by OpenZeppelin Upgradeable Contracts. -const WARN_UNUSED_PARAMETER = '5667'; -const WARN_CODE_SIZE = '5574'; +const WARN_UNUSED_PARAMETER = "5667"; +const WARN_CODE_SIZE = "5574"; const IGNORED_WARNINGS = [WARN_UNUSED_PARAMETER, WARN_CODE_SIZE]; // Overriding this task so that warnings are considered errors. task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { - const errors = output.errors && output.errors.filter(e => !IGNORED_WARNINGS.includes(e.errorCode)) || []; + const errors = + (output.errors && + output.errors.filter((e) => !IGNORED_WARNINGS.includes(e.errorCode))) || + []; await run(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, { output: { ...output, errors }, @@ -27,16 +30,20 @@ task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { } }); -task(TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, async ({ compilationJobs }, _, runSuper) => { - const CHUNK_SIZE = 100; - const chunks = []; - for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { - chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); - } - const mergedChunks = await Promise.all(chunks.map(cj => runSuper({ compilationJobs: cj }))); - return mergedChunks.flat(); -}); - +task( + TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, + async ({ compilationJobs }, _, runSuper) => { + const CHUNK_SIZE = 100; + const chunks = []; + for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { + chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); + } + const mergedChunks = await Promise.all( + chunks.map((cj) => runSuper({ compilationJobs: cj })), + ); + return mergedChunks.flat(); + }, +); /** * @type import('hardhat/config').HardhatUserConfig diff --git a/packages/core/solidity/print-versioned.js b/packages/core/solidity/print-versioned.js index 2bc0b8aa3..c3861b0fa 100644 --- a/packages/core/solidity/print-versioned.js +++ b/packages/core/solidity/print-versioned.js @@ -1 +1 @@ -module.exports = require('./dist/print-versioned'); +module.exports = require("./dist/print-versioned"); diff --git a/packages/core/solidity/print-versioned.ts b/packages/core/solidity/print-versioned.ts index 95629a774..ee52117b8 100644 --- a/packages/core/solidity/print-versioned.ts +++ b/packages/core/solidity/print-versioned.ts @@ -1 +1 @@ -export * from './src/print-versioned'; +export * from "./src/print-versioned"; diff --git a/packages/core/solidity/src/add-pausable.ts b/packages/core/solidity/src/add-pausable.ts index 7877905c7..e6fedab08 100644 --- a/packages/core/solidity/src/add-pausable.ts +++ b/packages/core/solidity/src/add-pausable.ts @@ -1,36 +1,40 @@ -import type { ContractBuilder, BaseFunction } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; +import type { ContractBuilder, BaseFunction } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; -export function addPausable(c: ContractBuilder, access: Access, pausableFns: BaseFunction[]) { +export function addPausable( + c: ContractBuilder, + access: Access, + pausableFns: BaseFunction[], +) { c.addParent({ - name: 'Pausable', - path: '@openzeppelin/contracts/utils/Pausable.sol', + name: "Pausable", + path: "@openzeppelin/contracts/utils/Pausable.sol", }); for (const fn of pausableFns) { - c.addModifier('whenNotPaused', fn); + c.addModifier("whenNotPaused", fn); } addPauseFunctions(c, access); } export function addPauseFunctions(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.pause, access, 'PAUSER', 'pauser'); - c.addFunctionCode('_pause();', functions.pause); + requireAccessControl(c, functions.pause, access, "PAUSER", "pauser"); + c.addFunctionCode("_pause();", functions.pause); - requireAccessControl(c, functions.unpause, access, 'PAUSER', 'pauser'); - c.addFunctionCode('_unpause();', functions.unpause); + requireAccessControl(c, functions.unpause, access, "PAUSER", "pauser"); + c.addFunctionCode("_unpause();", functions.unpause); } const functions = defineFunctions({ pause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, unpause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, }); diff --git a/packages/core/solidity/src/api.ts b/packages/core/solidity/src/api.ts index 5a9a86190..e7c8f1941 100644 --- a/packages/core/solidity/src/api.ts +++ b/packages/core/solidity/src/api.ts @@ -1,27 +1,57 @@ -import type { CommonOptions } from './common-options'; -import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; -import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; -import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; -import { printStablecoin, defaults as stablecoinDefaults, isAccessControlRequired as stablecoinIsAccessControlRequired, StablecoinOptions } from './stablecoin'; -import { printGovernor, defaults as governorDefaults, isAccessControlRequired as governorIsAccessControlRequired, GovernorOptions } from './governor'; -import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; +import type { CommonOptions } from "./common-options"; +import { + printERC20, + defaults as erc20defaults, + isAccessControlRequired as erc20IsAccessControlRequired, + ERC20Options, +} from "./erc20"; +import { + printERC721, + defaults as erc721defaults, + isAccessControlRequired as erc721IsAccessControlRequired, + ERC721Options, +} from "./erc721"; +import { + printERC1155, + defaults as erc1155defaults, + isAccessControlRequired as erc1155IsAccessControlRequired, + ERC1155Options, +} from "./erc1155"; +import { + printStablecoin, + defaults as stablecoinDefaults, + isAccessControlRequired as stablecoinIsAccessControlRequired, + StablecoinOptions, +} from "./stablecoin"; +import { + printGovernor, + defaults as governorDefaults, + isAccessControlRequired as governorIsAccessControlRequired, + GovernorOptions, +} from "./governor"; +import { + printCustom, + defaults as customDefaults, + isAccessControlRequired as customIsAccessControlRequired, + CustomOptions, +} from "./custom"; export interface WizardContractAPI { /** * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. */ - print: (opts?: Options) => string, - + print: (opts?: Options) => string; + /** * The default options that are used for `print`. */ defaults: Required; /** - * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the - * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. + * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the + * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. */ - isAccessControlRequired: (opts: Partial) => boolean, + isAccessControlRequired: (opts: Partial) => boolean; } export type ERC20 = WizardContractAPI; @@ -35,35 +65,35 @@ export type Custom = WizardContractAPI; export const erc20: ERC20 = { print: printERC20, defaults: erc20defaults, - isAccessControlRequired: erc20IsAccessControlRequired -} + isAccessControlRequired: erc20IsAccessControlRequired, +}; export const erc721: ERC721 = { print: printERC721, defaults: erc721defaults, - isAccessControlRequired: erc721IsAccessControlRequired -} + isAccessControlRequired: erc721IsAccessControlRequired, +}; export const erc1155: ERC1155 = { print: printERC1155, defaults: erc1155defaults, - isAccessControlRequired: erc1155IsAccessControlRequired -} + isAccessControlRequired: erc1155IsAccessControlRequired, +}; export const stablecoin: Stablecoin = { print: printStablecoin, defaults: stablecoinDefaults, - isAccessControlRequired: stablecoinIsAccessControlRequired -} + isAccessControlRequired: stablecoinIsAccessControlRequired, +}; export const realWorldAsset: RealWorldAsset = { print: printStablecoin, defaults: stablecoinDefaults, - isAccessControlRequired: stablecoinIsAccessControlRequired -} + isAccessControlRequired: stablecoinIsAccessControlRequired, +}; export const governor: Governor = { print: printGovernor, defaults: governorDefaults, - isAccessControlRequired: governorIsAccessControlRequired -} + isAccessControlRequired: governorIsAccessControlRequired, +}; export const custom: Custom = { print: printCustom, defaults: customDefaults, - isAccessControlRequired: customIsAccessControlRequired -} \ No newline at end of file + isAccessControlRequired: customIsAccessControlRequired, +}; diff --git a/packages/core/solidity/src/common-functions.ts b/packages/core/solidity/src/common-functions.ts index 7d76c5f02..7647afdad 100644 --- a/packages/core/solidity/src/common-functions.ts +++ b/packages/core/solidity/src/common-functions.ts @@ -1,11 +1,9 @@ -import type { BaseFunction } from './contract'; +import type { BaseFunction } from "./contract"; export const supportsInterface: BaseFunction = { - name: 'supportsInterface', - kind: 'public', - args: [ - { name: 'interfaceId', type: 'bytes4' }, - ], - returns: ['bool'], - mutability: 'view', + name: "supportsInterface", + kind: "public", + args: [{ name: "interfaceId", type: "bytes4" }], + returns: ["bool"], + mutability: "view", }; diff --git a/packages/core/solidity/src/common-options.ts b/packages/core/solidity/src/common-options.ts index eb0cec1ba..9e3e59e9c 100644 --- a/packages/core/solidity/src/common-options.ts +++ b/packages/core/solidity/src/common-options.ts @@ -15,7 +15,9 @@ export interface CommonOptions { info?: Info; } -export function withCommonDefaults(opts: CommonOptions): Required { +export function withCommonDefaults( + opts: CommonOptions, +): Required { return { access: opts.access ?? false, upgradeable: opts.upgradeable ?? false, diff --git a/packages/core/solidity/src/contract.test.ts b/packages/core/solidity/src/contract.test.ts index b391a2445..1c74412f0 100644 --- a/packages/core/solidity/src/contract.test.ts +++ b/packages/core/solidity/src/contract.test.ts @@ -1,170 +1,169 @@ -import test from 'ava'; +import test from "ava"; -import { ContractBuilder } from './contract'; -import { printContract } from './print'; -import { TAG_SECURITY_CONTACT } from './set-info'; +import { ContractBuilder } from "./contract"; +import { printContract } from "./print"; +import { TAG_SECURITY_CONTACT } from "./set-info"; const toContractReference = (name: string) => { return { name: name, - } -} + }; +}; const toParentContract = (name: string, path: string) => { return { name: name, path: path, - } -} + }; +}; -test('contract basics', t => { - const Foo = new ContractBuilder('Foo'); +test("contract basics", (t) => { + const Foo = new ContractBuilder("Foo"); t.snapshot(printContract(Foo)); }); -test('contract with a parent', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with a parent", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar); t.snapshot(printContract(Foo)); }); -test('contract with two parents', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); - const Quux = toParentContract('Quux', './Quux.sol'); +test("contract with two parents", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); + const Quux = toParentContract("Quux", "./Quux.sol"); Foo.addParent(Bar); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test('contract with a parent with parameters', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with a parent with parameters", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar, ["param1", "param2"]); t.snapshot(printContract(Foo)); }); -test('contract with two parents only one with parameters', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); - const Quux = toParentContract('Quux', './Quux.sol'); +test("contract with two parents only one with parameters", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); + const Quux = toParentContract("Quux", "./Quux.sol"); Foo.addParent(Bar, ["param1", "param2"]); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test('contract with one override', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with one override", (t) => { + const Foo = new ContractBuilder("Foo"); const _beforeTokenTransfer = { - name: '_beforeTokenTransfer', - kind: 'internal' as const, + name: "_beforeTokenTransfer", + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }; - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test('contract with two overrides', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('ERC20Snapshot'), _beforeTokenTransfer); +test("contract with two overrides", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20Snapshot"), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test('contract with two different overrides', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with two different overrides", (t) => { + const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('OtherParent'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('ERC20'), _otherFunction); - Foo.addOverride(toContractReference('OtherParent'), _otherFunction); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("OtherParent"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20"), _otherFunction); + Foo.addOverride(toContractReference("OtherParent"), _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with a modifier', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addModifier('whenNotPaused', _otherFunction); +test("contract with a modifier", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addModifier("whenNotPaused", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with a modifier and override', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addModifier('whenNotPaused', _otherFunction); +test("contract with a modifier and override", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addModifier("whenNotPaused", _otherFunction); - Foo.addOverride(toContractReference('ERC20'), _otherFunction); - Foo.addOverride(toContractReference('OtherParent'), _otherFunction); + Foo.addOverride(toContractReference("ERC20"), _otherFunction); + Foo.addOverride(toContractReference("OtherParent"), _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with constructor code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); +test("contract with constructor code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code and a parent', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with constructor code and a parent", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar, ["param1", "param2"]); - Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); + Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); t.snapshot(printContract(Foo)); }); -test('contract with function code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); +test("contract with function code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with overridden function with code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference('Bar'), _otherFunction); - Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); +test("contract with overridden function with code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addOverride(toContractReference("Bar"), _otherFunction); + Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with one variable', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addVariable('uint value = 42;'); +test("contract with one variable", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addVariable("uint value = 42;"); t.snapshot(printContract(Foo)); }); -test('contract with two variables', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addVariable('uint value = 42;'); +test("contract with two variables", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addVariable("uint value = 42;"); Foo.addVariable('string name = "john";'); t.snapshot(printContract(Foo)); }); -test('name with special characters', t => { - const Foo = new ContractBuilder('foo bar baz'); +test("name with special characters", (t) => { + const Foo = new ContractBuilder("foo bar baz"); t.snapshot(printContract(Foo)); }); -test('contract with info', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addNatspecTag(TAG_SECURITY_CONTACT, 'security@example.com'); +test("contract with info", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addNatspecTag(TAG_SECURITY_CONTACT, "security@example.com"); t.snapshot(printContract(Foo)); }); - const _beforeTokenTransfer = { - name: '_beforeTokenTransfer', - kind: 'internal' as const, + name: "_beforeTokenTransfer", + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }; const _otherFunction = { - name: '_otherFunction', - kind: 'internal' as const, + name: "_otherFunction", + kind: "internal" as const, args: [], }; diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index 7ac998d20..560bf4cae 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -64,7 +64,7 @@ const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; function maxMutability( a: FunctionMutability, - b: FunctionMutability + b: FunctionMutability, ): FunctionMutability { return mutabilityRank[ Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) @@ -148,7 +148,7 @@ export class ContractBuilder implements Contract { addOverride( parent: ReferencedContract, baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); fn.override.add(parent); @@ -205,7 +205,7 @@ export class ContractBuilder implements Contract { addFunctionCode( code: string, baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); if (fn.final) { @@ -220,7 +220,7 @@ export class ContractBuilder implements Contract { setFunctionBody( code: string[], baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { diff --git a/packages/core/solidity/src/custom.test.ts b/packages/core/solidity/src/custom.test.ts index 2cfb60ce4..3299f1cbe 100644 --- a/packages/core/solidity/src/custom.test.ts +++ b/packages/core/solidity/src/custom.test.ts @@ -1,13 +1,13 @@ -import test from 'ava'; -import { custom } from '.'; +import test from "ava"; +import { custom } from "."; -import { buildCustom, CustomOptions } from './custom'; -import { printContract } from './print'; +import { buildCustom, CustomOptions } from "./custom"; +import { printContract } from "./print"; function testCustom(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildCustom({ - name: 'MyContract', + name: "MyContract", ...opts, }); t.snapshot(printContract(c)); @@ -17,75 +17,80 @@ function testCustom(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, t => { - t.is(custom.print(opts), printContract(buildCustom({ - name: 'MyContract', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: CustomOptions) { + test(title, (t) => { + t.is( + custom.print(opts), + printContract( + buildCustom({ + name: "MyContract", + ...opts, + }), + ), + ); }); } -testCustom('custom', {}); +testCustom("custom", {}); -testCustom('pausable', { +testCustom("pausable", { pausable: true, }); -testCustom('upgradeable transparent', { - upgradeable: 'transparent', +testCustom("upgradeable transparent", { + upgradeable: "transparent", }); -testCustom('upgradeable uups', { - upgradeable: 'uups', +testCustom("upgradeable uups", { + upgradeable: "uups", }); -testCustom('access control disabled', { +testCustom("access control disabled", { access: false, }); -testCustom('access control ownable', { - access: 'ownable', +testCustom("access control ownable", { + access: "ownable", }); -testCustom('access control roles', { - access: 'roles', +testCustom("access control roles", { + access: "roles", }); -testCustom('access control managed', { - access: 'managed', +testCustom("access control managed", { + access: "managed", }); -testCustom('upgradeable uups with access control disabled', { +testCustom("upgradeable uups with access control disabled", { // API should override access to true since it is required for UUPS access: false, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('custom API default'); +testAPIEquivalence("custom API default"); -testAPIEquivalence('custom API basic', { name: 'CustomContract' }); +testAPIEquivalence("custom API basic", { name: "CustomContract" }); -testAPIEquivalence('custom API full upgradeable', { - name: 'CustomContract', - access: 'roles', +testAPIEquivalence("custom API full upgradeable", { + name: "CustomContract", + access: "roles", pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('custom API full upgradeable with managed', { - name: 'CustomContract', - access: 'managed', +testAPIEquivalence("custom API full upgradeable with managed", { + name: "CustomContract", + access: "managed", pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('custom API assert defaults', async t => { +test("custom API assert defaults", async (t) => { t.is(custom.print(custom.defaults), custom.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(custom.isAccessControlRequired({ pausable: true }), true); - t.is(custom.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(custom.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(custom.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(custom.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/custom.ts b/packages/core/solidity/src/custom.ts index b82ca2c67..b167015ed 100644 --- a/packages/core/solidity/src/custom.ts +++ b/packages/core/solidity/src/custom.ts @@ -1,10 +1,14 @@ -import { Contract, ContractBuilder } from './contract'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { setAccessControl } from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { printContract } from "./print"; export interface CustomOptions extends CommonOptions { name: string; @@ -12,7 +16,7 @@ export interface CustomOptions extends CommonOptions { } export const defaults: Required = { - name: 'MyContract', + name: "MyContract", pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, @@ -32,7 +36,7 @@ export function printCustom(opts: CustomOptions = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.pausable || opts.upgradeable === 'uups'; + return opts.pausable || opts.upgradeable === "uups"; } export function buildCustom(opts: CustomOptions): Contract { @@ -52,4 +56,3 @@ export function buildCustom(opts: CustomOptions): Contract { return c; } - diff --git a/packages/core/solidity/src/erc1155.test.ts b/packages/core/solidity/src/erc1155.test.ts index d34232159..91e20ceae 100644 --- a/packages/core/solidity/src/erc1155.test.ts +++ b/packages/core/solidity/src/erc1155.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc1155 } from '.'; +import test from "ava"; +import { erc1155 } from "."; -import { buildERC1155, ERC1155Options } from './erc1155'; -import { printContract } from './print'; +import { buildERC1155, ERC1155Options } from "./erc1155"; +import { printContract } from "./print"; function testERC1155(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC1155({ - name: 'MyToken', - uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + name: "MyToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", ...opts, }); t.snapshot(printContract(c)); @@ -18,103 +18,123 @@ function testERC1155(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, t => { - t.is(erc1155.print(opts), printContract(buildERC1155({ - name: 'MyToken', - uri: '', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC1155Options) { + test(title, (t) => { + t.is( + erc1155.print(opts), + printContract( + buildERC1155({ + name: "MyToken", + uri: "", + ...opts, + }), + ), + ); }); } -testERC1155('basic', {}); +testERC1155("basic", {}); -testERC1155('basic + roles', { - access: 'roles', +testERC1155("basic + roles", { + access: "roles", }); -testERC1155('basic + managed', { - access: 'managed', +testERC1155("basic + managed", { + access: "managed", }); -testERC1155('no updatable uri', { +testERC1155("no updatable uri", { updatableUri: false, }); -testERC1155('burnable', { +testERC1155("burnable", { burnable: true, }); -testERC1155('pausable', { +testERC1155("pausable", { pausable: true, }); -testERC1155('mintable', { +testERC1155("mintable", { mintable: true, }); -testERC1155('mintable + roles', { +testERC1155("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC1155('mintable + managed', { +testERC1155("mintable + managed", { mintable: true, - access: 'managed', + access: "managed", }); -testERC1155('supply tracking', { +testERC1155("supply tracking", { supply: true, }); -testERC1155('full upgradeable transparent', { +testERC1155("full upgradeable transparent", { mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC1155('full upgradeable uups', { +testERC1155("full upgradeable uups", { mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC1155('full upgradeable transparent with managed', { +testERC1155("full upgradeable transparent with managed", { mintable: true, - access: 'managed', + access: "managed", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomToken', uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/' }); +testAPIEquivalence("API basic", { + name: "CustomToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +}); -testAPIEquivalence('API full upgradeable', { - name: 'CustomToken', - uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', +testAPIEquivalence("API full upgradeable", { + name: "CustomToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test('API isAccessControlRequired', async t => { - t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: 'uups' }), true); +test("API isAccessControlRequired", async (t) => { + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ + updatableUri: false, + upgradeable: "uups", + }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false}), false); + t.is(erc1155.isAccessControlRequired({ updatableUri: false }), false); t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/erc1155.ts b/packages/core/solidity/src/erc1155.ts index 8631e4220..1ec08a189 100644 --- a/packages/core/solidity/src/erc1155.ts +++ b/packages/core/solidity/src/erc1155.ts @@ -1,12 +1,20 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { supportsInterface } from './common-functions'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { supportsInterface } from "./common-functions"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; export interface ERC1155Options extends CommonOptions { name: string; @@ -19,8 +27,8 @@ export interface ERC1155Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - uri: '', + name: "MyToken", + uri: "", burnable: false, pausable: false, mintable: false, @@ -28,7 +36,7 @@ export const defaults: Required = { updatableUri: true, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; function withDefaults(opts: ERC1155Options): Required { @@ -47,8 +55,15 @@ export function printERC1155(opts: ERC1155Options = defaults): string { return printContract(buildERC1155(opts)); } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.updatableUri !== false || opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial, +): boolean { + return ( + opts.mintable || + opts.pausable || + opts.updatableUri !== false || + opts.upgradeable === "uups" + ); } export function buildERC1155(opts: ERC1155Options): Contract { @@ -89,8 +104,8 @@ export function buildERC1155(opts: ERC1155Options): Contract { function addBase(c: ContractBuilder, uri: string) { const ERC1155 = { - name: 'ERC1155', - path: '@openzeppelin/contracts/token/ERC1155/ERC1155.sol', + name: "ERC1155", + path: "@openzeppelin/contracts/token/ERC1155/ERC1155.sol", }; c.addParent(ERC1155, [uri]); @@ -100,8 +115,8 @@ function addBase(c: ContractBuilder, uri: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC1155Pausable = { - name: 'ERC1155Pausable', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol', + name: "ERC1155Pausable", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol", }; c.addParent(ERC1155Pausable); c.addOverride(ERC1155Pausable, functions._update); @@ -111,27 +126,27 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC1155Burnable', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol', + name: "ERC1155Burnable", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol", }); } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); - requireAccessControl(c, functions.mintBatch, access, 'MINTER', 'minter'); - c.addFunctionCode('_mint(account, id, amount, data);', functions.mint); - c.addFunctionCode('_mintBatch(to, ids, amounts, data);', functions.mintBatch); + requireAccessControl(c, functions.mint, access, "MINTER", "minter"); + requireAccessControl(c, functions.mintBatch, access, "MINTER", "minter"); + c.addFunctionCode("_mint(account, id, amount, data);", functions.mint); + c.addFunctionCode("_mintBatch(to, ids, amounts, data);", functions.mintBatch); } function addSetUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.setURI, access, 'URI_SETTER', undefined); - c.addFunctionCode('_setURI(newuri);', functions.setURI); + requireAccessControl(c, functions.setURI, access, "URI_SETTER", undefined); + c.addFunctionCode("_setURI(newuri);", functions.setURI); } function addSupply(c: ContractBuilder) { const ERC1155Supply = { - name: 'ERC1155Supply', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol', + name: "ERC1155Supply", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol", }; c.addParent(ERC1155Supply); c.addOverride(ERC1155Supply, functions._update); @@ -139,39 +154,37 @@ function addSupply(c: ContractBuilder) { const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'ids', type: 'uint256[] memory' }, - { name: 'values', type: 'uint256[] memory' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "ids", type: "uint256[] memory" }, + { name: "values", type: "uint256[] memory" }, ], }, setURI: { - kind: 'public' as const, - args: [ - { name: 'newuri', type: 'string memory' }, - ], + kind: "public" as const, + args: [{ name: "newuri", type: "string memory" }], }, mint: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'account', type: 'address' }, - { name: 'id', type: 'uint256' }, - { name: 'amount', type: 'uint256' }, - { name: 'data', type: 'bytes memory' }, + { name: "account", type: "address" }, + { name: "id", type: "uint256" }, + { name: "amount", type: "uint256" }, + { name: "data", type: "bytes memory" }, ], }, mintBatch: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'ids', type: 'uint256[] memory' }, - { name: 'amounts', type: 'uint256[] memory' }, - { name: 'data', type: 'bytes memory' }, + { name: "to", type: "address" }, + { name: "ids", type: "uint256[] memory" }, + { name: "amounts", type: "uint256[] memory" }, + { name: "data", type: "bytes memory" }, ], }, }); diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 7cd107d26..4a1cbb586 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc20 } from '.'; +import test from "ava"; +import { erc20 } from "."; -import { buildERC20, ERC20Options } from './erc20'; -import { printContract } from './print'; +import { buildERC20, ERC20Options } from "./erc20"; +import { printContract } from "./print"; function testERC20(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC20({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -18,141 +18,146 @@ function testERC20(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, t => { - t.is(erc20.print(opts), printContract(buildERC20({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC20Options) { + test(title, (t) => { + t.is( + erc20.print(opts), + printContract( + buildERC20({ + name: "MyToken", + symbol: "MTK", + ...opts, + }), + ), + ); }); } -testERC20('basic erc20', {}); +testERC20("basic erc20", {}); -testERC20('erc20 burnable', { +testERC20("erc20 burnable", { burnable: true, }); -testERC20('erc20 pausable', { +testERC20("erc20 pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 pausable with roles', { +testERC20("erc20 pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 pausable with managed', { +testERC20("erc20 pausable with managed", { pausable: true, - access: 'managed', + access: "managed", }); -testERC20('erc20 burnable pausable', { +testERC20("erc20 burnable pausable", { burnable: true, pausable: true, }); -testERC20('erc20 preminted', { - premint: '1000', +testERC20("erc20 preminted", { + premint: "1000", }); -testERC20('erc20 premint of 0', { - premint: '0', +testERC20("erc20 premint of 0", { + premint: "0", }); -testERC20('erc20 mintable', { +testERC20("erc20 mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 mintable with roles', { +testERC20("erc20 mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 permit', { +testERC20("erc20 permit", { permit: true, }); -testERC20('erc20 votes', { +testERC20("erc20 votes", { votes: true, }); -testERC20('erc20 votes + blocknumber', { - votes: 'blocknumber', +testERC20("erc20 votes + blocknumber", { + votes: "blocknumber", }); -testERC20('erc20 votes + timestamp', { - votes: 'timestamp', +testERC20("erc20 votes + timestamp", { + votes: "timestamp", }); -testERC20('erc20 flashmint', { +testERC20("erc20 flashmint", { flashmint: true, }); -testERC20('erc20 full upgradeable transparent', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable transparent", { + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC20('erc20 full upgradeable uups', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable uups", { + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC20('erc20 full upgradeable uups managed', { - premint: '2000', - access: 'managed', +testERC20("erc20 full upgradeable uups managed", { + premint: "2000", + access: "managed", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('erc20 API default'); +testAPIEquivalence("erc20 API default"); -testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('erc20 API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', - premint: '2000', - access: 'roles', +testAPIEquivalence("erc20 API full upgradeable", { + name: "CustomToken", + symbol: "CTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('erc20 API assert defaults', async t => { +test("erc20 API assert defaults", async (t) => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test('erc20 API isAccessControlRequired', async t => { +test("erc20 API isAccessControlRequired", async (t) => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(erc20.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(erc20.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index ce0b9e49c..8a2643e09 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -199,7 +199,7 @@ function addVotes(c: ContractBuilder, clockMode: ClockMode) { { name: "Nonces", }, - functions.nonces + functions.nonces, ); setClockMode(c, ERC20Votes, clockMode); diff --git a/packages/core/solidity/src/erc721.test.ts b/packages/core/solidity/src/erc721.test.ts index 4b39a2472..96b608678 100644 --- a/packages/core/solidity/src/erc721.test.ts +++ b/packages/core/solidity/src/erc721.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc721 } from '.'; +import test from "ava"; +import { erc721 } from "."; -import { buildERC721, ERC721Options } from './erc721'; -import { printContract } from './print'; +import { buildERC721, ERC721Options } from "./erc721"; +import { printContract } from "./print"; function testERC721(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -18,135 +18,141 @@ function testERC721(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, t => { - t.is(erc721.print(opts), printContract(buildERC721({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC721Options) { + test(title, (t) => { + t.is( + erc721.print(opts), + printContract( + buildERC721({ + name: "MyToken", + symbol: "MTK", + ...opts, + }), + ), + ); }); } -testERC721('basic', {}); +testERC721("basic", {}); -testERC721('base uri', { - baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', +testERC721("base uri", { + baseUri: + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", }); -testERC721('enumerable', { +testERC721("enumerable", { enumerable: true, }); -testERC721('uri storage', { +testERC721("uri storage", { uriStorage: true, }); -testERC721('mintable + uri storage', { +testERC721("mintable + uri storage", { mintable: true, uriStorage: true, }); -testERC721('mintable + uri storage + incremental', { +testERC721("mintable + uri storage + incremental", { mintable: true, uriStorage: true, incremental: true, }); -testERC721('burnable', { +testERC721("burnable", { burnable: true, }); -testERC721('burnable + uri storage', { +testERC721("burnable + uri storage", { uriStorage: true, burnable: true, }); -testERC721('pausable', { +testERC721("pausable", { pausable: true, }); -testERC721('mintable', { +testERC721("mintable", { mintable: true, }); -testERC721('mintable + roles', { +testERC721("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC721('mintable + managed', { +testERC721("mintable + managed", { mintable: true, - access: 'managed', + access: "managed", }); -testERC721('mintable + incremental', { +testERC721("mintable + incremental", { mintable: true, incremental: true, }); -testERC721('votes', { +testERC721("votes", { votes: true, }); -testERC721('votes + blocknumber', { - votes: 'blocknumber', +testERC721("votes + blocknumber", { + votes: "blocknumber", }); -testERC721('votes + timestamp', { - votes: 'timestamp', +testERC721("votes + timestamp", { + votes: "timestamp", }); -testERC721('full upgradeable transparent', { +testERC721("full upgradeable transparent", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC721('full upgradeable uups', { +testERC721("full upgradeable uups", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC721('full upgradeable uups + managed', { +testERC721("full upgradeable uups + managed", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', - access: 'managed', + upgradeable: "uups", + access: "managed", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', +testAPIEquivalence("API full upgradeable", { + name: "CustomToken", + symbol: "CTK", mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(erc721.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(erc721.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/erc721.ts b/packages/core/solidity/src/erc721.ts index aa9a80c31..123bc91f4 100644 --- a/packages/core/solidity/src/erc721.ts +++ b/packages/core/solidity/src/erc721.ts @@ -1,13 +1,21 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { supportsInterface } from './common-functions'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; -import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { supportsInterface } from "./common-functions"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; +import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; export interface ERC721Options extends CommonOptions { name: string; @@ -27,9 +35,9 @@ export interface ERC721Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', - baseUri: '', + name: "MyToken", + symbol: "MTK", + baseUri: "", enumerable: false, uriStorage: false, burnable: false, @@ -62,7 +70,7 @@ export function printERC721(opts: ERC721Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; + return opts.mintable || opts.pausable || opts.upgradeable === "uups"; } export function buildERC721(opts: ERC721Options): Contract { @@ -112,8 +120,8 @@ export function buildERC721(opts: ERC721Options): Contract { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC721Pausable = { - name: 'ERC721Pausable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol', + name: "ERC721Pausable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol", }; c.addParent(ERC721Pausable); c.addOverride(ERC721Pausable, functions._update); @@ -122,8 +130,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { } const ERC721 = { - name: 'ERC721', - path: '@openzeppelin/contracts/token/ERC721/ERC721.sol', + name: "ERC721", + path: "@openzeppelin/contracts/token/ERC721/ERC721.sol", }; function addBase(c: ContractBuilder, name: string, symbol: string) { @@ -142,8 +150,8 @@ function addBaseURI(c: ContractBuilder, baseUri: string) { function addEnumerable(c: ContractBuilder) { const ERC721Enumerable = { - name: 'ERC721Enumerable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol', + name: "ERC721Enumerable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol", }; c.addParent(ERC721Enumerable); @@ -154,8 +162,8 @@ function addEnumerable(c: ContractBuilder) { function addURIStorage(c: ContractBuilder) { const ERC721URIStorage = { - name: 'ERC721URIStorage', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol', + name: "ERC721URIStorage", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol", }; c.addParent(ERC721URIStorage); @@ -165,38 +173,43 @@ function addURIStorage(c: ContractBuilder) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC721Burnable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol', + name: "ERC721Burnable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol", }); } -function addMintable(c: ContractBuilder, access: Access, incremental = false, uriStorage = false) { +function addMintable( + c: ContractBuilder, + access: Access, + incremental = false, + uriStorage = false, +) { const fn = getMintFunction(incremental, uriStorage); - requireAccessControl(c, fn, access, 'MINTER', 'minter'); + requireAccessControl(c, fn, access, "MINTER", "minter"); if (incremental) { - c.addVariable('uint256 private _nextTokenId;'); - c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn); - c.addFunctionCode('_safeMint(to, tokenId);', fn); + c.addVariable("uint256 private _nextTokenId;"); + c.addFunctionCode("uint256 tokenId = _nextTokenId++;", fn); + c.addFunctionCode("_safeMint(to, tokenId);", fn); } else { - c.addFunctionCode('_safeMint(to, tokenId);', fn); + c.addFunctionCode("_safeMint(to, tokenId);", fn); } if (uriStorage) { - c.addFunctionCode('_setTokenURI(tokenId, uri);', fn); + c.addFunctionCode("_setTokenURI(tokenId, uri);", fn); } } function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const EIP712 = { - name: 'EIP712', - path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol', + name: "EIP712", + path: "@openzeppelin/contracts/utils/cryptography/EIP712.sol", }; c.addParent(EIP712, [name, "1"]); const ERC721Votes = { - name: 'ERC721Votes', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol', + name: "ERC721Votes", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol", }; c.addParent(ERC721Votes); @@ -208,55 +221,51 @@ function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'tokenId', type: 'uint256' }, - { name: 'auth', type: 'address' }, + { name: "to", type: "address" }, + { name: "tokenId", type: "uint256" }, + { name: "auth", type: "address" }, ], - returns: ['address'], + returns: ["address"], }, tokenURI: { - kind: 'public' as const, - args: [ - { name: 'tokenId', type: 'uint256' }, - ], - returns: ['string memory'], - mutability: 'view' as const, + kind: "public" as const, + args: [{ name: "tokenId", type: "uint256" }], + returns: ["string memory"], + mutability: "view" as const, }, _baseURI: { - kind: 'internal' as const, + kind: "internal" as const, args: [], - returns: ['string memory'], - mutability: 'pure' as const, + returns: ["string memory"], + mutability: "pure" as const, }, _increaseBalance: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'account', type: 'address' }, - { name: 'value', type: 'uint128' }, + { name: "account", type: "address" }, + { name: "value", type: "uint128" }, ], }, }); function getMintFunction(incremental: boolean, uriStorage: boolean) { const fn = { - name: 'safeMint', - kind: 'public' as const, - args: [ - { name: 'to', type: 'address' }, - ], + name: "safeMint", + kind: "public" as const, + args: [{ name: "to", type: "address" }], }; if (!incremental) { - fn.args.push({ name: 'tokenId', type: 'uint256' }); + fn.args.push({ name: "tokenId", type: "uint256" }); } if (uriStorage) { - fn.args.push({ name: 'uri', type: 'string memory' }); + fn.args.push({ name: "uri", type: "string memory" }); } return fn; diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index e37ac9562..c4b282064 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -5,7 +5,7 @@ type Alternatives = { }; export function* generateAlternatives( - blueprint: B + blueprint: B, ): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, @@ -16,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]) + entries.map((e) => [e.key, e.values[e.current % e.limit]]), ) as Alternatives; } diff --git a/packages/core/solidity/src/generate/custom.ts b/packages/core/solidity/src/generate/custom.ts index 40207666a..884032068 100644 --- a/packages/core/solidity/src/generate/custom.ts +++ b/packages/core/solidity/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from '../custom'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { CustomOptions } from "../custom"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyContract'], + name: ["MyContract"], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/solidity/src/generate/erc1155.ts b/packages/core/solidity/src/generate/erc1155.ts index fdb9c09fe..d340602a9 100644 --- a/packages/core/solidity/src/generate/erc1155.ts +++ b/packages/core/solidity/src/generate/erc1155.ts @@ -1,14 +1,14 @@ -import type { ERC1155Options } from '../erc1155'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC1155Options } from "../erc1155"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - uri: ['https://example.com/'], + name: ["MyToken"], + uri: ["https://example.com/"], burnable: booleans, pausable: booleans, mintable: booleans, diff --git a/packages/core/solidity/src/generate/erc20.ts b/packages/core/solidity/src/generate/erc20.ts index 929da1af5..5c3d6eb48 100644 --- a/packages/core/solidity/src/generate/erc20.ts +++ b/packages/core/solidity/src/generate/erc20.ts @@ -1,22 +1,22 @@ -import type { ERC20Options } from '../erc20'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC20Options } from "../erc20"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], + name: ["MyToken"], + symbol: ["MTK"], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - votes: [ ...booleans, ...clockModeOptions ] as const, + votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ['1'], + premint: ["1"], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/solidity/src/generate/erc721.ts b/packages/core/solidity/src/generate/erc721.ts index 4bf97e744..06d1ca530 100644 --- a/packages/core/solidity/src/generate/erc721.ts +++ b/packages/core/solidity/src/generate/erc721.ts @@ -1,16 +1,16 @@ -import type { ERC721Options } from '../erc721'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC721Options } from "../erc721"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + symbol: ["MTK"], + baseUri: ["https://example.com/"], enumerable: booleans, uriStorage: booleans, burnable: booleans, @@ -20,7 +20,7 @@ const blueprint = { access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, - votes: [ ...booleans, ...clockModeOptions ] as const, + votes: [...booleans, ...clockModeOptions] as const, }; export function* generateERC721Options(): Generator> { diff --git a/packages/core/solidity/src/generate/governor.ts b/packages/core/solidity/src/generate/governor.ts index a5a93f26a..66679caae 100644 --- a/packages/core/solidity/src/generate/governor.ts +++ b/packages/core/solidity/src/generate/governor.ts @@ -1,22 +1,27 @@ -import { defaults, GovernorOptions, timelockOptions, votesOptions } from '../governor'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { + defaults, + GovernorOptions, + timelockOptions, + votesOptions, +} from "../governor"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyGovernor'], - delay: ['1 week'], - period: ['1 week'], + name: ["MyGovernor"], + delay: ["1 week"], + period: ["1 week"], blockTime: [defaults.blockTime], - proposalThreshold: ['0', '1000'], + proposalThreshold: ["0", "1000"], decimals: [18], - quorumMode: ['percent', 'absolute'] as const, + quorumMode: ["percent", "absolute"] as const, quorumPercent: [4], - quorumAbsolute: ['1000'], + quorumAbsolute: ["1000"], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, @@ -27,6 +32,8 @@ const blueprint = { info: infoOptions, }; -export function* generateGovernorOptions(): Generator> { +export function* generateGovernorOptions(): Generator< + Required +> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/solidity/src/generate/sources.ts b/packages/core/solidity/src/generate/sources.ts index a6745f6eb..3ee3ad00c 100644 --- a/packages/core/solidity/src/generate/sources.ts +++ b/packages/core/solidity/src/generate/sources.ts @@ -1,63 +1,63 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import crypto from 'crypto'; - -import { generateERC20Options } from './erc20'; -import { generateERC721Options } from './erc721'; -import { generateERC1155Options } from './erc1155'; -import { generateStablecoinOptions } from './stablecoin'; -import { generateGovernorOptions } from './governor'; -import { generateCustomOptions } from './custom'; -import { buildGeneric, GenericOptions, KindedOptions } from '../build-generic'; -import { printContract } from '../print'; -import { OptionsError } from '../error'; -import { findCover } from '../utils/find-cover'; -import type { Contract } from '../contract'; - -type Subset = 'all' | 'minimal-cover'; +import { promises as fs } from "fs"; +import path from "path"; +import crypto from "crypto"; + +import { generateERC20Options } from "./erc20"; +import { generateERC721Options } from "./erc721"; +import { generateERC1155Options } from "./erc1155"; +import { generateStablecoinOptions } from "./stablecoin"; +import { generateGovernorOptions } from "./governor"; +import { generateCustomOptions } from "./custom"; +import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; +import { printContract } from "../print"; +import { OptionsError } from "../error"; +import { findCover } from "../utils/find-cover"; +import type { Contract } from "../contract"; + +type Subset = "all" | "minimal-cover"; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === 'ERC20') { + if (!kind || kind === "ERC20") { for (const kindOpts of generateERC20Options()) { - yield { kind: 'ERC20', ...kindOpts }; + yield { kind: "ERC20", ...kindOpts }; } } - if (!kind || kind === 'ERC721') { + if (!kind || kind === "ERC721") { for (const kindOpts of generateERC721Options()) { - yield { kind: 'ERC721', ...kindOpts }; + yield { kind: "ERC721", ...kindOpts }; } } - if (!kind || kind === 'ERC1155') { + if (!kind || kind === "ERC1155") { for (const kindOpts of generateERC1155Options()) { - yield { kind: 'ERC1155', ...kindOpts }; + yield { kind: "ERC1155", ...kindOpts }; } } - if (!kind || kind === 'Stablecoin') { + if (!kind || kind === "Stablecoin") { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: 'Stablecoin', ...kindOpts }; + yield { kind: "Stablecoin", ...kindOpts }; } } - if (!kind || kind === 'RealWorldAsset') { + if (!kind || kind === "RealWorldAsset") { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: 'RealWorldAsset', ...kindOpts }; + yield { kind: "RealWorldAsset", ...kindOpts }; } } - if (!kind || kind === 'Governor') { + if (!kind || kind === "Governor") { for (const kindOpts of generateGovernorOptions()) { - yield { kind: 'Governor', ...kindOpts }; + yield { kind: "Governor", ...kindOpts }; } } - if (!kind || kind === 'Custom') { + if (!kind || kind === "Custom") { for (const kindOpts of generateCustomOptions()) { - yield { kind: 'Custom', ...kindOpts }; + yield { kind: "Custom", ...kindOpts }; } } } @@ -72,15 +72,18 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { +function generateContractSubset( + subset: Subset, + kind?: Kind, +): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { const id = crypto - .createHash('sha1') + .createHash("sha1") .update(JSON.stringify(options)) .digest() - .toString('hex'); + .toString("hex"); try { const contract = buildGeneric(options); @@ -94,28 +97,42 @@ function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[ } } - if (subset === 'all') { + if (subset === "all") { return contracts; } else { - const getParents = (c: GeneratedContract) => c.contract.parents.map(p => p.contract.path); + const getParents = (c: GeneratedContract) => + c.contract.parents.map((p) => p.contract.path); return [ - ...findCover(contracts.filter(c => c.options.upgradeable), getParents), - ...findCover(contracts.filter(c => !c.options.upgradeable), getParents), + ...findCover( + contracts.filter((c) => c.options.upgradeable), + getParents, + ), + ...findCover( + contracts.filter((c) => !c.options.upgradeable), + getParents, + ), ]; } } -export function* generateSources(subset: Subset, kind?: Kind): Generator { +export function* generateSources( + subset: Subset, + kind?: Kind, +): Generator { for (const c of generateContractSubset(subset, kind)) { const source = printContract(c.contract); yield { ...c, source }; } } -export async function writeGeneratedSources(dir: string, subset: Subset, kind?: Kind): Promise { +export async function writeGeneratedSources( + dir: string, + subset: Subset, + kind?: Kind, +): Promise { await fs.mkdir(dir, { recursive: true }); for (const { id, source } of generateSources(subset, kind)) { - await fs.writeFile(path.format({ dir, name: id, ext: '.sol' }), source); + await fs.writeFile(path.format({ dir, name: id, ext: ".sol" }), source); } } diff --git a/packages/core/solidity/src/generate/stablecoin.ts b/packages/core/solidity/src/generate/stablecoin.ts index 358052aa9..e20841856 100644 --- a/packages/core/solidity/src/generate/stablecoin.ts +++ b/packages/core/solidity/src/generate/stablecoin.ts @@ -1,30 +1,32 @@ -import type { StablecoinOptions } from '../stablecoin'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { StablecoinOptions } from "../stablecoin"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyStablecoin'], - symbol: ['MST'], + name: ["MyStablecoin"], + symbol: ["MST"], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - limitations: [false, 'allowlist', 'blocklist'] as const, - votes: [ ...booleans, ...clockModeOptions ] as const, + limitations: [false, "allowlist", "blocklist"] as const, + votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ['1'], + premint: ["1"], custodian: booleans, access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateStablecoinOptions(): Generator> { +export function* generateStablecoinOptions(): Generator< + Required +> { for (const opts of generateAlternatives(blueprint)) { yield { ...opts, upgradeable: false }; } diff --git a/packages/core/solidity/src/get-imports.test.ts b/packages/core/solidity/src/get-imports.test.ts index 5678fdc57..089ce3678 100644 --- a/packages/core/solidity/src/get-imports.test.ts +++ b/packages/core/solidity/src/get-imports.test.ts @@ -1,86 +1,97 @@ -import test from 'ava'; +import test from "ava"; -import { getImports } from './get-imports'; -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { generateSources } from './generate/sources'; -import { buildGeneric } from './build-generic'; +import { getImports } from "./get-imports"; +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { generateSources } from "./generate/sources"; +import { buildGeneric } from "./build-generic"; -test('erc20 basic', t => { - const c = buildERC20({ name: 'MyToken', symbol: 'MTK', permit: false }); +test("erc20 basic", (t) => { + const c = buildERC20({ name: "MyToken", symbol: "MTK", permit: false }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/token/ERC20/ERC20.sol', - '@openzeppelin/contracts/token/ERC20/IERC20.sol', - '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol', - '@openzeppelin/contracts/utils/Context.sol', + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/token/ERC20/ERC20.sol", + "@openzeppelin/contracts/token/ERC20/IERC20.sol", + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol", + "@openzeppelin/contracts/utils/Context.sol", ]); }); -test('erc721 auto increment', t => { - const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true }); +test("erc721 auto increment", (t) => { + const c = buildERC721({ + name: "MyToken", + symbol: "MTK", + mintable: true, + incremental: true, + }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts/access/Ownable.sol', - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/token/ERC721/ERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', - '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', - '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', - '@openzeppelin/contracts/utils/Context.sol', - '@openzeppelin/contracts/utils/Panic.sol', - '@openzeppelin/contracts/utils/Strings.sol', - '@openzeppelin/contracts/utils/introspection/ERC165.sol', - '@openzeppelin/contracts/utils/introspection/IERC165.sol', - '@openzeppelin/contracts/utils/math/Math.sol', - '@openzeppelin/contracts/utils/math/SafeCast.sol', - '@openzeppelin/contracts/utils/math/SignedMath.sol', + "@openzeppelin/contracts/access/Ownable.sol", + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/token/ERC721/ERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", + "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", + "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", + "@openzeppelin/contracts/utils/Context.sol", + "@openzeppelin/contracts/utils/Panic.sol", + "@openzeppelin/contracts/utils/Strings.sol", + "@openzeppelin/contracts/utils/introspection/ERC165.sol", + "@openzeppelin/contracts/utils/introspection/IERC165.sol", + "@openzeppelin/contracts/utils/math/Math.sol", + "@openzeppelin/contracts/utils/math/SafeCast.sol", + "@openzeppelin/contracts/utils/math/SignedMath.sol", ]); }); -test('erc721 auto increment uups', t => { - const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true, upgradeable: 'uups' }); +test("erc721 auto increment uups", (t) => { + const c = buildERC721({ + name: "MyToken", + symbol: "MTK", + mintable: true, + incremental: true, + upgradeable: "uups", + }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', - '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol', - '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol', - '@openzeppelin/contracts/interfaces/IERC1967.sol', - '@openzeppelin/contracts/interfaces/draft-IERC1822.sol', - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol', - '@openzeppelin/contracts/proxy/beacon/IBeacon.sol', - '@openzeppelin/contracts/token/ERC721/IERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', - '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', - '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', - '@openzeppelin/contracts/utils/Address.sol', - '@openzeppelin/contracts/utils/Errors.sol', - '@openzeppelin/contracts/utils/Panic.sol', - '@openzeppelin/contracts/utils/StorageSlot.sol', - '@openzeppelin/contracts/utils/Strings.sol', - '@openzeppelin/contracts/utils/introspection/IERC165.sol', - '@openzeppelin/contracts/utils/math/Math.sol', - '@openzeppelin/contracts/utils/math/SafeCast.sol', - '@openzeppelin/contracts/utils/math/SignedMath.sol', + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", + "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol", + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol", + "@openzeppelin/contracts/interfaces/IERC1967.sol", + "@openzeppelin/contracts/interfaces/draft-IERC1822.sol", + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol", + "@openzeppelin/contracts/proxy/beacon/IBeacon.sol", + "@openzeppelin/contracts/token/ERC721/IERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", + "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", + "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", + "@openzeppelin/contracts/utils/Address.sol", + "@openzeppelin/contracts/utils/Errors.sol", + "@openzeppelin/contracts/utils/Panic.sol", + "@openzeppelin/contracts/utils/StorageSlot.sol", + "@openzeppelin/contracts/utils/Strings.sol", + "@openzeppelin/contracts/utils/introspection/IERC165.sol", + "@openzeppelin/contracts/utils/math/Math.sol", + "@openzeppelin/contracts/utils/math/SafeCast.sol", + "@openzeppelin/contracts/utils/math/SignedMath.sol", ]); }); -test('can get imports for all combinations', t => { - for (const { options } of generateSources('all')) { +test("can get imports for all combinations", (t) => { + for (const { options } of generateSources("all")) { const c = buildGeneric(options); getImports(c); } t.pass(); -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/get-imports.ts b/packages/core/solidity/src/get-imports.ts index 9c51ab86b..4a5ca111b 100644 --- a/packages/core/solidity/src/get-imports.ts +++ b/packages/core/solidity/src/get-imports.ts @@ -1,8 +1,8 @@ -import type { Contract } from './contract'; -import { reachable } from './utils/transitive-closure'; +import type { Contract } from "./contract"; +import { reachable } from "./utils/transitive-closure"; -import contracts from '../openzeppelin-contracts'; -import { withHelpers } from './options'; +import contracts from "../openzeppelin-contracts"; +import { withHelpers } from "./options"; export interface SolcInputSources { [source: string]: { @@ -11,10 +11,10 @@ export interface SolcInputSources { } /** -* Gets the source code for all imports of a contract, including all transitive dependencies, -* in a format compatible with the Solidity compiler input's `sources` field. -* -* Does not include the contract itself (use `printContract` for that if needed). + * Gets the source code for all imports of a contract, including all transitive dependencies, + * in a format compatible with the Solidity compiler input's `sources` field. + * + * Does not include the contract itself (use `printContract` for that if needed). * * @param c The contract to get imports for. * @returns A record of import paths to `content` that contains the source code for each contract. @@ -24,10 +24,10 @@ export function getImports(c: Contract): SolcInputSources { const result: SolcInputSources = {}; - const fileName = c.name + '.sol'; + const fileName = c.name + ".sol"; const dependencies = { - [fileName]: c.imports.map(i => transformImport(i).path), + [fileName]: c.imports.map((i) => transformImport(i).path), ...contracts.dependencies, }; @@ -42,4 +42,4 @@ export function getImports(c: Contract): SolcInputSources { } return result; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/governor.test.ts b/packages/core/solidity/src/governor.test.ts index ee34b0a4b..b22c706d0 100644 --- a/packages/core/solidity/src/governor.test.ts +++ b/packages/core/solidity/src/governor.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; -import { governor } from '.'; +import test from "ava"; +import { governor } from "."; -import { buildGovernor, GovernorOptions } from './governor'; -import { printContract } from './print'; +import { buildGovernor, GovernorOptions } from "./governor"; +import { printContract } from "./print"; function testGovernor(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildGovernor({ - name: 'MyGovernor', - delay: '1 week', - period: '1 week', + name: "MyGovernor", + delay: "1 week", + period: "1 week", settings: false, ...opts, }); @@ -20,133 +20,147 @@ function testGovernor(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, t => { - t.is(governor.print(opts), printContract(buildGovernor({ - name: 'MyGovernor', - delay: '1 day', - period: '1 week', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: GovernorOptions) { + test(title, (t) => { + t.is( + governor.print(opts), + printContract( + buildGovernor({ + name: "MyGovernor", + delay: "1 day", + period: "1 week", + ...opts, + }), + ), + ); }); } -testGovernor('governor with proposal threshold', { - proposalThreshold: '1', +testGovernor("governor with proposal threshold", { + proposalThreshold: "1", }); -testGovernor('governor with custom block time', { +testGovernor("governor with custom block time", { blockTime: 6, }); -testGovernor('governor with custom decimals', { +testGovernor("governor with custom decimals", { decimals: 6, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '1', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "1", }); -testGovernor('governor with 0 decimals', { +testGovernor("governor with 0 decimals", { decimals: 0, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '1', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "1", }); -testGovernor('governor with settings', { +testGovernor("governor with settings", { settings: true, - proposalThreshold: '1', + proposalThreshold: "1", }); -testGovernor('governor with storage', { +testGovernor("governor with storage", { storage: true, }); -testGovernor('governor with erc20votes', { - votes: 'erc20votes', +testGovernor("governor with erc20votes", { + votes: "erc20votes", }); -testGovernor('governor with erc721votes', { - votes: 'erc721votes', +testGovernor("governor with erc721votes", { + votes: "erc721votes", }); -testGovernor('governor with erc721votes omit decimals', { - votes: 'erc721votes', +testGovernor("governor with erc721votes omit decimals", { + votes: "erc721votes", decimals: 6, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '5', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "5", }); -testGovernor('governor with erc721votes settings omit decimals', { - votes: 'erc721votes', +testGovernor("governor with erc721votes settings omit decimals", { + votes: "erc721votes", decimals: 6, - proposalThreshold: '10', + proposalThreshold: "10", settings: true, }); -testGovernor('governor with percent quorum', { - quorumMode: 'percent', +testGovernor("governor with percent quorum", { + quorumMode: "percent", quorumPercent: 6, }); -testGovernor('governor with fractional percent quorum', { - quorumMode: 'percent', +testGovernor("governor with fractional percent quorum", { + quorumMode: "percent", quorumPercent: 0.5, }); -testGovernor('governor with openzeppelin timelock', { - timelock: 'openzeppelin', +testGovernor("governor with openzeppelin timelock", { + timelock: "openzeppelin", }); -testGovernor('governor with compound timelock', { - timelock: 'compound', +testGovernor("governor with compound timelock", { + timelock: "compound", }); -testGovernor('governor with blocknumber, updatable settings', { - clockMode: 'blocknumber', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with blocknumber, updatable settings", { + clockMode: "blocknumber", + delay: "1 day", + period: "2 weeks", settings: true, }); -testGovernor('governor with blocknumber, non-updatable settings', { - clockMode: 'blocknumber', - delay: '1 block', - period: '2 weeks', +testGovernor("governor with blocknumber, non-updatable settings", { + clockMode: "blocknumber", + delay: "1 block", + period: "2 weeks", settings: false, }); -testGovernor('governor with timestamp clock mode, updatable settings', { - clockMode: 'timestamp', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with timestamp clock mode, updatable settings", { + clockMode: "timestamp", + delay: "1 day", + period: "2 weeks", settings: true, }); -testGovernor('governor with timestamp clock mode, non-updatable settings', { - clockMode: 'timestamp', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with timestamp clock mode, non-updatable settings", { + clockMode: "timestamp", + delay: "1 day", + period: "2 weeks", settings: false, }); -testGovernor('governor with erc20votes, upgradable', { - votes: 'erc20votes', - upgradeable: 'uups', +testGovernor("governor with erc20votes, upgradable", { + votes: "erc20votes", + upgradeable: "uups", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomGovernor', delay: '2 weeks', period: '2 week' }); +testAPIEquivalence("API basic", { + name: "CustomGovernor", + delay: "2 weeks", + period: "2 week", +}); -testAPIEquivalence('API basic upgradeable', { name: 'CustomGovernor', delay: '2 weeks', period: '2 week', upgradeable: 'uups' }); +testAPIEquivalence("API basic upgradeable", { + name: "CustomGovernor", + delay: "2 weeks", + period: "2 week", + upgradeable: "uups", +}); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(governor.print(governor.defaults), governor.print()); }); -test('API isAccessControlRequired', async t => { - t.is(governor.isAccessControlRequired({ upgradeable: 'uups' }), true); +test("API isAccessControlRequired", async (t) => { + t.is(governor.isAccessControlRequired({ upgradeable: "uups" }), true); t.is(governor.isAccessControlRequired({}), false); -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 5f61a21e2..4e32d38b3 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -64,7 +64,7 @@ export interface GovernorOptions extends CommonOptions { } export function isAccessControlRequired( - opts: Partial + opts: Partial, ): boolean { return opts.upgradeable === "uups"; } @@ -151,7 +151,7 @@ function addSettings(c: ContractBuilder, allOpts: Required) { } function getVotingDelay( - opts: Required + opts: Required, ): { lit: string } | { note: string; value: number } { try { if (opts.clockMode === "timestamp") { @@ -176,7 +176,7 @@ function getVotingDelay( } function getVotingPeriod( - opts: Required + opts: Required, ): { lit: string } | { note: string; value: number } { try { if (opts.clockMode === "timestamp") { @@ -232,7 +232,7 @@ function getProposalThreshold({ function setVotingParameters( c: ContractBuilder, - opts: Required + opts: Required, ) { const delayBlocks = getVotingDelay(opts); if ("lit" in delayBlocks) { @@ -240,7 +240,7 @@ function setVotingParameters( } else { c.setFunctionBody( [`return ${delayBlocks.value}; // ${delayBlocks.note}`], - functions.votingDelay + functions.votingDelay, ); } @@ -250,14 +250,14 @@ function setVotingParameters( } else { c.setFunctionBody( [`return ${periodBlocks.value}; // ${periodBlocks.note}`], - functions.votingPeriod + functions.votingPeriod, ); } } function setProposalThreshold( c: ContractBuilder, - opts: Required + opts: Required, ) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -295,7 +295,7 @@ function addVotes(c: ContractBuilder) { name: "GovernorVotes", path: `@openzeppelin/contracts/governance/extensions/GovernorVotes.sol`, }, - [{ lit: tokenArg }] + [{ lit: tokenArg }], ); } @@ -322,7 +322,7 @@ function addQuorum(c: ContractBuilder, opts: Required) { c.setFunctionBody( [`return ${quorumFractionDenominator};`], functions.quorumDenominator, - "pure" + "pure", ); } @@ -386,7 +386,7 @@ function getQuorumFractionComponents(quorumPercent: number): { quorumPercentSegments[1] !== undefined ) { quorumFractionNumerator = parseInt( - quorumPercentSegments[0].concat(quorumPercentSegments[1]) + quorumPercentSegments[0].concat(quorumPercentSegments[1]), ); const decimals = quorumPercentSegments[1].length; quorumFractionDenominator = "100"; @@ -399,7 +399,7 @@ function getQuorumFractionComponents(quorumPercent: number): { function addTimelock( c: ContractBuilder, - { timelock }: Required + { timelock }: Required, ) { if (timelock === false) { return; diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index b6e0ed24c..2cfc6b1ee 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -1,24 +1,32 @@ -export type { GenericOptions, KindedOptions } from './build-generic'; -export { buildGeneric } from './build-generic'; +export type { GenericOptions, KindedOptions } from "./build-generic"; +export { buildGeneric } from "./build-generic"; -export type { Contract } from './contract'; -export { ContractBuilder } from './contract'; +export type { Contract } from "./contract"; +export { ContractBuilder } from "./contract"; -export { printContract } from './print'; +export { printContract } from "./print"; -export type { Access } from './set-access-control'; -export type { Upgradeable } from './set-upgradeable'; -export type { Info } from './set-info'; +export type { Access } from "./set-access-control"; +export type { Upgradeable } from "./set-upgradeable"; +export type { Info } from "./set-info"; -export { premintPattern } from './erc20'; -export { defaults as infoDefaults } from './set-info'; +export { premintPattern } from "./erc20"; +export { defaults as infoDefaults } from "./set-info"; -export type { OptionsErrorMessages } from './error'; -export { OptionsError } from './error'; +export type { OptionsErrorMessages } from "./error"; +export { OptionsError } from "./error"; -export type { Kind } from './kind'; -export { sanitizeKind } from './kind'; +export type { Kind } from "./kind"; +export { sanitizeKind } from "./kind"; -export { erc20, erc721, erc1155, stablecoin, realWorldAsset, governor, custom } from './api'; +export { + erc20, + erc721, + erc1155, + stablecoin, + realWorldAsset, + governor, + custom, +} from "./api"; -export { compatibleContractsSemver } from './utils/version'; \ No newline at end of file +export { compatibleContractsSemver } from "./utils/version"; diff --git a/packages/core/solidity/src/infer-transpiled.test.ts b/packages/core/solidity/src/infer-transpiled.test.ts index c6ead4149..262d80660 100644 --- a/packages/core/solidity/src/infer-transpiled.test.ts +++ b/packages/core/solidity/src/infer-transpiled.test.ts @@ -1,12 +1,12 @@ -import test from 'ava'; -import { inferTranspiled } from './infer-transpiled'; +import test from "ava"; +import { inferTranspiled } from "./infer-transpiled"; -test('infer transpiled', t => { - t.true(inferTranspiled({ name: 'Foo' })); - t.true(inferTranspiled({ name: 'Foo', transpiled: true })); - t.false(inferTranspiled({ name: 'Foo', transpiled: false })); +test("infer transpiled", (t) => { + t.true(inferTranspiled({ name: "Foo" })); + t.true(inferTranspiled({ name: "Foo", transpiled: true })); + t.false(inferTranspiled({ name: "Foo", transpiled: false })); - t.false(inferTranspiled({ name: 'IFoo' })); - t.true(inferTranspiled({ name: 'IFoo', transpiled: true })); - t.false(inferTranspiled({ name: 'IFoo', transpiled: false })); -}); \ No newline at end of file + t.false(inferTranspiled({ name: "IFoo" })); + t.true(inferTranspiled({ name: "IFoo", transpiled: true })); + t.false(inferTranspiled({ name: "IFoo", transpiled: false })); +}); diff --git a/packages/core/solidity/src/infer-transpiled.ts b/packages/core/solidity/src/infer-transpiled.ts index 52eeba679..9089099e1 100644 --- a/packages/core/solidity/src/infer-transpiled.ts +++ b/packages/core/solidity/src/infer-transpiled.ts @@ -2,4 +2,4 @@ import type { ReferencedContract } from "./contract"; export function inferTranspiled(c: ReferencedContract): boolean { return c.transpiled ?? !/^I[A-Z]/.test(c.name); -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/kind.ts b/packages/core/solidity/src/kind.ts index de78812fc..844bfddde 100644 --- a/packages/core/solidity/src/kind.ts +++ b/packages/core/solidity/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from './build-generic'; +import type { GenericOptions } from "./build-generic"; -export type Kind = GenericOptions['kind']; +export type Kind = GenericOptions["kind"]; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === 'string') { - const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); + if (typeof kind === "string") { + const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return 'ERC20'; + return "ERC20"; } function isKind(value: Kind | T): value is Kind { switch (value) { - case 'ERC20': - case 'ERC1155': - case 'ERC721': - case 'Stablecoin': - case 'RealWorldAsset': - case 'Governor': - case 'Custom': + case "ERC20": + case "ERC1155": + case "ERC721": + case "Stablecoin": + case "RealWorldAsset": + case "Governor": + case "Custom": return true; default: { @@ -30,4 +30,3 @@ function isKind(value: Kind | T): value is Kind { } } } - diff --git a/packages/core/solidity/src/options.ts b/packages/core/solidity/src/options.ts index b86fe6c4f..7ae76a616 100644 --- a/packages/core/solidity/src/options.ts +++ b/packages/core/solidity/src/options.ts @@ -1,15 +1,15 @@ -import path from 'path'; +import path from "path"; -import type { Contract, ReferencedContract, ImportContract } from './contract'; -import { inferTranspiled } from './infer-transpiled'; +import type { Contract, ReferencedContract, ImportContract } from "./contract"; +import { inferTranspiled } from "./infer-transpiled"; const upgradeableName = (n: string) => { - if (n === 'Initializable') { + if (n === "Initializable") { return n; } else { - return n.replace(/(Upgradeable)?(?=\.|$)/, 'Upgradeable'); + return n.replace(/(Upgradeable)?(?=\.|$)/, "Upgradeable"); } -} +}; const upgradeableImport = (p: ImportContract): ImportContract => { const { dir, ext, name } = path.parse(p.path); @@ -19,10 +19,13 @@ const upgradeableImport = (p: ImportContract): ImportContract => { name: upgradeableName(p.name), // Contract name path: path.posix.format({ ext, - dir: dir.replace(/^@openzeppelin\/contracts/, '@openzeppelin/contracts-upgradeable'), + dir: dir.replace( + /^@openzeppelin\/contracts/, + "@openzeppelin/contracts-upgradeable", + ), name: upgradeableName(name), // Solidity file name }), - } + }; }; export interface Options { @@ -36,12 +39,16 @@ export interface Helpers extends Required { export function withHelpers(contract: Contract, opts: Options = {}): Helpers { const contractUpgradeable = contract.upgradeable; - const transformName = (n: ReferencedContract) => contractUpgradeable && inferTranspiled(n) ? upgradeableName(n.name) : n.name; + const transformName = (n: ReferencedContract) => + contractUpgradeable && inferTranspiled(n) + ? upgradeableName(n.name) + : n.name; return { upgradeable: contractUpgradeable, transformName, - transformImport: p1 => { - const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; + transformImport: (p1) => { + const p2 = + contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; return opts.transformImport?.(p2) ?? p2; }, }; diff --git a/packages/core/solidity/src/print-versioned.ts b/packages/core/solidity/src/print-versioned.ts index 471b42c09..4f2a6a59d 100644 --- a/packages/core/solidity/src/print-versioned.ts +++ b/packages/core/solidity/src/print-versioned.ts @@ -1,16 +1,17 @@ -import contracts from '../openzeppelin-contracts'; +import contracts from "../openzeppelin-contracts"; import type { Contract } from "./contract"; import { printContract } from "./print"; export function printContractVersioned(contract: Contract): string { return printContract(contract, { - transformImport: p => { + transformImport: (p) => { return { ...p, - path: p.path.replace(/^@openzeppelin\/contracts(-upgradeable)?/, `$&@${contracts.version}`), - } - } + path: p.path.replace( + /^@openzeppelin\/contracts(-upgradeable)?/, + `$&@${contracts.version}`, + ), + }; + }, }); } - - diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index 9f30967fe..0868529dc 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -19,7 +19,7 @@ export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); const fns = mapValues(sortedFunctions(contract), (fns) => - fns.map((fn) => printFunction(fn, helpers)) + fns.map((fn) => printFunction(fn, helpers)), ); const hasOverrides = fns.override.some((l) => l.length > 0); @@ -50,18 +50,18 @@ export function printContract(contract: Contract, opts?: Options): string { hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], - ...fns.override + ...fns.override, ), `}`, - ] - ) + ], + ), ); } function printInheritance( contract: Contract, - { transformName }: Helpers + { transformName }: Helpers, ): [] | [string] { if (contract.parents.length > 0) { return [ @@ -82,14 +82,14 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { (helpers.upgradeable && parentsWithInitializers.length > 0) ) { const parents = parentsWithInitializers.flatMap((p) => - printParentConstructor(p, helpers) + printParentConstructor(p, helpers), ); const modifiers = helpers.upgradeable ? ["initializer public"] : parents; const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); const body = helpers.upgradeable ? spaceBetween( parents.map((p) => p + ";"), - contract.constructorCode + contract.constructorCode, ) : contract.constructorCode; const head = helpers.upgradeable ? "function initialize" : "constructor"; @@ -144,7 +144,7 @@ function sortedFunctions(contract: Contract): SortedFunctions { function printParentConstructor( { contract, params }: Parent, - helpers: Helpers + helpers: Helpers, ): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; @@ -197,7 +197,7 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { modifiers.push(`override`); } else if (fn.override.size > 1) { modifiers.push( - `override(${[...fn.override].map(transformName).join(", ")})` + `override(${[...fn.override].map(transformName).join(", ")})`, ); } @@ -220,7 +220,7 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { "function " + fn.name, fn.args.map((a) => printArgument(a, helpers)), modifiers, - code + code, ); } else { return []; @@ -234,7 +234,7 @@ function printFunction2( kindedName: string, args: string[], modifiers: string[], - code: Lines[] + code: Lines[], ): Lines[] { const fn: Lines[] = [...comments]; @@ -246,7 +246,7 @@ function printFunction2( if (headingLength <= 72) { fn.push( - [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" ") + [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" "), ); } else { fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); @@ -261,7 +261,7 @@ function printFunction2( function printArgument( arg: FunctionArgument, - { transformName }: Helpers + { transformName }: Helpers, ): string { let type: string; if (typeof arg.type === "string") { @@ -293,7 +293,7 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { imports.map((p) => { const importContract = helpers.transformImport(p); lines.push( - `import {${importContract.name}} from "${importContract.path}";` + `import {${importContract.name}} from "${importContract.path}";`, ); }); diff --git a/packages/core/solidity/src/scripts/prepare.ts b/packages/core/solidity/src/scripts/prepare.ts index a499b2005..50974bc2d 100644 --- a/packages/core/solidity/src/scripts/prepare.ts +++ b/packages/core/solidity/src/scripts/prepare.ts @@ -1,41 +1,51 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import hre from 'hardhat'; -import type { BuildInfo } from 'hardhat/types'; -import { findAll } from 'solidity-ast/utils'; -import { rimraf } from 'rimraf'; +import { promises as fs } from "fs"; +import path from "path"; +import hre from "hardhat"; +import type { BuildInfo } from "hardhat/types"; +import { findAll } from "solidity-ast/utils"; +import { rimraf } from "rimraf"; import { version } from "@openzeppelin/contracts/package.json"; -import type { OpenZeppelinContracts } from '../../openzeppelin-contracts'; -import { writeGeneratedSources } from '../generate/sources'; -import { mapValues } from '../utils/map-values'; -import { transitiveClosure } from '../utils/transitive-closure'; +import type { OpenZeppelinContracts } from "../../openzeppelin-contracts"; +import { writeGeneratedSources } from "../generate/sources"; +import { mapValues } from "../utils/map-values"; +import { transitiveClosure } from "../utils/transitive-closure"; async function main() { - const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated'); + const generatedSourcesPath = path.join(hre.config.paths.sources, "generated"); await rimraf(generatedSourcesPath); - await writeGeneratedSources(generatedSourcesPath, 'minimal-cover'); - await hre.run('compile'); + await writeGeneratedSources(generatedSourcesPath, "minimal-cover"); + await hre.run("compile"); const dependencies: Record> = {}; const sources: Record = {}; for (const buildInfoPath of await hre.artifacts.getBuildInfoPaths()) { const buildInfo: BuildInfo = JSON.parse( - await fs.readFile(buildInfoPath, 'utf8'), + await fs.readFile(buildInfoPath, "utf8"), ); - for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { + for (const [sourceFile, { ast }] of Object.entries( + buildInfo.output.sources, + )) { + if ( + sourceFile.startsWith("@openzeppelin/contracts") || + sourceFile.startsWith("@openzeppelin/community-contracts") + ) { const sourceDependencies = (dependencies[sourceFile] ??= new Set()); - for (const imp of findAll('ImportDirective', ast)) { + for (const imp of findAll("ImportDirective", ast)) { sourceDependencies.add(imp.absolutePath); } } } - for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { + for (const [sourceFile, { content }] of Object.entries( + buildInfo.input.sources, + )) { + if ( + sourceFile.startsWith("@openzeppelin/contracts") || + sourceFile.startsWith("@openzeppelin/community-contracts") + ) { sources[sourceFile] = content; } } @@ -44,13 +54,18 @@ async function main() { const contracts: OpenZeppelinContracts = { version, sources, - dependencies: mapValues(transitiveClosure(dependencies), d => Array.from(d)), + dependencies: mapValues(transitiveClosure(dependencies), (d) => + Array.from(d), + ), }; - await fs.writeFile('openzeppelin-contracts.json', JSON.stringify(contracts, null, 2)); + await fs.writeFile( + "openzeppelin-contracts.json", + JSON.stringify(contracts, null, 2), + ); } -main().catch(e => { +main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/packages/core/solidity/src/set-access-control.ts b/packages/core/solidity/src/set-access-control.ts index 7394c31c1..0147594f2 100644 --- a/packages/core/solidity/src/set-access-control.ts +++ b/packages/core/solidity/src/set-access-control.ts @@ -1,40 +1,40 @@ -import type { ContractBuilder, BaseFunction } from './contract'; -import { supportsInterface } from './common-functions'; +import type { ContractBuilder, BaseFunction } from "./contract"; +import { supportsInterface } from "./common-functions"; -export const accessOptions = [false, 'ownable', 'roles', 'managed'] as const; +export const accessOptions = [false, "ownable", "roles", "managed"] as const; -export type Access = typeof accessOptions[number]; +export type Access = (typeof accessOptions)[number]; /** * Sets access control for the contract by adding inheritance. */ export function setAccessControl(c: ContractBuilder, access: Access) { switch (access) { - case 'ownable': { - if (c.addParent(parents.Ownable, [ {lit: 'initialOwner'} ])) { + case "ownable": { + if (c.addParent(parents.Ownable, [{ lit: "initialOwner" }])) { c.addConstructorArgument({ - type: 'address', - name: 'initialOwner' + type: "address", + name: "initialOwner", }); } break; } - case 'roles': { + case "roles": { if (c.addParent(parents.AccessControl)) { c.addConstructorArgument({ - type: 'address', - name: 'defaultAdmin' + type: "address", + name: "defaultAdmin", }); - c.addConstructorCode('_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);'); + c.addConstructorCode("_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);"); } c.addOverride(parents.AccessControl, supportsInterface); break; } - case 'managed': { - if (c.addParent(parents.AccessManaged, [ {lit: 'initialAuthority'} ])) { + case "managed": { + if (c.addParent(parents.AccessManaged, [{ lit: "initialAuthority" }])) { c.addConstructorArgument({ - type: 'address', - name: 'initialAuthority' + type: "address", + name: "initialAuthority", }); } break; @@ -45,30 +45,38 @@ export function setAccessControl(c: ContractBuilder, access: Access) { /** * Enables access control for the contract and restricts the given function with access control. */ -export function requireAccessControl(c: ContractBuilder, fn: BaseFunction, access: Access, roleIdPrefix: string, roleOwner: string | undefined) { +export function requireAccessControl( + c: ContractBuilder, + fn: BaseFunction, + access: Access, + roleIdPrefix: string, + roleOwner: string | undefined, +) { if (access === false) { - access = 'ownable'; + access = "ownable"; } - + setAccessControl(c, access); switch (access) { - case 'ownable': { - c.addModifier('onlyOwner', fn); + case "ownable": { + c.addModifier("onlyOwner", fn); break; } - case 'roles': { - const roleId = roleIdPrefix + '_ROLE'; - const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); + case "roles": { + const roleId = roleIdPrefix + "_ROLE"; + const addedConstant = c.addVariable( + `bytes32 public constant ${roleId} = keccak256("${roleId}");`, + ); if (roleOwner && addedConstant) { - c.addConstructorArgument({type: 'address', name: roleOwner}); + c.addConstructorArgument({ type: "address", name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } c.addModifier(`onlyRole(${roleId})`, fn); break; } - case 'managed': { - c.addModifier('restricted', fn); + case "managed": { + c.addModifier("restricted", fn); break; } } @@ -76,15 +84,15 @@ export function requireAccessControl(c: ContractBuilder, fn: BaseFunction, acces const parents = { Ownable: { - name: 'Ownable', - path: '@openzeppelin/contracts/access/Ownable.sol', + name: "Ownable", + path: "@openzeppelin/contracts/access/Ownable.sol", }, AccessControl: { - name: 'AccessControl', - path: '@openzeppelin/contracts/access/AccessControl.sol', + name: "AccessControl", + path: "@openzeppelin/contracts/access/AccessControl.sol", }, AccessManaged: { - name: 'AccessManaged', - path: '@openzeppelin/contracts/access/manager/AccessManaged.sol', + name: "AccessManaged", + path: "@openzeppelin/contracts/access/manager/AccessManaged.sol", }, }; diff --git a/packages/core/solidity/src/set-clock-mode.ts b/packages/core/solidity/src/set-clock-mode.ts index 65387cdd6..3569b1dfe 100644 --- a/packages/core/solidity/src/set-clock-mode.ts +++ b/packages/core/solidity/src/set-clock-mode.ts @@ -1,33 +1,40 @@ import type { ContractBuilder, ReferencedContract } from "./contract"; import { defineFunctions } from "./utils/define-functions"; -export const clockModeOptions = ['blocknumber', 'timestamp'] as const; -export const clockModeDefault = 'blocknumber' as const; -export type ClockMode = typeof clockModeOptions[number]; +export const clockModeOptions = ["blocknumber", "timestamp"] as const; +export const clockModeDefault = "blocknumber" as const; +export type ClockMode = (typeof clockModeOptions)[number]; const functions = defineFunctions({ clock: { - kind: 'public' as const, + kind: "public" as const, args: [], - returns: ['uint48'], - mutability: 'view' as const, + returns: ["uint48"], + mutability: "view" as const, }, CLOCK_MODE: { - kind: 'public' as const, + kind: "public" as const, args: [], - returns: ['string memory'], - mutability: 'pure' as const, - } + returns: ["string memory"], + mutability: "pure" as const, + }, }); -export function setClockMode(c: ContractBuilder, parent: ReferencedContract, votes: ClockMode) { - if (votes === 'timestamp') { +export function setClockMode( + c: ContractBuilder, + parent: ReferencedContract, + votes: ClockMode, +) { + if (votes === "timestamp") { c.addOverride(parent, functions.clock); - c.setFunctionBody(['return uint48(block.timestamp);'], functions.clock); + c.setFunctionBody(["return uint48(block.timestamp);"], functions.clock); - c.setFunctionComments(['// solhint-disable-next-line func-name-mixedcase'], functions.CLOCK_MODE); + c.setFunctionComments( + ["// solhint-disable-next-line func-name-mixedcase"], + functions.CLOCK_MODE, + ); c.addOverride(parent, functions.CLOCK_MODE); c.setFunctionBody(['return "mode=timestamp";'], functions.CLOCK_MODE); } -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/set-info.ts b/packages/core/solidity/src/set-info.ts index bf5b2300d..9821cd29a 100644 --- a/packages/core/solidity/src/set-info.ts +++ b/packages/core/solidity/src/set-info.ts @@ -2,18 +2,21 @@ import type { ContractBuilder } from "./contract"; export const TAG_SECURITY_CONTACT = `@custom:security-contact`; -export const infoOptions = [{}, { securityContact: 'security@example.com', license: 'WTFPL' }] as const; +export const infoOptions = [ + {}, + { securityContact: "security@example.com", license: "WTFPL" }, +] as const; -export const defaults: Info = { license: 'MIT' }; +export const defaults: Info = { license: "MIT" }; export type Info = { securityContact?: string; license?: string; -} +}; export function setInfo(c: ContractBuilder, info: Info) { const { securityContact, license } = info; - + if (securityContact) { c.addNatspecTag(TAG_SECURITY_CONTACT, securityContact); } diff --git a/packages/core/solidity/src/set-upgradeable.ts b/packages/core/solidity/src/set-upgradeable.ts index 04fcccc03..71073d743 100644 --- a/packages/core/solidity/src/set-upgradeable.ts +++ b/packages/core/solidity/src/set-upgradeable.ts @@ -1,12 +1,16 @@ -import type { ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; +import type { ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; -export const upgradeableOptions = [false, 'transparent', 'uups'] as const; +export const upgradeableOptions = [false, "transparent", "uups"] as const; -export type Upgradeable = typeof upgradeableOptions[number]; +export type Upgradeable = (typeof upgradeableOptions)[number]; -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { +export function setUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + access: Access, +) { if (upgradeable === false) { return; } @@ -14,18 +18,25 @@ export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, acc c.upgradeable = true; c.addParent({ - name: 'Initializable', - path: '@openzeppelin/contracts/proxy/utils/Initializable.sol', + name: "Initializable", + path: "@openzeppelin/contracts/proxy/utils/Initializable.sol", }); switch (upgradeable) { - case 'transparent': break; + case "transparent": + break; - case 'uups': { - requireAccessControl(c, functions._authorizeUpgrade, access, 'UPGRADER', 'upgrader'); + case "uups": { + requireAccessControl( + c, + functions._authorizeUpgrade, + access, + "UPGRADER", + "upgrader", + ); const UUPSUpgradeable = { - name: 'UUPSUpgradeable', - path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol', + name: "UUPSUpgradeable", + path: "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol", }; c.addParent(UUPSUpgradeable); c.addOverride(UUPSUpgradeable, functions._authorizeUpgrade); @@ -35,16 +46,14 @@ export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, acc default: { const _: never = upgradeable; - throw new Error('Unknown value for `upgradeable`'); + throw new Error("Unknown value for `upgradeable`"); } } } const functions = defineFunctions({ _authorizeUpgrade: { - args: [ - { name: 'newImplementation', type: 'address' }, - ], - kind: 'internal', + args: [{ name: "newImplementation", type: "address" }], + kind: "internal", }, }); diff --git a/packages/core/solidity/src/stablecoin.test.ts b/packages/core/solidity/src/stablecoin.test.ts index a3980f869..35d205e0c 100644 --- a/packages/core/solidity/src/stablecoin.test.ts +++ b/packages/core/solidity/src/stablecoin.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { stablecoin } from '.'; +import test from "ava"; +import { stablecoin } from "."; -import { buildStablecoin, StablecoinOptions } from './stablecoin'; -import { printContract } from './print'; +import { buildStablecoin, StablecoinOptions } from "./stablecoin"; +import { printContract } from "./print"; function testStablecoin(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildStablecoin({ - name: 'MyStablecoin', - symbol: 'MST', + name: "MyStablecoin", + symbol: "MST", ...opts, }); t.snapshot(printContract(c)); @@ -18,118 +18,126 @@ function testStablecoin(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: StablecoinOptions) { - test(title, t => { - t.is(stablecoin.print(opts), printContract(buildStablecoin({ - name: 'MyStablecoin', - symbol: 'MST', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: StablecoinOptions) { + test(title, (t) => { + t.is( + stablecoin.print(opts), + printContract( + buildStablecoin({ + name: "MyStablecoin", + symbol: "MST", + ...opts, + }), + ), + ); }); } -testStablecoin('basic stablecoin', {}); +testStablecoin("basic stablecoin", {}); -testStablecoin('stablecoin burnable', { +testStablecoin("stablecoin burnable", { burnable: true, }); -testStablecoin('stablecoin pausable', { +testStablecoin("stablecoin pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testStablecoin('stablecoin pausable with roles', { +testStablecoin("stablecoin pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testStablecoin('stablecoin pausable with managed', { +testStablecoin("stablecoin pausable with managed", { pausable: true, - access: 'managed', + access: "managed", }); -testStablecoin('stablecoin burnable pausable', { +testStablecoin("stablecoin burnable pausable", { burnable: true, pausable: true, }); -testStablecoin('stablecoin preminted', { - premint: '1000', +testStablecoin("stablecoin preminted", { + premint: "1000", }); -testStablecoin('stablecoin premint of 0', { - premint: '0', +testStablecoin("stablecoin premint of 0", { + premint: "0", }); -testStablecoin('stablecoin mintable', { +testStablecoin("stablecoin mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testStablecoin('stablecoin mintable with roles', { +testStablecoin("stablecoin mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testStablecoin('stablecoin permit', { +testStablecoin("stablecoin permit", { permit: true, }); -testStablecoin('stablecoin custodian', { +testStablecoin("stablecoin custodian", { custodian: true, }); -testStablecoin('stablecoin allowlist', { - limitations: 'allowlist', +testStablecoin("stablecoin allowlist", { + limitations: "allowlist", }); -testStablecoin('stablecoin blocklist', { - limitations: 'blocklist', +testStablecoin("stablecoin blocklist", { + limitations: "blocklist", }); -testStablecoin('stablecoin votes', { +testStablecoin("stablecoin votes", { votes: true, }); -testStablecoin('stablecoin votes + blocknumber', { - votes: 'blocknumber', +testStablecoin("stablecoin votes + blocknumber", { + votes: "blocknumber", }); -testStablecoin('stablecoin votes + timestamp', { - votes: 'timestamp', +testStablecoin("stablecoin votes + timestamp", { + votes: "timestamp", }); -testStablecoin('stablecoin flashmint', { +testStablecoin("stablecoin flashmint", { flashmint: true, }); -testAPIEquivalence('stablecoin API default'); +testAPIEquivalence("stablecoin API default"); -testAPIEquivalence('stablecoin API basic', { name: 'CustomStablecoin', symbol: 'CST' }); +testAPIEquivalence("stablecoin API basic", { + name: "CustomStablecoin", + symbol: "CST", +}); -testAPIEquivalence('stablecoin API full', { - name: 'CustomStablecoin', - symbol: 'CST', - premint: '2000', - access: 'roles', +testAPIEquivalence("stablecoin API full", { + name: "CustomStablecoin", + symbol: "CST", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - limitations: 'allowlist', - custodian: true + limitations: "allowlist", + custodian: true, }); -test('stablecoin API assert defaults', async t => { +test("stablecoin API assert defaults", async (t) => { t.is(stablecoin.print(stablecoin.defaults), stablecoin.print()); }); -test('stablecoin API isAccessControlRequired', async t => { +test("stablecoin API isAccessControlRequired", async (t) => { t.is(stablecoin.isAccessControlRequired({ mintable: true }), true); t.is(stablecoin.isAccessControlRequired({ pausable: true }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: 'allowlist' }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: 'blocklist' }), true); -}); \ No newline at end of file + t.is(stablecoin.isAccessControlRequired({ limitations: "allowlist" }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: "blocklist" }), true); +}); diff --git a/packages/core/solidity/src/stablecoin.ts b/packages/core/solidity/src/stablecoin.ts index 66ba36c0d..af0dec852 100644 --- a/packages/core/solidity/src/stablecoin.ts +++ b/packages/core/solidity/src/stablecoin.ts @@ -1,8 +1,18 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; -import { printContract } from './print'; -import { buildERC20, ERC20Options, defaults as erc20defaults, withDefaults as withERC20Defaults, functions as erc20functions } from './erc20'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; +import { printContract } from "./print"; +import { + buildERC20, + ERC20Options, + defaults as erc20defaults, + withDefaults as withERC20Defaults, + functions as erc20functions, +} from "./erc20"; export interface StablecoinOptions extends ERC20Options { limitations?: false | "allowlist" | "blocklist"; @@ -11,8 +21,8 @@ export interface StablecoinOptions extends ERC20Options { export const defaults: Required = { ...erc20defaults, - name: 'MyStablecoin', - symbol: 'MST', + name: "MyStablecoin", + symbol: "MST", limitations: false, custodian: false, } as const; @@ -31,8 +41,16 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string { return printContract(buildStablecoin(opts)); } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial, +): boolean { + return ( + opts.mintable || + opts.limitations !== false || + opts.custodian || + opts.pausable || + opts.upgradeable === "uups" + ); } export function buildStablecoin(opts: StablecoinOptions): Contract { @@ -54,32 +72,39 @@ export function buildStablecoin(opts: StablecoinOptions): Contract { return c; } -function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') { - const type = mode === 'allowlist'; +function addLimitations( + c: ContractBuilder, + access: Access, + mode: boolean | "allowlist" | "blocklist", +) { + const type = mode === "allowlist"; const ERC20Limitation = { - name: type ? 'ERC20Allowlist' : 'ERC20Blocklist', - path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`, + name: type ? "ERC20Allowlist" : "ERC20Blocklist", + path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? "ERC20Allowlist" : "ERC20Blocklist"}.sol`, }; c.addParent(ERC20Limitation); c.addOverride(ERC20Limitation, functions._update); c.addOverride(ERC20Limitation, functions._approve); - const [addFn, removeFn] = type + const [addFn, removeFn] = type ? [functions.allowUser, functions.disallowUser] : [functions.blockUser, functions.unblockUser]; - requireAccessControl(c, addFn, access, 'LIMITER', 'limiter'); - c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn); + requireAccessControl(c, addFn, access, "LIMITER", "limiter"); + c.addFunctionCode(`_${type ? "allowUser" : "blockUser"}(user);`, addFn); - requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter'); - c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn); + requireAccessControl(c, removeFn, access, "LIMITER", "limiter"); + c.addFunctionCode( + `_${type ? "disallowUser" : "unblockUser"}(user);`, + removeFn, + ); } function addCustodian(c: ContractBuilder, access: Access) { const ERC20Custodian = { - name: 'ERC20Custodian', - path: '@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol', + name: "ERC20Custodian", + path: "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol", }; c.addParent(ERC20Custodian); @@ -87,36 +112,41 @@ function addCustodian(c: ContractBuilder, access: Access) { c.addOverride(ERC20Custodian, functions._isCustodian); if (access === false) { - access = 'ownable'; + access = "ownable"; } - + setAccessControl(c, access); switch (access) { - case 'ownable': { + case "ownable": { c.setFunctionBody([`return user == owner();`], functions._isCustodian); break; } - case 'roles': { - const roleOwner = 'custodian'; - const roleId = 'CUSTODIAN_ROLE'; - const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); + case "roles": { + const roleOwner = "custodian"; + const roleId = "CUSTODIAN_ROLE"; + const addedConstant = c.addVariable( + `bytes32 public constant ${roleId} = keccak256("${roleId}");`, + ); if (roleOwner && addedConstant) { - c.addConstructorArgument({type: 'address', name: roleOwner}); + c.addConstructorArgument({ type: "address", name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } - c.setFunctionBody([`return hasRole(CUSTODIAN_ROLE, user);`], functions._isCustodian); + c.setFunctionBody( + [`return hasRole(CUSTODIAN_ROLE, user);`], + functions._isCustodian, + ); break; } - case 'managed': { + case "managed": { c.addImportOnly({ - name: 'AuthorityUtils', + name: "AuthorityUtils", path: `@openzeppelin/contracts/access/manager/AuthorityUtils.sol`, }); const logic = [ `(bool immediate,) = AuthorityUtils.canCallWithDelay(authority(), user, address(this), bytes4(_msgData()[0:4]));`, - `return immediate;` - ] + `return immediate;`, + ]; c.setFunctionBody(logic, functions._isCustodian); break; } @@ -127,41 +157,30 @@ const functions = { ...erc20functions, ...defineFunctions({ _isCustodian: { - kind: 'internal' as const, - args: [ - { name: 'user', type: 'address' }, - ], - returns: ['bool'], - mutability: 'view' as const + kind: "internal" as const, + args: [{ name: "user", type: "address" }], + returns: ["bool"], + mutability: "view" as const, }, allowUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, disallowUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, blockUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, unblockUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, - }) + }), }; - diff --git a/packages/core/solidity/src/test.ts b/packages/core/solidity/src/test.ts index 9ff1738b2..e0e7211cb 100644 --- a/packages/core/solidity/src/test.ts +++ b/packages/core/solidity/src/test.ts @@ -1,46 +1,49 @@ -import { promises as fs } from 'fs'; -import _test, { TestFn, ExecutionContext } from 'ava'; -import hre from 'hardhat'; -import path from 'path'; +import { promises as fs } from "fs"; +import _test, { TestFn, ExecutionContext } from "ava"; +import hre from "hardhat"; +import path from "path"; -import { generateSources, writeGeneratedSources } from './generate/sources'; -import type { GenericOptions, KindedOptions } from './build-generic'; -import { custom, erc1155, stablecoin, erc20, erc721, governor } from './api'; +import { generateSources, writeGeneratedSources } from "./generate/sources"; +import type { GenericOptions, KindedOptions } from "./build-generic"; +import { custom, erc1155, stablecoin, erc20, erc721, governor } from "./api"; interface Context { - generatedSourcesPath: string + generatedSourcesPath: string; } const test = _test as TestFn; -test.serial('erc20 result compiles', async t => { - await testCompile(t, 'ERC20'); +test.serial("erc20 result compiles", async (t) => { + await testCompile(t, "ERC20"); }); -test.serial('erc721 result compiles', async t => { - await testCompile(t, 'ERC721'); +test.serial("erc721 result compiles", async (t) => { + await testCompile(t, "ERC721"); }); -test.serial('erc1155 result compiles', async t => { - await testCompile(t, 'ERC1155'); +test.serial("erc1155 result compiles", async (t) => { + await testCompile(t, "ERC1155"); }); -test.serial('stablecoin result compiles', async t => { - await testCompile(t, 'Stablecoin'); +test.serial("stablecoin result compiles", async (t) => { + await testCompile(t, "Stablecoin"); }); -test.serial('governor result compiles', async t => { - await testCompile(t, 'Governor'); +test.serial("governor result compiles", async (t) => { + await testCompile(t, "Governor"); }); -test.serial('custom result compiles', async t => { - await testCompile(t, 'Custom'); +test.serial("custom result compiles", async (t) => { + await testCompile(t, "Custom"); }); -async function testCompile(t: ExecutionContext, kind: keyof KindedOptions) { +async function testCompile( + t: ExecutionContext, + kind: keyof KindedOptions, +) { const generatedSourcesPath = path.join(hre.config.paths.sources, `generated`); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, 'all', kind); + await writeGeneratedSources(generatedSourcesPath, "all", kind); // We only want to check that contracts compile and we don't care about any // of the outputs. Setting empty outputSelection causes compilation to go a @@ -49,41 +52,49 @@ async function testCompile(t: ExecutionContext, kind: keyof KindedOptio settings.outputSelection = {}; } - await hre.run('compile'); + await hre.run("compile"); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { - switch(opts.kind) { - case 'ERC20': + switch (opts.kind) { + case "ERC20": return erc20.isAccessControlRequired(opts); - case 'ERC721': + case "ERC721": return erc721.isAccessControlRequired(opts); - case 'ERC1155': + case "ERC1155": return erc1155.isAccessControlRequired(opts); - case 'Stablecoin': + case "Stablecoin": return stablecoin.isAccessControlRequired(opts); - case 'RealWorldAsset': + case "RealWorldAsset": return stablecoin.isAccessControlRequired(opts); - case 'Governor': + case "Governor": return governor.isAccessControlRequired(opts); - case 'Custom': + case "Custom": return custom.isAccessControlRequired(opts); default: throw new Error("No such kind"); } } -test('is access control required', async t => { - for (const contract of generateSources('all')) { +test("is access control required", async (t) => { + for (const contract of generateSources("all")) { const regexOwnable = /import.*Ownable(Upgradeable)?.sol.*/gm; if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.regex( + contract.source, + regexOwnable, + JSON.stringify(contract.options), + ); } else { - t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.notRegex( + contract.source, + regexOwnable, + JSON.stringify(contract.options), + ); } } } -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/utils/define-functions.ts b/packages/core/solidity/src/utils/define-functions.ts index c1f664e7e..c05316bce 100644 --- a/packages/core/solidity/src/utils/define-functions.ts +++ b/packages/core/solidity/src/utils/define-functions.ts @@ -1,6 +1,6 @@ -import type { BaseFunction } from '../contract'; +import type { BaseFunction } from "../contract"; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; export function defineFunctions( fns: Record, diff --git a/packages/core/solidity/src/utils/duration.ts b/packages/core/solidity/src/utils/duration.ts index a0a4549f6..32c273fff 100644 --- a/packages/core/solidity/src/utils/duration.ts +++ b/packages/core/solidity/src/utils/duration.ts @@ -1,6 +1,17 @@ -const durationUnits = ['block', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; -type DurationUnit = typeof durationUnits[number]; -export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); +const durationUnits = [ + "block", + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", +] as const; +type DurationUnit = (typeof durationUnits)[number]; +export const durationPattern = new RegExp( + `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, +); const second = 1; const minute = 60 * second; @@ -15,15 +26,15 @@ export function durationToBlocks(duration: string, blockTime: number): number { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = parseFloat(match[1]!); const unit = match[2]! as DurationUnit; - if (unit === 'block') { + if (unit === "block") { if (!Number.isInteger(value)) { - throw new Error('Invalid number of blocks'); + throw new Error("Invalid number of blocks"); } return value; @@ -37,16 +48,16 @@ export function durationToTimestamp(duration: string): string { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = match[1]!; const unit = match[2]! as DurationUnit; // unit must be a Solidity supported time unit - if (unit === 'block' || unit === 'month' || unit === 'year') { - throw new Error('Invalid unit for timestamp'); + if (unit === "block" || unit === "month" || unit === "year") { + throw new Error("Invalid unit for timestamp"); } return `${value} ${unit}s`; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/utils/find-cover.ts b/packages/core/solidity/src/utils/find-cover.ts index 939ed9240..0cc7f0bb6 100644 --- a/packages/core/solidity/src/utils/find-cover.ts +++ b/packages/core/solidity/src/utils/find-cover.ts @@ -1,11 +1,14 @@ -import { sortedBy } from './sorted-by'; +import { sortedBy } from "./sorted-by"; // Greedy approximation of minimum set cover. -export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { +export function findCover( + sets: T[], + getElements: (set: T) => unknown[], +): T[] { const sortedSets = sortedBy( - sets.map(set => ({ set, elems: getElements(set) })), - s => -s.elems.length, + sets.map((set) => ({ set, elems: getElements(set) })), + (s) => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/solidity/src/utils/format-lines.ts b/packages/core/solidity/src/utils/format-lines.ts index 1860507f8..a0ab46e87 100644 --- a/packages/core/solidity/src/utils/format-lines.ts +++ b/packages/core/solidity/src/utils/format-lines.ts @@ -1,13 +1,16 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol('whitespace'); +const whitespace = Symbol("whitespace"); export function formatLines(...lines: Lines[]): string { return formatLinesWithSpaces(4, ...lines); } -export function formatLinesWithSpaces(spacesPerIndent: number, ...lines: Lines[]): string { - return [...indentEach(0, lines, spacesPerIndent)].join('\n') + '\n'; +export function formatLinesWithSpaces( + spacesPerIndent: number, + ...lines: Lines[] +): string { + return [...indentEach(0, lines, spacesPerIndent)].join("\n") + "\n"; } function* indentEach( @@ -17,18 +20,18 @@ function* indentEach( ): Generator { for (const line of lines) { if (line === whitespace) { - yield ''; + yield ""; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line, spacesPerIndent); } else { - yield ' '.repeat(indent * spacesPerIndent) + line; + yield " ".repeat(indent * spacesPerIndent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter(l => l.length > 0) - .flatMap(l => [whitespace, ...l]) + .filter((l) => l.length > 0) + .flatMap((l) => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index 1d3a359b2..b177c69a4 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function mapValues( obj: Record, - fn: (val: V) => W + fn: (val: V) => W, ): Record { const res = {} as Record; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/to-identifier.test.ts b/packages/core/solidity/src/utils/to-identifier.test.ts index 6af3b3a11..870f062ff 100644 --- a/packages/core/solidity/src/utils/to-identifier.test.ts +++ b/packages/core/solidity/src/utils/to-identifier.test.ts @@ -1,20 +1,20 @@ -import test from 'ava'; +import test from "ava"; -import { toIdentifier } from './to-identifier'; +import { toIdentifier } from "./to-identifier"; -test('unmodified', t => { - t.is(toIdentifier('abc'), 'abc'); +test("unmodified", (t) => { + t.is(toIdentifier("abc"), "abc"); }); -test('remove leading specials', t => { - t.is(toIdentifier('--abc'), 'abc'); +test("remove leading specials", (t) => { + t.is(toIdentifier("--abc"), "abc"); }); -test('remove specials and upcase next char', t => { - t.is(toIdentifier('abc-def'), 'abcDef'); - t.is(toIdentifier('abc--def'), 'abcDef'); +test("remove specials and upcase next char", (t) => { + t.is(toIdentifier("abc-def"), "abcDef"); + t.is(toIdentifier("abc--def"), "abcDef"); }); -test('capitalize', t => { - t.is(toIdentifier('abc', true), 'Abc'); +test("capitalize", (t) => { + t.is(toIdentifier("abc", true), "Abc"); }); diff --git a/packages/core/solidity/src/utils/to-identifier.ts b/packages/core/solidity/src/utils/to-identifier.ts index 2e4a2f9f4..eb49e670f 100644 --- a/packages/core/solidity/src/utils/to-identifier.ts +++ b/packages/core/solidity/src/utils/to-identifier.ts @@ -1,7 +1,8 @@ export function toIdentifier(str: string, capitalize = false): string { return str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/^[^a-zA-Z$_]+/, '') - .replace(/^(.)/, c => capitalize ? c.toUpperCase() : c) + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/^[^a-zA-Z$_]+/, "") + .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w$]+(.?)/g, (_, c) => c.toUpperCase()); } diff --git a/packages/core/solidity/src/utils/transitive-closure.ts b/packages/core/solidity/src/utils/transitive-closure.ts index f2e04ba2d..1b56e79fc 100644 --- a/packages/core/solidity/src/utils/transitive-closure.ts +++ b/packages/core/solidity/src/utils/transitive-closure.ts @@ -1,6 +1,8 @@ type T = string; -export function transitiveClosure(obj: Record>): Record> { +export function transitiveClosure( + obj: Record>, +): Record> { const closure = {} as Record>; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/version.test.ts b/packages/core/solidity/src/utils/version.test.ts index ceac997fb..bc114df8c 100644 --- a/packages/core/solidity/src/utils/version.test.ts +++ b/packages/core/solidity/src/utils/version.test.ts @@ -1,12 +1,14 @@ -import test from 'ava'; +import test from "ava"; -import semver from 'semver'; +import semver from "semver"; -import { compatibleContractsSemver } from './version'; -import contracts from '../../openzeppelin-contracts'; +import { compatibleContractsSemver } from "./version"; +import contracts from "../../openzeppelin-contracts"; -test('installed contracts satisfies compatible range', t => { - t.true(semver.satisfies(contracts.version, compatibleContractsSemver), +test("installed contracts satisfies compatible range", (t) => { + t.true( + semver.satisfies(contracts.version, compatibleContractsSemver), `Installed contracts version ${contracts.version} does not satisfy compatible range ${compatibleContractsSemver}. -Check whether the compatible range is up to date.`); +Check whether the compatible range is up to date.`, + ); }); diff --git a/packages/core/solidity/src/utils/version.ts b/packages/core/solidity/src/utils/version.ts index 3daddc218..31bf323b7 100644 --- a/packages/core/solidity/src/utils/version.ts +++ b/packages/core/solidity/src/utils/version.ts @@ -1,4 +1,4 @@ /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = '^5.0.0'; +export const compatibleContractsSemver = "^5.0.0"; diff --git a/packages/core/solidity/src/zip-foundry.test.ts b/packages/core/solidity/src/zip-foundry.test.ts index eef1c8791..ae1a640fc 100644 --- a/packages/core/solidity/src/zip-foundry.test.ts +++ b/packages/core/solidity/src/zip-foundry.test.ts @@ -1,21 +1,21 @@ -import _test, { TestFn, ExecutionContext } from 'ava'; - -import { zipFoundry } from './zip-foundry'; - -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { buildERC1155 } from './erc1155'; -import { buildCustom } from './custom'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import util from 'util'; +import _test, { TestFn, ExecutionContext } from "ava"; + +import { zipFoundry } from "./zip-foundry"; + +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { buildERC1155 } from "./erc1155"; +import { buildCustom } from "./custom"; +import { promises as fs } from "fs"; +import path from "path"; +import os from "os"; +import util from "util"; import child from "child_process"; -import type { Contract } from './contract'; -import { rimraf } from 'rimraf'; -import type { JSZipObject } from 'jszip'; -import type JSZip from 'jszip'; -import type { GenericOptions } from './build-generic'; +import type { Contract } from "./contract"; +import { rimraf } from "rimraf"; +import type { JSZipObject } from "jszip"; +import type JSZip from "jszip"; +import type { GenericOptions } from "./build-generic"; interface Context { tempFolder: string; @@ -23,21 +23,23 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async t => { - t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); +test.beforeEach(async (t) => { + t.context.tempFolder = await fs.mkdtemp( + path.join(os.tmpdir(), "openzeppelin-wizard-"), + ); }); -test.afterEach.always(async t => { +test.afterEach.always(async (t) => { await rimraf(t.context.tempFolder); }); -test.serial('erc20 full', async t => { +test.serial("erc20 full", async (t) => { const opts: GenericOptions = { - kind: 'ERC20', - name: 'My Token', - symbol: 'MTK', - premint: '2000', - access: 'roles', + kind: "ERC20", + name: "My Token", + symbol: "MTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, @@ -49,43 +51,74 @@ test.serial('erc20 full', async t => { await runTest(c, t, opts); }); -test.serial('erc20 uups, roles', async t => { - const opts: GenericOptions = { kind: 'ERC20', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'roles' }; +test.serial("erc20 uups, roles", async (t) => { + const opts: GenericOptions = { + kind: "ERC20", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + access: "roles", + }; const c = buildERC20(opts); await runTest(c, t, opts); }); -test.serial('erc721 uups, ownable', async t => { - const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'ownable' }; +test.serial("erc721 uups, ownable", async (t) => { + const opts: GenericOptions = { + kind: "ERC721", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + access: "ownable", + }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial('erc1155 basic', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}' }; +test.serial("erc1155 basic", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('erc1155 transparent, ownable', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}', upgradeable: 'transparent', access: 'ownable' }; +test.serial("erc1155 transparent, ownable", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + upgradeable: "transparent", + access: "ownable", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('custom basic', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; +test.serial("custom basic", async (t) => { + const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial('custom transparent, managed', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract', upgradeable: 'transparent', access: 'managed' }; +test.serial("custom transparent, managed", async (t) => { + const opts: GenericOptions = { + kind: "Custom", + name: "My Contract", + upgradeable: "transparent", + access: "managed", + }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { +async function runTest( + c: Contract, + t: ExecutionContext, + opts: GenericOptions, +) { const zip = await zipFoundry(c, opts); assertLayout(zip, c, t); @@ -94,20 +127,26 @@ async function runTest(c: Contract, t: ExecutionContext, opts: GenericO } function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { - const sorted = Object.values(zip.files).map(f => f.name).sort(); + const sorted = Object.values(zip.files) + .map((f) => f.name) + .sort(); t.deepEqual(sorted, [ - 'README.md', - 'script/', + "README.md", + "script/", `script/${c.name}.s.sol`, - 'setup.sh', - 'src/', + "setup.sh", + "src/", `src/${c.name}.sol`, - 'test/', + "test/", `test/${c.name}.t.sol`, ]); } -async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { +async function extractAndRunPackage( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -117,16 +156,22 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); + await fs.writeFile( + path.join(tempFolder, item.name), + await asString(item), + ); } } - const setGitUser = 'git init && git config user.email "test@test.test" && git config user.name "Test"'; - const setup = 'bash setup.sh'; - const test = 'forge test' + (c.upgradeable ? ' --force' : ''); - const script = `forge script script/${c.name}.s.sol` + (c.upgradeable ? ' --force' : ''); + const setGitUser = + 'git init && git config user.email "test@test.test" && git config user.name "Test"'; + const setup = "bash setup.sh"; + const test = "forge test" + (c.upgradeable ? " --force" : ""); + const script = + `forge script script/${c.name}.s.sol` + (c.upgradeable ? " --force" : ""); - const exec = (cmd: string) => util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: '' } }); + const exec = (cmd: string) => + util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: "" } }); const command = `cd "${tempFolder}" && ${setGitUser} && ${setup} && ${test} && ${script}`; const result = await exec(command); @@ -145,8 +190,13 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext t.regex(rerunResult.stdout, /Foundry project already initialized\./); } -async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { - const normalizeVersion = (text: string) => text.replace(/\bv\d+\.\d+\.\d+\b/g, 'vX.Y.Z'); +async function assertContents( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { + const normalizeVersion = (text: string) => + text.replace(/\bv\d+\.\d+\.\d+\b/g, "vX.Y.Z"); const contentComparison = [ normalizeVersion(await getItemString(zip, `setup.sh`)), @@ -168,5 +218,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async('arraybuffer')).toString(); + return Buffer.from(await item.async("arraybuffer")).toString(); } diff --git a/packages/core/solidity/src/zip-foundry.ts b/packages/core/solidity/src/zip-foundry.ts index 96b88122e..4a0e5c8af 100644 --- a/packages/core/solidity/src/zip-foundry.ts +++ b/packages/core/solidity/src/zip-foundry.ts @@ -2,33 +2,33 @@ import JSZip from "jszip"; import type { GenericOptions } from "./build-generic"; import type { Contract } from "./contract"; import { printContract } from "./print"; -import SOLIDITY_VERSION from './solidity-version.json'; -import contracts from '../openzeppelin-contracts'; -import { formatLinesWithSpaces, Lines, spaceBetween } from "./utils/format-lines"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import contracts from "../openzeppelin-contracts"; +import { + formatLinesWithSpaces, + Lines, + spaceBetween, +} from "./utils/format-lines"; function getHeader(c: Contract) { return [ `// SPDX-License-Identifier: ${c.license}`, - `pragma solidity ^${SOLIDITY_VERSION};` + `pragma solidity ^${SOLIDITY_VERSION};`, ]; } const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getHeader(c), - getImports(c), - getTestCase(c), - ), + ...spaceBetween(getHeader(c), getImports(c), getTestCase(c)), ); function getImports(c: Contract) { - const result = [ - 'import {Test} from "forge-std/Test.sol";', - ]; + const result = ['import {Test} from "forge-std/Test.sol";']; if (c.upgradeable) { - result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + result.push( + 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', + ); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -39,32 +39,30 @@ const test = (c: Contract, opts?: GenericOptions) => { return [ `contract ${c.name}Test is Test {`, spaceBetween( + [`${c.name} public instance;`], [ - `${c.name} public instance;`, - ], - [ - 'function setUp() public {', + "function setUp() public {", getAddressVariables(c, args), getDeploymentCode(c, args), - '}', + "}", ], getContractSpecificTestFunction(), ), - '}', + "}", ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === 'transparent') { + if (opts?.upgradeable === "transparent") { return [ `address proxy = Upgrades.deployTransparentProxy(`, [ `"${c.name}.sol",`, `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `instance = ${c.name}(proxy);`, ]; } else { @@ -72,23 +70,25 @@ const test = (c: Contract, opts?: GenericOptions) => { `address proxy = Upgrades.deployUUPSProxy(`, [ `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `instance = ${c.name}(proxy);`, ]; } } else { - return [ - `instance = new ${c.name}(${args.join(', ')});`, - ]; + return [`instance = new ${c.name}(${args.join(", ")});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; let i = 1; // private key index starts from 1 since it must be non-zero - if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + if ( + c.upgradeable && + opts?.upgradeable === "transparent" && + !args.includes("initialOwner") + ) { vars.push(`address initialOwner = vm.addr(${i++});`); } for (const arg of args) { @@ -100,37 +100,31 @@ const test = (c: Contract, opts?: GenericOptions) => { function getContractSpecificTestFunction(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case 'ERC20': - case 'ERC721': + case "ERC20": + case "ERC721": return [ - 'function testName() public view {', - [ - `assertEq(instance.name(), "${opts.name}");` - ], - '}', + "function testName() public view {", + [`assertEq(instance.name(), "${opts.name}");`], + "}", ]; - case 'ERC1155': + case "ERC1155": return [ - 'function testUri() public view {', - [ - `assertEq(instance.uri(0), "${opts.uri}");` - ], - '}', + "function testUri() public view {", + [`assertEq(instance.uri(0), "${opts.uri}");`], + "}", ]; - case 'Governor': - case 'Custom': + case "Governor": + case "Custom": return [ - 'function testSomething() public {', - [ - '// Add your test here', - ], - '}', - ] + "function testSomething() public {", + ["// Add your test here"], + "}", + ]; default: - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); } } return []; @@ -140,7 +134,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === 'address') { + if (constructorArg.type === "address") { args.push(constructorArg.name); } } @@ -150,11 +144,7 @@ function getAddressArgs(c: Contract): string[] { const script = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getHeader(c), - getImports(c), - getScript(c), - ), + ...spaceBetween(getHeader(c), getImports(c), getScript(c)), ); function getImports(c: Contract) { @@ -163,7 +153,9 @@ const script = (c: Contract, opts?: GenericOptions) => { 'import {console} from "forge-std/console.sol";', ]; if (c.upgradeable) { - result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + result.push( + 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', + ); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -172,39 +164,39 @@ const script = (c: Contract, opts?: GenericOptions) => { function getScript(c: Contract) { const args = getAddressArgs(c); const deploymentLines = [ - 'vm.startBroadcast();', + "vm.startBroadcast();", ...getAddressVariables(c, args), ...getDeploymentCode(c, args), - `console.log("${c.upgradeable ? 'Proxy' : 'Contract'} deployed to %s", address(instance));`, - 'vm.stopBroadcast();', + `console.log("${c.upgradeable ? "Proxy" : "Contract"} deployed to %s", address(instance));`, + "vm.stopBroadcast();", ]; return [ `contract ${c.name}Script is Script {`, spaceBetween( + ["function setUp() public {}"], [ - 'function setUp() public {}', - ], - [ - 'function run() public {', - args.length > 0 ? addTodoAndCommentOut(deploymentLines) : deploymentLines, - '}', + "function run() public {", + args.length > 0 + ? addTodoAndCommentOut(deploymentLines) + : deploymentLines, + "}", ], ), - '}', + "}", ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === 'transparent') { + if (opts?.upgradeable === "transparent") { return [ `address proxy = Upgrades.deployTransparentProxy(`, [ `"${c.name}.sol",`, `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `${c.name} instance = ${c.name}(proxy);`, ]; } else { @@ -212,23 +204,25 @@ const script = (c: Contract, opts?: GenericOptions) => { `address proxy = Upgrades.deployUUPSProxy(`, [ `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `${c.name} instance = ${c.name}(proxy);`, ]; } } else { - return [ - `${c.name} instance = new ${c.name}(${args.join(', ')});`, - ]; + return [`${c.name} instance = new ${c.name}(${args.join(", ")});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; - if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { - vars.push('address initialOwner = ;'); + if ( + c.upgradeable && + opts?.upgradeable === "transparent" && + !args.includes("initialOwner") + ) { + vars.push("address initialOwner = ;"); } for (const arg of args) { vars.push(`address ${arg} = ;`); @@ -238,10 +232,10 @@ const script = (c: Contract, opts?: GenericOptions) => { function addTodoAndCommentOut(lines: Lines[]) { return [ - '// TODO: Set addresses for the variables below, then uncomment the following section:', - '/*', + "// TODO: Set addresses for the variables below, then uncomment the following section:", + "/*", ...lines, - '*/', + "*/", ]; } }; @@ -274,14 +268,18 @@ then # Initialize sample Foundry project forge init --force --no-commit --quiet -${c.upgradeable ? `\ +${ + c.upgradeable + ? `\ # Install OpenZeppelin Contracts and Upgrades forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v${contracts.version} --no-commit --quiet forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet\ -` : `\ +` + : `\ # Install OpenZeppelin Contracts forge install OpenZeppelin/openzeppelin-contracts@v${contracts.version} --no-commit --quiet\ -`} +` +} # Remove unneeded Foundry template files rm src/Counter.sol @@ -297,7 +295,9 @@ ${c.upgradeable ? `\ then echo "" >> remappings.txt fi -${c.upgradeable ? `\ +${ + c.upgradeable + ? `\ echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt @@ -307,9 +307,11 @@ ${c.upgradeable ? `\ echo "ast = true" >> foundry.toml echo "build_info = true" >> foundry.toml echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml\ -` : `\ +` + : `\ echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt\ -`} +` +} # Perform initial git commit git add . @@ -339,7 +341,7 @@ bash setup.sh ## Testing the contract \`\`\` -forge test${c.upgradeable ? ' --force' : ''} +forge test${c.upgradeable ? " --force" : ""} \`\`\` ## Deploying the contract @@ -347,7 +349,7 @@ forge test${c.upgradeable ? ' --force' : ''} You can simulate a deployment by running the script: \`\`\` -forge script script/${c.name}.s.sol${c.upgradeable ? ' --force' : ''} +forge script script/${c.name}.s.sol${c.upgradeable ? " --force" : ""} \`\`\` See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information. @@ -359,8 +361,8 @@ export async function zipFoundry(c: Contract, opts?: GenericOptions) { zip.file(`src/${c.name}.sol`, printContract(c)); zip.file(`test/${c.name}.t.sol`, test(c, opts)); zip.file(`script/${c.name}.s.sol`, script(c, opts)); - zip.file('setup.sh', setupSh(c)); - zip.file('README.md', readme(c)); + zip.file("setup.sh", setupSh(c)); + zip.file("README.md", readme(c)); return zip; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/zip-hardhat.test.ts b/packages/core/solidity/src/zip-hardhat.test.ts index 7ad696284..30c1380f8 100644 --- a/packages/core/solidity/src/zip-hardhat.test.ts +++ b/packages/core/solidity/src/zip-hardhat.test.ts @@ -1,21 +1,21 @@ -import _test, { TestFn, ExecutionContext } from 'ava'; - -import { zipHardhat } from './zip-hardhat'; - -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { buildERC1155 } from './erc1155'; -import { buildCustom } from './custom'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import util from 'util'; +import _test, { TestFn, ExecutionContext } from "ava"; + +import { zipHardhat } from "./zip-hardhat"; + +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { buildERC1155 } from "./erc1155"; +import { buildCustom } from "./custom"; +import { promises as fs } from "fs"; +import path from "path"; +import os from "os"; +import util from "util"; import child from "child_process"; -import type { Contract } from './contract'; -import { rimraf } from 'rimraf'; -import type { JSZipObject } from 'jszip'; -import type JSZip from 'jszip'; -import type { GenericOptions } from './build-generic'; +import type { Contract } from "./contract"; +import { rimraf } from "rimraf"; +import type { JSZipObject } from "jszip"; +import type JSZip from "jszip"; +import type { GenericOptions } from "./build-generic"; interface Context { tempFolder: string; @@ -23,21 +23,23 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async t => { - t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); +test.beforeEach(async (t) => { + t.context.tempFolder = await fs.mkdtemp( + path.join(os.tmpdir(), "openzeppelin-wizard-"), + ); }); -test.afterEach.always(async t => { +test.afterEach.always(async (t) => { await rimraf(t.context.tempFolder); }); -test.serial('erc20 full', async t => { +test.serial("erc20 full", async (t) => { const opts: GenericOptions = { - kind: 'ERC20', - name: 'My Token', - symbol: 'MTK', - premint: '2000', - access: 'roles', + kind: "ERC20", + name: "My Token", + symbol: "MTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, @@ -49,31 +51,48 @@ test.serial('erc20 full', async t => { await runTest(c, t, opts); }); -test.serial('erc721 upgradeable', async t => { - const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK', upgradeable: 'uups' }; +test.serial("erc721 upgradeable", async (t) => { + const opts: GenericOptions = { + kind: "ERC721", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial('erc1155 basic', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}' }; +test.serial("erc1155 basic", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('custom basic', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; +test.serial("custom basic", async (t) => { + const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial('custom upgradeable', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract', upgradeable: 'transparent' }; +test.serial("custom upgradeable", async (t) => { + const opts: GenericOptions = { + kind: "Custom", + name: "My Contract", + upgradeable: "transparent", + }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { +async function runTest( + c: Contract, + t: ExecutionContext, + opts: GenericOptions, +) { const zip = await zipHardhat(c, opts); assertLayout(zip, c, t); @@ -82,24 +101,30 @@ async function runTest(c: Contract, t: ExecutionContext, opts: GenericO } function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { - const sorted = Object.values(zip.files).map(f => f.name).sort(); + const sorted = Object.values(zip.files) + .map((f) => f.name) + .sort(); t.deepEqual(sorted, [ - '.gitignore', - 'README.md', - 'contracts/', + ".gitignore", + "README.md", + "contracts/", `contracts/${c.name}.sol`, - 'hardhat.config.ts', - 'package-lock.json', - 'package.json', - 'scripts/', - 'scripts/deploy.ts', - 'test/', - 'test/test.ts', - 'tsconfig.json', + "hardhat.config.ts", + "package-lock.json", + "package.json", + "scripts/", + "scripts/deploy.ts", + "test/", + "test/test.ts", + "tsconfig.json", ]); } -async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { +async function extractAndRunPackage( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -109,14 +134,17 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); + await fs.writeFile( + path.join(tempFolder, item.name), + await asString(item), + ); } } let command = `cd "${tempFolder}" && npm install && npm test`; if (c.constructorArgs === undefined) { // only test deploying the contract if there are no constructor args needed - command += ' && npx hardhat run scripts/deploy.ts'; + command += " && npx hardhat run scripts/deploy.ts"; } const exec = util.promisify(child.exec); @@ -128,13 +156,17 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext } } -async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { +async function assertContents( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const contentComparison = [ await getItemString(zip, `contracts/${c.name}.sol`), - await getItemString(zip, 'hardhat.config.ts'), - await getItemString(zip, 'package.json'), - await getItemString(zip, 'scripts/deploy.ts'), - await getItemString(zip, 'test/test.ts'), + await getItemString(zip, "hardhat.config.ts"), + await getItemString(zip, "package.json"), + await getItemString(zip, "scripts/deploy.ts"), + await getItemString(zip, "test/test.ts"), ]; t.snapshot(contentComparison); @@ -149,5 +181,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async('arraybuffer')).toString(); + return Buffer.from(await item.async("arraybuffer")).toString(); } diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index 8f09757d5..f9092b67f 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -58,7 +58,7 @@ artifacts const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween(getImports(c), getTestCase(c)) + ...spaceBetween(getImports(c), getTestCase(c)), ); function getTestCase(c: Contract) { @@ -76,7 +76,7 @@ const test = (c: Contract, opts?: GenericOptions) => { `const instance = await ${getDeploymentCall(c, args)};`, "await instance.waitForDeployment();", ], - getExpects() + getExpects(), ), "});", ], @@ -116,7 +116,7 @@ const test = (c: Contract, opts?: GenericOptions) => { const vars = []; for (let i = 0; i < args.length; i++) { vars.push( - `const ${args[i]} = (await ethers.getSigners())[${i}].address;` + `const ${args[i]} = (await ethers.getSigners())[${i}].address;`, ); } return vars; diff --git a/packages/core/solidity/zip-env-foundry.js b/packages/core/solidity/zip-env-foundry.js index 836eb4d8e..5cfb903a7 100644 --- a/packages/core/solidity/zip-env-foundry.js +++ b/packages/core/solidity/zip-env-foundry.js @@ -1 +1 @@ -module.exports = require('./dist/zip-foundry'); +module.exports = require("./dist/zip-foundry"); diff --git a/packages/core/solidity/zip-env-foundry.ts b/packages/core/solidity/zip-env-foundry.ts index 17e647662..7b167f759 100644 --- a/packages/core/solidity/zip-env-foundry.ts +++ b/packages/core/solidity/zip-env-foundry.ts @@ -1 +1 @@ -export * from './src/zip-foundry'; \ No newline at end of file +export * from "./src/zip-foundry"; diff --git a/packages/core/solidity/zip-env-hardhat.js b/packages/core/solidity/zip-env-hardhat.js index 2d13c70df..5ce54ca53 100644 --- a/packages/core/solidity/zip-env-hardhat.js +++ b/packages/core/solidity/zip-env-hardhat.js @@ -1 +1 @@ -module.exports = require('./dist/zip-hardhat'); +module.exports = require("./dist/zip-hardhat"); diff --git a/packages/core/solidity/zip-env-hardhat.ts b/packages/core/solidity/zip-env-hardhat.ts index 353801961..3934d6d7f 100644 --- a/packages/core/solidity/zip-env-hardhat.ts +++ b/packages/core/solidity/zip-env-hardhat.ts @@ -1 +1 @@ -export * from './src/zip-hardhat'; \ No newline at end of file +export * from "./src/zip-hardhat"; diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index fd010e506..938e5370d 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -35,7 +35,7 @@ export default async (req: Request) => { const validatedMessages = data.messages.filter( (message: { role: string; content: string }) => { return message.content.length < 500; - } + }, ); const messages = [ diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js index 3dd96fd1e..0b17b641f 100644 --- a/packages/ui/postcss.config.js +++ b/packages/ui/postcss.config.js @@ -1,11 +1,7 @@ -const nesting = require('tailwindcss/nesting'); -const tailwindcss = require('tailwindcss'); -const autoprefixer = require('autoprefixer'); +const nesting = require("tailwindcss/nesting"); +const tailwindcss = require("tailwindcss"); +const autoprefixer = require("autoprefixer"); module.exports = { - plugins: [ - nesting, - tailwindcss, - autoprefixer, - ], + plugins: [nesting, tailwindcss, autoprefixer], }; diff --git a/packages/ui/rollup.config.mjs b/packages/ui/rollup.config.mjs index 28b9628c9..65f0d7635 100644 --- a/packages/ui/rollup.config.mjs +++ b/packages/ui/rollup.config.mjs @@ -1,27 +1,27 @@ -import svelte from 'rollup-plugin-svelte'; -import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; -import resolve from '@rollup/plugin-node-resolve'; -import replace from '@rollup/plugin-replace'; -import alias from '@rollup/plugin-alias'; -import livereload from 'rollup-plugin-livereload'; -import { terser } from 'rollup-plugin-terser'; -import typescript from '@rollup/plugin-typescript'; -import styles from 'rollup-plugin-styles'; -import proc from 'child_process'; -import events from 'events'; -import serve from './rollup.server.mjs'; +import svelte from "rollup-plugin-svelte"; +import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; +import resolve from "@rollup/plugin-node-resolve"; +import replace from "@rollup/plugin-replace"; +import alias from "@rollup/plugin-alias"; +import livereload from "rollup-plugin-livereload"; +import { terser } from "rollup-plugin-terser"; +import typescript from "@rollup/plugin-typescript"; +import styles from "rollup-plugin-styles"; +import proc from "child_process"; +import events from "events"; +import serve from "./rollup.server.mjs"; const production = !process.env.ROLLUP_WATCH; -process.env.NODE_ENV = production ? 'production' : 'development'; +process.env.NODE_ENV = production ? "production" : "development"; // Watch the `public` directory and refresh the // browser on changes when not in production const livereloader = !production && livereload({ - watch: 'public', + watch: "public", port: 35731, }); @@ -30,8 +30,11 @@ function onStartRun(cmd, ...args) { return { async buildStart() { if (ran) return; - const child = proc.spawn(cmd, args, { stdio: 'inherit', shell: process.platform == 'win32' }); - const [code, signal] = await events.once(child, 'exit'); + const child = proc.spawn(cmd, args, { + stdio: "inherit", + shell: process.platform == "win32", + }); + const [code, signal] = await events.once(child, "exit"); if (code || signal) { throw new Error(`Command \`${cmd}\` failed`); } @@ -43,37 +46,37 @@ function onStartRun(cmd, ...args) { /** @type import('rollup').RollupOptions */ export default [ { - input: 'src/standalone.js', + input: "src/standalone.js", output: { - dir: 'public/build', - assetFileNames: '[name][extname]', + dir: "public/build", + assetFileNames: "[name][extname]", sourcemap: true, }, onwarn(warning, warn) { - if (warning.code !== 'EMPTY_BUNDLE') { + if (warning.code !== "EMPTY_BUNDLE") { warn(warning); } }, plugins: [ styles({ - include: 'src/standalone.css', - mode: ['extract'], + include: "src/standalone.css", + mode: ["extract"], url: false, sourceMap: true, }), ], }, { - input: 'src/embed.ts', + input: "src/embed.ts", output: { sourcemap: true, - format: 'iife', - name: 'embed', - file: 'public/build/embed.js', + format: "iife", + name: "embed", + file: "public/build/embed.js", }, plugins: [ typescript({ - include: ['src/**/*.ts'], + include: ["src/**/*.ts"], sourceMap: true, inlineSources: true, }), @@ -87,44 +90,45 @@ export default [ }, { preserveEntrySignatures: false, - input: 'src/main.ts', + input: "src/main.ts", output: { sourcemap: true, - format: 'es', - dir: 'public/build', - chunkFileNames: '[name].js', - assetFileNames: '[name][extname]', + format: "es", + dir: "public/build", + chunkFileNames: "[name].js", + assetFileNames: "[name][extname]", }, plugins: [ // Generate openzeppelin-contracts.js data file - onStartRun(...'yarn --cwd ../core/solidity prepare'.split(' ')), + onStartRun(..."yarn --cwd ../core/solidity prepare".split(" ")), - svelte(await import('./svelte.config.js')), + svelte(await import("./svelte.config.js")), styles({ - mode: ['extract', 'bundle.css'], + mode: ["extract", "bundle.css"], sourceMap: true, }), alias({ entries: { - path: 'path-browserify', - 'highlight.js/lib/languages/python': '../../node_modules/highlight.js/lib/languages/python.js', + path: "path-browserify", + "highlight.js/lib/languages/python": + "../../node_modules/highlight.js/lib/languages/python.js", }, }), resolve({ browser: true, - dedupe: ['svelte'], - mainFields: ['ts:main', 'module', 'main'], + dedupe: ["svelte"], + mainFields: ["ts:main", "module", "main"], preferBuiltins: false, }), replace({ preventAssignment: true, - include: '../../**/node_modules/**/*', - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), + include: "../../**/node_modules/**/*", + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), + "process.env.NODE_DEBUG": JSON.stringify(process.env.NODE_DEBUG), }), json(), @@ -132,7 +136,7 @@ export default [ commonjs(), typescript({ - include: ['src/**/*.ts', '../core/*/src/**/*.ts'], + include: ["src/**/*.ts", "../core/*/src/**/*.ts"], sourceMap: true, inlineSources: true, }), diff --git a/packages/ui/rollup.server.mjs b/packages/ui/rollup.server.mjs index 33e18e92c..e456b3d2e 100644 --- a/packages/ui/rollup.server.mjs +++ b/packages/ui/rollup.server.mjs @@ -1,4 +1,4 @@ -import proc from 'child_process'; +import proc from "child_process"; const state = (global.ROLLUP_SERVER = global.ROLLUP_SERVER || { server: undefined, @@ -12,17 +12,13 @@ export default function serve() { return { writeBundle() { if (state.server) return; - state.server = proc.spawn( - 'npm', - ['run', 'start', '--', '--dev'], - { - stdio: ['ignore', 'inherit', 'inherit'], - shell: true, - }, - ); + state.server = proc.spawn("npm", ["run", "start", "--", "--dev"], { + stdio: ["ignore", "inherit", "inherit"], + shell: true, + }); - process.on('SIGTERM', toExit); - process.on('exit', toExit); + process.on("SIGTERM", toExit); + process.on("exit", toExit); }, }; } diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index 0db04cc5d..6f3becaf5 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -24,7 +24,7 @@ export function injectHyperlinks(code: string) { if (suffix === "::{") { // Multiple components are imported, so remove components and link to the parent .cairo file replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported @@ -33,7 +33,7 @@ export function injectHyperlinks(code: string) { const mapping = componentMappings[componentName]; const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } diff --git a/packages/ui/src/common/initial-options.ts b/packages/ui/src/common/initial-options.ts index 9a4ec972d..fb217fa7d 100644 --- a/packages/ui/src/common/initial-options.ts +++ b/packages/ui/src/common/initial-options.ts @@ -1 +1,5 @@ -export interface InitialOptions { name?: string, symbol?: string, premint?: string }; \ No newline at end of file +export interface InitialOptions { + name?: string; + symbol?: string; + premint?: string; +} diff --git a/packages/ui/src/common/post-config.ts b/packages/ui/src/common/post-config.ts index cfc3f4955..365cd3c80 100644 --- a/packages/ui/src/common/post-config.ts +++ b/packages/ui/src/common/post-config.ts @@ -1,5 +1,5 @@ -import type { GenericOptions as SolidityOptions } from '@openzeppelin/wizard'; -import type { GenericOptions as CairoOptions } from '@openzeppelin/wizard-cairo'; +import type { GenericOptions as SolidityOptions } from "@openzeppelin/wizard"; +import type { GenericOptions as CairoOptions } from "@openzeppelin/wizard-cairo"; declare global { interface Window { @@ -7,12 +7,23 @@ declare global { } } -export type Action = 'copy' | 'remix' | 'download-file' | 'download-hardhat' | 'download-foundry' | 'defender'; -export type Language = 'solidity' | 'cairo' | 'stylus' | 'stellar'; +export type Action = + | "copy" + | "remix" + | "download-file" + | "download-hardhat" + | "download-foundry" + | "defender"; +export type Language = "solidity" | "cairo" | "stylus" | "stellar"; export async function postConfig( - opts: Required | Required, - action: Action, - language: Language) { - window.gtag?.('event', 'wizard_action', { ...opts, action, wizard_lang: language }); -} \ No newline at end of file + opts: Required | Required, + action: Action, + language: Language, +) { + window.gtag?.("event", "wizard_action", { + ...opts, + action, + wizard_lang: language, + }); +} diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index 6f9a3a3f0..c63bba812 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -33,7 +33,7 @@ export function postMessage(msg: Message) { export function postMessageToIframe(id: "defender-deploy", msg: Message) { const iframe: HTMLIFrameElement | null = document.getElementById( - id + id, ) as HTMLIFrameElement; if (iframe) { iframe.contentWindow?.postMessage(msg, "*"); diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index 6e8e7ce8e..52b9097ed 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -14,7 +14,7 @@ export function resizeToFit(node: HTMLInputElement) { "border-right-width", ].map((p) => style.getPropertyValue(p)); const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( - " + " + " + ", )})`; node.style.setProperty("width", result); }; diff --git a/packages/ui/src/embed.ts b/packages/ui/src/embed.ts index 2ac1e629d..0ea4c6d52 100644 --- a/packages/ui/src/embed.ts +++ b/packages/ui/src/embed.ts @@ -1,7 +1,7 @@ -import type { Message } from './common/post-message'; +import type { Message } from "./common/post-message"; -if (!document.currentScript || !('src' in document.currentScript)) { - throw new Error('Unknown script URL'); +if (!document.currentScript || !("src" in document.currentScript)) { + throw new Error("Unknown script URL"); } const currentScript = new URL(document.currentScript.src); @@ -9,59 +9,61 @@ const currentScript = new URL(document.currentScript.src); const iframes = new WeakMap(); let unsupportedVersion: boolean = false; -const unsupportedVersionFrameHeight = 'auto'; +const unsupportedVersionFrameHeight = "auto"; -window.addEventListener('message', function (e: MessageEvent) { +window.addEventListener("message", function (e: MessageEvent) { if (e.source) { - if (e.data.kind === 'oz-wizard-unsupported-version') { + if (e.data.kind === "oz-wizard-unsupported-version") { unsupportedVersion = true; const iframe = iframes.get(e.source); if (iframe) { iframe.style.height = unsupportedVersionFrameHeight; } - } else if (e.data.kind === 'oz-wizard-resize') { + } else if (e.data.kind === "oz-wizard-resize") { const iframe = iframes.get(e.source); if (iframe) { - iframe.style.height = unsupportedVersion ? unsupportedVersionFrameHeight : 'calc(100vh - 100px)'; + iframe.style.height = unsupportedVersion + ? unsupportedVersionFrameHeight + : "calc(100vh - 100px)"; } } } }); onDOMContentLoaded(function () { - const wizards = document.querySelectorAll('oz-wizard'); + const wizards = document.querySelectorAll("oz-wizard"); for (const w of wizards) { - w.style.display = 'block'; + w.style.display = "block"; - const src = new URL('embed', currentScript.origin); + const src = new URL("embed", currentScript.origin); - setSearchParam(w, src.searchParams, 'data-lang', 'lang'); - setSearchParam(w, src.searchParams, 'data-tab', 'tab'); - setSearchParam(w, src.searchParams, 'version', 'version'); + setSearchParam(w, src.searchParams, "data-lang", "lang"); + setSearchParam(w, src.searchParams, "data-tab", "tab"); + setSearchParam(w, src.searchParams, "version", "version"); - const sync = w.getAttribute('data-sync-url'); + const sync = w.getAttribute("data-sync-url"); - if (sync === 'fragment') { + if (sync === "fragment") { // Uses format: #tab&key=value&key=value... - const fragments = window.location.hash.replace('#', '').split('&'); + const fragments = window.location.hash.replace("#", "").split("&"); for (const fragment of fragments) { - const [key, value] = fragment.split('=', 2); + const [key, value] = fragment.split("=", 2); if (key && value) { src.searchParams.set(key, value); } else { - src.searchParams.set('tab', fragment); + src.searchParams.set("tab", fragment); } } } - const iframe = document.createElement('iframe'); + const iframe = document.createElement("iframe"); iframe.src = src.toString(); - iframe.style.display = 'block'; - iframe.style.border = '0'; - iframe.style.width = '100%'; - iframe.style.height = 'calc(100vh - 100px)'; - iframe.allow = 'clipboard-write'; + iframe.style.display = "block"; + iframe.style.border = "0"; + iframe.style.width = "100%"; + iframe.style.height = "calc(100vh - 100px)"; + iframe.allow = "clipboard-write"; w.appendChild(iframe); @@ -69,9 +71,9 @@ onDOMContentLoaded(function () { iframes.set(iframe.contentWindow, iframe); } - if (sync === 'fragment') { - window.addEventListener('message', (e: MessageEvent) => { - if (e.source && e.data.kind === 'oz-wizard-tab-change') { + if (sync === "fragment") { + window.addEventListener("message", (e: MessageEvent) => { + if (e.source && e.data.kind === "oz-wizard-tab-change") { if (iframe === iframes.get(e.source)) { window.location.hash = e.data.tab; } @@ -81,7 +83,12 @@ onDOMContentLoaded(function () { } }); -function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam: string, param: string) { +function setSearchParam( + w: HTMLElement, + searchParams: URLSearchParams, + dataParam: string, + param: string, +) { const value = w.getAttribute(dataParam) ?? w.getAttribute(param); if (value) { searchParams.set(param, value); @@ -89,8 +96,8 @@ function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam } function onDOMContentLoaded(callback: () => void) { - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', callback); + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", callback); } else { callback(); } diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 77efa3002..8f5b9305c 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -2,10 +2,18 @@ import { version as contractsVersion } from "@openzeppelin/contracts/package.jso export function injectHyperlinks(code: string) { // We are modifying HTML, so use HTML escaped chars. The pattern excludes paths that include /../ in the URL. - const contractsRegex = /"(@openzeppelin\/)(contracts-upgradeable\/|contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g - const communityContractsRegex = /"(@openzeppelin\/)(community-contracts\/contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g + const contractsRegex = + /"(@openzeppelin\/)(contracts-upgradeable\/|contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; + const communityContractsRegex = + /"(@openzeppelin\/)(community-contracts\/contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; - return code. - replace(contractsRegex, `"$1$2$3"`). - replace(communityContractsRegex, `"$1$2$3"`); + return code + .replace( + contractsRegex, + `"$1$2$3"`, + ) + .replace( + communityContractsRegex, + `"$1$2$3"`, + ); } diff --git a/packages/ui/src/solidity/remix.ts b/packages/ui/src/solidity/remix.ts index a43087074..ee479a157 100644 --- a/packages/ui/src/solidity/remix.ts +++ b/packages/ui/src/solidity/remix.ts @@ -1,8 +1,8 @@ export function remixURL(code: string, upgradeable = false): URL { - const remix = new URL('https://remix.ethereum.org'); - remix.searchParams.set('code', btoa(code).replace(/=*$/, '')); + const remix = new URL("https://remix.ethereum.org"); + remix.searchParams.set("code", btoa(code).replace(/=*$/, "")); if (upgradeable) { - remix.searchParams.set('deployProxy', upgradeable.toString()); + remix.searchParams.set("deployProxy", upgradeable.toString()); } return remix; } diff --git a/packages/ui/src/solidity/wiz-functions.ts b/packages/ui/src/solidity/wiz-functions.ts index 2c8ee6d8a..be6877bd3 100644 --- a/packages/ui/src/solidity/wiz-functions.ts +++ b/packages/ui/src/solidity/wiz-functions.ts @@ -1,153 +1,292 @@ const commonOptions = { // 'false' gets converted to false - access: { type: 'string', enum: ['false', 'ownable', 'roles', 'managed'], description: 'The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.' }, + access: { + type: "string", + enum: ["false", "ownable", "roles", "managed"], + description: + "The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.", + }, // 'false' gets converted to false - upgradeable: { type: 'string', enum: ['false', 'transparent', 'uups'], description: 'Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.' }, + upgradeable: { + type: "string", + enum: ["false", "transparent", "uups"], + description: + "Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.", + }, info: { - type: 'object', - description: 'Metadata about the contract and author', + type: "object", + description: "Metadata about the contract and author", properties: { - securityContact: { type: 'string', description: 'Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.' }, - license: { type: 'string', description: 'The license used by the contract, default is "MIT"' } - } - } -} + securityContact: { + type: "string", + description: + "Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.", + }, + license: { + type: "string", + description: 'The license used by the contract, default is "MIT"', + }, + }, + }, +}; const repeatedOptions = { - name: { type: 'string', description: 'The name of the contract' }, - symbol: { type: 'string', description: 'The short symbol for the token' }, - burnable: { type: 'boolean', description: 'Whether token holders will be able to destroy their tokens' }, - pausable: { type: 'boolean', description: 'Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.' }, - mintable: { type: 'boolean', description: 'Whether privileged accounts will be able to create more supply or emit more tokens' }, -} + name: { type: "string", description: "The name of the contract" }, + symbol: { type: "string", description: "The short symbol for the token" }, + burnable: { + type: "boolean", + description: "Whether token holders will be able to destroy their tokens", + }, + pausable: { + type: "boolean", + description: + "Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.", + }, + mintable: { + type: "boolean", + description: + "Whether privileged accounts will be able to create more supply or emit more tokens", + }, +}; export const erc20Function = { - name: 'erc20', - description: 'Make a fungible token per the ERC-20 standard', + name: "erc20", + description: "Make a fungible token per the ERC-20 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, - premint: { type: 'number', description: 'The number of tokens to premint for the deployer.' }, + premint: { + type: "number", + description: "The number of tokens to premint for the deployer.", + }, mintable: repeatedOptions.mintable, - permit: { type: 'boolean', description: 'Whether without paying gas, token holders will be able to allow third parties to transfer from their account.' }, + permit: { + type: "boolean", + description: + "Whether without paying gas, token holders will be able to allow third parties to transfer from their account.", + }, // 'false' gets converted to false - votes: { type: 'string', enum: ['false', 'blocknumber', 'timestamp'], description: 'Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.'}, - flashmint: { type: 'boolean', description: 'Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they\'re returned in the same transaction.' }, - ...commonOptions + votes: { + type: "string", + enum: ["false", "blocknumber", "timestamp"], + description: + "Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + }, + flashmint: { + type: "boolean", + description: + "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", + }, + ...commonOptions, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const erc721Function = { - name: 'erc721', - description: 'Make a non-fungible token per the ERC-721 standard', + name: "erc721", + description: "Make a non-fungible token per the ERC-721 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, - baseUri: { type: 'string', description: 'A base uri for the token' }, - enumerable: { type: 'boolean', description: 'Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.' }, - uriStorage: { type: 'boolean', description: 'Allows updating token URIs for individual token IDs'}, + baseUri: { type: "string", description: "A base uri for the token" }, + enumerable: { + type: "boolean", + description: + "Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.", + }, + uriStorage: { + type: "boolean", + description: "Allows updating token URIs for individual token IDs", + }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, - incremental: { type: 'boolean', description: 'Whether new tokens will be automatically assigned an incremental id' }, + incremental: { + type: "boolean", + description: + "Whether new tokens will be automatically assigned an incremental id", + }, // 'false' gets converted to false - votes: { type: 'string', enum: ['false', 'blocknumber', 'timestamp'], description: 'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.'}, - ...commonOptions + votes: { + type: "string", + enum: ["false", "blocknumber", "timestamp"], + description: + "Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + }, + ...commonOptions, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const erc1155Function = { - name: 'erc1155', - description: 'Make a non-fungible token per the ERC-1155 standard', + name: "erc1155", + description: "Make a non-fungible token per the ERC-1155 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, - uri: { type: 'string', description: 'The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.' }, + uri: { + type: "string", + description: + "The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.", + }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, - supply: { type: 'boolean', description: 'Whether to keep track of total supply of tokens' }, - updatableUri: { type: 'boolean', description: 'Whether privileged accounts will be able to set a new URI for all token types' }, - ...commonOptions + supply: { + type: "boolean", + description: "Whether to keep track of total supply of tokens", + }, + updatableUri: { + type: "boolean", + description: + "Whether privileged accounts will be able to set a new URI for all token types", + }, + ...commonOptions, }, - required: ['name', 'uri'], - } -} + required: ["name", "uri"], + }, +}; export const stablecoinFunction = { - name: 'stablecoin', - description: 'Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', + name: "stablecoin", + description: + "Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", parameters: { - type: 'object', + type: "object", properties: { ...erc20Function.parameters.properties, - custodian: { type: 'boolean', description: 'Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.' }, + custodian: { + type: "boolean", + description: + "Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.", + }, // 'false' gets converted to false - limitations: { type: 'string', enum: ['false', 'allowlist', 'blocklist'], description: 'Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.' }, - upgradeable: { type: 'string', enum: ['false'], description: 'Upgradeability is not yet available for features that use @openzeppelin/community-contracts' }, + limitations: { + type: "string", + enum: ["false", "allowlist", "blocklist"], + description: + "Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.", + }, + upgradeable: { + type: "string", + enum: ["false"], + description: + "Upgradeability is not yet available for features that use @openzeppelin/community-contracts", + }, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const realWorldAssetFunction = { - name: 'realworldasset', - description: 'Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', + name: "realworldasset", + description: + "Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", parameters: stablecoinFunction.parameters, -} +}; export const governorFunction = { - name: 'governor', - description: 'Make a contract to implement governance, such as for a DAO', + name: "governor", + description: "Make a contract to implement governance, such as for a DAO", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, - delay: { type: 'string', description: 'The delay since proposal is created until voting starts, default is "1 day"' }, - period: { type: 'string', description: 'The length of period during which people can cast their vote, default is "1 week"' }, - blockTime: { type: 'number', description: 'The number of seconds assumed for a block, default is 12' }, + delay: { + type: "string", + description: + 'The delay since proposal is created until voting starts, default is "1 day"', + }, + period: { + type: "string", + description: + 'The length of period during which people can cast their vote, default is "1 week"', + }, + blockTime: { + type: "number", + description: "The number of seconds assumed for a block, default is 12", + }, // gets converted to a string to follow the API - proposalThreshold: { type: 'number', description: 'Minimum number of votes an account must have to create a proposal, default is 0.' }, - decimals: { type: 'number', description: 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)' }, - quorumMode: { type: 'string', enum: ['percent', 'absolute'], description: 'The type of quorum mode to use' }, - quorumPercent: { type: 'number', description: 'The percent required, in cases of quorumMode equals percent' }, + proposalThreshold: { + type: "number", + description: + "Minimum number of votes an account must have to create a proposal, default is 0.", + }, + decimals: { + type: "number", + description: + "The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)", + }, + quorumMode: { + type: "string", + enum: ["percent", "absolute"], + description: "The type of quorum mode to use", + }, + quorumPercent: { + type: "number", + description: + "The percent required, in cases of quorumMode equals percent", + }, // gets converted to a string to follow the API - quorumAbsolute: { type: 'number', description: 'The absolute quorum required, in cases of quorumMode equals absolute' }, - votes: { type: 'string', enum: ['erc20votes', 'erc721votes'], description: 'The type of voting to use' }, - clockMode: { type: 'string', enum: ['blocknumber', 'timestamp'], description: 'The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.' }, + quorumAbsolute: { + type: "number", + description: + "The absolute quorum required, in cases of quorumMode equals absolute", + }, + votes: { + type: "string", + enum: ["erc20votes", "erc721votes"], + description: "The type of voting to use", + }, + clockMode: { + type: "string", + enum: ["blocknumber", "timestamp"], + description: + "The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.", + }, // 'false' gets converted to false - timelock: { type: 'string', enum: ['false', 'openzeppelin', 'compound'], description: 'The type of timelock to use' }, - storage: { type: 'boolean', description: 'Enable storage of proposal details and enumerability of proposals' }, - settings: { type: 'boolean', description: 'Allow governance to update voting settings (delay, period, proposal threshold)' }, - ...commonOptions + timelock: { + type: "string", + enum: ["false", "openzeppelin", "compound"], + description: "The type of timelock to use", + }, + storage: { + type: "boolean", + description: + "Enable storage of proposal details and enumerability of proposals", + }, + settings: { + type: "boolean", + description: + "Allow governance to update voting settings (delay, period, proposal threshold)", + }, + ...commonOptions, }, - required: ['name', 'delay', 'period'], - } -} + required: ["name", "delay", "period"], + }, +}; export const customFunction = { - name: 'custom', - description: 'Make a custom smart contract', + name: "custom", + description: "Make a custom smart contract", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, pausable: repeatedOptions.pausable, - ...commonOptions + ...commonOptions, }, - required: ['name'], - } -} + required: ["name"], + }, +}; diff --git a/packages/ui/src/standalone.js b/packages/ui/src/standalone.js index fe423caf7..66b97fc1d 100644 --- a/packages/ui/src/standalone.js +++ b/packages/ui/src/standalone.js @@ -1,3 +1,3 @@ // Used as Rollup entry point to preprocess CSS for wizard.openzeppelin.com -import './standalone.css'; +import "./standalone.css"; diff --git a/packages/ui/svelte.config.js b/packages/ui/svelte.config.js index dc6f828f6..f043d9dd9 100644 --- a/packages/ui/svelte.config.js +++ b/packages/ui/svelte.config.js @@ -1,4 +1,4 @@ -const sveltePreprocess = require('svelte-preprocess'); +const sveltePreprocess = require("svelte-preprocess"); const production = !process.env.ROLLUP_WATCH; diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js index ddd5b15e0..cafb69e25 100644 --- a/packages/ui/tailwind.config.js +++ b/packages/ui/tailwind.config.js @@ -1,39 +1,38 @@ module.exports = { content: [ - './src/**/*.{html,svelte}', + "./src/**/*.{html,svelte}", // Using glob patterns results in infinite loop - './public/index.html', - './public/cairo.html', - './public/stylus.html', - './public/stellar.html', - './public/embed.html', + "./public/index.html", + "./public/cairo.html", + "./public/stylus.html", + "./public/stellar.html", + "./public/embed.html", ], theme: { extend: { keyframes: { - 'fade-in': { - '0%': { opacity: '0' }, - '100%': { opacity: '1' }, + "fade-in": { + "0%": { opacity: "0" }, + "100%": { opacity: "1" }, }, - 'fade-up': { - '0%': { opacity: '0', transform: 'translateY(1rem)' }, - '100%': { opacity: '1', transform: 'translateY(0)' }, + "fade-up": { + "0%": { opacity: "0", transform: "translateY(1rem)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, }, - 'fade-down': { - '0%': { opacity: '0', transform: 'translateY(-1rem)' }, - '100%': { opacity: '1', transform: 'translateY(0)' }, + "fade-down": { + "0%": { opacity: "0", transform: "translateY(-1rem)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, }, }, animation: { - 'fade-in': 'fade-in 0.3s ease-out', - 'fade-up': 'fade-up 0.2s ease-out', - 'fade-down': 'fade-down 0.5s ease-out', - 'spin-slow': 'spin 2s linear infinite', + "fade-in": "fade-in 0.3s ease-out", + "fade-up": "fade-up 0.2s ease-out", + "fade-down": "fade-down 0.5s ease-out", + "spin-slow": "spin 2s linear infinite", }, - - } + }, }, - plugins: [] + plugins: [], }; diff --git a/scripts/bump-changelog.js b/scripts/bump-changelog.js index 541b8e0c3..de6f39a3d 100644 --- a/scripts/bump-changelog.js +++ b/scripts/bump-changelog.js @@ -1,19 +1,22 @@ #!/usr/bin/env node -const { version } = require(process.cwd() + '/package.json'); -const [date] = new Date().toISOString().split('T'); +const { version } = require(process.cwd() + "/package.json"); +const [date] = new Date().toISOString().split("T"); -const fs = require('fs'); -const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); +const fs = require("fs"); +const changelog = fs.readFileSync("CHANGELOG.md", "utf8"); const unreleased = /^## Unreleased$/im; if (!unreleased.test(changelog)) { - console.error('Missing changelog entry'); + console.error("Missing changelog entry"); process.exit(1); } -fs.writeFileSync('CHANGELOG.md', changelog.replace(unreleased, `## ${version} (${date})`)); +fs.writeFileSync( + "CHANGELOG.md", + changelog.replace(unreleased, `## ${version} (${date})`), +); -const proc = require('child_process'); -proc.execSync('git add CHANGELOG.md', { stdio: 'inherit' }); \ No newline at end of file +const proc = require("child_process"); +proc.execSync("git add CHANGELOG.md", { stdio: "inherit" }); From ce5fcd3f9b8c97ff718f6d85c040c58fb264af9b Mon Sep 17 00:00:00 2001 From: CoveMB Date: Thu, 20 Feb 2025 20:35:24 -0500 Subject: [PATCH 03/16] Add consistent-type-imports rule --- .gitignore | 1 + .prettierrc | 7 + eslint.config.mjs | 53 +-- packages/core/cairo/ava.config.js | 8 +- packages/core/cairo/src/account.test.ts | 111 ++--- packages/core/cairo/src/account.ts | 123 +++--- packages/core/cairo/src/add-pausable.ts | 53 +-- packages/core/cairo/src/api.ts | 51 +-- packages/core/cairo/src/build-generic.ts | 51 ++- packages/core/cairo/src/common-components.ts | 82 ++-- packages/core/cairo/src/common-options.ts | 26 +- packages/core/cairo/src/contract.test.ts | 108 +++-- packages/core/cairo/src/contract.ts | 52 +-- packages/core/cairo/src/custom.test.ts | 49 +-- packages/core/cairo/src/custom.ts | 23 +- packages/core/cairo/src/erc1155.test.ts | 97 ++--- packages/core/cairo/src/erc1155.ts | 212 ++++------ packages/core/cairo/src/erc20.test.ts | 164 ++++---- packages/core/cairo/src/erc20.ts | 178 ++++---- packages/core/cairo/src/erc721.test.ts | 114 +++-- packages/core/cairo/src/erc721.ts | 190 ++++----- packages/core/cairo/src/error.ts | 2 +- packages/core/cairo/src/external-trait.ts | 10 +- packages/core/cairo/src/generate/account.ts | 10 +- .../core/cairo/src/generate/alternatives.ts | 8 +- packages/core/cairo/src/generate/custom.ts | 12 +- packages/core/cairo/src/generate/erc1155.ts | 22 +- packages/core/cairo/src/generate/erc20.ts | 20 +- packages/core/cairo/src/generate/erc721.ts | 28 +- packages/core/cairo/src/generate/governor.ts | 33 +- packages/core/cairo/src/generate/sources.ts | 121 +++--- packages/core/cairo/src/generate/vesting.ts | 16 +- packages/core/cairo/src/governor.test.ts | 187 ++++----- packages/core/cairo/src/governor.ts | 393 ++++++++---------- packages/core/cairo/src/index.ts | 68 ++- packages/core/cairo/src/kind.ts | 24 +- packages/core/cairo/src/print.ts | 239 ++++------- .../cairo/src/scripts/update-scarb-project.ts | 46 +- packages/core/cairo/src/set-access-control.ts | 113 +++-- packages/core/cairo/src/set-info.ts | 6 +- packages/core/cairo/src/set-royalty-info.ts | 103 ++--- packages/core/cairo/src/set-upgradeable.ts | 101 ++--- packages/core/cairo/src/test.ts | 93 ++--- .../cairo/src/utils/convert-strings.test.ts | 120 +++--- .../core/cairo/src/utils/convert-strings.ts | 50 +-- .../core/cairo/src/utils/define-components.ts | 19 +- .../core/cairo/src/utils/define-functions.ts | 19 +- packages/core/cairo/src/utils/duration.ts | 16 +- packages/core/cairo/src/utils/find-cover.ts | 11 +- packages/core/cairo/src/utils/format-lines.ts | 17 +- packages/core/cairo/src/utils/version.test.ts | 8 +- packages/core/cairo/src/utils/version.ts | 10 +- packages/core/cairo/src/vesting.test.ts | 72 ++-- packages/core/cairo/src/vesting.ts | 143 +++---- packages/core/solidity/ava.config.js | 8 +- packages/core/solidity/get-imports.d.ts | 2 +- packages/core/solidity/get-imports.js | 2 +- packages/core/solidity/hardhat.config.js | 42 +- packages/core/solidity/print-versioned.js | 2 +- packages/core/solidity/print-versioned.ts | 2 +- packages/core/solidity/src/add-pausable.ts | 31 +- packages/core/solidity/src/api.ts | 26 +- packages/core/solidity/src/build-generic.ts | 50 ++- .../core/solidity/src/common-functions.ts | 12 +- packages/core/solidity/src/common-options.ts | 12 +- packages/core/solidity/src/contract.test.ts | 162 ++++---- packages/core/solidity/src/contract.ts | 66 +-- packages/core/solidity/src/custom.test.ts | 75 ++-- packages/core/solidity/src/custom.ts | 24 +- packages/core/solidity/src/erc1155.test.ts | 101 +++-- packages/core/solidity/src/erc1155.ts | 106 +++-- packages/core/solidity/src/erc20.test.ts | 117 +++--- packages/core/solidity/src/erc20.ts | 124 +++--- packages/core/solidity/src/erc721.test.ts | 98 ++--- packages/core/solidity/src/erc721.ts | 129 +++--- packages/core/solidity/src/error.ts | 2 +- .../solidity/src/generate/alternatives.ts | 8 +- packages/core/solidity/src/generate/custom.ts | 12 +- .../core/solidity/src/generate/erc1155.ts | 14 +- packages/core/solidity/src/generate/erc20.ts | 18 +- packages/core/solidity/src/generate/erc721.ts | 18 +- .../core/solidity/src/generate/governor.ts | 34 +- .../core/solidity/src/generate/sources.ts | 96 ++--- .../core/solidity/src/generate/stablecoin.ts | 24 +- .../core/solidity/src/get-imports.test.ts | 122 +++--- packages/core/solidity/src/get-imports.ts | 12 +- packages/core/solidity/src/governor.test.ts | 157 +++---- packages/core/solidity/src/governor.ts | 308 ++++++-------- packages/core/solidity/src/index.ts | 40 +- .../solidity/src/infer-transpiled.test.ts | 18 +- .../core/solidity/src/infer-transpiled.ts | 2 +- packages/core/solidity/src/kind.ts | 24 +- packages/core/solidity/src/options.ts | 24 +- packages/core/solidity/src/print-versioned.ts | 13 +- packages/core/solidity/src/print.ts | 147 +++---- packages/core/solidity/src/scripts/prepare.ts | 63 ++- .../core/solidity/src/set-access-control.ts | 62 ++- packages/core/solidity/src/set-clock-mode.ts | 35 +- packages/core/solidity/src/set-info.ts | 9 +- packages/core/solidity/src/set-upgradeable.ts | 41 +- packages/core/solidity/src/stablecoin.test.ts | 107 ++--- packages/core/solidity/src/stablecoin.ts | 111 ++--- packages/core/solidity/src/test.ts | 80 ++-- .../solidity/src/utils/define-functions.ts | 19 +- packages/core/solidity/src/utils/duration.ts | 27 +- .../core/solidity/src/utils/find-cover.ts | 11 +- .../core/solidity/src/utils/format-lines.ts | 23 +- .../core/solidity/src/utils/map-values.ts | 5 +- .../solidity/src/utils/to-identifier.test.ts | 22 +- .../core/solidity/src/utils/to-identifier.ts | 8 +- .../solidity/src/utils/transitive-closure.ts | 4 +- .../core/solidity/src/utils/version.test.ts | 10 +- packages/core/solidity/src/utils/version.ts | 2 +- .../core/solidity/src/zip-foundry.test.ts | 168 ++++---- packages/core/solidity/src/zip-foundry.ts | 176 +++----- .../core/solidity/src/zip-hardhat.test.ts | 146 +++---- packages/core/solidity/src/zip-hardhat.ts | 104 ++--- packages/core/solidity/zip-env-foundry.js | 2 +- packages/core/solidity/zip-env-foundry.ts | 2 +- packages/core/solidity/zip-env-hardhat.js | 2 +- packages/core/solidity/zip-env-hardhat.ts | 2 +- packages/ui/api/ai.ts | 34 +- packages/ui/postcss.config.js | 6 +- packages/ui/rollup.config.mjs | 91 ++-- packages/ui/rollup.server.mjs | 10 +- packages/ui/src/cairo/highlightjs.ts | 4 +- packages/ui/src/cairo/inject-hyperlinks.ts | 28 +- packages/ui/src/common/error-tooltip.ts | 8 +- packages/ui/src/common/post-config.ts | 16 +- packages/ui/src/common/post-message.ts | 28 +- packages/ui/src/common/resize-to-fit.ts | 27 +- packages/ui/src/embed.ts | 69 ++- packages/ui/src/main.ts | 54 ++- packages/ui/src/solidity/highlightjs.ts | 4 +- packages/ui/src/solidity/inject-hyperlinks.ts | 2 +- packages/ui/src/solidity/remix.ts | 6 +- packages/ui/src/solidity/wiz-functions.ts | 225 +++++----- packages/ui/src/standalone.js | 2 +- packages/ui/svelte.config.js | 2 +- packages/ui/tailwind.config.js | 38 +- scripts/bump-changelog.js | 19 +- 141 files changed, 3551 insertions(+), 4540 deletions(-) create mode 100644 .prettierrc diff --git a/.gitignore b/.gitignore index c61302beb..63a8b9661 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules .env .env.local +.vscode \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..b62b39794 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "all", + "printWidth": 120, + "bracketSpacing": true +} diff --git a/eslint.config.mjs b/eslint.config.mjs index e5715f264..c0969cd4d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,62 +1,49 @@ // @ts-check -import eslint from "@eslint/js"; -import prettierRecommended from "eslint-plugin-prettier/recommended"; -import unicornPlugin from "eslint-plugin-unicorn"; -import typescriptEslint from "typescript-eslint"; +import eslint from '@eslint/js'; +import prettierPluginRecommended from 'eslint-plugin-prettier/recommended'; +import unicornPlugin from 'eslint-plugin-unicorn'; +import typescriptEslint from 'typescript-eslint'; export default typescriptEslint.config( eslint.configs.recommended, typescriptEslint.configs.strict, - prettierRecommended, + prettierPluginRecommended, { plugins: { unicorn: unicornPlugin, }, rules: { - "@typescript-eslint/no-unused-vars": [ - "warn", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': 'error', + 'prettier/prettier': [ + 'error', + { singleQuote: true, arrowParens: 'avoid', trailingComma: 'all', printWidth: 120, bracketSpacing: true }, ], - "@typescript-eslint/no-non-null-assertion": "warn", }, }, { - ignores: [ - "node_modules/", - "*.sol", - "packages/*/node_modules/", - "packages/**/dist/", - "packages/**/build/", - ], + ignores: ['node_modules/', '*.sol', 'packages/*/node_modules/', 'packages/**/dist/', 'packages/**/build/'], }, { - files: ["**/*.config.js"], + files: ['**/*.config.js'], languageOptions: { - sourceType: "commonjs", + sourceType: 'commonjs', }, }, { - files: ["**/*.js"], + files: ['**/*.mjs', '**/*.js'], languageOptions: { - sourceType: "commonjs", - }, - rules: { - "@typescript-eslint/*": "off", - }, - }, - { - files: ["**/*.mjs", "**/*.js"], - languageOptions: { - sourceType: "commonjs", + sourceType: 'commonjs', globals: { - process: "readonly", - global: "readonly", - console: "readonly", + process: 'readonly', + global: 'readonly', + console: 'readonly', }, }, rules: { - "@typescript-eslint/no-require-imports": "off", + '@typescript-eslint/no-require-imports': 'off', }, }, ); diff --git a/packages/core/cairo/ava.config.js b/packages/core/cairo/ava.config.js index a39075959..e39146f7a 100644 --- a/packages/core/cairo/ava.config.js +++ b/packages/core/cairo/ava.config.js @@ -1,9 +1,9 @@ module.exports = { - extensions: ["ts"], - require: ["ts-node/register"], + extensions: ['ts'], + require: ['ts-node/register'], watchmode: { - ignoreChanges: ["contracts", "artifacts", "cache"], + ignoreChanges: ['contracts', 'artifacts', 'cache'], }, - timeout: "10m", + timeout: '10m', workerThreads: false, }; diff --git a/packages/core/cairo/src/account.test.ts b/packages/core/cairo/src/account.test.ts index 4b4097e5c..cad307c75 100644 --- a/packages/core/cairo/src/account.test.ts +++ b/packages/core/cairo/src/account.test.ts @@ -1,15 +1,16 @@ -import test from "ava"; +import test from 'ava'; -import { buildAccount, AccountOptions } from "./account"; -import { printContract } from "./print"; +import type { AccountOptions } from './account'; +import { buildAccount } from './account'; +import { printContract } from './print'; -import { account } from "."; +import { account } from '.'; function testAccount(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildAccount({ - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', ...opts, }); t.snapshot(printContract(c)); @@ -17,10 +18,10 @@ function testAccount(title: string, opts: Partial) { } function testEthAccount(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildAccount({ - name: "MyAccount", - type: "eth", + name: 'MyAccount', + type: 'eth', ...opts, }); t.snapshot(printContract(c)); @@ -31,13 +32,13 @@ function testEthAccount(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: AccountOptions) { - test(title, (t) => { + test(title, t => { t.is( account.print(opts), printContract( buildAccount({ - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -49,15 +50,15 @@ function testAPIEquivalence(title: string, opts?: AccountOptions) { }); } -testAccount("default full account, mixin + upgradeable", {}); +testAccount('default full account, mixin + upgradeable', {}); -testAccount("default full account, mixin + non-upgradeable", { +testAccount('default full account, mixin + non-upgradeable', { upgradeable: false, }); -testAccount("explicit full account, mixin + upgradeable", { - name: "MyAccount", - type: "stark", +testAccount('explicit full account, mixin + upgradeable', { + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -65,9 +66,9 @@ testAccount("explicit full account, mixin + upgradeable", { upgradeable: true, }); -testAccount("explicit full account, mixin + non-upgradeable", { - name: "MyAccount", - type: "stark", +testAccount('explicit full account, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -75,14 +76,14 @@ testAccount("explicit full account, mixin + non-upgradeable", { upgradeable: false, }); -testAccount("basic account, upgradeable", { +testAccount('basic account, upgradeable', { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testAccount("basic account, non-upgradeable", { +testAccount('basic account, non-upgradeable', { declare: false, deploy: false, pubkey: false, @@ -90,54 +91,54 @@ testAccount("basic account, non-upgradeable", { upgradeable: false, }); -testAccount("account outside execution", { +testAccount('account outside execution', { deploy: false, pubkey: false, declare: false, }); -testAccount("account declarer", { +testAccount('account declarer', { deploy: false, pubkey: false, outsideExecution: false, }); -testAccount("account deployable", { +testAccount('account deployable', { declare: false, pubkey: false, outsideExecution: false, }); -testAccount("account public key", { +testAccount('account public key', { declare: false, deploy: false, outsideExecution: false, }); -testAccount("account declarer, deployable", { +testAccount('account declarer, deployable', { pubkey: false, outsideExecution: false, }); -testAccount("account declarer, public key", { +testAccount('account declarer, public key', { deploy: false, outsideExecution: false, }); -testAccount("account deployable, public key", { +testAccount('account deployable, public key', { declare: false, outsideExecution: false, }); -testEthAccount("default full ethAccount, mixin + upgradeable", {}); +testEthAccount('default full ethAccount, mixin + upgradeable', {}); -testEthAccount("default full ethAccount, mixin + non-upgradeable", { +testEthAccount('default full ethAccount, mixin + non-upgradeable', { upgradeable: false, }); -testEthAccount("explicit full ethAccount, mixin + upgradeable", { - name: "MyAccount", - type: "eth", +testEthAccount('explicit full ethAccount, mixin + upgradeable', { + name: 'MyAccount', + type: 'eth', declare: true, deploy: true, pubkey: true, @@ -145,9 +146,9 @@ testEthAccount("explicit full ethAccount, mixin + upgradeable", { upgradeable: true, }); -testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { - name: "MyAccount", - type: "eth", +testEthAccount('explicit full ethAccount, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'eth', declare: true, deploy: true, pubkey: true, @@ -155,14 +156,14 @@ testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { upgradeable: false, }); -testEthAccount("basic ethAccount, upgradeable", { +testEthAccount('basic ethAccount, upgradeable', { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount("basic ethAccount, non-upgradeable", { +testEthAccount('basic ethAccount, non-upgradeable', { declare: false, deploy: false, pubkey: false, @@ -170,50 +171,50 @@ testEthAccount("basic ethAccount, non-upgradeable", { upgradeable: false, }); -testEthAccount("ethAccount outside execution", { +testEthAccount('ethAccount outside execution', { deploy: false, pubkey: false, declare: false, }); -testEthAccount("ethAccount declarer", { +testEthAccount('ethAccount declarer', { deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount deployable", { +testEthAccount('ethAccount deployable', { declare: false, pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount public key", { +testEthAccount('ethAccount public key', { declare: false, deploy: false, outsideExecution: false, }); -testEthAccount("ethAccount declarer, deployable", { +testEthAccount('ethAccount declarer, deployable', { pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount declarer, public key", { +testEthAccount('ethAccount declarer, public key', { deploy: false, outsideExecution: false, }); -testEthAccount("ethAccount deployable, public key", { +testEthAccount('ethAccount deployable, public key', { declare: false, outsideExecution: false, }); -testAPIEquivalence("account API default"); +testAPIEquivalence('account API default'); -testAPIEquivalence("account API basic", { - name: "CustomAccount", - type: "stark", +testAPIEquivalence('account API basic', { + name: 'CustomAccount', + type: 'stark', declare: false, deploy: false, pubkey: false, @@ -221,9 +222,9 @@ testAPIEquivalence("account API basic", { upgradeable: false, }); -testAPIEquivalence("account API full upgradeable", { - name: "CustomAccount", - type: "stark", +testAPIEquivalence('account API full upgradeable', { + name: 'CustomAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -231,6 +232,6 @@ testAPIEquivalence("account API full upgradeable", { upgradeable: true, }); -test("account API assert defaults", async (t) => { +test('account API assert defaults', async t => { t.is(account.print(account.defaults), account.print()); }); diff --git a/packages/core/cairo/src/account.ts b/packages/core/cairo/src/account.ts index 5f1659de9..f07293ede 100644 --- a/packages/core/cairo/src/account.ts +++ b/packages/core/cairo/src/account.ts @@ -1,18 +1,20 @@ -import { Contract, ContractBuilder } from "./contract"; -import { CommonOptions, withCommonDefaults } from "./common-options"; -import { defaults as commonDefaults } from "./common-options"; -import { setAccountUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { printContract } from "./print"; -import { addSRC5Component } from "./common-components"; - -export const accountTypes = ["stark", "eth"] as const; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults } from './common-options'; +import { defaults as commonDefaults } from './common-options'; +import { setAccountUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; + +export const accountTypes = ['stark', 'eth'] as const; export type Account = (typeof accountTypes)[number]; export const defaults: Required = { - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -52,22 +54,14 @@ export function buildAccount(opts: AccountOptions): Contract { const allOpts = withDefaults(opts); switch (allOpts.type) { - case "stark": - c.addConstructorArgument({ name: "public_key", type: "felt252" }); - c.addComponent( - components.AccountComponent, - [{ lit: "public_key" }], - true, - ); + case 'stark': + c.addConstructorArgument({ name: 'public_key', type: 'felt252' }); + c.addComponent(components.AccountComponent, [{ lit: 'public_key' }], true); break; - case "eth": - c.addUseClause("openzeppelin::account::interface", "EthPublicKey"); - c.addConstructorArgument({ name: "public_key", type: "EthPublicKey" }); - c.addComponent( - components.EthAccountComponent, - [{ lit: "public_key" }], - true, - ); + case 'eth': + c.addUseClause('openzeppelin::account::interface', 'EthPublicKey'); + c.addConstructorArgument({ name: 'public_key', type: 'EthPublicKey' }); + c.addComponent(components.EthAccountComponent, [{ lit: 'public_key' }], true); break; } @@ -103,11 +97,11 @@ function addSRC6(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "SRC6Impl", + name: 'SRC6Impl', value: `${baseComponent}::SRC6Impl`, }); c.addImplToComponent(componentType, { - name: "SRC6CamelOnlyImpl", + name: 'SRC6CamelOnlyImpl', value: `${baseComponent}::SRC6CamelOnlyImpl`, }); @@ -118,7 +112,7 @@ function addDeclarer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "DeclarerImpl", + name: 'DeclarerImpl', value: `${baseComponent}::DeclarerImpl`, }); } @@ -127,7 +121,7 @@ function addDeployer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "DeployableImpl", + name: 'DeployableImpl', value: `${baseComponent}::DeployableImpl`, }); } @@ -136,23 +130,22 @@ function addPublicKey(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "PublicKeyImpl", + name: 'PublicKeyImpl', value: `${baseComponent}::PublicKeyImpl`, }); c.addImplToComponent(componentType, { - name: "PublicKeyCamelImpl", + name: 'PublicKeyCamelImpl', value: `${baseComponent}::PublicKeyCamelImpl`, }); } function addOutsideExecution(c: ContractBuilder) { - c.addUseClause("openzeppelin::account::extensions", "SRC9Component"); + c.addUseClause('openzeppelin::account::extensions', 'SRC9Component'); c.addComponent(components.SRC9Component, [], true); } function addAccountMixin(c: ContractBuilder, accountType: Account) { - const accountMixinImpl = - accountType === "stark" ? "AccountMixinImpl" : "EthAccountMixinImpl"; + const accountMixinImpl = accountType === 'stark' ? 'AccountMixinImpl' : 'EthAccountMixinImpl'; const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { @@ -160,76 +153,74 @@ function addAccountMixin(c: ContractBuilder, accountType: Account) { value: `${baseComponent}::${accountMixinImpl}`, }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } -function getBaseCompAndCompType( - accountType: Account, -): [string, typeof componentType] { +function getBaseCompAndCompType(accountType: Account): [string, typeof componentType] { const [baseComponent, componentType] = - accountType === "stark" - ? ["AccountComponent", components.AccountComponent] - : ["EthAccountComponent", components.EthAccountComponent]; + accountType === 'stark' + ? ['AccountComponent', components.AccountComponent] + : ['EthAccountComponent', components.EthAccountComponent]; return [baseComponent, componentType]; } const components = defineComponents({ AccountComponent: { - path: "openzeppelin::account", + path: 'openzeppelin::account', substorage: { - name: "account", - type: "AccountComponent::Storage", + name: 'account', + type: 'AccountComponent::Storage', }, event: { - name: "AccountEvent", - type: "AccountComponent::Event", + name: 'AccountEvent', + type: 'AccountComponent::Event', }, impls: [ { - name: "AccountInternalImpl", + name: 'AccountInternalImpl', embed: false, - value: "AccountComponent::InternalImpl", + value: 'AccountComponent::InternalImpl', }, ], }, EthAccountComponent: { - path: "openzeppelin::account::eth_account", + path: 'openzeppelin::account::eth_account', substorage: { - name: "eth_account", - type: "EthAccountComponent::Storage", + name: 'eth_account', + type: 'EthAccountComponent::Storage', }, event: { - name: "EthAccountEvent", - type: "EthAccountComponent::Event", + name: 'EthAccountEvent', + type: 'EthAccountComponent::Event', }, impls: [ { - name: "EthAccountInternalImpl", + name: 'EthAccountInternalImpl', embed: false, - value: "EthAccountComponent::InternalImpl", + value: 'EthAccountComponent::InternalImpl', }, ], }, SRC9Component: { - path: "openzeppelin::account::extensions", + path: 'openzeppelin::account::extensions', substorage: { - name: "src9", - type: "SRC9Component::Storage", + name: 'src9', + type: 'SRC9Component::Storage', }, event: { - name: "SRC9Event", - type: "SRC9Component::Event", + name: 'SRC9Event', + type: 'SRC9Component::Event', }, impls: [ { - name: "OutsideExecutionV2Impl", - value: "SRC9Component::OutsideExecutionV2Impl", + name: 'OutsideExecutionV2Impl', + value: 'SRC9Component::OutsideExecutionV2Impl', }, { - name: "OutsideExecutionInternalImpl", + name: 'OutsideExecutionInternalImpl', embed: false, - value: "SRC9Component::InternalImpl", + value: 'SRC9Component::InternalImpl', }, ], }, diff --git a/packages/core/cairo/src/add-pausable.ts b/packages/core/cairo/src/add-pausable.ts index 4b33dd23b..6d4f1c3c4 100644 --- a/packages/core/cairo/src/add-pausable.ts +++ b/packages/core/cairo/src/add-pausable.ts @@ -1,53 +1,40 @@ -import { getSelfArg } from "./common-options"; -import type { ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; -import { defineComponents } from "./utils/define-components"; -import { externalTrait } from "./external-trait"; +import { getSelfArg } from './common-options'; +import type { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; +import { defineComponents } from './utils/define-components'; +import { externalTrait } from './external-trait'; export function addPausable(c: ContractBuilder, access: Access) { c.addComponent(components.PausableComponent, [], false); c.addFunction(externalTrait, functions.pause); c.addFunction(externalTrait, functions.unpause); - requireAccessControl( - c, - externalTrait, - functions.pause, - access, - "PAUSER", - "pauser", - ); - requireAccessControl( - c, - externalTrait, - functions.unpause, - access, - "PAUSER", - "pauser", - ); + requireAccessControl(c, externalTrait, functions.pause, access, 'PAUSER', 'pauser'); + requireAccessControl(c, externalTrait, functions.unpause, access, 'PAUSER', 'pauser'); } const components = defineComponents({ PausableComponent: { - path: "openzeppelin::security::pausable", + path: 'openzeppelin::security::pausable', substorage: { - name: "pausable", - type: "PausableComponent::Storage", + name: 'pausable', + type: 'PausableComponent::Storage', }, event: { - name: "PausableEvent", - type: "PausableComponent::Event", + name: 'PausableEvent', + type: 'PausableComponent::Event', }, impls: [ { - name: "PausableImpl", - value: "PausableComponent::PausableImpl", + name: 'PausableImpl', + value: 'PausableComponent::PausableImpl', }, { - name: "PausableInternalImpl", + name: 'PausableInternalImpl', embed: false, - value: "PausableComponent::InternalImpl", + value: 'PausableComponent::InternalImpl', }, ], }, @@ -56,10 +43,10 @@ const components = defineComponents({ const functions = defineFunctions({ pause: { args: [getSelfArg()], - code: ["self.pausable.pause()"], + code: ['self.pausable.pause()'], }, unpause: { args: [getSelfArg()], - code: ["self.pausable.unpause()"], + code: ['self.pausable.unpause()'], }, }); diff --git a/packages/core/cairo/src/api.ts b/packages/core/cairo/src/api.ts index 67bca280b..dd8daf73e 100644 --- a/packages/core/cairo/src/api.ts +++ b/packages/core/cairo/src/api.ts @@ -1,43 +1,34 @@ -import type { CommonOptions, CommonContractOptions } from "./common-options"; +import type { CommonOptions, CommonContractOptions } from './common-options'; +import type { ERC20Options } from './erc20'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, - ERC20Options, -} from "./erc20"; +} from './erc20'; +import type { ERC721Options } from './erc721'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, - ERC721Options, -} from "./erc721"; +} from './erc721'; +import type { ERC1155Options } from './erc1155'; import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, - ERC1155Options, -} from "./erc1155"; -import { - printAccount, - defaults as accountDefaults, - AccountOptions, -} from "./account"; -import { - printGovernor, - defaults as governorDefaults, - GovernorOptions, -} from "./governor"; +} from './erc1155'; +import type { AccountOptions } from './account'; +import { printAccount, defaults as accountDefaults } from './account'; +import type { GovernorOptions } from './governor'; +import { printGovernor, defaults as governorDefaults } from './governor'; +import type { CustomOptions } from './custom'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, - CustomOptions, -} from "./custom"; -import { - printVesting, - defaults as vestingDefaults, - VestingOptions, -} from "./vesting"; +} from './custom'; +import type { VestingOptions } from './vesting'; +import { printVesting, defaults as vestingDefaults } from './vesting'; export interface WizardAccountAPI { /** @@ -71,17 +62,13 @@ export interface AccessControlAPI { isAccessControlRequired: (opts: Partial) => boolean; } -export type ERC20 = WizardContractAPI & - AccessControlAPI; -export type ERC721 = WizardContractAPI & - AccessControlAPI; -export type ERC1155 = WizardContractAPI & - AccessControlAPI; +export type ERC20 = WizardContractAPI & AccessControlAPI; +export type ERC721 = WizardContractAPI & AccessControlAPI; +export type ERC1155 = WizardContractAPI & AccessControlAPI; export type Account = WizardAccountAPI; export type Governor = WizardContractAPI; export type Vesting = WizardContractAPI; -export type Custom = WizardContractAPI & - AccessControlAPI; +export type Custom = WizardContractAPI & AccessControlAPI; export const erc20: ERC20 = { print: printERC20, diff --git a/packages/core/cairo/src/build-generic.ts b/packages/core/cairo/src/build-generic.ts index abfab3d16..a956ed94f 100644 --- a/packages/core/cairo/src/build-generic.ts +++ b/packages/core/cairo/src/build-generic.ts @@ -1,49 +1,56 @@ -import { ERC20Options, buildERC20 } from "./erc20"; -import { ERC721Options, buildERC721 } from "./erc721"; -import { ERC1155Options, buildERC1155 } from "./erc1155"; -import { CustomOptions, buildCustom } from "./custom"; -import { AccountOptions, buildAccount } from "./account"; -import { GovernorOptions, buildGovernor } from "./governor"; -import { VestingOptions, buildVesting } from "./vesting"; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import type { AccountOptions } from './account'; +import { buildAccount } from './account'; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import type { VestingOptions } from './vesting'; +import { buildVesting } from './vesting'; export interface KindedOptions { - ERC20: { kind: "ERC20" } & ERC20Options; - ERC721: { kind: "ERC721" } & ERC721Options; - ERC1155: { kind: "ERC1155" } & ERC1155Options; - Account: { kind: "Account" } & AccountOptions; - Governor: { kind: "Governor" } & GovernorOptions; - Vesting: { kind: "Vesting" } & VestingOptions; - Custom: { kind: "Custom" } & CustomOptions; + ERC20: { kind: 'ERC20' } & ERC20Options; + ERC721: { kind: 'ERC721' } & ERC721Options; + ERC1155: { kind: 'ERC1155' } & ERC1155Options; + Account: { kind: 'Account' } & AccountOptions; + Governor: { kind: 'Governor' } & GovernorOptions; + Vesting: { kind: 'Vesting' } & VestingOptions; + Custom: { kind: 'Custom' } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return buildERC20(opts); - case "ERC721": + case 'ERC721': return buildERC721(opts); - case "ERC1155": + case 'ERC1155': return buildERC1155(opts); - case "Account": + case 'Account': return buildAccount(opts); - case "Governor": + case 'Governor': return buildGovernor(opts); - case "Vesting": + case 'Vesting': return buildVesting(opts); - case "Custom": + case 'Custom': return buildCustom(opts); default: { const _: never = opts; - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } } diff --git a/packages/core/cairo/src/common-components.ts b/packages/core/cairo/src/common-components.ts index 90615a056..bdfcef0e8 100644 --- a/packages/core/cairo/src/common-components.ts +++ b/packages/core/cairo/src/common-components.ts @@ -1,56 +1,56 @@ -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { defineComponents } from "./utils/define-components"; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; -export const tokenTypes = ["ERC20", "ERC721"] as const; +export const tokenTypes = ['ERC20', 'ERC721'] as const; export type Token = (typeof tokenTypes)[number]; const components = defineComponents({ SRC5Component: { - path: "openzeppelin::introspection::src5", + path: 'openzeppelin::introspection::src5', substorage: { - name: "src5", - type: "SRC5Component::Storage", + name: 'src5', + type: 'SRC5Component::Storage', }, event: { - name: "SRC5Event", - type: "SRC5Component::Event", + name: 'SRC5Event', + type: 'SRC5Component::Event', }, impls: [], }, VotesComponent: { - path: "openzeppelin::governance::votes", + path: 'openzeppelin::governance::votes', substorage: { - name: "votes", - type: "VotesComponent::Storage", + name: 'votes', + type: 'VotesComponent::Storage', }, event: { - name: "VotesEvent", - type: "VotesComponent::Event", + name: 'VotesEvent', + type: 'VotesComponent::Event', }, impls: [ { - name: "VotesInternalImpl", + name: 'VotesInternalImpl', embed: false, - value: "VotesComponent::InternalImpl", + value: 'VotesComponent::InternalImpl', }, ], }, NoncesComponent: { - path: "openzeppelin::utils::cryptography::nonces", + path: 'openzeppelin::utils::cryptography::nonces', substorage: { - name: "nonces", - type: "NoncesComponent::Storage", + name: 'nonces', + type: 'NoncesComponent::Storage', }, event: { - name: "NoncesEvent", - type: "NoncesComponent::Event", + name: 'NoncesEvent', + type: 'NoncesComponent::Event', }, impls: [ { - name: "NoncesImpl", - value: "NoncesComponent::NoncesImpl", + name: 'NoncesImpl', + value: 'NoncesComponent::NoncesImpl', }, ], }, @@ -59,42 +59,32 @@ const components = defineComponents({ export function addSRC5Component(c: ContractBuilder, section?: string) { c.addComponent(components.SRC5Component, [], false); - if (!c.interfaceFlags.has("ISRC5")) { + if (!c.interfaceFlags.has('ISRC5')) { c.addImplToComponent(components.SRC5Component, { - name: "SRC5Impl", - value: "SRC5Component::SRC5Impl", + name: 'SRC5Impl', + value: 'SRC5Component::SRC5Impl', section, }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); } } -export function addVotesComponent( - c: ContractBuilder, - name: string, - version: string, - section?: string, -) { +export function addVotesComponent(c: ContractBuilder, name: string, version: string, section?: string) { addSNIP12Metadata(c, name, version, section); c.addComponent(components.NoncesComponent, [], false); c.addComponent(components.VotesComponent, [], false); c.addImplToComponent(components.VotesComponent, { - name: "VotesImpl", + name: 'VotesImpl', value: `VotesComponent::VotesImpl`, }); } -export function addSNIP12Metadata( - c: ContractBuilder, - name: string, - version: string, - section?: string, -) { - c.addUseClause("openzeppelin::utils::cryptography::snip12", "SNIP12Metadata"); +export function addSNIP12Metadata(c: ContractBuilder, name: string, version: string, section?: string) { + c.addUseClause('openzeppelin::utils::cryptography::snip12', 'SNIP12Metadata'); const SNIP12Metadata: BaseImplementedTrait = { - name: "SNIP12MetadataImpl", - of: "SNIP12Metadata", + name: 'SNIP12MetadataImpl', + of: 'SNIP12Metadata', tags: [], priority: 0, section, @@ -102,16 +92,16 @@ export function addSNIP12Metadata( c.addImplementedTrait(SNIP12Metadata); c.addFunction(SNIP12Metadata, { - name: "name", + name: 'name', args: [], - returns: "felt252", + returns: 'felt252', code: [`'${name}'`], }); c.addFunction(SNIP12Metadata, { - name: "version", + name: 'version', args: [], - returns: "felt252", + returns: 'felt252', code: [`'${version}'`], }); } diff --git a/packages/core/cairo/src/common-options.ts b/packages/core/cairo/src/common-options.ts index de54d727c..5cdee43e0 100644 --- a/packages/core/cairo/src/common-options.ts +++ b/packages/core/cairo/src/common-options.ts @@ -1,8 +1,8 @@ -import type { Argument } from "./contract"; -import type { Access } from "./set-access-control"; -import type { Info } from "./set-info"; -import { defaults as infoDefaults } from "./set-info"; -import type { Upgradeable } from "./set-upgradeable"; +import type { Argument } from './contract'; +import type { Access } from './set-access-control'; +import type { Info } from './set-info'; +import { defaults as infoDefaults } from './set-info'; +import type { Upgradeable } from './set-upgradeable'; export const defaults: Required = { upgradeable: true, @@ -23,28 +23,24 @@ export interface CommonContractOptions extends CommonOptions { access?: Access; } -export function withCommonDefaults( - opts: CommonOptions, -): Required { +export function withCommonDefaults(opts: CommonOptions): Required { return { upgradeable: opts.upgradeable ?? defaults.upgradeable, info: opts.info ?? defaults.info, }; } -export function withCommonContractDefaults( - opts: CommonContractOptions, -): Required { +export function withCommonContractDefaults(opts: CommonContractOptions): Required { return { ...withCommonDefaults(opts), access: opts.access ?? contractDefaults.access, }; } -export function getSelfArg(scope: "external" | "view" = "external"): Argument { - if (scope === "view") { - return { name: "self", type: "@ContractState" }; +export function getSelfArg(scope: 'external' | 'view' = 'external'): Argument { + if (scope === 'view') { + return { name: 'self', type: '@ContractState' }; } else { - return { name: "ref self", type: "ContractState" }; + return { name: 'ref self', type: 'ContractState' }; } } diff --git a/packages/core/cairo/src/contract.test.ts b/packages/core/cairo/src/contract.test.ts index c6351dcc2..a7675a25f 100644 --- a/packages/core/cairo/src/contract.test.ts +++ b/packages/core/cairo/src/contract.test.ts @@ -1,112 +1,108 @@ -import test from "ava"; +import test from 'ava'; -import { - ContractBuilder, - BaseFunction, - BaseImplementedTrait, - Component, -} from "./contract"; -import { printContract } from "./print"; +import type { BaseFunction, BaseImplementedTrait, Component } from './contract'; +import { ContractBuilder } from './contract'; +import { printContract } from './print'; const FOO_COMPONENT: Component = { - name: "FooComponent", - path: "some::path", + name: 'FooComponent', + path: 'some::path', substorage: { - name: "foo", - type: "FooComponent::Storage", + name: 'foo', + type: 'FooComponent::Storage', }, event: { - name: "FooEvent", - type: "FooComponent::Event", + name: 'FooEvent', + type: 'FooComponent::Event', }, impls: [ { - name: "FooImpl", - value: "FooComponent::FooImpl", + name: 'FooImpl', + value: 'FooComponent::FooImpl', }, { - name: "FooInternalImpl", + name: 'FooInternalImpl', embed: false, - value: "FooComponent::InternalImpl", + value: 'FooComponent::InternalImpl', }, ], }; -test("contract basics", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract basics', t => { + const Foo = new ContractBuilder('Foo'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("someFunction()"); +test('contract with constructor code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('someFunction()'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code with semicolon", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("someFunction();"); +test('contract with constructor code with semicolon', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('someFunction();'); t.snapshot(printContract(Foo)); }); -test("contract with function code before", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with function code before', t => { + const Foo = new ContractBuilder('Foo'); const trait: BaseImplementedTrait = { - name: "External", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'External', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: "someFunction", + name: 'someFunction', args: [], - code: ["someFunction()"], + code: ['someFunction()'], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, "before()"); + Foo.addFunctionCodeBefore(trait, fn, 'before()'); t.snapshot(printContract(Foo)); }); -test("contract with function code before with semicolons", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with function code before with semicolons', t => { + const Foo = new ContractBuilder('Foo'); const trait: BaseImplementedTrait = { - name: "External", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'External', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: "someFunction", + name: 'someFunction', args: [], - code: ["someFunction();"], + code: ['someFunction();'], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, "before();"); + Foo.addFunctionCodeBefore(trait, fn, 'before();'); t.snapshot(printContract(Foo)); }); -test("contract with initializer params", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with initializer params', t => { + const Foo = new ContractBuilder('Foo'); - Foo.addComponent(FOO_COMPONENT, ["param1"], true); + Foo.addComponent(FOO_COMPONENT, ['param1'], true); t.snapshot(printContract(Foo)); }); -test("contract with standalone import", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with standalone import', t => { + const Foo = new ContractBuilder('Foo'); Foo.addComponent(FOO_COMPONENT); - Foo.addUseClause("some::library", "SomeLibrary"); + Foo.addUseClause('some::library', 'SomeLibrary'); t.snapshot(printContract(Foo)); }); -test("contract with sorted use clauses", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with sorted use clauses', t => { + const Foo = new ContractBuilder('Foo'); Foo.addComponent(FOO_COMPONENT); - Foo.addUseClause("some::library", "SomeLibrary"); - Foo.addUseClause("another::library", "AnotherLibrary"); - Foo.addUseClause("another::library", "Foo", { alias: "Custom2" }); - Foo.addUseClause("another::library", "Foo", { alias: "Custom1" }); + Foo.addUseClause('some::library', 'SomeLibrary'); + Foo.addUseClause('another::library', 'AnotherLibrary'); + Foo.addUseClause('another::library', 'Foo', { alias: 'Custom2' }); + Foo.addUseClause('another::library', 'Foo', { alias: 'Custom1' }); t.snapshot(printContract(Foo)); }); diff --git a/packages/core/cairo/src/contract.ts b/packages/core/cairo/src/contract.ts index b876ee2ee..05ef6778b 100644 --- a/packages/core/cairo/src/contract.ts +++ b/packages/core/cairo/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from "./utils/convert-strings"; +import { toIdentifier } from './utils/convert-strings'; export interface Contract { license: string; @@ -14,12 +14,7 @@ export interface Contract { superVariables: Variable[]; } -export type Value = - | string - | number - | bigint - | { lit: string } - | { note: string; value: Value }; +export type Value = string | number | bigint | { lit: string } | { note: string; value: Value }; export interface UseClause { containerPath: string; @@ -105,7 +100,7 @@ export interface Argument { export class ContractBuilder implements Contract { readonly name: string; readonly account: boolean; - license = "MIT"; + license = 'MIT'; upgradeable = false; readonly constructorArgs: Argument[] = []; @@ -150,14 +145,10 @@ export class ContractBuilder implements Contract { return this.interfaceFlagsSet; } - addUseClause( - containerPath: string, - name: string, - options?: { groupable?: boolean; alias?: string }, - ): void { + addUseClause(containerPath: string, name: string, options?: { groupable?: boolean; alias?: string }): void { // groupable defaults to true const groupable = options?.groupable ?? true; - const alias = options?.alias ?? ""; + const alias = options?.alias ?? ''; const uniqueName = alias.length > 0 ? alias : name; const present = this.useClausesMap.has(uniqueName); if (!present) { @@ -170,11 +161,7 @@ export class ContractBuilder implements Contract { } } - addComponent( - component: Component, - params: Value[] = [], - initializable: boolean = true, - ): boolean { + addComponent(component: Component, params: Value[] = [], initializable: boolean = true): boolean { this.addUseClause(component.path, component.name); const key = component.name; const present = this.componentsMap.has(key); @@ -197,7 +184,7 @@ export class ContractBuilder implements Contract { throw new Error(`Component ${component.name} has not been added yet`); } - if (!c.impls.some((i) => i.name === impl.name)) { + if (!c.impls.some(i => i.name === impl.name)) { c.impls.push(impl); } } @@ -216,7 +203,7 @@ export class ContractBuilder implements Contract { return false; } else { this.superVariablesMap.set(variable.name, variable); - this.addUseClause("super", variable.name); + this.addUseClause('super', variable.name); return true; } } @@ -241,10 +228,7 @@ export class ContractBuilder implements Contract { } } - addSuperVariableToTrait( - baseTrait: BaseImplementedTrait, - newVar: Variable, - ): boolean { + addSuperVariableToTrait(baseTrait: BaseImplementedTrait, newVar: Variable): boolean { const trait = this.addImplementedTrait(baseTrait); for (const existingVar of trait.superVariables) { if (existingVar.name === newVar.name) { @@ -265,10 +249,7 @@ export class ContractBuilder implements Contract { return true; } - addFunction( - baseTrait: BaseImplementedTrait, - fn: BaseFunction, - ): ContractFunction { + addFunction(baseTrait: BaseImplementedTrait, fn: BaseFunction): ContractFunction { const t = this.addImplementedTrait(baseTrait); const signature = this.getFunctionSignature(fn); @@ -276,10 +257,7 @@ export class ContractBuilder implements Contract { // Look for the existing function with the same signature and return it if found for (let i = 0; i < t.functions.length; i++) { const existingFn = t.functions[i]; - if ( - existingFn !== undefined && - this.getFunctionSignature(existingFn) === signature - ) { + if (existingFn !== undefined && this.getFunctionSignature(existingFn) === signature) { return existingFn; } } @@ -295,14 +273,10 @@ export class ContractBuilder implements Contract { } private getFunctionSignature(fn: BaseFunction): string { - return [fn.name, "(", ...fn.args.map((a) => a.name), ")"].join(""); + return [fn.name, '(', ...fn.args.map(a => a.name), ')'].join(''); } - addFunctionCodeBefore( - baseTrait: BaseImplementedTrait, - fn: BaseFunction, - codeBefore: string, - ): void { + addFunctionCodeBefore(baseTrait: BaseImplementedTrait, fn: BaseFunction, codeBefore: string): void { this.addImplementedTrait(baseTrait); const existingFn = this.addFunction(baseTrait, fn); existingFn.codeBefore = [...(existingFn.codeBefore ?? []), codeBefore]; diff --git a/packages/core/cairo/src/custom.test.ts b/packages/core/cairo/src/custom.test.ts index 85b3fb9be..9b3059094 100644 --- a/packages/core/cairo/src/custom.test.ts +++ b/packages/core/cairo/src/custom.test.ts @@ -1,13 +1,14 @@ -import test from "ava"; -import { custom } from "."; +import test from 'ava'; +import { custom } from '.'; -import { buildCustom, CustomOptions } from "./custom"; -import { printContract } from "./print"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import { printContract } from './print'; function testCustom(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }); t.snapshot(printContract(c)); @@ -18,12 +19,12 @@ function testCustom(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, (t) => { + test(title, t => { t.is( custom.print(opts), printContract( buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }), ), @@ -31,54 +32,54 @@ function testAPIEquivalence(title: string, opts?: CustomOptions) { }); } -testCustom("custom non-upgradeable", { +testCustom('custom non-upgradeable', { upgradeable: false, }); -testCustom("custom defaults", {}); +testCustom('custom defaults', {}); -testCustom("pausable", { +testCustom('pausable', { pausable: true, }); -testCustom("upgradeable", { +testCustom('upgradeable', { upgradeable: true, }); -testCustom("access control disabled", { +testCustom('access control disabled', { upgradeable: false, access: false, }); -testCustom("access control ownable", { - access: "ownable", +testCustom('access control ownable', { + access: 'ownable', }); -testCustom("access control roles", { - access: "roles", +testCustom('access control roles', { + access: 'roles', }); -testCustom("pausable with access control disabled", { +testCustom('pausable with access control disabled', { // API should override access to true since it is required for pausable access: false, pausable: true, upgradeable: false, }); -testAPIEquivalence("custom API default"); +testAPIEquivalence('custom API default'); -testAPIEquivalence("custom API full upgradeable", { - name: "CustomContract", - access: "roles", +testAPIEquivalence('custom API full upgradeable', { + name: 'CustomContract', + access: 'roles', pausable: true, upgradeable: true, }); -test("custom API assert defaults", async (t) => { +test('custom API assert defaults', async t => { t.is(custom.print(custom.defaults), custom.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(custom.isAccessControlRequired({ pausable: true }), true); t.is(custom.isAccessControlRequired({ upgradeable: true }), true); }); diff --git a/packages/core/cairo/src/custom.ts b/packages/core/cairo/src/custom.ts index a97ea2c7a..3fe6bc86a 100644 --- a/packages/core/cairo/src/custom.ts +++ b/packages/core/cairo/src/custom.ts @@ -1,17 +1,16 @@ -import { Contract, ContractBuilder } from "./contract"; -import { setAccessControl } from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { - CommonContractOptions, - withCommonContractDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; export const defaults: Required = { - name: "MyContract", + name: 'MyContract', pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, diff --git a/packages/core/cairo/src/erc1155.test.ts b/packages/core/cairo/src/erc1155.test.ts index 55f9b240c..066e86c4b 100644 --- a/packages/core/cairo/src/erc1155.test.ts +++ b/packages/core/cairo/src/erc1155.test.ts @@ -1,14 +1,14 @@ -import test from "ava"; -import { erc1155 } from "."; +import test from 'ava'; +import { erc1155 } from '.'; -import { buildERC1155, ERC1155Options } from "./erc1155"; -import { printContract } from "./print"; -import { royaltyInfoOptions } from "./set-royalty-info"; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; -const NAME = "MyToken"; -const CUSTOM_NAME = "CustomToken"; -const BASE_URI = - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; const allFeaturesON: Partial = { mintable: true, @@ -19,7 +19,7 @@ const allFeaturesON: Partial = { } as const; function testERC1155(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC1155({ name: NAME, baseUri: BASE_URI, @@ -33,13 +33,13 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, (t) => { + test(title, t => { t.is( erc1155.print(opts), printContract( buildERC1155({ name: NAME, - baseUri: "", + baseUri: '', ...opts, }), ), @@ -47,102 +47,93 @@ function testAPIEquivalence(title: string, opts?: ERC1155Options) { }); } -testERC1155("basic non-upgradeable", { +testERC1155('basic non-upgradeable', { upgradeable: false, }); -testERC1155("basic", {}); +testERC1155('basic', {}); -testERC1155("basic + roles", { - access: "roles", +testERC1155('basic + roles', { + access: 'roles', }); -testERC1155("no updatable uri", { +testERC1155('no updatable uri', { updatableUri: false, }); -testERC1155("burnable", { +testERC1155('burnable', { burnable: true, }); -testERC1155("pausable", { +testERC1155('pausable', { pausable: true, }); -testERC1155("mintable", { +testERC1155('mintable', { mintable: true, }); -testERC1155("mintable + roles", { +testERC1155('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC1155("royalty info disabled", { +testERC1155('royalty info disabled', { royaltyInfo: royaltyInfoOptions.disabled, }); -testERC1155("royalty info enabled default + ownable", { +testERC1155('royalty info enabled default + ownable', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "ownable", + access: 'ownable', }); -testERC1155("royalty info enabled default + roles", { +testERC1155('royalty info enabled default + roles', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "roles", + access: 'roles', }); -testERC1155("royalty info enabled custom + ownable", { +testERC1155('royalty info enabled custom + ownable', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "ownable", + access: 'ownable', }); -testERC1155("royalty info enabled custom + roles", { +testERC1155('royalty info enabled custom + roles', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "roles", + access: 'roles', }); -testERC1155("full non-upgradeable", { +testERC1155('full non-upgradeable', { ...allFeaturesON, - access: "roles", + access: 'roles', upgradeable: false, }); -testERC1155("full upgradeable", { +testERC1155('full upgradeable', { ...allFeaturesON, - access: "roles", + access: 'roles', upgradeable: true, }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: CUSTOM_NAME, baseUri: BASE_URI }); +testAPIEquivalence('API basic', { name: CUSTOM_NAME, baseUri: BASE_URI }); -testAPIEquivalence("API full upgradeable", { +testAPIEquivalence('API full upgradeable', { ...allFeaturesON, name: CUSTOM_NAME, baseUri: BASE_URI, - access: "roles", + access: 'roles', upgradeable: true, }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), - true, - ); +test('API isAccessControlRequired', async t => { + t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), true); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); t.is( erc1155.isAccessControlRequired({ diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index 59baee0c8..78788a7f6 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -1,33 +1,25 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { addSRC5Component } from "./common-components"; -import { externalTrait } from "./external-trait"; -import { toByteArray } from "./utils/convert-strings"; -import { - RoyaltyInfoOptions, - setRoyaltyInfo, - defaults as royaltyInfoDefaults, -} from "./set-royalty-info"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; +import { externalTrait } from './external-trait'; +import { toByteArray } from './utils/convert-strings'; +import type { RoyaltyInfoOptions } from './set-royalty-info'; +import { setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; export const defaults: Required = { - name: "MyToken", - baseUri: "", + name: 'MyToken', + baseUri: '', burnable: false, pausable: false, mintable: false, @@ -64,9 +56,7 @@ function withDefaults(opts: ERC1155Options): Required { }; } -export function isAccessControlRequired( - opts: Partial, -): boolean { +export function isAccessControlRequired(opts: Partial): boolean { return ( opts.mintable === true || opts.pausable === true || @@ -114,42 +104,39 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable; if (usesCustomHooks) { const hooksTrait = { - name: "ERC1155HooksImpl", - of: "ERC1155Component::ERC1155HooksTrait", + name: 'ERC1155HooksImpl', + of: 'ERC1155Component::ERC1155HooksTrait', tags: [], priority: 1, }; c.addImplementedTrait(hooksTrait); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addFunction(hooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", + name: 'ref self', type: `ERC1155Component::ComponentState`, }, - { name: "from", type: "ContractAddress" }, - { name: "to", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, - ], - code: [ - "let contract_state = self.get_contract()", - "contract_state.pausable.assert_not_paused()", + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, ], + code: ['let contract_state = self.get_contract()', 'contract_state.pausable.assert_not_paused()'], }); } else { - c.addUseClause("openzeppelin::token::erc1155", "ERC1155HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc1155', 'ERC1155HooksEmptyImpl'); } } function addERC1155Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC1155Component, { - name: "ERC1155MixinImpl", - value: "ERC1155Component::ERC1155MixinImpl", + name: 'ERC1155MixinImpl', + value: 'ERC1155Component::ERC1155MixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } @@ -158,45 +145,24 @@ function addBase(c: ContractBuilder, baseUri: string) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("starknet", "ContractAddress"); - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); c.addFunction(externalTrait, functions.batch_burn); c.addFunction(externalTrait, functions.batchBurn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.mint, - access, - "MINTER", - "minter", - ); - requireAccessControl( - c, - externalTrait, - functions.batch_mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); + requireAccessControl(c, externalTrait, functions.batch_mint, access, 'MINTER', 'minter'); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. c.addFunction(externalTrait, functions.batchMint); } function addSetBaseUri(c: ContractBuilder, access: Access) { - requireAccessControl( - c, - externalTrait, - functions.set_base_uri, - access, - "URI_SETTER", - "uri_setter", - ); + requireAccessControl(c, externalTrait, functions.set_base_uri, access, 'URI_SETTER', 'uri_setter'); // Camel case version of set_base_uri. Access control is already set on set_base_uri. c.addFunction(externalTrait, functions.setBaseUri); @@ -204,20 +170,20 @@ function addSetBaseUri(c: ContractBuilder, access: Access) { const components = defineComponents({ ERC1155Component: { - path: "openzeppelin::token::erc1155", + path: 'openzeppelin::token::erc1155', substorage: { - name: "erc1155", - type: "ERC1155Component::Storage", + name: 'erc1155', + type: 'ERC1155Component::Storage', }, event: { - name: "ERC1155Event", - type: "ERC1155Component::Event", + name: 'ERC1155Event', + type: 'ERC1155Component::Event', }, impls: [ { - name: "ERC1155InternalImpl", + name: 'ERC1155InternalImpl', embed: false, - value: "ERC1155Component::InternalImpl", + value: 'ERC1155Component::InternalImpl', }, ], }, @@ -227,82 +193,78 @@ const functions = defineFunctions({ burn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "value", type: "u256" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, ], code: [ - "let caller = get_caller_address();", - "if account != caller {", - " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", - "}", - "self.erc1155.burn(account, token_id, value);", + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.burn(account, token_id, value);', ], }, batch_burn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, ], code: [ - "let caller = get_caller_address();", - "if account != caller {", - " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", - "}", - "self.erc1155.batch_burn(account, token_ids, values);", + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.batch_burn(account, token_ids, values);', ], }, batchBurn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "tokenIds", type: "Span" }, - { name: "values", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, ], - code: ["self.batch_burn(account, tokenIds, values);"], + code: ['self.batch_burn(account, tokenIds, values);'], }, mint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "value", type: "u256" }, - { name: "data", type: "Span" }, - ], - code: [ - "self.erc1155.mint_with_acceptance_check(account, token_id, value, data);", + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, + { name: 'data', type: 'Span' }, ], + code: ['self.erc1155.mint_with_acceptance_check(account, token_id, value, data);'], }, batch_mint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, - { name: "data", type: "Span" }, - ], - code: [ - "self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);", + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, ], + code: ['self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);'], }, batchMint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "tokenIds", type: "Span" }, - { name: "values", type: "Span" }, - { name: "data", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, ], - code: ["self.batch_mint(account, tokenIds, values, data);"], + code: ['self.batch_mint(account, tokenIds, values, data);'], }, set_base_uri: { - args: [getSelfArg(), { name: "base_uri", type: "ByteArray" }], - code: ["self.erc1155._set_base_uri(base_uri);"], + args: [getSelfArg(), { name: 'base_uri', type: 'ByteArray' }], + code: ['self.erc1155._set_base_uri(base_uri);'], }, setBaseUri: { - args: [getSelfArg(), { name: "baseUri", type: "ByteArray" }], - code: ["self.set_base_uri(baseUri);"], + args: [getSelfArg(), { name: 'baseUri', type: 'ByteArray' }], + code: ['self.set_base_uri(baseUri);'], }, }); diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 798659d7b..45fb5bf87 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -1,15 +1,17 @@ -import test from "ava"; +import test from 'ava'; -import { buildERC20, ERC20Options, getInitialSupply } from "./erc20"; -import { printContract } from "./print"; +import type { ERC20Options } from './erc20'; +import { buildERC20, getInitialSupply } from './erc20'; +import { printContract } from './print'; -import { erc20, OptionsError } from "."; +import type { OptionsError } from '.'; +import { erc20 } from '.'; function testERC20(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -20,13 +22,13 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, (t) => { + test(title, t => { t.is( erc20.print(opts), printContract( buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -34,171 +36,165 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { }); } -testERC20("basic erc20, non-upgradeable", { +testERC20('basic erc20, non-upgradeable', { upgradeable: false, }); -testERC20("basic erc20", {}); +testERC20('basic erc20', {}); -testERC20("erc20 burnable", { +testERC20('erc20 burnable', { burnable: true, }); -testERC20("erc20 pausable", { +testERC20('erc20 pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 pausable with roles", { +testERC20('erc20 pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 burnable pausable", { +testERC20('erc20 burnable pausable', { burnable: true, pausable: true, }); -testERC20("erc20 preminted", { - premint: "1000", +testERC20('erc20 preminted', { + premint: '1000', }); -testERC20("erc20 premint of 0", { - premint: "0", +testERC20('erc20 premint of 0', { + premint: '0', }); -testERC20("erc20 mintable", { +testERC20('erc20 mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 mintable with roles", { +testERC20('erc20 mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 votes", { +testERC20('erc20 votes', { votes: true, - appName: "MY_DAPP_NAME", + appName: 'MY_DAPP_NAME', }); -testERC20("erc20 votes, version", { +testERC20('erc20 votes, version', { votes: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -test("erc20 votes, no name", async (t) => { +test('erc20 votes, no name', async t => { const error = t.throws(() => buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', votes: true, }), ); - t.is( - (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); }); -test("erc20 votes, empty version", async (t) => { +test('erc20 votes, empty version', async t => { const error = t.throws(() => buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', votes: true, - appName: "MY_DAPP_NAME", - appVersion: "", // avoids default value of v1 + appName: 'MY_DAPP_NAME', + appVersion: '', // avoids default value of v1 }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -testERC20("erc20 votes, non-upgradeable", { +testERC20('erc20 votes, non-upgradeable', { votes: true, - appName: "MY_DAPP_NAME", + appName: 'MY_DAPP_NAME', upgradeable: false, }); -testERC20("erc20 full, non-upgradeable", { - premint: "2000", - access: "ownable", +testERC20('erc20 full, non-upgradeable', { + premint: '2000', + access: 'ownable', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: false, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testERC20("erc20 full upgradeable", { - premint: "2000", - access: "ownable", +testERC20('erc20 full upgradeable', { + premint: '2000', + access: 'ownable', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testERC20("erc20 full upgradeable with roles", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable with roles', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testAPIEquivalence("erc20 API default"); +testAPIEquivalence('erc20 API default'); -testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("erc20 API full upgradeable", { - name: "CustomToken", - symbol: "CTK", - premint: "2000", - access: "roles", +testAPIEquivalence('erc20 API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -test("erc20 API assert defaults", async (t) => { +test('erc20 API assert defaults', async t => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test("erc20 API isAccessControlRequired", async (t) => { +test('erc20 API isAccessControlRequired', async t => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); t.is(erc20.isAccessControlRequired({ upgradeable: true }), true); }); -test("erc20 getInitialSupply", async (t) => { - t.is(getInitialSupply("1000", 18), "1000000000000000000000"); - t.is(getInitialSupply("1000.1", 18), "1000100000000000000000"); - t.is(getInitialSupply(".1", 18), "100000000000000000"); - t.is(getInitialSupply(".01", 2), "1"); +test('erc20 getInitialSupply', async t => { + t.is(getInitialSupply('1000', 18), '1000000000000000000000'); + t.is(getInitialSupply('1000.1', 18), '1000100000000000000000'); + t.is(getInitialSupply('.1', 18), '100000000000000000'); + t.is(getInitialSupply('.01', 2), '1'); - let error = t.throws(() => getInitialSupply(".01", 1)); + let error = t.throws(() => getInitialSupply('.01', 1)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, "Too many decimals"); + t.is((error as OptionsError).messages.premint, 'Too many decimals'); - error = t.throws(() => getInitialSupply("1.1.1", 18)); + error = t.throws(() => getInitialSupply('1.1.1', 18)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, "Not a valid number"); + t.is((error as OptionsError).messages.premint, 'Not a valid number'); }); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 3334cd78b..e780f48ad 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -1,36 +1,31 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { OptionsError } from "./error"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { externalTrait } from "./external-trait"; -import { toByteArray, toFelt252, toUint } from "./utils/convert-strings"; -import { addVotesComponent } from "./common-components"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { OptionsError } from './error'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { externalTrait } from './external-trait'; +import { toByteArray, toFelt252, toUint } from './utils/convert-strings'; +import { addVotesComponent } from './common-components'; export const defaults: Required = { - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', burnable: false, pausable: false, - premint: "0", + premint: '0', mintable: false, votes: false, - appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: "v1", + appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: 'v1', access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info, @@ -67,11 +62,7 @@ function withDefaults(opts: ERC20Options): Required { } export function isAccessControlRequired(opts: Partial): boolean { - return ( - opts.mintable === true || - opts.pausable === true || - opts.upgradeable === true - ); + return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; } export function buildERC20(opts: ERC20Options): Contract { @@ -111,8 +102,8 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable || allOpts.votes; if (usesCustomHooks) { const hooksTrait = { - name: "ERC20HooksImpl", - of: "ERC20Component::ERC20HooksTrait", + name: 'ERC20HooksImpl', + of: 'ERC20Component::ERC20HooksTrait', tags: [], priority: 1, }; @@ -120,73 +111,73 @@ function addHooks(c: ContractBuilder, allOpts: Required) { if (allOpts.pausable) { const beforeUpdateFn = c.addFunction(hooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", - type: "ERC20Component::ComponentState", + name: 'ref self', + type: 'ERC20Component::ComponentState', }, - { name: "from", type: "ContractAddress" }, - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, + { name: 'from', type: 'ContractAddress' }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'amount', type: 'u256' }, ], code: [], }); beforeUpdateFn.code.push( - "let contract_state = self.get_contract();", - "contract_state.pausable.assert_not_paused();", + 'let contract_state = self.get_contract();', + 'contract_state.pausable.assert_not_paused();', ); } if (allOpts.votes) { if (!allOpts.appName) { throw new OptionsError({ - appName: "Application Name is required when Votes are enabled", + appName: 'Application Name is required when Votes are enabled', }); } if (!allOpts.appVersion) { throw new OptionsError({ - appVersion: "Application Version is required when Votes are enabled", + appVersion: 'Application Version is required when Votes are enabled', }); } addVotesComponent( c, - toFelt252(allOpts.appName, "appName"), - toFelt252(allOpts.appVersion, "appVersion"), - "SNIP12 Metadata", + toFelt252(allOpts.appName, 'appName'), + toFelt252(allOpts.appVersion, 'appVersion'), + 'SNIP12 Metadata', ); const afterUpdateFn = c.addFunction(hooksTrait, { - name: "after_update", + name: 'after_update', args: [ { - name: "ref self", - type: "ERC20Component::ComponentState", + name: 'ref self', + type: 'ERC20Component::ComponentState', }, - { name: "from", type: "ContractAddress" }, - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, + { name: 'from', type: 'ContractAddress' }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'amount', type: 'u256' }, ], code: [], }); afterUpdateFn.code.push( - "let mut contract_state = self.get_contract_mut();", - "contract_state.votes.transfer_voting_units(from, recipient, amount);", + 'let mut contract_state = self.get_contract_mut();', + 'contract_state.votes.transfer_voting_units(from, recipient, amount);', ); } } else { - c.addUseClause("openzeppelin::token::erc20", "ERC20HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc20', 'ERC20HooksEmptyImpl'); } } function addERC20Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC20Component, { - name: "ERC20MixinImpl", - value: "ERC20Component::ERC20MixinImpl", + name: 'ERC20MixinImpl', + value: 'ERC20Component::ERC20MixinImpl', }); } @@ -195,28 +186,24 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); } export const premintPattern = /^(\d*\.?\d*)$/; function addPremint(c: ContractBuilder, amount: string) { - if (amount !== undefined && amount !== "0") { + if (amount !== undefined && amount !== '0') { if (!premintPattern.test(amount)) { throw new OptionsError({ - premint: "Not a valid number", + premint: 'Not a valid number', }); } - const premintAbsolute = toUint( - getInitialSupply(amount, 18), - "premint", - "u256", - ); + const premintAbsolute = toUint(getInitialSupply(amount, 18), 'premint', 'u256'); - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: "recipient", type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: 'recipient', type: 'ContractAddress' }); c.addConstructorCode(`self.erc20.mint(recipient, ${premintAbsolute})`); } } @@ -231,65 +218,58 @@ function addPremint(c: ContractBuilder, amount: string) { */ export function getInitialSupply(premint: string, decimals: number): string { let result; - const premintSegments = premint.split("."); + const premintSegments = premint.split('.'); if (premintSegments.length > 2) { throw new OptionsError({ - premint: "Not a valid number", + premint: 'Not a valid number', }); } else { - const firstSegment = premintSegments[0] ?? ""; - let lastSegment = premintSegments[1] ?? ""; + const firstSegment = premintSegments[0] ?? ''; + let lastSegment = premintSegments[1] ?? ''; if (decimals > lastSegment.length) { try { - lastSegment += "0".repeat(decimals - lastSegment.length); + lastSegment += '0'.repeat(decimals - lastSegment.length); } catch { // .repeat gives an error if decimals number is too large throw new OptionsError({ - premint: "Decimals number too large", + premint: 'Decimals number too large', }); } } else if (decimals < lastSegment.length) { throw new OptionsError({ - premint: "Too many decimals", + premint: 'Too many decimals', }); } // concat segments without leading zeros - result = firstSegment.concat(lastSegment).replace(/^0+/, ""); + result = firstSegment.concat(lastSegment).replace(/^0+/, ''); } if (result.length === 0) { - result = "0"; + result = '0'; } return result; } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); } const components = defineComponents({ ERC20Component: { - path: "openzeppelin::token::erc20", + path: 'openzeppelin::token::erc20', substorage: { - name: "erc20", - type: "ERC20Component::Storage", + name: 'erc20', + type: 'ERC20Component::Storage', }, event: { - name: "ERC20Event", - type: "ERC20Component::Event", + name: 'ERC20Event', + type: 'ERC20Component::Event', }, impls: [ { - name: "ERC20InternalImpl", + name: 'ERC20InternalImpl', embed: false, - value: "ERC20Component::InternalImpl", + value: 'ERC20Component::InternalImpl', }, ], }, @@ -297,15 +277,11 @@ const components = defineComponents({ const functions = defineFunctions({ burn: { - args: [getSelfArg(), { name: "value", type: "u256" }], - code: ["self.erc20.burn(get_caller_address(), value);"], + args: [getSelfArg(), { name: 'value', type: 'u256' }], + code: ['self.erc20.burn(get_caller_address(), value);'], }, mint: { - args: [ - getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, - ], - code: ["self.erc20.mint(recipient, amount);"], + args: [getSelfArg(), { name: 'recipient', type: 'ContractAddress' }, { name: 'amount', type: 'u256' }], + code: ['self.erc20.mint(recipient, amount);'], }, }); diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index 7f260e482..b5f4583e9 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -1,19 +1,20 @@ -import test from "ava"; +import test from 'ava'; -import { buildERC721, ERC721Options } from "./erc721"; -import { printContract } from "./print"; -import { royaltyInfoOptions } from "./set-royalty-info"; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; -import { erc721, OptionsError } from "."; +import type { OptionsError } from '.'; +import { erc721 } from '.'; -const NAME = "MyToken"; -const CUSTOM_NAME = "CustomToken"; -const SYMBOL = "MTK"; -const CUSTOM_SYMBOL = "CTK"; -const APP_NAME = "MY_DAPP_NAME"; -const APP_VERSION = "MY_DAPP_VERSION"; -const BASE_URI = - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const SYMBOL = 'MTK'; +const CUSTOM_SYMBOL = 'CTK'; +const APP_NAME = 'MY_DAPP_NAME'; +const APP_VERSION = 'MY_DAPP_VERSION'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; const allFeaturesON: Partial = { mintable: true, @@ -28,7 +29,7 @@ const allFeaturesON: Partial = { } as const; function testERC721(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC721({ name: NAME, symbol: SYMBOL, @@ -42,7 +43,7 @@ function testERC721(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, (t) => { + test(title, t => { t.is( erc721.print(opts), printContract( @@ -56,83 +57,83 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { }); } -testERC721("basic non-upgradeable", { +testERC721('basic non-upgradeable', { upgradeable: false, }); -testERC721("basic", {}); +testERC721('basic', {}); -testERC721("base uri", { +testERC721('base uri', { baseUri: BASE_URI, }); -testERC721("burnable", { +testERC721('burnable', { burnable: true, }); -testERC721("pausable", { +testERC721('pausable', { pausable: true, }); -testERC721("mintable", { +testERC721('mintable', { mintable: true, }); -testERC721("enumerable", { +testERC721('enumerable', { enumerable: true, }); -testERC721("pausable + enumerable", { +testERC721('pausable + enumerable', { pausable: true, enumerable: true, }); -testERC721("mintable + roles", { +testERC721('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC721("royalty info disabled", { +testERC721('royalty info disabled', { royaltyInfo: royaltyInfoOptions.disabled, }); -testERC721("royalty info enabled default + ownable", { +testERC721('royalty info enabled default + ownable', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "ownable", + access: 'ownable', }); -testERC721("royalty info enabled default + roles", { +testERC721('royalty info enabled default + roles', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "roles", + access: 'roles', }); -testERC721("royalty info enabled custom + ownable", { +testERC721('royalty info enabled custom + ownable', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "ownable", + access: 'ownable', }); -testERC721("royalty info enabled custom + roles", { +testERC721('royalty info enabled custom + roles', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "roles", + access: 'roles', }); -testERC721("full non-upgradeable", { +testERC721('full non-upgradeable', { ...allFeaturesON, upgradeable: false, }); -testERC721("erc721 votes", { +testERC721('erc721 votes', { votes: true, appName: APP_NAME, }); -testERC721("erc721 votes, version", { +testERC721('erc721 votes, version', { votes: true, appName: APP_NAME, appVersion: APP_VERSION, }); -test("erc721 votes, no name", async (t) => { +test('erc721 votes, no name', async t => { const error = t.throws(() => buildERC721({ name: NAME, @@ -140,67 +141,58 @@ test("erc721 votes, no name", async (t) => { votes: true, }), ); - t.is( - (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); }); -test("erc721 votes, no version", async (t) => { +test('erc721 votes, no version', async t => { const error = t.throws(() => buildERC721({ name: NAME, symbol: SYMBOL, votes: true, appName: APP_NAME, - appVersion: "", + appVersion: '', }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -test("erc721 votes, empty version", async (t) => { +test('erc721 votes, empty version', async t => { const error = t.throws(() => buildERC721({ name: NAME, symbol: SYMBOL, votes: true, appName: APP_NAME, - appVersion: "", // avoids default value of v1 + appVersion: '', // avoids default value of v1 }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -testERC721("erc721 votes, non-upgradeable", { +testERC721('erc721 votes, non-upgradeable', { votes: true, appName: APP_NAME, upgradeable: false, }); -testERC721("full upgradeable", allFeaturesON); +testERC721('full upgradeable', allFeaturesON); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); +testAPIEquivalence('API basic', { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); -testAPIEquivalence("API full upgradeable", { +testAPIEquivalence('API full upgradeable', { ...allFeaturesON, name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL, }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); t.is(erc721.isAccessControlRequired({ upgradeable: true }), true); diff --git a/packages/core/cairo/src/erc721.ts b/packages/core/cairo/src/erc721.ts index 12f7ecd34..6ddaf2efd 100644 --- a/packages/core/cairo/src/erc721.ts +++ b/packages/core/cairo/src/erc721.ts @@ -1,43 +1,35 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { addSRC5Component, addVotesComponent } from "./common-components"; -import { externalTrait } from "./external-trait"; -import { toByteArray, toFelt252 } from "./utils/convert-strings"; -import { OptionsError } from "./error"; -import { - RoyaltyInfoOptions, - setRoyaltyInfo, - defaults as royaltyInfoDefaults, -} from "./set-royalty-info"; +import type { BaseImplementedTrait, Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { addSRC5Component, addVotesComponent } from './common-components'; +import { externalTrait } from './external-trait'; +import { toByteArray, toFelt252 } from './utils/convert-strings'; +import { OptionsError } from './error'; +import type { RoyaltyInfoOptions } from './set-royalty-info'; +import { setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; export const defaults: Required = { - name: "MyToken", - symbol: "MTK", - baseUri: "", + name: 'MyToken', + symbol: 'MTK', + baseUri: '', burnable: false, pausable: false, mintable: false, enumerable: false, votes: false, royaltyInfo: royaltyInfoDefaults, - appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: "v1", + appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: 'v1', access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info, @@ -79,10 +71,7 @@ function withDefaults(opts: ERC721Options): Required { export function isAccessControlRequired(opts: Partial): boolean { return ( - opts.mintable === true || - opts.pausable === true || - opts.upgradeable === true || - opts.royaltyInfo?.enabled === true + opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.royaltyInfo?.enabled === true ); } @@ -91,12 +80,7 @@ export function buildERC721(opts: ERC721Options): Contract { const allOpts = withDefaults(opts); - addBase( - c, - toByteArray(allOpts.name), - toByteArray(allOpts.symbol), - toByteArray(allOpts.baseUri), - ); + addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.baseUri)); addERC721Mixin(c); if (allOpts.pausable) { @@ -130,83 +114,74 @@ function addHooks(c: ContractBuilder, opts: Required) { if (usesCustomHooks) { const ERC721HooksTrait: BaseImplementedTrait = { name: `ERC721HooksImpl`, - of: "ERC721Component::ERC721HooksTrait", + of: 'ERC721Component::ERC721HooksTrait', tags: [], priority: 0, }; c.addImplementedTrait(ERC721HooksTrait); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); const requiresMutState = opts.enumerable || opts.votes; const initStateLine = requiresMutState - ? "let mut contract_state = self.get_contract_mut()" - : "let contract_state = self.get_contract()"; + ? 'let mut contract_state = self.get_contract_mut()' + : 'let contract_state = self.get_contract()'; const beforeUpdateCode = [initStateLine]; if (opts.pausable) { - beforeUpdateCode.push("contract_state.pausable.assert_not_paused()"); + beforeUpdateCode.push('contract_state.pausable.assert_not_paused()'); } if (opts.enumerable) { - beforeUpdateCode.push( - "contract_state.erc721_enumerable.before_update(to, token_id)", - ); + beforeUpdateCode.push('contract_state.erc721_enumerable.before_update(to, token_id)'); } if (opts.votes) { if (!opts.appName) { throw new OptionsError({ - appName: "Application Name is required when Votes are enabled", + appName: 'Application Name is required when Votes are enabled', }); } if (!opts.appVersion) { throw new OptionsError({ - appVersion: "Application Version is required when Votes are enabled", + appVersion: 'Application Version is required when Votes are enabled', }); } addVotesComponent( c, - toFelt252(opts.appName, "appName"), - toFelt252(opts.appVersion, "appVersion"), - "SNIP12 Metadata", - ); - beforeUpdateCode.push("let previous_owner = self._owner_of(token_id);"); - beforeUpdateCode.push( - "contract_state.votes.transfer_voting_units(previous_owner, to, 1);", + toFelt252(opts.appName, 'appName'), + toFelt252(opts.appVersion, 'appVersion'), + 'SNIP12 Metadata', ); + beforeUpdateCode.push('let previous_owner = self._owner_of(token_id);'); + beforeUpdateCode.push('contract_state.votes.transfer_voting_units(previous_owner, to, 1);'); } c.addFunction(ERC721HooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", + name: 'ref self', type: `ERC721Component::ComponentState`, }, - { name: "to", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "auth", type: "ContractAddress" }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'auth', type: 'ContractAddress' }, ], code: beforeUpdateCode, }); } else { - c.addUseClause("openzeppelin::token::erc721", "ERC721HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc721', 'ERC721HooksEmptyImpl'); } } function addERC721Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC721Component, { - name: "ERC721MixinImpl", - value: "ERC721Component::ERC721MixinImpl", + name: 'ERC721MixinImpl', + value: 'ERC721Component::ERC721MixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } -function addBase( - c: ContractBuilder, - name: string, - symbol: string, - baseUri: string, -) { +function addBase(c: ContractBuilder, name: string, symbol: string, baseUri: string) { c.addComponent(components.ERC721Component, [name, symbol, baseUri], true); } @@ -215,22 +190,15 @@ function addEnumerable(c: ContractBuilder) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("core::num::traits", "Zero"); - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('core::num::traits', 'Zero'); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.safe_mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.safe_mint, access, 'MINTER', 'minter'); // Camel case version of safe_mint. Access control and pausable are already set on safe_mint. c.addFunction(externalTrait, functions.safeMint); @@ -238,42 +206,42 @@ function addMintable(c: ContractBuilder, access: Access) { const components = defineComponents({ ERC721Component: { - path: "openzeppelin::token::erc721", + path: 'openzeppelin::token::erc721', substorage: { - name: "erc721", - type: "ERC721Component::Storage", + name: 'erc721', + type: 'ERC721Component::Storage', }, event: { - name: "ERC721Event", - type: "ERC721Component::Event", + name: 'ERC721Event', + type: 'ERC721Component::Event', }, impls: [ { - name: "ERC721InternalImpl", + name: 'ERC721InternalImpl', embed: false, - value: "ERC721Component::InternalImpl", + value: 'ERC721Component::InternalImpl', }, ], }, ERC721EnumerableComponent: { - path: "openzeppelin::token::erc721::extensions", + path: 'openzeppelin::token::erc721::extensions', substorage: { - name: "erc721_enumerable", - type: "ERC721EnumerableComponent::Storage", + name: 'erc721_enumerable', + type: 'ERC721EnumerableComponent::Storage', }, event: { - name: "ERC721EnumerableEvent", - type: "ERC721EnumerableComponent::Event", + name: 'ERC721EnumerableEvent', + type: 'ERC721EnumerableComponent::Event', }, impls: [ { - name: "ERC721EnumerableImpl", - value: "ERC721EnumerableComponent::ERC721EnumerableImpl", + name: 'ERC721EnumerableImpl', + value: 'ERC721EnumerableComponent::ERC721EnumerableImpl', }, { - name: "ERC721EnumerableInternalImpl", + name: 'ERC721EnumerableInternalImpl', embed: false, - value: "ERC721EnumerableComponent::InternalImpl", + value: 'ERC721EnumerableComponent::InternalImpl', }, ], }, @@ -281,25 +249,25 @@ const components = defineComponents({ const functions = defineFunctions({ burn: { - args: [getSelfArg(), { name: "token_id", type: "u256" }], - code: ["self.erc721.update(Zero::zero(), token_id, get_caller_address());"], + args: [getSelfArg(), { name: 'token_id', type: 'u256' }], + code: ['self.erc721.update(Zero::zero(), token_id, get_caller_address());'], }, safe_mint: { args: [ getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "data", type: "Span" }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'data', type: 'Span' }, ], - code: ["self.erc721.safe_mint(recipient, token_id, data);"], + code: ['self.erc721.safe_mint(recipient, token_id, data);'], }, safeMint: { args: [ getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "tokenId", type: "u256" }, - { name: "data", type: "Span" }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'tokenId', type: 'u256' }, + { name: 'data', type: 'Span' }, ], - code: ["self.safe_mint(recipient, tokenId, data);"], + code: ['self.safe_mint(recipient, tokenId, data);'], }, }); diff --git a/packages/core/cairo/src/error.ts b/packages/core/cairo/src/error.ts index 8b3d2b8e3..a26433319 100644 --- a/packages/core/cairo/src/error.ts +++ b/packages/core/cairo/src/error.ts @@ -2,6 +2,6 @@ export type OptionsErrorMessages = { [prop in string]?: string }; export class OptionsError extends Error { constructor(readonly messages: OptionsErrorMessages) { - super("Invalid options"); + super('Invalid options'); } } diff --git a/packages/core/cairo/src/external-trait.ts b/packages/core/cairo/src/external-trait.ts index 04e671d6d..152ab3ed2 100644 --- a/packages/core/cairo/src/external-trait.ts +++ b/packages/core/cairo/src/external-trait.ts @@ -1,8 +1,8 @@ -import type { BaseImplementedTrait } from "./contract"; +import type { BaseImplementedTrait } from './contract'; export const externalTrait: BaseImplementedTrait = { - name: "ExternalImpl", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'ExternalImpl', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; diff --git a/packages/core/cairo/src/generate/account.ts b/packages/core/cairo/src/generate/account.ts index b61bf2056..175ba8df9 100644 --- a/packages/core/cairo/src/generate/account.ts +++ b/packages/core/cairo/src/generate/account.ts @@ -1,12 +1,12 @@ -import { accountTypes, type AccountOptions } from "../account"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import { accountTypes, type AccountOptions } from '../account'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyAccount"], + name: ['MyAccount'], type: accountTypes, declare: booleans, deploy: booleans, diff --git a/packages/core/cairo/src/generate/alternatives.ts b/packages/core/cairo/src/generate/alternatives.ts index c4b282064..b66e733cf 100644 --- a/packages/core/cairo/src/generate/alternatives.ts +++ b/packages/core/cairo/src/generate/alternatives.ts @@ -4,9 +4,7 @@ type Alternatives = { [k in keyof B]: B[k][number]; }; -export function* generateAlternatives( - blueprint: B, -): Generator> { +export function* generateAlternatives(blueprint: B): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, values, @@ -15,9 +13,7 @@ export function* generateAlternatives( })); for (; !done(); advance()) { - yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]), - ) as Alternatives; + yield Object.fromEntries(entries.map(e => [e.key, e.values[e.current % e.limit]])) as Alternatives; } function done() { diff --git a/packages/core/cairo/src/generate/custom.ts b/packages/core/cairo/src/generate/custom.ts index 884032068..40207666a 100644 --- a/packages/core/cairo/src/generate/custom.ts +++ b/packages/core/cairo/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from "../custom"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { CustomOptions } from '../custom'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyContract"], + name: ['MyContract'], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/cairo/src/generate/erc1155.ts b/packages/core/cairo/src/generate/erc1155.ts index 9fc09c9ca..07170fcbd 100644 --- a/packages/core/cairo/src/generate/erc1155.ts +++ b/packages/core/cairo/src/generate/erc1155.ts @@ -1,25 +1,21 @@ -import type { ERC1155Options } from "../erc1155"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { royaltyInfoOptions } from "../set-royalty-info"; -import { generateAlternatives } from "./alternatives"; +import type { ERC1155Options } from '../erc1155'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + baseUri: ['https://example.com/'], burnable: booleans, pausable: booleans, mintable: booleans, updatableUri: booleans, upgradeable: upgradeableOptions, - royaltyInfo: [ - royaltyInfoOptions.disabled, - royaltyInfoOptions.enabledDefault, - royaltyInfoOptions.enabledCustom, - ], + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], access: accessOptions, info: infoOptions, }; diff --git a/packages/core/cairo/src/generate/erc20.ts b/packages/core/cairo/src/generate/erc20.ts index 2f8235af1..51659af86 100644 --- a/packages/core/cairo/src/generate/erc20.ts +++ b/packages/core/cairo/src/generate/erc20.ts @@ -1,21 +1,21 @@ -import type { ERC20Options } from "../erc20"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC20Options } from '../erc20'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], + name: ['MyToken'], + symbol: ['MTK'], burnable: booleans, pausable: booleans, mintable: booleans, - premint: ["1"], + premint: ['1'], votes: booleans, - appName: ["MyApp"], - appVersion: ["v1"], + appName: ['MyApp'], + appVersion: ['v1'], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/erc721.ts b/packages/core/cairo/src/generate/erc721.ts index f3003120a..e78a94f15 100644 --- a/packages/core/cairo/src/generate/erc721.ts +++ b/packages/core/cairo/src/generate/erc721.ts @@ -1,28 +1,24 @@ -import type { ERC721Options } from "../erc721"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { royaltyInfoOptions } from "../set-royalty-info"; -import { generateAlternatives } from "./alternatives"; +import type { ERC721Options } from '../erc721'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + symbol: ['MTK'], + baseUri: ['https://example.com/'], burnable: booleans, enumerable: booleans, votes: booleans, - appName: ["MyApp"], - appVersion: ["v1"], + appName: ['MyApp'], + appVersion: ['v1'], pausable: booleans, mintable: booleans, - royaltyInfo: [ - royaltyInfoOptions.disabled, - royaltyInfoOptions.enabledDefault, - royaltyInfoOptions.enabledCustom, - ], + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/governor.ts b/packages/core/cairo/src/generate/governor.ts index 6d55cb762..9cb8d6f26 100644 --- a/packages/core/cairo/src/generate/governor.ts +++ b/packages/core/cairo/src/generate/governor.ts @@ -1,37 +1,30 @@ -import { - clockModeOptions, - GovernorOptions, - quorumModeOptions, - timelockOptions, - votesOptions, -} from "../governor"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { GovernorOptions } from '../governor'; +import { clockModeOptions, quorumModeOptions, timelockOptions, votesOptions } from '../governor'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyGovernor"], - delay: ["1 day"], - period: ["1 week"], - proposalThreshold: ["1"], + name: ['MyGovernor'], + delay: ['1 day'], + period: ['1 week'], + proposalThreshold: ['1'], decimals: [18], quorumMode: quorumModeOptions, quorumPercent: [10], - quorumAbsolute: ["10"], + quorumAbsolute: ['10'], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, settings: booleans, - appName: ["Openzeppelin Governor"], - appVersion: ["v1"], + appName: ['Openzeppelin Governor'], + appVersion: ['v1'], upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateGovernorOptions(): Generator< - Required -> { +export function* generateGovernorOptions(): Generator> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index ab6ebd08d..44d777f26 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -1,64 +1,65 @@ -import { promises as fs } from "fs"; -import path from "path"; -import crypto from "crypto"; - -import { generateERC20Options } from "./erc20"; -import { generateERC721Options } from "./erc721"; -import { generateERC1155Options } from "./erc1155"; -import { generateAccountOptions } from "./account"; -import { generateCustomOptions } from "./custom"; -import { generateGovernorOptions } from "./governor"; -import { generateVestingOptions } from "./vesting"; -import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; -import { printContract } from "../print"; -import { OptionsError } from "../error"; -import { findCover } from "../utils/find-cover"; -import type { Contract } from "../contract"; - -type Subset = "all" | "minimal-cover"; +import { promises as fs } from 'fs'; +import path from 'path'; +import crypto from 'crypto'; + +import { generateERC20Options } from './erc20'; +import { generateERC721Options } from './erc721'; +import { generateERC1155Options } from './erc1155'; +import { generateAccountOptions } from './account'; +import { generateCustomOptions } from './custom'; +import { generateGovernorOptions } from './governor'; +import { generateVestingOptions } from './vesting'; +import type { GenericOptions, KindedOptions } from '../build-generic'; +import { buildGeneric } from '../build-generic'; +import { printContract } from '../print'; +import { OptionsError } from '../error'; +import { findCover } from '../utils/find-cover'; +import type { Contract } from '../contract'; + +type Subset = 'all' | 'minimal-cover'; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === "ERC20") { + if (!kind || kind === 'ERC20') { for (const kindOpts of generateERC20Options()) { - yield { kind: "ERC20", ...kindOpts }; + yield { kind: 'ERC20', ...kindOpts }; } } - if (!kind || kind === "ERC721") { + if (!kind || kind === 'ERC721') { for (const kindOpts of generateERC721Options()) { - yield { kind: "ERC721", ...kindOpts }; + yield { kind: 'ERC721', ...kindOpts }; } } - if (!kind || kind === "ERC1155") { + if (!kind || kind === 'ERC1155') { for (const kindOpts of generateERC1155Options()) { - yield { kind: "ERC1155", ...kindOpts }; + yield { kind: 'ERC1155', ...kindOpts }; } } - if (!kind || kind === "Account") { + if (!kind || kind === 'Account') { for (const kindOpts of generateAccountOptions()) { - yield { kind: "Account", ...kindOpts }; + yield { kind: 'Account', ...kindOpts }; } } - if (!kind || kind === "Custom") { + if (!kind || kind === 'Custom') { for (const kindOpts of generateCustomOptions()) { - yield { kind: "Custom", ...kindOpts }; + yield { kind: 'Custom', ...kindOpts }; } } - if (!kind || kind === "Governor") { + if (!kind || kind === 'Governor') { for (const kindOpts of generateGovernorOptions()) { - yield { kind: "Governor", ...kindOpts }; + yield { kind: 'Governor', ...kindOpts }; } } - if (!kind || kind === "Vesting") { + if (!kind || kind === 'Vesting') { for (const kindOpts of generateVestingOptions()) { - yield { kind: "Vesting", ...kindOpts }; + yield { kind: 'Vesting', ...kindOpts }; } } } @@ -73,18 +74,11 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset( - subset: Subset, - kind?: Kind, -): GeneratedContract[] { +function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { - const id = crypto - .createHash("sha1") - .update(JSON.stringify(options)) - .digest() - .toString("hex"); + const id = crypto.createHash('sha1').update(JSON.stringify(options)).digest().toString('hex'); try { const contract = buildGeneric(options); @@ -98,48 +92,37 @@ function generateContractSubset( } } - if (subset === "all") { + if (subset === 'all') { return contracts; } else { - const getParents = (c: GeneratedContract) => - c.contract.components.map((p) => p.path); + const getParents = (c: GeneratedContract) => c.contract.components.map(p => p.path); function filterByUpgradeableSetTo(isUpgradeable: boolean) { return (c: GeneratedContract) => { switch (c.options.kind) { - case "Vesting": + case 'Vesting': return isUpgradeable === false; - case "Account": - case "ERC20": - case "ERC721": - case "ERC1155": - case "Governor": - case "Custom": + case 'Account': + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Governor': + case 'Custom': return c.options.upgradeable === isUpgradeable; default: { const _: never = c.options; - throw new Error("Unknown kind"); + throw new Error('Unknown kind'); } } }; } return [ - ...findCover( - contracts.filter(filterByUpgradeableSetTo(true)), - getParents, - ), - ...findCover( - contracts.filter(filterByUpgradeableSetTo(false)), - getParents, - ), + ...findCover(contracts.filter(filterByUpgradeableSetTo(true)), getParents), + ...findCover(contracts.filter(filterByUpgradeableSetTo(false)), getParents), ]; } } -export function* generateSources( - subset: Subset, - uniqueName?: boolean, - kind?: Kind, -): Generator { +export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Kind): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { if (uniqueName) { @@ -159,13 +142,9 @@ export async function writeGeneratedSources( await fs.mkdir(dir, { recursive: true }); const contractNames = []; - for (const { id, contract, source } of generateSources( - subset, - uniqueName, - kind, - )) { + for (const { id, contract, source } of generateSources(subset, uniqueName, kind)) { const name = uniqueName ? contract.name : id; - await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); + await fs.writeFile(path.format({ dir, name, ext: '.cairo' }), source); contractNames.push(name); } diff --git a/packages/core/cairo/src/generate/vesting.ts b/packages/core/cairo/src/generate/vesting.ts index 49a34f72e..9be2e455d 100644 --- a/packages/core/cairo/src/generate/vesting.ts +++ b/packages/core/cairo/src/generate/vesting.ts @@ -1,13 +1,13 @@ -import { infoOptions } from "../set-info"; -import type { VestingOptions } from "../vesting"; -import { generateAlternatives } from "./alternatives"; +import { infoOptions } from '../set-info'; +import type { VestingOptions } from '../vesting'; +import { generateAlternatives } from './alternatives'; const blueprint = { - name: ["MyVesting"], - startDate: ["2024-12-31T23:59"], - duration: ["90 days", "1 year"], - cliffDuration: ["0 seconds", "30 day"], - schedule: ["linear", "custom"] as const, + name: ['MyVesting'], + startDate: ['2024-12-31T23:59'], + duration: ['90 days', '1 year'], + cliffDuration: ['0 seconds', '30 day'], + schedule: ['linear', 'custom'] as const, info: infoOptions, }; diff --git a/packages/core/cairo/src/governor.test.ts b/packages/core/cairo/src/governor.test.ts index 561e4dc08..0f102c18f 100644 --- a/packages/core/cairo/src/governor.test.ts +++ b/packages/core/cairo/src/governor.test.ts @@ -1,17 +1,18 @@ -import test from "ava"; -import { governor } from "."; +import test from 'ava'; +import { governor } from '.'; -import { buildGovernor, GovernorOptions } from "./governor"; -import { printContract } from "./print"; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import { printContract } from './print'; -const NAME = "MyGovernor"; +const NAME = 'MyGovernor'; function testGovernor(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildGovernor({ name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', ...opts, }); t.snapshot(printContract(c)); @@ -22,14 +23,14 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, (t) => { + test(title, t => { t.is( governor.print(opts), printContract( buildGovernor({ name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', ...opts, }), ), @@ -37,150 +38,150 @@ function testAPIEquivalence(title: string, opts?: GovernorOptions) { }); } -testGovernor("basic + upgradeable", { +testGovernor('basic + upgradeable', { upgradeable: true, }); -testGovernor("basic non-upgradeable", { +testGovernor('basic non-upgradeable', { upgradeable: false, }); -testGovernor("erc20 votes + timelock", { - votes: "erc20votes", - timelock: "openzeppelin", +testGovernor('erc20 votes + timelock', { + votes: 'erc20votes', + timelock: 'openzeppelin', }); -testGovernor("erc721 votes + timelock", { - votes: "erc721votes", - timelock: "openzeppelin", +testGovernor('erc721 votes + timelock', { + votes: 'erc721votes', + timelock: 'openzeppelin', }); -testGovernor("custom name", { - name: "CustomGovernor", +testGovernor('custom name', { + name: 'CustomGovernor', }); -testGovernor("custom settings", { - delay: "2 hours", - period: "1 year", - proposalThreshold: "300", +testGovernor('custom settings', { + delay: '2 hours', + period: '1 year', + proposalThreshold: '300', settings: true, }); -testGovernor("quorum mode absolute", { - quorumMode: "absolute", - quorumAbsolute: "200", +testGovernor('quorum mode absolute', { + quorumMode: 'absolute', + quorumAbsolute: '200', }); -testGovernor("quorum mode percent", { - quorumMode: "percent", +testGovernor('quorum mode percent', { + quorumMode: 'percent', quorumPercent: 40, }); -testGovernor("custom snip12 metadata", { - appName: "Governor", - appVersion: "v3", +testGovernor('custom snip12 metadata', { + appName: 'Governor', + appVersion: 'v3', }); -testGovernor("all options", { +testGovernor('all options', { name: NAME, - delay: "4 day", - period: "4 week", - proposalThreshold: "500", + delay: '4 day', + period: '4 week', + proposalThreshold: '500', decimals: 10, - quorumMode: "absolute", + quorumMode: 'absolute', quorumPercent: 50, - quorumAbsolute: "200", - votes: "erc721votes", - clockMode: "timestamp", - timelock: "openzeppelin", + quorumAbsolute: '200', + votes: 'erc721votes', + clockMode: 'timestamp', + timelock: 'openzeppelin', settings: true, - appName: "MyApp2", - appVersion: "v5", + appName: 'MyApp2', + appVersion: 'v5', upgradeable: true, }); -testAPIEquivalence("API basic + upgradeable", { +testAPIEquivalence('API basic + upgradeable', { name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', upgradeable: true, }); -testAPIEquivalence("API basic non-upgradeable", { +testAPIEquivalence('API basic non-upgradeable', { name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', upgradeable: false, }); -testAPIEquivalence("API erc20 votes + timelock", { +testAPIEquivalence('API erc20 votes + timelock', { name: NAME, - delay: "1 day", - period: "1 week", - votes: "erc20votes", - timelock: "openzeppelin", + delay: '1 day', + period: '1 week', + votes: 'erc20votes', + timelock: 'openzeppelin', }); -testAPIEquivalence("API erc721 votes + timelock", { +testAPIEquivalence('API erc721 votes + timelock', { name: NAME, - delay: "1 day", - period: "1 week", - votes: "erc721votes", - timelock: "openzeppelin", + delay: '1 day', + period: '1 week', + votes: 'erc721votes', + timelock: 'openzeppelin', }); -testAPIEquivalence("API custom name", { - name: "CustomGovernor", - delay: "1 day", - period: "1 week", +testAPIEquivalence('API custom name', { + name: 'CustomGovernor', + delay: '1 day', + period: '1 week', }); -testAPIEquivalence("API custom settings", { +testAPIEquivalence('API custom settings', { name: NAME, - delay: "2 hours", - period: "1 year", - proposalThreshold: "300", + delay: '2 hours', + period: '1 year', + proposalThreshold: '300', settings: true, }); -testAPIEquivalence("API quorum mode absolute", { +testAPIEquivalence('API quorum mode absolute', { name: NAME, - delay: "1 day", - period: "1 week", - quorumMode: "absolute", - quorumAbsolute: "200", + delay: '1 day', + period: '1 week', + quorumMode: 'absolute', + quorumAbsolute: '200', }); -testAPIEquivalence("API quorum mode percent", { +testAPIEquivalence('API quorum mode percent', { name: NAME, - delay: "1 day", - period: "1 week", - quorumMode: "percent", + delay: '1 day', + period: '1 week', + quorumMode: 'percent', quorumPercent: 40, }); -testAPIEquivalence("API custom snip12 metadata", { +testAPIEquivalence('API custom snip12 metadata', { name: NAME, - delay: "1 day", - period: "1 week", - appName: "Governor", - appVersion: "v3", + delay: '1 day', + period: '1 week', + appName: 'Governor', + appVersion: 'v3', }); -testAPIEquivalence("API all options", { +testAPIEquivalence('API all options', { name: NAME, - delay: "4 day", - period: "4 week", - proposalThreshold: "500", + delay: '4 day', + period: '4 week', + proposalThreshold: '500', decimals: 10, - quorumMode: "absolute", + quorumMode: 'absolute', quorumPercent: 50, - quorumAbsolute: "200", - votes: "erc721votes", - clockMode: "timestamp", - timelock: "openzeppelin", + quorumAbsolute: '200', + votes: 'erc721votes', + clockMode: 'timestamp', + timelock: 'openzeppelin', settings: true, - appName: "MyApp2", - appVersion: "v5", + appName: 'MyApp2', + appVersion: 'v5', upgradeable: true, }); diff --git a/packages/core/cairo/src/governor.ts b/packages/core/cairo/src/governor.ts index 50b6c65ef..f0c05f2e7 100644 --- a/packages/core/cairo/src/governor.ts +++ b/packages/core/cairo/src/governor.ts @@ -1,51 +1,49 @@ -import { - contractDefaults as commonDefaults, - withCommonDefaults, -} from "./common-options"; -import type { CommonOptions } from "./common-options"; -import { ContractBuilder, Contract } from "./contract"; -import { OptionsError } from "./error"; -import { printContract } from "./print"; -import { setInfo } from "./set-info"; -import { setUpgradeableGovernor } from "./set-upgradeable"; -import { defineComponents } from "./utils/define-components"; -import { durationToTimestamp } from "./utils/duration"; -import { addSNIP12Metadata, addSRC5Component } from "./common-components"; -import { toUint } from "./utils/convert-strings"; -export const clockModeOptions = ["timestamp"] as const; -export const clockModeDefault = "timestamp" as const; +import { contractDefaults as commonDefaults, withCommonDefaults } from './common-options'; +import type { CommonOptions } from './common-options'; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { OptionsError } from './error'; +import { printContract } from './print'; +import { setInfo } from './set-info'; +import { setUpgradeableGovernor } from './set-upgradeable'; +import { defineComponents } from './utils/define-components'; +import { durationToTimestamp } from './utils/duration'; +import { addSNIP12Metadata, addSRC5Component } from './common-components'; +import { toUint } from './utils/convert-strings'; +export const clockModeOptions = ['timestamp'] as const; +export const clockModeDefault = 'timestamp' as const; export type ClockMode = (typeof clockModeOptions)[number]; -const extensionPath = "openzeppelin::governance::governor::extensions"; -const extensionExternalSection = "Extensions (external)"; -const extensionInternalSection = "Extensions (internal)"; +const extensionPath = 'openzeppelin::governance::governor::extensions'; +const extensionExternalSection = 'Extensions (external)'; +const extensionInternalSection = 'Extensions (internal)'; export const defaults: Required = { - name: "MyGovernor", - delay: "1 day", - period: "1 week", - votes: "erc20votes", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', + votes: 'erc20votes', clockMode: clockModeDefault, - timelock: "openzeppelin", + timelock: 'openzeppelin', decimals: 18, - proposalThreshold: "0", - quorumMode: "percent", + proposalThreshold: '0', + quorumMode: 'percent', quorumPercent: 4, - quorumAbsolute: "", + quorumAbsolute: '', settings: true, upgradeable: commonDefaults.upgradeable, - appName: "OpenZeppelin Governor", - appVersion: "v1", + appName: 'OpenZeppelin Governor', + appVersion: 'v1', info: commonDefaults.info, } as const; -export const quorumModeOptions = ["percent", "absolute"] as const; +export const quorumModeOptions = ['percent', 'absolute'] as const; export type QuorumMode = (typeof quorumModeOptions)[number]; -export const votesOptions = ["erc20votes", "erc721votes"] as const; +export const votesOptions = ['erc20votes', 'erc721votes'] as const; export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, "openzeppelin"] as const; +export const timelockOptions = [false, 'openzeppelin'] as const; export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { @@ -96,8 +94,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { validateDecimals(allOpts.decimals); addBase(c, allOpts); - addSRC5Component(c, "SRC5"); - addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, "SNIP12 Metadata"); + addSRC5Component(c, 'SRC5'); + addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, 'SNIP12 Metadata'); addCounting(c, allOpts); addQuorumAndVotes(c, allOpts); addSettings(c, allOpts); @@ -110,43 +108,42 @@ export function buildGovernor(opts: GovernorOptions): Contract { const components = defineComponents({ GovernorComponent: { - path: "openzeppelin::governance::governor", + path: 'openzeppelin::governance::governor', substorage: { - name: "governor", - type: "GovernorComponent::Storage", + name: 'governor', + type: 'GovernorComponent::Storage', }, event: { - name: "GovernorEvent", - type: "GovernorComponent::Event", + name: 'GovernorEvent', + type: 'GovernorComponent::Event', }, impls: [ { - name: "GovernorImpl", - value: "GovernorComponent::GovernorImpl", - section: "Governor Core", + name: 'GovernorImpl', + value: 'GovernorComponent::GovernorImpl', + section: 'Governor Core', }, ], }, GovernorSettingsComponent: { path: extensionPath, substorage: { - name: "governor_settings", - type: "GovernorSettingsComponent::Storage", + name: 'governor_settings', + type: 'GovernorSettingsComponent::Storage', }, event: { - name: "GovernorSettingsEvent", - type: "GovernorSettingsComponent::Event", + name: 'GovernorSettingsEvent', + type: 'GovernorSettingsComponent::Event', }, impls: [ { - name: "GovernorSettingsAdminImpl", - value: - "GovernorSettingsComponent::GovernorSettingsAdminImpl", + name: 'GovernorSettingsAdminImpl', + value: 'GovernorSettingsComponent::GovernorSettingsAdminImpl', section: extensionExternalSection, }, { - name: "GovernorSettingsImpl", - value: "GovernorSettingsComponent::GovernorSettings", + name: 'GovernorSettingsImpl', + value: 'GovernorSettingsComponent::GovernorSettings', embed: false, section: extensionInternalSection, }, @@ -155,22 +152,22 @@ const components = defineComponents({ GovernorVotesComponent: { path: extensionPath, substorage: { - name: "governor_votes", - type: "GovernorVotesComponent::Storage", + name: 'governor_votes', + type: 'GovernorVotesComponent::Storage', }, event: { - name: "GovernorVotesEvent", - type: "GovernorVotesComponent::Event", + name: 'GovernorVotesEvent', + type: 'GovernorVotesComponent::Event', }, impls: [ { - name: "VotesTokenImpl", - value: "GovernorVotesComponent::VotesTokenImpl", + name: 'VotesTokenImpl', + value: 'GovernorVotesComponent::VotesTokenImpl', section: extensionExternalSection, }, { - name: "GovernorVotesImpl", - value: "GovernorVotesComponent::GovernorVotes", + name: 'GovernorVotesImpl', + value: 'GovernorVotesComponent::GovernorVotes', embed: false, section: extensionInternalSection, }, @@ -179,32 +176,29 @@ const components = defineComponents({ GovernorVotesQuorumFractionComponent: { path: extensionPath, substorage: { - name: "governor_votes", - type: "GovernorVotesQuorumFractionComponent::Storage", + name: 'governor_votes', + type: 'GovernorVotesQuorumFractionComponent::Storage', }, event: { - name: "GovernorVotesEvent", - type: "GovernorVotesQuorumFractionComponent::Event", + name: 'GovernorVotesEvent', + type: 'GovernorVotesQuorumFractionComponent::Event', }, impls: [ { - name: "GovernorQuorumImpl", - value: - "GovernorVotesQuorumFractionComponent::GovernorQuorum", + name: 'GovernorQuorumImpl', + value: 'GovernorVotesQuorumFractionComponent::GovernorQuorum', embed: false, section: extensionInternalSection, }, { - name: "GovernorVotesImpl", - value: - "GovernorVotesQuorumFractionComponent::GovernorVotes", + name: 'GovernorVotesImpl', + value: 'GovernorVotesQuorumFractionComponent::GovernorVotes', embed: false, section: extensionInternalSection, }, { - name: "QuorumFractionImpl", - value: - "GovernorVotesQuorumFractionComponent::QuorumFractionImpl", + name: 'QuorumFractionImpl', + value: 'GovernorVotesQuorumFractionComponent::QuorumFractionImpl', section: extensionExternalSection, }, ], @@ -212,18 +206,17 @@ const components = defineComponents({ GovernorCountingSimpleComponent: { path: extensionPath, substorage: { - name: "governor_counting", - type: "GovernorCountingSimpleComponent::Storage", + name: 'governor_counting', + type: 'GovernorCountingSimpleComponent::Storage', }, event: { - name: "GovernorCountingSimpleEvent", - type: "GovernorCountingSimpleComponent::Event", + name: 'GovernorCountingSimpleEvent', + type: 'GovernorCountingSimpleComponent::Event', }, impls: [ { - name: "GovernorCountingSimpleImpl", - value: - "GovernorCountingSimpleComponent::GovernorCounting", + name: 'GovernorCountingSimpleImpl', + value: 'GovernorCountingSimpleComponent::GovernorCounting', embed: false, section: extensionInternalSection, }, @@ -232,18 +225,17 @@ const components = defineComponents({ GovernorCoreExecutionComponent: { path: extensionPath, substorage: { - name: "governor_execution", - type: "GovernorCoreExecutionComponent::Storage", + name: 'governor_execution', + type: 'GovernorCoreExecutionComponent::Storage', }, event: { - name: "GovernorCoreExecutionEvent", - type: "GovernorCoreExecutionComponent::Event", + name: 'GovernorCoreExecutionEvent', + type: 'GovernorCoreExecutionComponent::Event', }, impls: [ { - name: "GovernorCoreExecutionImpl", - value: - "GovernorCoreExecutionComponent::GovernorExecution", + name: 'GovernorCoreExecutionImpl', + value: 'GovernorCoreExecutionComponent::GovernorExecution', embed: false, section: extensionInternalSection, }, @@ -252,24 +244,22 @@ const components = defineComponents({ GovernorTimelockExecutionComponent: { path: extensionPath, substorage: { - name: "governor_timelock_execution", - type: "GovernorTimelockExecutionComponent::Storage", + name: 'governor_timelock_execution', + type: 'GovernorTimelockExecutionComponent::Storage', }, event: { - name: "GovernorTimelockExecutionEvent", - type: "GovernorTimelockExecutionComponent::Event", + name: 'GovernorTimelockExecutionEvent', + type: 'GovernorTimelockExecutionComponent::Event', }, impls: [ { - name: "TimelockedImpl", - value: - "GovernorTimelockExecutionComponent::TimelockedImpl", + name: 'TimelockedImpl', + value: 'GovernorTimelockExecutionComponent::TimelockedImpl', section: extensionExternalSection, }, { - name: "GovernorTimelockExecutionImpl", - value: - "GovernorTimelockExecutionComponent::GovernorExecution", + name: 'GovernorTimelockExecutionImpl', + value: 'GovernorTimelockExecutionComponent::GovernorExecution', embed: false, section: extensionInternalSection, }, @@ -278,52 +268,44 @@ const components = defineComponents({ }); function addBase(c: ContractBuilder, _: GovernorOptions) { - c.addUseClause("starknet", "ContractAddress"); - c.addUseClause("openzeppelin::governance::governor", "DefaultConfig"); - c.addConstructorArgument({ name: "votes_token", type: "ContractAddress" }); - c.addUseClause( - "openzeppelin::governance::governor::GovernorComponent", - "InternalTrait", - { alias: "GovernorInternalTrait" }, - ); + c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause('openzeppelin::governance::governor', 'DefaultConfig'); + c.addConstructorArgument({ name: 'votes_token', type: 'ContractAddress' }); + c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalTrait', { + alias: 'GovernorInternalTrait', + }); c.addComponent(components.GovernorComponent, [], true); } function addSettings(c: ContractBuilder, allOpts: Required) { c.addConstant({ - name: "VOTING_DELAY", - type: "u64", + name: 'VOTING_DELAY', + type: 'u64', value: getVotingDelay(allOpts).toString(), comment: allOpts.delay, inlineComment: true, }); c.addConstant({ - name: "VOTING_PERIOD", - type: "u64", + name: 'VOTING_PERIOD', + type: 'u64', value: getVotingPeriod(allOpts).toString(), comment: allOpts.period, inlineComment: true, }); c.addConstant({ - name: "PROPOSAL_THRESHOLD", - type: "u256", + name: 'PROPOSAL_THRESHOLD', + type: 'u256', ...getProposalThreshold(allOpts), inlineComment: true, }); if (allOpts.settings) { - c.addUseClause( - `${extensionPath}::GovernorSettingsComponent`, - "InternalTrait", - { alias: "GovernorSettingsInternalTrait" }, - ); + c.addUseClause(`${extensionPath}::GovernorSettingsComponent`, 'InternalTrait', { + alias: 'GovernorSettingsInternalTrait', + }); c.addComponent( components.GovernorSettingsComponent, - [ - { lit: "VOTING_DELAY" }, - { lit: "VOTING_PERIOD" }, - { lit: "PROPOSAL_THRESHOLD" }, - ], + [{ lit: 'VOTING_DELAY' }, { lit: 'VOTING_PERIOD' }, { lit: 'PROPOSAL_THRESHOLD' }], true, ); } else { @@ -333,10 +315,10 @@ function addSettings(c: ContractBuilder, allOpts: Required) { function getVotingDelay(opts: Required): number { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return durationToTimestamp(opts.delay); } else { - throw new Error("Invalid clock mode"); + throw new Error('Invalid clock mode'); } } catch (e) { if (e instanceof Error) { @@ -351,10 +333,10 @@ function getVotingDelay(opts: Required): number { function getVotingPeriod(opts: Required): number { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return durationToTimestamp(opts.period); } else { - throw new Error("Invalid clock mode"); + throw new Error('Invalid clock mode'); } } catch (e) { if (e instanceof Error) { @@ -370,31 +352,26 @@ function getVotingPeriod(opts: Required): number { function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: "Not a valid number", + decimals: 'Not a valid number', }); } } -function getProposalThreshold({ - proposalThreshold, - decimals, - votes, -}: Required): { value: string; comment?: string } { +function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): { + value: string; + comment?: string; +} { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: "Not a valid number", + proposalThreshold: 'Not a valid number', }); } - if ( - /^0+$/.test(proposalThreshold) || - decimals === 0 || - votes === "erc721votes" - ) { + if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { return { value: proposalThreshold }; } else { let value = `${BigInt(proposalThreshold) * BigInt(10) ** BigInt(decimals)}`; - value = toUint(value, "proposalThreshold", "u256").toString(); + value = toUint(value, 'proposalThreshold', 'u256').toString(); return { value: `${value}`, comment: `${proposalThreshold} * pow!(10, ${decimals})`, @@ -402,82 +379,76 @@ function getProposalThreshold({ } } -function addSettingsLocalImpl( - c: ContractBuilder, - _: Required, -) { +function addSettingsLocalImpl(c: ContractBuilder, _: Required) { const settingsTrait = { - name: "GovernorSettings", - of: "GovernorComponent::GovernorSettingsTrait", + name: 'GovernorSettings', + of: 'GovernorComponent::GovernorSettingsTrait', tags: [], - section: "Locally implemented extensions", + section: 'Locally implemented extensions', priority: 2, }; c.addImplementedTrait(settingsTrait); c.addFunction(settingsTrait, { - name: "voting_delay", + name: 'voting_delay', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u64", - code: ["VOTING_DELAY"], + returns: 'u64', + code: ['VOTING_DELAY'], }); c.addFunction(settingsTrait, { - name: "voting_period", + name: 'voting_period', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u64", - code: ["VOTING_PERIOD"], + returns: 'u64', + code: ['VOTING_PERIOD'], }); c.addFunction(settingsTrait, { - name: "proposal_threshold", + name: 'proposal_threshold', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u256", - code: ["PROPOSAL_THRESHOLD"], + returns: 'u256', + code: ['PROPOSAL_THRESHOLD'], }); } -function addQuorumAndVotes( - c: ContractBuilder, - allOpts: Required, -) { - if (allOpts.quorumMode === "percent") { +function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { + if (allOpts.quorumMode === 'percent') { if (allOpts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } addVotesQuorumFractionComponent(c, allOpts.quorumPercent); - } else if (allOpts.quorumMode === "absolute") { + } else if (allOpts.quorumMode === 'absolute') { if (!numberPattern.test(allOpts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: "Not a valid number", + quorumAbsolute: 'Not a valid number', }); } let quorum: string; - let comment = ""; - if (allOpts.decimals === 0 || allOpts.votes === "erc721votes") { + let comment = ''; + if (allOpts.decimals === 0 || allOpts.votes === 'erc721votes') { quorum = `${allOpts.quorumAbsolute}`; } else { quorum = `${BigInt(allOpts.quorumAbsolute) * BigInt(10) ** BigInt(allOpts.decimals)}`; - quorum = toUint(quorum, "quorumAbsolute", "u256").toString(); + quorum = toUint(quorum, 'quorumAbsolute', 'u256').toString(); comment = `${allOpts.quorumAbsolute} * pow!(10, ${allOpts.decimals})`; } @@ -486,75 +457,62 @@ function addQuorumAndVotes( } } -function addVotesQuorumFractionComponent( - c: ContractBuilder, - quorumNumerator: number, -) { +function addVotesQuorumFractionComponent(c: ContractBuilder, quorumNumerator: number) { c.addConstant({ - name: "QUORUM_NUMERATOR", - type: "u256", + name: 'QUORUM_NUMERATOR', + type: 'u256', value: (quorumNumerator * 10).toString(), comment: `${quorumNumerator}%`, inlineComment: true, }); - c.addUseClause( - `${extensionPath}::GovernorVotesQuorumFractionComponent`, - "InternalTrait", - { alias: "GovernorVotesQuorumFractionInternalTrait" }, - ); + c.addUseClause(`${extensionPath}::GovernorVotesQuorumFractionComponent`, 'InternalTrait', { + alias: 'GovernorVotesQuorumFractionInternalTrait', + }); c.addComponent( components.GovernorVotesQuorumFractionComponent, - [{ lit: "votes_token" }, { lit: "QUORUM_NUMERATOR" }], + [{ lit: 'votes_token' }, { lit: 'QUORUM_NUMERATOR' }], true, ); } function addVotesComponent(c: ContractBuilder, _: Required) { - c.addUseClause(`${extensionPath}::GovernorVotesComponent`, "InternalTrait", { - alias: "GovernorVotesInternalTrait", + c.addUseClause(`${extensionPath}::GovernorVotesComponent`, 'InternalTrait', { + alias: 'GovernorVotesInternalTrait', }); - c.addComponent( - components.GovernorVotesComponent, - [{ lit: "votes_token" }], - true, - ); + c.addComponent(components.GovernorVotesComponent, [{ lit: 'votes_token' }], true); } -function addQuorumLocalImpl( - c: ContractBuilder, - quorum: string, - comment: string, -) { +function addQuorumLocalImpl(c: ContractBuilder, quorum: string, comment: string) { c.addConstant({ - name: "QUORUM", - type: "u256", + name: 'QUORUM', + type: 'u256', value: quorum, comment, inlineComment: true, }); const quorumTrait = { - name: "GovernorQuorum", - of: "GovernorComponent::GovernorQuorumTrait", + name: 'GovernorQuorum', + of: 'GovernorComponent::GovernorQuorumTrait', tags: [], - section: "Locally implemented extensions", + section: 'Locally implemented extensions', priority: 1, }; c.addImplementedTrait(quorumTrait); c.addFunction(quorumTrait, { - name: "quorum", + name: 'quorum', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, { - name: "timepoint", - type: "u64", + name: 'timepoint', + type: 'u64', }, ], - returns: "u256", - code: ["QUORUM"], + returns: 'u256', + code: ['QUORUM'], }); } @@ -562,27 +520,18 @@ function addCounting(c: ContractBuilder, _: Required) { c.addComponent(components.GovernorCountingSimpleComponent, [], false); } -function addExecution( - c: ContractBuilder, - { timelock }: Required, -) { +function addExecution(c: ContractBuilder, { timelock }: Required) { if (timelock === false) { c.addComponent(components.GovernorCoreExecutionComponent, [], false); } else { c.addConstructorArgument({ - name: "timelock_controller", - type: "ContractAddress", + name: 'timelock_controller', + type: 'ContractAddress', }); - c.addUseClause( - `${extensionPath}::GovernorTimelockExecutionComponent`, - "InternalTrait", - { alias: "GovernorTimelockExecutionInternalTrait" }, - ); - c.addComponent( - components.GovernorTimelockExecutionComponent, - [{ lit: "timelock_controller" }], - true, - ); + c.addUseClause(`${extensionPath}::GovernorTimelockExecutionComponent`, 'InternalTrait', { + alias: 'GovernorTimelockExecutionInternalTrait', + }); + c.addComponent(components.GovernorTimelockExecutionComponent, [{ lit: 'timelock_controller' }], true); } } diff --git a/packages/core/cairo/src/index.ts b/packages/core/cairo/src/index.ts index 461ec1823..5f102fabb 100644 --- a/packages/core/cairo/src/index.ts +++ b/packages/core/cairo/src/index.ts @@ -1,40 +1,28 @@ -export type { GenericOptions, KindedOptions } from "./build-generic"; -export { buildGeneric } from "./build-generic"; - -export type { Contract } from "./contract"; -export { ContractBuilder } from "./contract"; - -export { printContract } from "./print"; - -export type { Access } from "./set-access-control"; -export type { Account } from "./account"; -export type { Upgradeable } from "./set-upgradeable"; -export type { Info } from "./set-info"; -export type { RoyaltyInfoOptions } from "./set-royalty-info"; - -export { premintPattern } from "./erc20"; - -export { defaults as infoDefaults } from "./set-info"; -export { defaults as royaltyInfoDefaults } from "./set-royalty-info"; - -export type { OptionsErrorMessages } from "./error"; -export { OptionsError } from "./error"; - -export type { Kind } from "./kind"; -export { sanitizeKind } from "./kind"; - -export { - contractsVersion, - contractsVersionTag, - compatibleContractsSemver, -} from "./utils/version"; - -export { - erc20, - erc721, - erc1155, - account, - governor, - vesting, - custom, -} from "./api"; +export type { GenericOptions, KindedOptions } from './build-generic'; +export { buildGeneric } from './build-generic'; + +export type { Contract } from './contract'; +export { ContractBuilder } from './contract'; + +export { printContract } from './print'; + +export type { Access } from './set-access-control'; +export type { Account } from './account'; +export type { Upgradeable } from './set-upgradeable'; +export type { Info } from './set-info'; +export type { RoyaltyInfoOptions } from './set-royalty-info'; + +export { premintPattern } from './erc20'; + +export { defaults as infoDefaults } from './set-info'; +export { defaults as royaltyInfoDefaults } from './set-royalty-info'; + +export type { OptionsErrorMessages } from './error'; +export { OptionsError } from './error'; + +export type { Kind } from './kind'; +export { sanitizeKind } from './kind'; + +export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; + +export { erc20, erc721, erc1155, account, governor, vesting, custom } from './api'; diff --git a/packages/core/cairo/src/kind.ts b/packages/core/cairo/src/kind.ts index c205c7715..272bb1a79 100644 --- a/packages/core/cairo/src/kind.ts +++ b/packages/core/cairo/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from "./build-generic"; +import type { GenericOptions } from './build-generic'; -export type Kind = GenericOptions["kind"]; +export type Kind = GenericOptions['kind']; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === "string") { - const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); + if (typeof kind === 'string') { + const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return "ERC20"; + return 'ERC20'; } function isKind(value: Kind | T): value is Kind { switch (value) { - case "ERC20": - case "ERC721": - case "ERC1155": - case "Account": - case "Governor": - case "Vesting": - case "Custom": + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Account': + case 'Governor': + case 'Vesting': + case 'Custom': return true; default: { diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index 2c55c403c..30eecae76 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -7,21 +7,20 @@ import type { ContractFunction, ImplementedTrait, UseClause, -} from "./contract"; +} from './contract'; -import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; -import { getSelfArg } from "./common-options"; -import { compatibleContractsSemver } from "./utils/version"; +import { formatLines, spaceBetween } from './utils/format-lines'; +import type { Lines } from './utils/format-lines'; +import { getSelfArg } from './common-options'; +import { compatibleContractsSemver } from './utils/version'; -const DEFAULT_SECTION = "1. with no section"; -const STANDALONE_IMPORTS_GROUP = "Standalone Imports"; +const DEFAULT_SECTION = '1. with no section'; +const STANDALONE_IMPORTS_GROUP = 'Standalone Imports'; const MAX_USE_CLAUSE_LINE_LENGTH = 90; -const TAB = "\t"; +const TAB = '\t'; export function printContract(contract: Contract): string { - const contractAttribute = contract.account - ? "#[starknet::contract(account)]" - : "#[starknet::contract]"; + const contractAttribute = contract.account ? '#[starknet::contract(account)]' : '#[starknet::contract]'; return formatLines( ...spaceBetween( [ @@ -49,62 +48,41 @@ export function printContract(contract: Contract): string { } function withSemicolons(lines: string[]): string[] { - return lines.map((line) => (line.endsWith(";") ? line : line + ";")); + return lines.map(line => (line.endsWith(';') ? line : line + ';')); } function printSuperVariables(contract: Contract): string[] { - return withSemicolons( - contract.superVariables.map( - (v) => `const ${v.name}: ${v.type} = ${v.value}`, - ), - ); + return withSemicolons(contract.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); } function printUseClauses(contract: Contract): Lines[] { const useClauses = sortUseClauses(contract); // group by containerPath - const grouped = useClauses.reduce( - ( - result: { [containerPath: string]: UseClause[] }, - useClause: UseClause, - ) => { - if (useClause.groupable) { - (result[useClause.containerPath] = - result[useClause.containerPath] || []).push(useClause); - } else { - (result[STANDALONE_IMPORTS_GROUP] = - result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); - } - return result; - }, - {}, - ); + const grouped = useClauses.reduce((result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => { + if (useClause.groupable) { + (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause); + } else { + (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); + } + return result; + }, {}); - const lines = Object.entries(grouped).flatMap(([groupName, group]) => - getLinesFromUseClausesGroup(group, groupName), - ); - return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); + const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName)); + return lines.flatMap(line => splitLongUseClauseLine(line.toString())); } -function getLinesFromUseClausesGroup( - group: UseClause[], - groupName: string, -): Lines[] { +function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { - lines.push( - `use ${useClause.containerPath}::${nameWithAlias(useClause)};`, - ); + lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`); } } else { if (group.length == 1) { lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`); } else if (group.length > 1) { - const names = group - .map((useClause) => nameWithAlias(useClause)) - .join(", "); + const names = group.map(useClause => nameWithAlias(useClause)).join(', '); lines.push(`use ${groupName}::{${names}};`); } } @@ -112,21 +90,19 @@ function getLinesFromUseClausesGroup( } function nameWithAlias(useClause: UseClause): string { - return useClause.alias - ? `${useClause.name} as ${useClause.alias}` - : useClause.name; + return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name; } // TODO: remove this when we can use a formatting js library function splitLongUseClauseLine(line: string): Lines[] { const lines = []; - const containsBraces = line.indexOf("{") !== -1; + const containsBraces = line.indexOf('{') !== -1; if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) { // split at the first brace - lines.push(line.slice(0, line.indexOf("{") + 1)); - lines.push(...splitLongLineInner(line.slice(line.indexOf("{") + 1, -2))); - lines.push("};"); + lines.push(line.slice(0, line.indexOf('{') + 1)); + lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2))); + lines.push('};'); } else { lines.push(line); } @@ -137,7 +113,7 @@ function splitLongLineInner(line: string): Lines[] { const lines = []; if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) { const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH); - const lastCommaIndex = max_accessible_string.lastIndexOf(","); + const lastCommaIndex = max_accessible_string.lastIndexOf(','); if (lastCommaIndex !== -1) { lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1)); lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2))); @@ -167,17 +143,11 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); } else if (commented) { - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`); } else { - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); } } return lines; @@ -194,20 +164,17 @@ function printComponentDeclarations(contract: Contract): Lines[] { } function printImpls(contract: Contract): Lines[] { - const impls = contract.components.flatMap((c) => c.impls); + const impls = contract.components.flatMap(c => c.impls); // group by section - const grouped = impls.reduce( - (result: { [section: string]: Impl[] }, current: Impl) => { - // default section depends on embed - // embed defaults to true - const embed = current.embed ?? true; - const section = current.section ?? (embed ? "External" : "Internal"); - (result[section] = result[section] || []).push(current); - return result; - }, - {}, - ); + const grouped = impls.reduce((result: { [section: string]: Impl[] }, current: Impl) => { + // default section depends on embed + // embed defaults to true + const embed = current.embed ?? true; + const section = current.section ?? (embed ? 'External' : 'Internal'); + (result[section] = result[section] || []).push(current); + return result; + }, {}); const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) @@ -218,7 +185,7 @@ function printImpls(contract: Contract): Lines[] { function printSection(section: string, impls: Impl[]): Lines[] { const lines = []; lines.push(`// ${section}`); - impls.map((impl) => lines.push(...printImpl(impl))); + impls.map(impl => lines.push(...printImpl(impl))); return lines; } @@ -226,7 +193,7 @@ function printImpl(impl: Impl): Lines[] { const lines = []; // embed is optional, default to true if (impl.embed ?? true) { - lines.push("#[abi(embed_v0)]"); + lines.push('#[abi(embed_v0)]'); } lines.push(`impl ${impl.name} = ${impl.value};`); return lines; @@ -235,33 +202,31 @@ function printImpl(impl: Impl): Lines[] { function printStorage(contract: Contract): (string | string[])[] { const lines = []; // storage is required regardless of whether there are components - lines.push("#[storage]"); - lines.push("struct Storage {"); + lines.push('#[storage]'); + lines.push('struct Storage {'); const storageLines = []; for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); - storageLines.push( - `${component.substorage.name}: ${component.substorage.type},`, - ); + storageLines.push(`${component.substorage.name}: ${component.substorage.type},`); } lines.push(storageLines); - lines.push("}"); + lines.push('}'); return lines; } function printEvents(contract: Contract): (string | string[])[] { const lines = []; if (contract.components.length > 0) { - lines.push("#[event]"); - lines.push("#[derive(Drop, starknet::Event)]"); - lines.push("enum Event {"); + lines.push('#[event]'); + lines.push('#[derive(Drop, starknet::Event)]'); + lines.push('enum Event {'); const eventLines = []; for (const component of contract.components) { - eventLines.push("#[flat]"); + eventLines.push('#[flat]'); eventLines.push(`${component.event.name}: ${component.event.type},`); } lines.push(eventLines); - lines.push("}"); + lines.push('}'); } return lines; } @@ -280,10 +245,7 @@ function printImplementedTraits(contract: Contract): Lines[] { // group by section const grouped = sortedTraits.reduce( - ( - result: { [section: string]: ImplementedTrait[] }, - current: ImplementedTrait, - ) => { + (result: { [section: string]: ImplementedTrait[] }, current: ImplementedTrait) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); @@ -294,27 +256,22 @@ function printImplementedTraits(contract: Contract): Lines[] { const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) - .map(([section, impls]) => - printImplementedTraitsSection(section, impls as ImplementedTrait[]), - ); + .map(([section, impls]) => printImplementedTraitsSection(section, impls as ImplementedTrait[])); return spaceBetween(...sections); } -function printImplementedTraitsSection( - section: string, - impls: ImplementedTrait[], -): Lines[] { +function printImplementedTraitsSection(section: string, impls: ImplementedTrait[]): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; if (!isDefaultSection) { - lines.push("//"); + lines.push('//'); lines.push(`// ${section}`); - lines.push("//"); + lines.push('//'); } impls.forEach((trait, index) => { if (index > 0 || !isDefaultSection) { - lines.push(""); + lines.push(''); } lines.push(...printImplementedTrait(trait)); }); @@ -323,36 +280,30 @@ function printImplementedTraitsSection( function printImplementedTrait(trait: ImplementedTrait): Lines[] { const implLines = []; - implLines.push(...trait.tags.map((t) => `#[${t}]`)); + implLines.push(...trait.tags.map(t => `#[${t}]`)); implLines.push(`impl ${trait.name} of ${trait.of} {`); - const superVars = withSemicolons( - trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`), - ); + const superVars = withSemicolons(trait.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); implLines.push(superVars); - const fns = trait.functions.map((fn) => printFunction(fn)); + const fns = trait.functions.map(fn => printFunction(fn)); implLines.push(spaceBetween(...fns)); - implLines.push("}"); + implLines.push('}'); return implLines; } function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; - const args = fn.args.map((a) => printArgument(a)); + const args = fn.args.map(a => printArgument(a)); const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code; for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; - const shouldEndWithSemicolon = - i < codeLines.length - 1 || fn.returns === undefined; + const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; if (line !== undefined && line.length > 0) { - if ( - shouldEndWithSemicolon && - !["{", "}", ";"].includes(line.charAt(line.length - 1)) - ) { - codeLines[i] += ";"; - } else if (!shouldEndWithSemicolon && line.endsWith(";")) { + if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { + codeLines[i] += ';'; + } else if (!shouldEndWithSemicolon && line.endsWith(';')) { codeLines[i] = line.slice(0, line.length - 1); } } @@ -362,26 +313,19 @@ function printFunction(fn: ContractFunction): Lines[] { } function printConstructor(contract: Contract): Lines[] { - const hasInitializers = contract.components.some( - (p) => p.initializer !== undefined, - ); + const hasInitializers = contract.components.some(p => p.initializer !== undefined); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { - const parents = contract.components - .filter(hasInitializer) - .flatMap((p) => printParentConstructor(p)); - const tag = "constructor"; - const head = "fn constructor"; + const parents = contract.components.filter(hasInitializer).flatMap(p => printParentConstructor(p)); + const tag = 'constructor'; + const head = 'fn constructor'; const args = [getSelfArg(), ...contract.constructorArgs]; - const body = spaceBetween( - withSemicolons(parents), - withSemicolons(contract.constructorCode), - ); + const body = spaceBetween(withSemicolons(parents), withSemicolons(contract.constructorCode)); const constructor = printFunction2( head, - args.map((a) => printArgument(a)), + args.map(a => printArgument(a)), tag, undefined, undefined, @@ -394,39 +338,34 @@ function printConstructor(contract: Contract): Lines[] { } function hasInitializer(parent: Component): boolean { - return ( - parent.initializer !== undefined && parent.substorage?.name !== undefined - ); + return parent.initializer !== undefined && parent.substorage?.name !== undefined; } -function printParentConstructor({ - substorage, - initializer, -}: Component): [] | [string] { +function printParentConstructor({ substorage, initializer }: Component): [] | [string] { if (initializer === undefined || substorage?.name === undefined) { return []; } const fn = `self.${substorage.name}.initializer`; - return [fn + "(" + initializer.params.map(printValue).join(", ") + ")"]; + return [fn + '(' + initializer.params.map(printValue).join(', ') + ')']; } export function printValue(value: Value): string { - if (typeof value === "object") { - if ("lit" in value) { + if (typeof value === 'object') { + if ('lit' in value) { return value.lit; - } else if ("note" in value) { + } else if ('note' in value) { // TODO: add /* ${value.note} */ after lsp is fixed return `${printValue(value.value)}`; } else { - throw Error("Unknown value type"); + throw Error('Unknown value type'); } - } else if (typeof value === "number") { + } else if (typeof value === 'number') { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { throw new Error(`Number not representable (${value})`); } - } else if (typeof value === "bigint") { + } else if (typeof value === 'bigint') { return `${value}`; } else { return `"${value}"`; @@ -452,20 +391,20 @@ function printFunction2( let accum = `${kindedName}(`; if (args.length > 0) { - const formattedArgs = args.join(", "); + const formattedArgs = args.join(', '); if (formattedArgs.length > 80) { fn.push(accum); - accum = ""; + accum = ''; // print each arg in a separate line - fn.push(args.map((arg) => `${arg},`)); + fn.push(args.map(arg => `${arg},`)); } else { accum += `${formattedArgs}`; } } - accum += ")"; + accum += ')'; if (returns === undefined) { - accum += " {"; + accum += ' {'; } else { accum += ` -> ${returns} {`; } @@ -475,7 +414,7 @@ function printFunction2( if (returnLine !== undefined) { fn.push([returnLine]); } - fn.push("}"); + fn.push('}'); return fn; } diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index ee5f24897..b6ee6a901 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -1,24 +1,15 @@ -import { promises as fs } from "fs"; -import path from "path"; +import { promises as fs } from 'fs'; +import path from 'path'; -import { writeGeneratedSources } from "../generate/sources"; -import { - contractsVersion, - edition, - cairoVersion, - scarbVersion, -} from "../utils/version"; +import { writeGeneratedSources } from '../generate/sources'; +import { contractsVersion, edition, cairoVersion, scarbVersion } from '../utils/version'; export async function updateScarbProject() { - const generatedSourcesPath = path.join("test_project", "src"); + const generatedSourcesPath = path.join('test_project', 'src'); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); // Generate the contracts source code - const contractNames = await writeGeneratedSources( - generatedSourcesPath, - "all", - true, - ); + const contractNames = await writeGeneratedSources(generatedSourcesPath, 'all', true); // Generate lib.cairo file writeLibCairo(contractNames); @@ -28,32 +19,23 @@ export async function updateScarbProject() { } async function writeLibCairo(contractNames: string[]) { - const libCairoPath = path.join("test_project/src", "lib.cairo"); - const libCairo = contractNames.map((name) => `pub mod ${name};\n`).join(""); + const libCairoPath = path.join('test_project/src', 'lib.cairo'); + const libCairo = contractNames.map(name => `pub mod ${name};\n`).join(''); await fs.writeFile(libCairoPath, libCairo); } async function updateScarbToml() { - const scarbTomlPath = path.join("test_project", "Scarb.toml"); + const scarbTomlPath = path.join('test_project', 'Scarb.toml'); - const currentContent = await fs.readFile(scarbTomlPath, "utf8"); + const currentContent = await fs.readFile(scarbTomlPath, 'utf8'); const updatedContent = currentContent .replace(/edition = "\w+"/, `edition = "${edition}"`) - .replace( - /cairo-version = "\d+\.\d+\.\d+"/, - `cairo-version = "${cairoVersion}"`, - ) - .replace( - /scarb-version = "\d+\.\d+\.\d+"/, - `scarb-version = "${scarbVersion}"`, - ) + .replace(/cairo-version = "\d+\.\d+\.\d+"/, `cairo-version = "${cairoVersion}"`) + .replace(/scarb-version = "\d+\.\d+\.\d+"/, `scarb-version = "${scarbVersion}"`) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) - .replace( - /openzeppelin = "\d+\.\d+\.\d+"/, - `openzeppelin = "${contractsVersion}"`, - ); + .replace(/openzeppelin = "\d+\.\d+\.\d+"/, `openzeppelin = "${contractsVersion}"`); - await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); + await fs.writeFile(scarbTomlPath, updatedContent, 'utf8'); } updateScarbProject(); diff --git a/packages/core/cairo/src/set-access-control.ts b/packages/core/cairo/src/set-access-control.ts index fe9d0c65a..d567c15c8 100644 --- a/packages/core/cairo/src/set-access-control.ts +++ b/packages/core/cairo/src/set-access-control.ts @@ -1,13 +1,9 @@ -import type { - BaseFunction, - BaseImplementedTrait, - ContractBuilder, -} from "./contract"; -import { defineComponents } from "./utils/define-components"; -import { addSRC5Component } from "./common-components"; +import type { BaseFunction, BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; +import { addSRC5Component } from './common-components'; -export const accessOptions = [false, "ownable", "roles"] as const; -export const DEFAULT_ACCESS_CONTROL = "ownable"; +export const accessOptions = [false, 'ownable', 'roles'] as const; +export const DEFAULT_ACCESS_CONTROL = 'ownable'; export type Access = (typeof accessOptions)[number]; @@ -16,49 +12,42 @@ export type Access = (typeof accessOptions)[number]; */ export function setAccessControl(c: ContractBuilder, access: Access): void { switch (access) { - case "ownable": { - c.addComponent(components.OwnableComponent, [{ lit: "owner" }], true); + case 'ownable': { + c.addComponent(components.OwnableComponent, [{ lit: 'owner' }], true); - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: "owner", type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: 'owner', type: 'ContractAddress' }); break; } - case "roles": { + case 'roles': { if (c.addComponent(components.AccessControlComponent, [], true)) { - if (c.interfaceFlags.has("ISRC5")) { + if (c.interfaceFlags.has('ISRC5')) { c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlImpl", - value: "AccessControlComponent::AccessControlImpl", + name: 'AccessControlImpl', + value: 'AccessControlComponent::AccessControlImpl', }); c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlCamelImpl", - value: - "AccessControlComponent::AccessControlCamelImpl", + name: 'AccessControlCamelImpl', + value: 'AccessControlComponent::AccessControlCamelImpl', }); } else { c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlMixinImpl", - value: - "AccessControlComponent::AccessControlMixinImpl", + name: 'AccessControlMixinImpl', + value: 'AccessControlComponent::AccessControlMixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); } addSRC5Component(c); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addConstructorArgument({ - name: "default_admin", - type: "ContractAddress", + name: 'default_admin', + type: 'ContractAddress', }); - c.addUseClause( - "openzeppelin::access::accesscontrol", - "DEFAULT_ADMIN_ROLE", - ); - c.addConstructorCode( - "self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)", - ); + c.addUseClause('openzeppelin::access::accesscontrol', 'DEFAULT_ADMIN_ROLE'); + c.addConstructorCode('self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)'); } break; } @@ -82,32 +71,26 @@ export function requireAccessControl( setAccessControl(c, access); switch (access) { - case "ownable": { - c.addFunctionCodeBefore(trait, fn, "self.ownable.assert_only_owner()"); + case 'ownable': { + c.addFunctionCodeBefore(trait, fn, 'self.ownable.assert_only_owner()'); break; } - case "roles": { - const roleId = roleIdPrefix + "_ROLE"; + case 'roles': { + const roleId = roleIdPrefix + '_ROLE'; const addedSuper = c.addSuperVariable({ name: roleId, - type: "felt252", + type: 'felt252', value: `selector!("${roleId}")`, }); if (roleOwner !== undefined) { - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: roleOwner, type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: roleOwner, type: 'ContractAddress' }); if (addedSuper) { - c.addConstructorCode( - `self.accesscontrol._grant_role(${roleId}, ${roleOwner})`, - ); + c.addConstructorCode(`self.accesscontrol._grant_role(${roleId}, ${roleOwner})`); } } - c.addFunctionCodeBefore( - trait, - fn, - `self.accesscontrol.assert_only_role(${roleId})`, - ); + c.addFunctionCodeBefore(trait, fn, `self.accesscontrol.assert_only_role(${roleId})`); break; } @@ -116,42 +99,42 @@ export function requireAccessControl( const components = defineComponents({ OwnableComponent: { - path: "openzeppelin::access::ownable", + path: 'openzeppelin::access::ownable', substorage: { - name: "ownable", - type: "OwnableComponent::Storage", + name: 'ownable', + type: 'OwnableComponent::Storage', }, event: { - name: "OwnableEvent", - type: "OwnableComponent::Event", + name: 'OwnableEvent', + type: 'OwnableComponent::Event', }, impls: [ { - name: "OwnableMixinImpl", - value: "OwnableComponent::OwnableMixinImpl", + name: 'OwnableMixinImpl', + value: 'OwnableComponent::OwnableMixinImpl', }, { - name: "OwnableInternalImpl", + name: 'OwnableInternalImpl', embed: false, - value: "OwnableComponent::InternalImpl", + value: 'OwnableComponent::InternalImpl', }, ], }, AccessControlComponent: { - path: "openzeppelin::access::accesscontrol", + path: 'openzeppelin::access::accesscontrol', substorage: { - name: "accesscontrol", - type: "AccessControlComponent::Storage", + name: 'accesscontrol', + type: 'AccessControlComponent::Storage', }, event: { - name: "AccessControlEvent", - type: "AccessControlComponent::Event", + name: 'AccessControlEvent', + type: 'AccessControlComponent::Event', }, impls: [ { - name: "AccessControlInternalImpl", + name: 'AccessControlInternalImpl', embed: false, - value: "AccessControlComponent::InternalImpl", + value: 'AccessControlComponent::InternalImpl', }, ], }, diff --git a/packages/core/cairo/src/set-info.ts b/packages/core/cairo/src/set-info.ts index 30a110db5..232662ae8 100644 --- a/packages/core/cairo/src/set-info.ts +++ b/packages/core/cairo/src/set-info.ts @@ -1,8 +1,8 @@ -import type { ContractBuilder } from "./contract"; +import type { ContractBuilder } from './contract'; -export const infoOptions = [{}, { license: "WTFPL" }] as const; +export const infoOptions = [{}, { license: 'WTFPL' }] as const; -export const defaults: Info = { license: "MIT" }; +export const defaults: Info = { license: 'MIT' }; export type Info = { license?: string; diff --git a/packages/core/cairo/src/set-royalty-info.ts b/packages/core/cairo/src/set-royalty-info.ts index fb27f0dfe..cd3920db8 100644 --- a/packages/core/cairo/src/set-royalty-info.ts +++ b/packages/core/cairo/src/set-royalty-info.ts @@ -1,18 +1,15 @@ -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { defineComponents } from "./utils/define-components"; -import { OptionsError } from "./error"; -import { toUint } from "./utils/convert-strings"; -import { - Access, - setAccessControl, - DEFAULT_ACCESS_CONTROL, -} from "./set-access-control"; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; +import { OptionsError } from './error'; +import { toUint } from './utils/convert-strings'; +import type { Access } from './set-access-control'; +import { setAccessControl, DEFAULT_ACCESS_CONTROL } from './set-access-control'; const DEFAULT_FEE_DENOMINATOR = BigInt(10_000); export const defaults: RoyaltyInfoOptions = { enabled: false, - defaultRoyaltyFraction: "0", + defaultRoyaltyFraction: '0', feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }; @@ -20,13 +17,13 @@ export const royaltyInfoOptions = { disabled: defaults, enabledDefault: { enabled: true, - defaultRoyaltyFraction: "500", + defaultRoyaltyFraction: '500', feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }, enabledCustom: { enabled: true, - defaultRoyaltyFraction: "15125", - feeDenominator: "100000", + defaultRoyaltyFraction: '15125', + feeDenominator: '100000', }, }; @@ -36,11 +33,7 @@ export type RoyaltyInfoOptions = { feeDenominator: string; }; -export function setRoyaltyInfo( - c: ContractBuilder, - options: RoyaltyInfoOptions, - access: Access, -): void { +export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, access: Access): void { if (!options.enabled) { return; } @@ -49,54 +42,48 @@ export function setRoyaltyInfo( } setAccessControl(c, access); - const { defaultRoyaltyFraction, feeDenominator } = - getRoyaltyParameters(options); - const initParams = [ - { lit: "default_royalty_receiver" }, - defaultRoyaltyFraction, - ]; + const { defaultRoyaltyFraction, feeDenominator } = getRoyaltyParameters(options); + const initParams = [{ lit: 'default_royalty_receiver' }, defaultRoyaltyFraction]; c.addComponent(components.ERC2981Component, initParams, true); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addConstructorArgument({ - name: "default_royalty_receiver", - type: "ContractAddress", + name: 'default_royalty_receiver', + type: 'ContractAddress', }); switch (access) { - case "ownable": + case 'ownable': c.addImplToComponent(components.ERC2981Component, { - name: "ERC2981AdminOwnableImpl", + name: 'ERC2981AdminOwnableImpl', value: `ERC2981Component::ERC2981AdminOwnableImpl`, }); break; - case "roles": + case 'roles': c.addImplToComponent(components.ERC2981Component, { - name: "ERC2981AdminAccessControlImpl", + name: 'ERC2981AdminAccessControlImpl', value: `ERC2981Component::ERC2981AdminAccessControlImpl`, }); c.addConstructorArgument({ - name: "royalty_admin", - type: "ContractAddress", + name: 'royalty_admin', + type: 'ContractAddress', }); - c.addConstructorCode( - "self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)", - ); + c.addConstructorCode('self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)'); break; } if (feeDenominator === DEFAULT_FEE_DENOMINATOR) { - c.addUseClause("openzeppelin::token::common::erc2981", "DefaultConfig"); + c.addUseClause('openzeppelin::token::common::erc2981', 'DefaultConfig'); } else { const trait: BaseImplementedTrait = { - name: "ERC2981ImmutableConfig", - of: "ERC2981Component::ImmutableConfig", + name: 'ERC2981ImmutableConfig', + of: 'ERC2981Component::ImmutableConfig', tags: [], }; c.addImplementedTrait(trait); c.addSuperVariableToTrait(trait, { - name: "FEE_DENOMINATOR", - type: "u128", + name: 'FEE_DENOMINATOR', + type: 'u128', value: feeDenominator.toString(), }); } @@ -106,20 +93,16 @@ function getRoyaltyParameters(opts: Required): { defaultRoyaltyFraction: bigint; feeDenominator: bigint; } { - const feeDenominator = toUint(opts.feeDenominator, "feeDenominator", "u128"); + const feeDenominator = toUint(opts.feeDenominator, 'feeDenominator', 'u128'); if (feeDenominator === BigInt(0)) { throw new OptionsError({ - feeDenominator: "Must be greater than 0", + feeDenominator: 'Must be greater than 0', }); } - const defaultRoyaltyFraction = toUint( - opts.defaultRoyaltyFraction, - "defaultRoyaltyFraction", - "u128", - ); + const defaultRoyaltyFraction = toUint(opts.defaultRoyaltyFraction, 'defaultRoyaltyFraction', 'u128'); if (defaultRoyaltyFraction > feeDenominator) { throw new OptionsError({ - defaultRoyaltyFraction: "Cannot be greater than fee denominator", + defaultRoyaltyFraction: 'Cannot be greater than fee denominator', }); } return { defaultRoyaltyFraction, feeDenominator }; @@ -127,27 +110,27 @@ function getRoyaltyParameters(opts: Required): { const components = defineComponents({ ERC2981Component: { - path: "openzeppelin::token::common::erc2981", + path: 'openzeppelin::token::common::erc2981', substorage: { - name: "erc2981", - type: "ERC2981Component::Storage", + name: 'erc2981', + type: 'ERC2981Component::Storage', }, event: { - name: "ERC2981Event", - type: "ERC2981Component::Event", + name: 'ERC2981Event', + type: 'ERC2981Component::Event', }, impls: [ { - name: "ERC2981Impl", - value: "ERC2981Component::ERC2981Impl", + name: 'ERC2981Impl', + value: 'ERC2981Component::ERC2981Impl', }, { - name: "ERC2981InfoImpl", - value: "ERC2981Component::ERC2981InfoImpl", + name: 'ERC2981InfoImpl', + value: 'ERC2981Component::ERC2981InfoImpl', }, { - name: "ERC2981InternalImpl", - value: "ERC2981Component::InternalImpl", + name: 'ERC2981InternalImpl', + value: 'ERC2981Component::InternalImpl', embed: false, }, ], diff --git a/packages/core/cairo/src/set-upgradeable.ts b/packages/core/cairo/src/set-upgradeable.ts index fdb955e54..8948c419a 100644 --- a/packages/core/cairo/src/set-upgradeable.ts +++ b/packages/core/cairo/src/set-upgradeable.ts @@ -1,18 +1,16 @@ -import { getSelfArg } from "./common-options"; -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineComponents } from "./utils/define-components"; -import { defineFunctions } from "./utils/define-functions"; -import type { Account } from "./account"; +import { getSelfArg } from './common-options'; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineComponents } from './utils/define-components'; +import { defineFunctions } from './utils/define-functions'; +import type { Account } from './account'; export const upgradeableOptions = [false, true] as const; export type Upgradeable = (typeof upgradeableOptions)[number]; -function setUpgradeableBase( - c: ContractBuilder, - upgradeable: Upgradeable, -): BaseImplementedTrait | undefined { +function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseImplementedTrait | undefined { if (upgradeable === false) { return undefined; } @@ -21,77 +19,44 @@ function setUpgradeableBase( c.addComponent(components.UpgradeableComponent, [], false); - c.addUseClause("openzeppelin::upgrades::interface", "IUpgradeable"); - c.addUseClause("starknet", "ClassHash"); + c.addUseClause('openzeppelin::upgrades::interface', 'IUpgradeable'); + c.addUseClause('starknet', 'ClassHash'); const t: BaseImplementedTrait = { - name: "UpgradeableImpl", - of: "IUpgradeable", - section: "Upgradeable", - tags: ["abi(embed_v0)"], + name: 'UpgradeableImpl', + of: 'IUpgradeable', + section: 'Upgradeable', + tags: ['abi(embed_v0)'], }; c.addImplementedTrait(t); return t; } -export function setUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - access: Access, -): void { +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - requireAccessControl( - c, - trait, - functions.upgrade, - access, - "UPGRADER", - "upgrader", - ); + requireAccessControl(c, trait, functions.upgrade, access, 'UPGRADER', 'upgrader'); } } -export function setUpgradeableGovernor( - c: ContractBuilder, - upgradeable: Upgradeable, -): void { +export function setUpgradeableGovernor(c: ContractBuilder, upgradeable: Upgradeable): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - c.addUseClause( - "openzeppelin::governance::governor::GovernorComponent", - "InternalExtendedImpl", - ); - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.governor.assert_only_governance()", - ); + c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalExtendedImpl'); + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.governor.assert_only_governance()'); } } -export function setAccountUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - type: Account, -): void { +export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { switch (type) { - case "stark": - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.account.assert_only_self()", - ); + case 'stark': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.account.assert_only_self()'); break; - case "eth": - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.eth_account.assert_only_self()", - ); + case 'eth': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.eth_account.assert_only_self()'); break; } } @@ -99,20 +64,20 @@ export function setAccountUpgradeable( const components = defineComponents({ UpgradeableComponent: { - path: "openzeppelin::upgrades", + path: 'openzeppelin::upgrades', substorage: { - name: "upgradeable", - type: "UpgradeableComponent::Storage", + name: 'upgradeable', + type: 'UpgradeableComponent::Storage', }, event: { - name: "UpgradeableEvent", - type: "UpgradeableComponent::Event", + name: 'UpgradeableEvent', + type: 'UpgradeableComponent::Event', }, impls: [ { - name: "UpgradeableInternalImpl", + name: 'UpgradeableInternalImpl', embed: false, - value: "UpgradeableComponent::InternalImpl", + value: 'UpgradeableComponent::InternalImpl', }, ], }, @@ -120,7 +85,7 @@ const components = defineComponents({ const functions = defineFunctions({ upgrade: { - args: [getSelfArg(), { name: "new_class_hash", type: "ClassHash" }], - code: ["self.upgradeable.upgrade(new_class_hash)"], + args: [getSelfArg(), { name: 'new_class_hash', type: 'ClassHash' }], + code: ['self.upgradeable.upgrade(new_class_hash)'], }, }); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index e5974c16a..28c6dbdc1 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -1,11 +1,12 @@ -import { promises as fs } from "fs"; -import os from "os"; -import _test, { TestFn, ExecutionContext } from "ava"; -import path from "path"; +import { promises as fs } from 'fs'; +import os from 'os'; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import path from 'path'; -import { generateSources, writeGeneratedSources } from "./generate/sources"; -import type { GenericOptions, KindedOptions } from "./build-generic"; -import { custom, erc20, erc721, erc1155 } from "./api"; +import { generateSources, writeGeneratedSources } from './generate/sources'; +import type { GenericOptions, KindedOptions } from './build-generic'; +import { custom, erc20, erc721, erc1155 } from './api'; interface Context { generatedSourcesPath: string; @@ -13,88 +14,76 @@ interface Context { const test = _test as TestFn; -test.serial("erc20 result generated", async (t) => { - await testGenerate(t, "ERC20"); +test.serial('erc20 result generated', async t => { + await testGenerate(t, 'ERC20'); }); -test.serial("erc721 result generated", async (t) => { - await testGenerate(t, "ERC721"); +test.serial('erc721 result generated', async t => { + await testGenerate(t, 'ERC721'); }); -test.serial("erc1155 result generated", async (t) => { - await testGenerate(t, "ERC1155"); +test.serial('erc1155 result generated', async t => { + await testGenerate(t, 'ERC1155'); }); -test.serial("account result generated", async (t) => { - await testGenerate(t, "Account"); +test.serial('account result generated', async t => { + await testGenerate(t, 'Account'); }); -test.serial("custom result generated", async (t) => { - await testGenerate(t, "Custom"); +test.serial('custom result generated', async t => { + await testGenerate(t, 'Custom'); }); -async function testGenerate( - t: ExecutionContext, - kind: keyof KindedOptions, -) { - const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); +async function testGenerate(t: ExecutionContext, kind: keyof KindedOptions) { + const generatedSourcesPath = path.join(os.tmpdir(), 'oz-wizard-cairo'); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, "all", true, kind); + await writeGeneratedSources(generatedSourcesPath, 'all', true, kind); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return erc20.isAccessControlRequired(opts); - case "ERC721": + case 'ERC721': return erc721.isAccessControlRequired(opts); - case "ERC1155": + case 'ERC1155': return erc1155.isAccessControlRequired(opts); - case "Account": - throw new Error("Not applicable for accounts"); - case "Custom": + case 'Account': + throw new Error('Not applicable for accounts'); + case 'Custom': return custom.isAccessControlRequired(opts); default: - throw new Error("No such kind"); + throw new Error('No such kind'); } } -test("is access control required", async (t) => { - for (const contract of generateSources("all")) { - const regexOwnable = - /(use openzeppelin::access::ownable::OwnableComponent)/gm; +test('is access control required', async t => { + for (const contract of generateSources('all')) { + const regexOwnable = /(use openzeppelin::access::ownable::OwnableComponent)/gm; switch (contract.options.kind) { - case "Account": - case "Governor": - case "Vesting": + case 'Account': + case 'Governor': + case 'Vesting': // These contracts have no access control option break; - case "ERC20": - case "ERC721": - case "ERC1155": - case "Custom": + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Custom': if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); } else { - t.notRegex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); } } break; default: { const _: never = contract.options; - throw new Error("Unknown kind"); + throw new Error('Unknown kind'); } } } diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index f4f9eca87..51bb4a7a9 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -1,119 +1,101 @@ -import test from "ava"; +import test from 'ava'; -import { toIdentifier, toByteArray, toFelt252 } from "./convert-strings"; -import { OptionsError } from "../error"; +import { toIdentifier, toByteArray, toFelt252 } from './convert-strings'; +import { OptionsError } from '../error'; -test("identifier - unmodified", (t) => { - t.is(toIdentifier("abc"), "abc"); +test('identifier - unmodified', t => { + t.is(toIdentifier('abc'), 'abc'); }); -test("identifier - remove leading specials", (t) => { - t.is(toIdentifier("--abc"), "abc"); +test('identifier - remove leading specials', t => { + t.is(toIdentifier('--abc'), 'abc'); }); -test("identifier - remove specials and upcase next char", (t) => { - t.is(toIdentifier("abc-def"), "abcDef"); - t.is(toIdentifier("abc--def"), "abcDef"); +test('identifier - remove specials and upcase next char', t => { + t.is(toIdentifier('abc-def'), 'abcDef'); + t.is(toIdentifier('abc--def'), 'abcDef'); }); -test("identifier - capitalize", (t) => { - t.is(toIdentifier("abc", true), "Abc"); +test('identifier - capitalize', t => { + t.is(toIdentifier('abc', true), 'Abc'); }); -test("identifier - remove accents", (t) => { - t.is(toIdentifier("ábc"), "abc"); +test('identifier - remove accents', t => { + t.is(toIdentifier('ábc'), 'abc'); }); -test("identifier - underscores", (t) => { - t.is(toIdentifier("_abc_"), "_abc_"); +test('identifier - underscores', t => { + t.is(toIdentifier('_abc_'), '_abc_'); }); -test("identifier - remove starting numbers", (t) => { - t.is(toIdentifier("123abc456"), "abc456"); +test('identifier - remove starting numbers', t => { + t.is(toIdentifier('123abc456'), 'abc456'); }); -test("identifier - empty string", (t) => { - const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); - t.is( - error.messages.name, - "Identifier is empty or does not have valid characters", - ); +test('identifier - empty string', t => { + const error = t.throws(() => toIdentifier(''), { instanceOf: OptionsError }); + t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); }); -test("identifier - no valid chars", (t) => { - const error = t.throws(() => toIdentifier("123"), { +test('identifier - no valid chars', t => { + const error = t.throws(() => toIdentifier('123'), { instanceOf: OptionsError, }); - t.is( - error.messages.name, - "Identifier is empty or does not have valid characters", - ); + t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); }); -test("toByteArray - unmodified", (t) => { - t.is(toByteArray("abc"), "abc"); +test('toByteArray - unmodified', t => { + t.is(toByteArray('abc'), 'abc'); }); -test("toByteArray - remove accents", (t) => { - t.is(toByteArray("ábc"), "abc"); +test('toByteArray - remove accents', t => { + t.is(toByteArray('ábc'), 'abc'); }); -test("toByteArray - remove non-ascii-printable characters", (t) => { - t.is(toByteArray("abc😀"), "abc"); +test('toByteArray - remove non-ascii-printable characters', t => { + t.is(toByteArray('abc😀'), 'abc'); }); -test("toByteArray - escape double quote", (t) => { +test('toByteArray - escape double quote', t => { t.is(toByteArray('abc"def'), 'abc\\"def'); }); -test("toByteArray - does not escape single quote", (t) => { +test('toByteArray - does not escape single quote', t => { t.is(toByteArray("abc'def"), "abc'def"); }); -test("toByteArray - escape backslash", (t) => { - t.is(toByteArray("abc\\def"), "abc\\\\def"); +test('toByteArray - escape backslash', t => { + t.is(toByteArray('abc\\def'), 'abc\\\\def'); }); -test("more than 31 characters", (t) => { - t.is( - toByteArray("A234567890123456789012345678901"), - "A234567890123456789012345678901", - ); - t.is( - toByteArray("A2345678901234567890123456789012"), - "A2345678901234567890123456789012", - ); +test('more than 31 characters', t => { + t.is(toByteArray('A234567890123456789012345678901'), 'A234567890123456789012345678901'); + t.is(toByteArray('A2345678901234567890123456789012'), 'A2345678901234567890123456789012'); }); -test("toFelt252 - unmodified", (t) => { - t.is(toFelt252("abc", "foo"), "abc"); +test('toFelt252 - unmodified', t => { + t.is(toFelt252('abc', 'foo'), 'abc'); }); -test("toFelt252 - remove accents", (t) => { - t.is(toFelt252("ábc", "foo"), "abc"); +test('toFelt252 - remove accents', t => { + t.is(toFelt252('ábc', 'foo'), 'abc'); }); -test("toFelt252 - remove non-ascii-printable characters", (t) => { - t.is(toFelt252("abc😀", "foo"), "abc"); +test('toFelt252 - remove non-ascii-printable characters', t => { + t.is(toFelt252('abc😀', 'foo'), 'abc'); }); -test("toFelt252 - escape single quote", (t) => { - t.is(toFelt252("abc'def", "foo"), "abc\\'def"); +test('toFelt252 - escape single quote', t => { + t.is(toFelt252("abc'def", 'foo'), "abc\\'def"); }); -test("toFelt252 - escape backslash", (t) => { - t.is(toFelt252("abc\\def", "foo"), "abc\\\\def"); +test('toFelt252 - escape backslash', t => { + t.is(toFelt252('abc\\def', 'foo'), 'abc\\\\def'); }); -test("toFelt252 - max 31 characters", (t) => { - t.is( - toFelt252("A234567890123456789012345678901", "foo"), - "A234567890123456789012345678901", - ); +test('toFelt252 - max 31 characters', t => { + t.is(toFelt252('A234567890123456789012345678901', 'foo'), 'A234567890123456789012345678901'); - const error = t.throws( - () => toFelt252("A2345678901234567890123456789012", "foo"), - { instanceOf: OptionsError }, - ); - t.is(error.messages.foo, "String is longer than 31 characters"); + const error = t.throws(() => toFelt252('A2345678901234567890123456789012', 'foo'), { instanceOf: OptionsError }); + t.is(error.messages.foo, 'String is longer than 31 characters'); }); diff --git a/packages/core/cairo/src/utils/convert-strings.ts b/packages/core/cairo/src/utils/convert-strings.ts index da8d0fe57..4f00d33d9 100644 --- a/packages/core/cairo/src/utils/convert-strings.ts +++ b/packages/core/cairo/src/utils/convert-strings.ts @@ -1,19 +1,19 @@ -import { OptionsError } from "../error"; +import { OptionsError } from '../error'; /** * Converts to an identifier according to the rules in https://docs.cairo-lang.org/language_constructs/identifiers.html */ export function toIdentifier(str: string, capitalize = false): string { const result = str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/^[^a-zA-Z_]+/, "") - .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/^[^a-zA-Z_]+/, '') + .replace(/^(.)/, c => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w]+(.?)/g, (_, c) => c.toUpperCase()); if (result.length === 0) { throw new OptionsError({ - name: "Identifier is empty or does not have valid characters", + name: 'Identifier is empty or does not have valid characters', }); } else { return result; @@ -25,10 +25,10 @@ export function toIdentifier(str: string, capitalize = false): string { */ export function toByteArray(str: string): string { return str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters - .replace(/(\\|")/g, (_, c) => "\\" + c); // escape backslash or double quotes + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters + .replace(/(\\|")/g, (_, c) => '\\' + c); // escape backslash or double quotes } /** @@ -36,14 +36,14 @@ export function toByteArray(str: string): string { */ export function toFelt252(str: string, field: string): string { const result = str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters - .replace(/(\\|')/g, (_, c) => "\\" + c); // escape backslash or single quote + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters + .replace(/(\\|')/g, (_, c) => '\\' + c); // escape backslash or single quote if (result.length > 31) { throw new OptionsError({ - [field]: "String is longer than 31 characters", + [field]: 'String is longer than 31 characters', }); } else { return result; @@ -55,12 +55,10 @@ function maxValueOfUint(bits: number): bigint { throw new Error(`Number of bits must be positive (actual '${bits}').`); } if (bits % 8 !== 0) { - throw new Error( - `The number of bits must be a multiple of 8 (actual '${bits}').`, - ); + throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`); } const bytes = bits / 8; - return BigInt("0x" + "ff".repeat(bytes)); + return BigInt('0x' + 'ff'.repeat(bytes)); } const UINT_MAX_VALUES = { @@ -77,16 +75,12 @@ export type UintType = keyof typeof UINT_MAX_VALUES; /** * Checks that a string/number value is a valid `uint` value and converts it to bigint */ -export function toUint( - value: number | string, - field: string, - type: UintType, -): bigint { +export function toUint(value: number | string, field: string, type: UintType): bigint { const valueAsStr = value.toString(); const isValidNumber = /^\d+$/.test(valueAsStr); if (!isValidNumber) { throw new OptionsError({ - [field]: "Not a valid number", + [field]: 'Not a valid number', }); } const numValue = BigInt(valueAsStr); @@ -101,10 +95,6 @@ export function toUint( /** * Checks that a string/number value is a valid `uint` value */ -export function validateUint( - value: number | string, - field: string, - type: UintType, -): void { +export function validateUint(value: number | string, field: string, type: UintType): void { const _ = toUint(value, field, type); } diff --git a/packages/core/cairo/src/utils/define-components.ts b/packages/core/cairo/src/utils/define-components.ts index f929db64f..4fb13f4b2 100644 --- a/packages/core/cairo/src/utils/define-components.ts +++ b/packages/core/cairo/src/utils/define-components.ts @@ -1,18 +1,9 @@ -import type { Component } from "../contract"; +import type { Component } from '../contract'; -type ImplicitNameComponent = Omit; +type ImplicitNameComponent = Omit; -export function defineComponents( - fns: Record, -): Record; +export function defineComponents(fns: Record): Record; -export function defineComponents( - modules: Record, -): Record { - return Object.fromEntries( - Object.entries(modules).map(([name, module]) => [ - name, - Object.assign({ name }, module), - ]), - ); +export function defineComponents(modules: Record): Record { + return Object.fromEntries(Object.entries(modules).map(([name, module]) => [name, Object.assign({ name }, module)])); } diff --git a/packages/core/cairo/src/utils/define-functions.ts b/packages/core/cairo/src/utils/define-functions.ts index c05316bce..3c89e6c76 100644 --- a/packages/core/cairo/src/utils/define-functions.ts +++ b/packages/core/cairo/src/utils/define-functions.ts @@ -1,18 +1,9 @@ -import type { BaseFunction } from "../contract"; +import type { BaseFunction } from '../contract'; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; -export function defineFunctions( - fns: Record, -): Record; +export function defineFunctions(fns: Record): Record; -export function defineFunctions( - fns: Record, -): Record { - return Object.fromEntries( - Object.entries(fns).map(([name, fn]) => [ - name, - Object.assign({ name }, fn), - ]), - ); +export function defineFunctions(fns: Record): Record { + return Object.fromEntries(Object.entries(fns).map(([name, fn]) => [name, Object.assign({ name }, fn)])); } diff --git a/packages/core/cairo/src/utils/duration.ts b/packages/core/cairo/src/utils/duration.ts index 224de5dfa..cfbaf279e 100644 --- a/packages/core/cairo/src/utils/duration.ts +++ b/packages/core/cairo/src/utils/duration.ts @@ -1,16 +1,6 @@ -const durationUnits = [ - "second", - "minute", - "hour", - "day", - "week", - "month", - "year", -] as const; +const durationUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; type DurationUnit = (typeof durationUnits)[number]; -export const durationPattern = new RegExp( - `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, -); +export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); const second = 1; const minute = 60 * second; @@ -25,7 +15,7 @@ export function durationToTimestamp(duration: string): number { const match = duration.trim().match(durationPattern); if (!match || match.length < 2) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = parseFloat(match[1]!); diff --git a/packages/core/cairo/src/utils/find-cover.ts b/packages/core/cairo/src/utils/find-cover.ts index 0cc7f0bb6..939ed9240 100644 --- a/packages/core/cairo/src/utils/find-cover.ts +++ b/packages/core/cairo/src/utils/find-cover.ts @@ -1,14 +1,11 @@ -import { sortedBy } from "./sorted-by"; +import { sortedBy } from './sorted-by'; // Greedy approximation of minimum set cover. -export function findCover( - sets: T[], - getElements: (set: T) => unknown[], -): T[] { +export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { const sortedSets = sortedBy( - sets.map((set) => ({ set, elems: getElements(set) })), - (s) => -s.elems.length, + sets.map(set => ({ set, elems: getElements(set) })), + s => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/cairo/src/utils/format-lines.ts b/packages/core/cairo/src/utils/format-lines.ts index 66a39941e..52af7ab52 100644 --- a/packages/core/cairo/src/utils/format-lines.ts +++ b/packages/core/cairo/src/utils/format-lines.ts @@ -1,29 +1,26 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol("whitespace"); +const whitespace = Symbol('whitespace'); export function formatLines(...lines: Lines[]): string { - return [...indentEach(0, lines)].join("\n") + "\n"; + return [...indentEach(0, lines)].join('\n') + '\n'; } -function* indentEach( - indent: number, - lines: Lines[], -): Generator { +function* indentEach(indent: number, lines: Lines[]): Generator { for (const line of lines) { if (line === whitespace) { - yield ""; + yield ''; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line); } else { - yield " ".repeat(indent) + line; + yield ' '.repeat(indent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter((l) => l.length > 0) - .flatMap((l) => [whitespace, ...l]) + .filter(l => l.length > 0) + .flatMap(l => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/cairo/src/utils/version.test.ts b/packages/core/cairo/src/utils/version.test.ts index a612a88e8..735d62527 100644 --- a/packages/core/cairo/src/utils/version.test.ts +++ b/packages/core/cairo/src/utils/version.test.ts @@ -1,10 +1,10 @@ -import test from "ava"; +import test from 'ava'; -import semver from "semver"; +import semver from 'semver'; -import { contractsVersion, compatibleContractsSemver } from "./version"; +import { contractsVersion, compatibleContractsSemver } from './version'; -test("latest target contracts satisfies compatible range", (t) => { +test('latest target contracts satisfies compatible range', t => { t.true( semver.satisfies(contractsVersion, compatibleContractsSemver), `Latest target contracts version ${contractsVersion} does not satisfy compatible range ${compatibleContractsSemver}. diff --git a/packages/core/cairo/src/utils/version.ts b/packages/core/cairo/src/utils/version.ts index 89eaca1a3..ccba00acc 100644 --- a/packages/core/cairo/src/utils/version.ts +++ b/packages/core/cairo/src/utils/version.ts @@ -1,17 +1,17 @@ /** * The actual latest version to use in links. */ -export const contractsVersion = "0.20.0"; +export const contractsVersion = '0.20.0'; export const contractsVersionTag = `v${contractsVersion}`; /** * Cairo compiler versions. */ -export const edition = "2024_07"; -export const cairoVersion = "2.9.1"; -export const scarbVersion = "2.9.1"; +export const edition = '2024_07'; +export const cairoVersion = '2.9.1'; +export const scarbVersion = '2.9.1'; /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = "^0.20.0"; +export const compatibleContractsSemver = '^0.20.0'; diff --git a/packages/core/cairo/src/vesting.test.ts b/packages/core/cairo/src/vesting.test.ts index cf0fa48fc..76139b28b 100644 --- a/packages/core/cairo/src/vesting.test.ts +++ b/packages/core/cairo/src/vesting.test.ts @@ -1,27 +1,29 @@ -import test from "ava"; -import { OptionsError, vesting } from "."; -import { buildVesting, VestingOptions } from "./vesting"; -import { printContract } from "./print"; +import test from 'ava'; +import type { OptionsError } from '.'; +import { vesting } from '.'; +import type { VestingOptions } from './vesting'; +import { buildVesting } from './vesting'; +import { printContract } from './print'; const defaults: VestingOptions = { - name: "MyVesting", - startDate: "", - duration: "0 day", - cliffDuration: "0 day", - schedule: "linear", + name: 'MyVesting', + startDate: '', + duration: '0 day', + cliffDuration: '0 day', + schedule: 'linear', }; -const CUSTOM_NAME = "CustomVesting"; -const CUSTOM_DATE = "2024-12-31T23:59"; -const CUSTOM_DURATION = "36 months"; -const CUSTOM_CLIFF = "90 days"; +const CUSTOM_NAME = 'CustomVesting'; +const CUSTOM_DATE = '2024-12-31T23:59'; +const CUSTOM_DURATION = '36 months'; +const CUSTOM_CLIFF = '90 days'; // // Test helpers // function testVesting(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildVesting({ ...defaults, ...opts, @@ -31,7 +33,7 @@ function testVesting(title: string, opts: Partial) { } function testAPIEquivalence(title: string, opts?: VestingOptions) { - test(title, (t) => { + test(title, t => { t.is( vesting.print(opts), printContract( @@ -48,82 +50,82 @@ function testAPIEquivalence(title: string, opts?: VestingOptions) { // Snapshot tests // -testVesting("custom name", { +testVesting('custom name', { name: CUSTOM_NAME, }); -testVesting("custom start date", { +testVesting('custom start date', { startDate: CUSTOM_DATE, }); -testVesting("custom duration", { +testVesting('custom duration', { duration: CUSTOM_DURATION, }); -testVesting("custom cliff", { +testVesting('custom cliff', { duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, }); -testVesting("custom schedule", { - schedule: "custom", +testVesting('custom schedule', { + schedule: 'custom', }); -testVesting("all custom settings", { +testVesting('all custom settings', { startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: "custom", + schedule: 'custom', }); // // API tests // -testAPIEquivalence("API custom name", { +testAPIEquivalence('API custom name', { ...defaults, name: CUSTOM_NAME, }); -testAPIEquivalence("API custom start date", { +testAPIEquivalence('API custom start date', { ...defaults, startDate: CUSTOM_DATE, }); -testAPIEquivalence("API custom duration", { +testAPIEquivalence('API custom duration', { ...defaults, duration: CUSTOM_DURATION, }); -testAPIEquivalence("API custom cliff", { +testAPIEquivalence('API custom cliff', { ...defaults, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, }); -testAPIEquivalence("API custom schedule", { +testAPIEquivalence('API custom schedule', { ...defaults, - schedule: "custom", + schedule: 'custom', }); -testAPIEquivalence("API all custom settings", { +testAPIEquivalence('API all custom settings', { ...defaults, startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: "custom", + schedule: 'custom', }); -test("cliff too high", async (t) => { +test('cliff too high', async t => { const error = t.throws(() => buildVesting({ ...defaults, - duration: "20 days", - cliffDuration: "21 days", + duration: '20 days', + cliffDuration: '21 days', }), ); t.is( (error as OptionsError).messages.cliffDuration, - "Cliff duration must be less than or equal to the total duration", + 'Cliff duration must be less than or equal to the total duration', ); }); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 398a509a9..451c42a80 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -1,22 +1,24 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { setAccessControl } from "./set-access-control"; -import { setUpgradeable } from "./set-upgradeable"; -import { Info, setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { printContract } from "./print"; -import { OptionsError } from "./error"; -import { durationToTimestamp } from "./utils/duration"; -import { toUint, validateUint } from "./utils/convert-strings"; - -export type VestingSchedule = "linear" | "custom"; +import type { BaseImplementedTrait, Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { contractDefaults as commonDefaults } from './common-options'; +import { setAccessControl } from './set-access-control'; +import { setUpgradeable } from './set-upgradeable'; +import type { Info } from './set-info'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { printContract } from './print'; +import { OptionsError } from './error'; +import { durationToTimestamp } from './utils/duration'; +import { toUint, validateUint } from './utils/convert-strings'; + +export type VestingSchedule = 'linear' | 'custom'; export const defaults: Required = { - name: "VestingWallet", - startDate: "", - duration: "0 day", - cliffDuration: "0 day", - schedule: "custom", + name: 'VestingWallet', + startDate: '', + duration: '0 day', + cliffDuration: '0 day', + schedule: 'custom', info: commonDefaults.info, } as const; @@ -53,7 +55,7 @@ export function buildVesting(opts: VestingOptions): Contract { setInfo(c, allOpts.info); // Vesting component depends on Ownable component - const access = "ownable"; + const access = 'ownable'; setAccessControl(c, access); // Must be non-upgradable to guarantee vesting according to the schedule @@ -63,107 +65,94 @@ export function buildVesting(opts: VestingOptions): Contract { } function addBase(c: ContractBuilder, opts: VestingOptions) { - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); const startDate = getVestingStart(opts); const totalDuration = getVestingDuration(opts); const cliffDuration = getCliffDuration(opts); validateDurations(totalDuration, cliffDuration); if (startDate !== undefined) { c.addConstant({ - name: "START", - type: "u64", + name: 'START', + type: 'u64', value: startDate.timestampInSec.toString(), comment: startDate.formattedDate, inlineComment: true, }); } else { c.addConstant({ - name: "START", - type: "u64", - value: "0", + name: 'START', + type: 'u64', + value: '0', }); } c.addConstant({ - name: "DURATION", - type: "u64", + name: 'DURATION', + type: 'u64', value: totalDuration.toString(), comment: opts.duration, inlineComment: true, }); c.addConstant({ - name: "CLIFF_DURATION", - type: "u64", + name: 'CLIFF_DURATION', + type: 'u64', value: cliffDuration.toString(), comment: opts.cliffDuration, inlineComment: true, }); - const initParams = [ - { lit: "START" }, - { lit: "DURATION" }, - { lit: "CLIFF_DURATION" }, - ]; + const initParams = [{ lit: 'START' }, { lit: 'DURATION' }, { lit: 'CLIFF_DURATION' }]; c.addComponent(components.VestingComponent, initParams, true); } function addSchedule(c: ContractBuilder, opts: VestingOptions) { switch (opts.schedule) { - case "linear": - c.addUseClause("openzeppelin::finance::vesting", "LinearVestingSchedule"); + case 'linear': + c.addUseClause('openzeppelin::finance::vesting', 'LinearVestingSchedule'); return; - case "custom": { + case 'custom': { const scheduleTrait: BaseImplementedTrait = { name: `VestingSchedule`, - of: "VestingComponent::VestingScheduleTrait", + of: 'VestingComponent::VestingScheduleTrait', tags: [], priority: 0, }; c.addImplementedTrait(scheduleTrait); c.addFunction(scheduleTrait, { - name: "calculate_vested_amount", - returns: "u256", + name: 'calculate_vested_amount', + returns: 'u256', args: [ { - name: "self", + name: 'self', type: `@VestingComponent::ComponentState`, }, - { name: "token", type: "ContractAddress" }, - { name: "total_allocation", type: "u256" }, - { name: "timestamp", type: "u64" }, - { name: "start", type: "u64" }, - { name: "duration", type: "u64" }, - { name: "cliff", type: "u64" }, - ], - code: [ - "// TODO: Must be implemented according to the desired vesting schedule", - "0", + { name: 'token', type: 'ContractAddress' }, + { name: 'total_allocation', type: 'u256' }, + { name: 'timestamp', type: 'u64' }, + { name: 'start', type: 'u64' }, + { name: 'duration', type: 'u64' }, + { name: 'cliff', type: 'u64' }, ], + code: ['// TODO: Must be implemented according to the desired vesting schedule', '0'], }); return; } } } -function getVestingStart( - opts: VestingOptions, -): { timestampInSec: bigint; formattedDate: string } | undefined { - if (opts.startDate === "" || opts.startDate === "NaN") { +function getVestingStart(opts: VestingOptions): { timestampInSec: bigint; formattedDate: string } | undefined { + if (opts.startDate === '' || opts.startDate === 'NaN') { return undefined; } const startDate = new Date(`${opts.startDate}Z`); const timestampInMillis = startDate.getTime(); - const timestampInSec = toUint( - Math.floor(timestampInMillis / 1000), - "startDate", - "u64", - ); - const formattedDate = startDate.toLocaleString("en-GB", { - day: "2-digit", - month: "short", - year: "numeric", - hour: "2-digit", - minute: "2-digit", + const timestampInSec = toUint(Math.floor(timestampInMillis / 1000), 'startDate', 'u64'); + const formattedDate = startDate.toLocaleString('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', hour12: false, - timeZone: "UTC", + timeZone: 'UTC', }); return { timestampInSec, formattedDate: `${formattedDate} (UTC)` }; } @@ -197,8 +186,8 @@ function getCliffDuration(opts: VestingOptions): number { } function validateDurations(duration: number, cliffDuration: number): void { - validateUint(duration, "duration", "u64"); - validateUint(cliffDuration, "cliffDuration", "u64"); + validateUint(duration, 'duration', 'u64'); + validateUint(cliffDuration, 'cliffDuration', 'u64'); if (cliffDuration > duration) { throw new OptionsError({ cliffDuration: `Cliff duration must be less than or equal to the total duration`, @@ -208,24 +197,24 @@ function validateDurations(duration: number, cliffDuration: number): void { const components = defineComponents({ VestingComponent: { - path: "openzeppelin::finance::vesting", + path: 'openzeppelin::finance::vesting', substorage: { - name: "vesting", - type: "VestingComponent::Storage", + name: 'vesting', + type: 'VestingComponent::Storage', }, event: { - name: "VestingEvent", - type: "VestingComponent::Event", + name: 'VestingEvent', + type: 'VestingComponent::Event', }, impls: [ { - name: "VestingImpl", - value: "VestingComponent::VestingImpl", + name: 'VestingImpl', + value: 'VestingComponent::VestingImpl', }, { - name: "VestingInternalImpl", + name: 'VestingInternalImpl', embed: false, - value: "VestingComponent::InternalImpl", + value: 'VestingComponent::InternalImpl', }, ], }, diff --git a/packages/core/solidity/ava.config.js b/packages/core/solidity/ava.config.js index a39075959..e39146f7a 100644 --- a/packages/core/solidity/ava.config.js +++ b/packages/core/solidity/ava.config.js @@ -1,9 +1,9 @@ module.exports = { - extensions: ["ts"], - require: ["ts-node/register"], + extensions: ['ts'], + require: ['ts-node/register'], watchmode: { - ignoreChanges: ["contracts", "artifacts", "cache"], + ignoreChanges: ['contracts', 'artifacts', 'cache'], }, - timeout: "10m", + timeout: '10m', workerThreads: false, }; diff --git a/packages/core/solidity/get-imports.d.ts b/packages/core/solidity/get-imports.d.ts index d89032e9e..4c0a44175 100644 --- a/packages/core/solidity/get-imports.d.ts +++ b/packages/core/solidity/get-imports.d.ts @@ -1 +1 @@ -export * from "./src/get-imports"; +export * from './src/get-imports'; diff --git a/packages/core/solidity/get-imports.js b/packages/core/solidity/get-imports.js index 52dd2841a..bc85ea526 100644 --- a/packages/core/solidity/get-imports.js +++ b/packages/core/solidity/get-imports.js @@ -1 +1 @@ -module.exports = require("./dist/get-imports"); +module.exports = require('./dist/get-imports'); diff --git a/packages/core/solidity/hardhat.config.js b/packages/core/solidity/hardhat.config.js index 01edf1775..fe8394ddd 100644 --- a/packages/core/solidity/hardhat.config.js +++ b/packages/core/solidity/hardhat.config.js @@ -1,24 +1,21 @@ -const { task } = require("hardhat/config"); -const { HardhatError } = require("hardhat/internal/core/errors"); -const { ERRORS } = require("hardhat/internal/core/errors-list"); +const { task } = require('hardhat/config'); +const { HardhatError } = require('hardhat/internal/core/errors'); +const { ERRORS } = require('hardhat/internal/core/errors-list'); const { TASK_COMPILE_SOLIDITY_CHECK_ERRORS, TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, -} = require("hardhat/builtin-tasks/task-names"); -const SOLIDITY_VERSION = require("./src/solidity-version.json"); +} = require('hardhat/builtin-tasks/task-names'); +const SOLIDITY_VERSION = require('./src/solidity-version.json'); // Unused parameter warnings are caused by OpenZeppelin Upgradeable Contracts. -const WARN_UNUSED_PARAMETER = "5667"; -const WARN_CODE_SIZE = "5574"; +const WARN_UNUSED_PARAMETER = '5667'; +const WARN_CODE_SIZE = '5574'; const IGNORED_WARNINGS = [WARN_UNUSED_PARAMETER, WARN_CODE_SIZE]; // Overriding this task so that warnings are considered errors. task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { - const errors = - (output.errors && - output.errors.filter((e) => !IGNORED_WARNINGS.includes(e.errorCode))) || - []; + const errors = (output.errors && output.errors.filter(e => !IGNORED_WARNINGS.includes(e.errorCode))) || []; await run(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, { output: { ...output, errors }, @@ -30,20 +27,15 @@ task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { } }); -task( - TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, - async ({ compilationJobs }, _, runSuper) => { - const CHUNK_SIZE = 100; - const chunks = []; - for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { - chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); - } - const mergedChunks = await Promise.all( - chunks.map((cj) => runSuper({ compilationJobs: cj })), - ); - return mergedChunks.flat(); - }, -); +task(TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, async ({ compilationJobs }, _, runSuper) => { + const CHUNK_SIZE = 100; + const chunks = []; + for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { + chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); + } + const mergedChunks = await Promise.all(chunks.map(cj => runSuper({ compilationJobs: cj }))); + return mergedChunks.flat(); +}); /** * @type import('hardhat/config').HardhatUserConfig diff --git a/packages/core/solidity/print-versioned.js b/packages/core/solidity/print-versioned.js index c3861b0fa..2bc0b8aa3 100644 --- a/packages/core/solidity/print-versioned.js +++ b/packages/core/solidity/print-versioned.js @@ -1 +1 @@ -module.exports = require("./dist/print-versioned"); +module.exports = require('./dist/print-versioned'); diff --git a/packages/core/solidity/print-versioned.ts b/packages/core/solidity/print-versioned.ts index ee52117b8..95629a774 100644 --- a/packages/core/solidity/print-versioned.ts +++ b/packages/core/solidity/print-versioned.ts @@ -1 +1 @@ -export * from "./src/print-versioned"; +export * from './src/print-versioned'; diff --git a/packages/core/solidity/src/add-pausable.ts b/packages/core/solidity/src/add-pausable.ts index e6fedab08..12fe36288 100644 --- a/packages/core/solidity/src/add-pausable.ts +++ b/packages/core/solidity/src/add-pausable.ts @@ -1,40 +1,37 @@ -import type { ContractBuilder, BaseFunction } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder, BaseFunction } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; -export function addPausable( - c: ContractBuilder, - access: Access, - pausableFns: BaseFunction[], -) { +export function addPausable(c: ContractBuilder, access: Access, pausableFns: BaseFunction[]) { c.addParent({ - name: "Pausable", - path: "@openzeppelin/contracts/utils/Pausable.sol", + name: 'Pausable', + path: '@openzeppelin/contracts/utils/Pausable.sol', }); for (const fn of pausableFns) { - c.addModifier("whenNotPaused", fn); + c.addModifier('whenNotPaused', fn); } addPauseFunctions(c, access); } export function addPauseFunctions(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.pause, access, "PAUSER", "pauser"); - c.addFunctionCode("_pause();", functions.pause); + requireAccessControl(c, functions.pause, access, 'PAUSER', 'pauser'); + c.addFunctionCode('_pause();', functions.pause); - requireAccessControl(c, functions.unpause, access, "PAUSER", "pauser"); - c.addFunctionCode("_unpause();", functions.unpause); + requireAccessControl(c, functions.unpause, access, 'PAUSER', 'pauser'); + c.addFunctionCode('_unpause();', functions.unpause); } const functions = defineFunctions({ pause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, unpause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, }); diff --git a/packages/core/solidity/src/api.ts b/packages/core/solidity/src/api.ts index e7c8f1941..43ce36e6d 100644 --- a/packages/core/solidity/src/api.ts +++ b/packages/core/solidity/src/api.ts @@ -1,40 +1,40 @@ -import type { CommonOptions } from "./common-options"; +import type { CommonOptions } from './common-options'; +import type { ERC20Options } from './erc20'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, - ERC20Options, -} from "./erc20"; +} from './erc20'; +import type { ERC721Options } from './erc721'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, - ERC721Options, -} from "./erc721"; +} from './erc721'; +import type { ERC1155Options } from './erc1155'; import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, - ERC1155Options, -} from "./erc1155"; +} from './erc1155'; +import type { StablecoinOptions } from './stablecoin'; import { printStablecoin, defaults as stablecoinDefaults, isAccessControlRequired as stablecoinIsAccessControlRequired, - StablecoinOptions, -} from "./stablecoin"; +} from './stablecoin'; +import type { GovernorOptions } from './governor'; import { printGovernor, defaults as governorDefaults, isAccessControlRequired as governorIsAccessControlRequired, - GovernorOptions, -} from "./governor"; +} from './governor'; +import type { CustomOptions } from './custom'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, - CustomOptions, -} from "./custom"; +} from './custom'; export interface WizardContractAPI { /** diff --git a/packages/core/solidity/src/build-generic.ts b/packages/core/solidity/src/build-generic.ts index 8487be2e3..fe64543fa 100644 --- a/packages/core/solidity/src/build-generic.ts +++ b/packages/core/solidity/src/build-generic.ts @@ -1,49 +1,55 @@ -import { CustomOptions, buildCustom } from "./custom"; -import { ERC20Options, buildERC20 } from "./erc20"; -import { ERC721Options, buildERC721 } from "./erc721"; -import { ERC1155Options, buildERC1155 } from "./erc1155"; -import { StablecoinOptions, buildStablecoin } from "./stablecoin"; -import { GovernorOptions, buildGovernor } from "./governor"; -import { Contract } from "./contract"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import type { StablecoinOptions } from './stablecoin'; +import { buildStablecoin } from './stablecoin'; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import type { Contract } from './contract'; export interface KindedOptions { - ERC20: { kind: "ERC20" } & ERC20Options; - ERC721: { kind: "ERC721" } & ERC721Options; - ERC1155: { kind: "ERC1155" } & ERC1155Options; - Stablecoin: { kind: "Stablecoin" } & StablecoinOptions; - RealWorldAsset: { kind: "RealWorldAsset" } & StablecoinOptions; - Governor: { kind: "Governor" } & GovernorOptions; - Custom: { kind: "Custom" } & CustomOptions; + ERC20: { kind: 'ERC20' } & ERC20Options; + ERC721: { kind: 'ERC721' } & ERC721Options; + ERC1155: { kind: 'ERC1155' } & ERC1155Options; + Stablecoin: { kind: 'Stablecoin' } & StablecoinOptions; + RealWorldAsset: { kind: 'RealWorldAsset' } & StablecoinOptions; + Governor: { kind: 'Governor' } & GovernorOptions; + Custom: { kind: 'Custom' } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions): Contract { switch (opts.kind) { - case "ERC20": + case 'ERC20': return buildERC20(opts); - case "ERC721": + case 'ERC721': return buildERC721(opts); - case "ERC1155": + case 'ERC1155': return buildERC1155(opts); - case "Stablecoin": + case 'Stablecoin': return buildStablecoin(opts); - case "RealWorldAsset": + case 'RealWorldAsset': return buildStablecoin(opts); - case "Governor": + case 'Governor': return buildGovernor(opts); - case "Custom": + case 'Custom': return buildCustom(opts); default: { const _: never = opts; - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } } diff --git a/packages/core/solidity/src/common-functions.ts b/packages/core/solidity/src/common-functions.ts index 7647afdad..cf311c898 100644 --- a/packages/core/solidity/src/common-functions.ts +++ b/packages/core/solidity/src/common-functions.ts @@ -1,9 +1,9 @@ -import type { BaseFunction } from "./contract"; +import type { BaseFunction } from './contract'; export const supportsInterface: BaseFunction = { - name: "supportsInterface", - kind: "public", - args: [{ name: "interfaceId", type: "bytes4" }], - returns: ["bool"], - mutability: "view", + name: 'supportsInterface', + kind: 'public', + args: [{ name: 'interfaceId', type: 'bytes4' }], + returns: ['bool'], + mutability: 'view', }; diff --git a/packages/core/solidity/src/common-options.ts b/packages/core/solidity/src/common-options.ts index 9e3e59e9c..b001ae5f6 100644 --- a/packages/core/solidity/src/common-options.ts +++ b/packages/core/solidity/src/common-options.ts @@ -1,7 +1,7 @@ -import type { Access } from "./set-access-control"; -import type { Info } from "./set-info"; -import { defaults as infoDefaults } from "./set-info"; -import type { Upgradeable } from "./set-upgradeable"; +import type { Access } from './set-access-control'; +import type { Info } from './set-info'; +import { defaults as infoDefaults } from './set-info'; +import type { Upgradeable } from './set-upgradeable'; export const defaults: Required = { access: false, @@ -15,9 +15,7 @@ export interface CommonOptions { info?: Info; } -export function withCommonDefaults( - opts: CommonOptions, -): Required { +export function withCommonDefaults(opts: CommonOptions): Required { return { access: opts.access ?? false, upgradeable: opts.upgradeable ?? false, diff --git a/packages/core/solidity/src/contract.test.ts b/packages/core/solidity/src/contract.test.ts index 1c74412f0..0a1824cee 100644 --- a/packages/core/solidity/src/contract.test.ts +++ b/packages/core/solidity/src/contract.test.ts @@ -1,8 +1,8 @@ -import test from "ava"; +import test from 'ava'; -import { ContractBuilder } from "./contract"; -import { printContract } from "./print"; -import { TAG_SECURITY_CONTACT } from "./set-info"; +import { ContractBuilder } from './contract'; +import { printContract } from './print'; +import { TAG_SECURITY_CONTACT } from './set-info'; const toContractReference = (name: string) => { return { @@ -17,153 +17,153 @@ const toParentContract = (name: string, path: string) => { }; }; -test("contract basics", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract basics', t => { + const Foo = new ContractBuilder('Foo'); t.snapshot(printContract(Foo)); }); -test("contract with a parent", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); +test('contract with a parent', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); Foo.addParent(Bar); t.snapshot(printContract(Foo)); }); -test("contract with two parents", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - const Quux = toParentContract("Quux", "./Quux.sol"); +test('contract with two parents', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + const Quux = toParentContract('Quux', './Quux.sol'); Foo.addParent(Bar); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test("contract with a parent with parameters", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - Foo.addParent(Bar, ["param1", "param2"]); +test('contract with a parent with parameters', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + Foo.addParent(Bar, ['param1', 'param2']); t.snapshot(printContract(Foo)); }); -test("contract with two parents only one with parameters", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - const Quux = toParentContract("Quux", "./Quux.sol"); - Foo.addParent(Bar, ["param1", "param2"]); +test('contract with two parents only one with parameters', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + const Quux = toParentContract('Quux', './Quux.sol'); + Foo.addParent(Bar, ['param1', 'param2']); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test("contract with one override", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with one override', t => { + const Foo = new ContractBuilder('Foo'); const _beforeTokenTransfer = { - name: "_beforeTokenTransfer", - kind: "internal" as const, + name: '_beforeTokenTransfer', + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }; - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test("contract with two overrides", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("ERC20Snapshot"), _beforeTokenTransfer); +test('contract with two overrides', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20Snapshot'), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test("contract with two different overrides", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with two different overrides', t => { + const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("OtherParent"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("ERC20"), _otherFunction); - Foo.addOverride(toContractReference("OtherParent"), _otherFunction); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('OtherParent'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20'), _otherFunction); + Foo.addOverride(toContractReference('OtherParent'), _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with a modifier", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addModifier("whenNotPaused", _otherFunction); +test('contract with a modifier', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addModifier('whenNotPaused', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with a modifier and override", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addModifier("whenNotPaused", _otherFunction); +test('contract with a modifier and override', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addModifier('whenNotPaused', _otherFunction); - Foo.addOverride(toContractReference("ERC20"), _otherFunction); - Foo.addOverride(toContractReference("OtherParent"), _otherFunction); + Foo.addOverride(toContractReference('ERC20'), _otherFunction); + Foo.addOverride(toContractReference('OtherParent'), _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with constructor code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); +test('contract with constructor code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code and a parent", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - Foo.addParent(Bar, ["param1", "param2"]); - Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); +test('contract with constructor code and a parent', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + Foo.addParent(Bar, ['param1', 'param2']); + Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); t.snapshot(printContract(Foo)); }); -test("contract with function code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); +test('contract with function code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with overridden function with code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference("Bar"), _otherFunction); - Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); +test('contract with overridden function with code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addOverride(toContractReference('Bar'), _otherFunction); + Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with one variable", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addVariable("uint value = 42;"); +test('contract with one variable', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addVariable('uint value = 42;'); t.snapshot(printContract(Foo)); }); -test("contract with two variables", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addVariable("uint value = 42;"); +test('contract with two variables', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addVariable('uint value = 42;'); Foo.addVariable('string name = "john";'); t.snapshot(printContract(Foo)); }); -test("name with special characters", (t) => { - const Foo = new ContractBuilder("foo bar baz"); +test('name with special characters', t => { + const Foo = new ContractBuilder('foo bar baz'); t.snapshot(printContract(Foo)); }); -test("contract with info", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addNatspecTag(TAG_SECURITY_CONTACT, "security@example.com"); +test('contract with info', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addNatspecTag(TAG_SECURITY_CONTACT, 'security@example.com'); t.snapshot(printContract(Foo)); }); const _beforeTokenTransfer = { - name: "_beforeTokenTransfer", - kind: "internal" as const, + name: '_beforeTokenTransfer', + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }; const _otherFunction = { - name: "_otherFunction", - kind: "internal" as const, + name: '_otherFunction', + kind: 'internal' as const, args: [], }; diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index 560bf4cae..6ed276124 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from "./utils/to-identifier"; +import { toIdentifier } from './utils/to-identifier'; export interface Contract { name: string; @@ -13,11 +13,7 @@ export interface Contract { upgradeable: boolean; } -export type Value = - | string - | number - | { lit: string } - | { note: string; value: Value }; +export type Value = string | number | { lit: string } | { note: string; value: Value }; export interface Parent { contract: ImportContract; @@ -56,19 +52,14 @@ export interface ContractFunction extends BaseFunction { comments: string[]; } -export type FunctionKind = "internal" | "public"; +export type FunctionKind = 'internal' | 'public'; export type FunctionMutability = (typeof mutabilityRank)[number]; // Order is important -const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; - -function maxMutability( - a: FunctionMutability, - b: FunctionMutability, -): FunctionMutability { - return mutabilityRank[ - Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) - ]!; +const mutabilityRank = ['pure', 'view', 'nonpayable', 'payable'] as const; + +function maxMutability(a: FunctionMutability, b: FunctionMutability): FunctionMutability { + return mutabilityRank[Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b))]!; } export interface FunctionArgument { @@ -83,7 +74,7 @@ export interface NatspecTag { export class ContractBuilder implements Contract { readonly name: string; - license: string = "MIT"; + license: string = 'MIT'; upgradeable = false; readonly using: Using[] = []; @@ -102,11 +93,11 @@ export class ContractBuilder implements Contract { get parents(): Parent[] { return [...this.parentMap.values()] - .filter((p) => !p.importOnly) + .filter(p => !p.importOnly) .sort((a, b) => { - if (a.contract.name === "Initializable") { + if (a.contract.name === 'Initializable') { return -1; - } else if (b.contract.name === "Initializable") { + } else if (b.contract.name === 'Initializable') { return 1; } else { return 0; @@ -115,10 +106,7 @@ export class ContractBuilder implements Contract { } get imports(): ImportContract[] { - return [ - ...[...this.parentMap.values()].map((p) => p.contract), - ...this.using.map((u) => u.library), - ]; + return [...[...this.parentMap.values()].map(p => p.contract), ...this.using.map(u => u.library)]; } get functions(): ContractFunction[] { @@ -145,11 +133,7 @@ export class ContractBuilder implements Contract { return !present; } - addOverride( - parent: ReferencedContract, - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); fn.override.add(parent); if (mutability) { @@ -164,18 +148,12 @@ export class ContractBuilder implements Contract { addNatspecTag(key: string, value: string) { // eslint-disable-next-line no-useless-escape - if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) - throw new Error(`Invalid natspec key: ${key}`); + if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) throw new Error(`Invalid natspec key: ${key}`); this.natspecTags.push({ key, value }); } private addFunction(baseFn: BaseFunction): ContractFunction { - const signature = [ - baseFn.name, - "(", - ...baseFn.args.map((a) => a.name), - ")", - ].join(""); + const signature = [baseFn.name, '(', ...baseFn.args.map(a => a.name), ')'].join(''); const got = this.functionMap.get(signature); if (got !== undefined) { return got; @@ -184,7 +162,7 @@ export class ContractBuilder implements Contract { override: new Set(), modifiers: [], code: [], - mutability: "nonpayable", + mutability: 'nonpayable', final: false, comments: [], ...baseFn, @@ -202,11 +180,7 @@ export class ContractBuilder implements Contract { this.constructorCode.push(code); } - addFunctionCode( - code: string, - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + addFunctionCode(code: string, baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); if (fn.final) { throw new Error(`Function ${baseFn.name} is already finalized`); @@ -217,11 +191,7 @@ export class ContractBuilder implements Contract { } } - setFunctionBody( - code: string[], - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + setFunctionBody(code: string[], baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { throw new Error(`Function ${baseFn.name} has additional code`); diff --git a/packages/core/solidity/src/custom.test.ts b/packages/core/solidity/src/custom.test.ts index 3299f1cbe..751b40277 100644 --- a/packages/core/solidity/src/custom.test.ts +++ b/packages/core/solidity/src/custom.test.ts @@ -1,13 +1,14 @@ -import test from "ava"; -import { custom } from "."; +import test from 'ava'; +import { custom } from '.'; -import { buildCustom, CustomOptions } from "./custom"; -import { printContract } from "./print"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import { printContract } from './print'; function testCustom(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }); t.snapshot(printContract(c)); @@ -18,12 +19,12 @@ function testCustom(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, (t) => { + test(title, t => { t.is( custom.print(opts), printContract( buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }), ), @@ -31,66 +32,66 @@ function testAPIEquivalence(title: string, opts?: CustomOptions) { }); } -testCustom("custom", {}); +testCustom('custom', {}); -testCustom("pausable", { +testCustom('pausable', { pausable: true, }); -testCustom("upgradeable transparent", { - upgradeable: "transparent", +testCustom('upgradeable transparent', { + upgradeable: 'transparent', }); -testCustom("upgradeable uups", { - upgradeable: "uups", +testCustom('upgradeable uups', { + upgradeable: 'uups', }); -testCustom("access control disabled", { +testCustom('access control disabled', { access: false, }); -testCustom("access control ownable", { - access: "ownable", +testCustom('access control ownable', { + access: 'ownable', }); -testCustom("access control roles", { - access: "roles", +testCustom('access control roles', { + access: 'roles', }); -testCustom("access control managed", { - access: "managed", +testCustom('access control managed', { + access: 'managed', }); -testCustom("upgradeable uups with access control disabled", { +testCustom('upgradeable uups with access control disabled', { // API should override access to true since it is required for UUPS access: false, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("custom API default"); +testAPIEquivalence('custom API default'); -testAPIEquivalence("custom API basic", { name: "CustomContract" }); +testAPIEquivalence('custom API basic', { name: 'CustomContract' }); -testAPIEquivalence("custom API full upgradeable", { - name: "CustomContract", - access: "roles", +testAPIEquivalence('custom API full upgradeable', { + name: 'CustomContract', + access: 'roles', pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("custom API full upgradeable with managed", { - name: "CustomContract", - access: "managed", +testAPIEquivalence('custom API full upgradeable with managed', { + name: 'CustomContract', + access: 'managed', pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("custom API assert defaults", async (t) => { +test('custom API assert defaults', async t => { t.is(custom.print(custom.defaults), custom.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(custom.isAccessControlRequired({ pausable: true }), true); - t.is(custom.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(custom.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(custom.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(custom.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/custom.ts b/packages/core/solidity/src/custom.ts index b167015ed..5baaf31b5 100644 --- a/packages/core/solidity/src/custom.ts +++ b/packages/core/solidity/src/custom.ts @@ -1,14 +1,12 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { setAccessControl } from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { printContract } from './print'; export interface CustomOptions extends CommonOptions { name: string; @@ -16,7 +14,7 @@ export interface CustomOptions extends CommonOptions { } export const defaults: Required = { - name: "MyContract", + name: 'MyContract', pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, @@ -36,7 +34,7 @@ export function printCustom(opts: CustomOptions = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.pausable || opts.upgradeable === "uups"; + return opts.pausable || opts.upgradeable === 'uups'; } export function buildCustom(opts: CustomOptions): Contract { diff --git a/packages/core/solidity/src/erc1155.test.ts b/packages/core/solidity/src/erc1155.test.ts index 91e20ceae..bea4ae13e 100644 --- a/packages/core/solidity/src/erc1155.test.ts +++ b/packages/core/solidity/src/erc1155.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc1155 } from "."; +import test from 'ava'; +import { erc1155 } from '.'; -import { buildERC1155, ERC1155Options } from "./erc1155"; -import { printContract } from "./print"; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import { printContract } from './print'; function testERC1155(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC1155({ - name: "MyToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", + name: 'MyToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, (t) => { + test(title, t => { t.is( erc1155.print(opts), printContract( buildERC1155({ - name: "MyToken", - uri: "", + name: 'MyToken', + uri: '', ...opts, }), ), @@ -33,104 +34,98 @@ function testAPIEquivalence(title: string, opts?: ERC1155Options) { }); } -testERC1155("basic", {}); +testERC1155('basic', {}); -testERC1155("basic + roles", { - access: "roles", +testERC1155('basic + roles', { + access: 'roles', }); -testERC1155("basic + managed", { - access: "managed", +testERC1155('basic + managed', { + access: 'managed', }); -testERC1155("no updatable uri", { +testERC1155('no updatable uri', { updatableUri: false, }); -testERC1155("burnable", { +testERC1155('burnable', { burnable: true, }); -testERC1155("pausable", { +testERC1155('pausable', { pausable: true, }); -testERC1155("mintable", { +testERC1155('mintable', { mintable: true, }); -testERC1155("mintable + roles", { +testERC1155('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC1155("mintable + managed", { +testERC1155('mintable + managed', { mintable: true, - access: "managed", + access: 'managed', }); -testERC1155("supply tracking", { +testERC1155('supply tracking', { supply: true, }); -testERC1155("full upgradeable transparent", { +testERC1155('full upgradeable transparent', { mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC1155("full upgradeable uups", { +testERC1155('full upgradeable uups', { mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC1155("full upgradeable transparent with managed", { +testERC1155('full upgradeable transparent with managed', { mintable: true, - access: "managed", + access: 'managed', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { - name: "CustomToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testAPIEquivalence('API basic', { + name: 'CustomToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', }); -testAPIEquivalence("API full upgradeable", { - name: "CustomToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testAPIEquivalence('API full upgradeable', { + name: 'CustomToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), - true, - ); +test('API isAccessControlRequired', async t => { + t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); t.is( erc1155.isAccessControlRequired({ updatableUri: false, - upgradeable: "uups", + upgradeable: 'uups', }), true, ); diff --git a/packages/core/solidity/src/erc1155.ts b/packages/core/solidity/src/erc1155.ts index 1ec08a189..9bb93ded1 100644 --- a/packages/core/solidity/src/erc1155.ts +++ b/packages/core/solidity/src/erc1155.ts @@ -1,20 +1,15 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { supportsInterface } from "./common-functions"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { supportsInterface } from './common-functions'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; export interface ERC1155Options extends CommonOptions { name: string; @@ -27,8 +22,8 @@ export interface ERC1155Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - uri: "", + name: 'MyToken', + uri: '', burnable: false, pausable: false, mintable: false, @@ -55,15 +50,8 @@ export function printERC1155(opts: ERC1155Options = defaults): string { return printContract(buildERC1155(opts)); } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return ( - opts.mintable || - opts.pausable || - opts.updatableUri !== false || - opts.upgradeable === "uups" - ); +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable || opts.pausable || opts.updatableUri !== false || opts.upgradeable === 'uups'; } export function buildERC1155(opts: ERC1155Options): Contract { @@ -104,8 +92,8 @@ export function buildERC1155(opts: ERC1155Options): Contract { function addBase(c: ContractBuilder, uri: string) { const ERC1155 = { - name: "ERC1155", - path: "@openzeppelin/contracts/token/ERC1155/ERC1155.sol", + name: 'ERC1155', + path: '@openzeppelin/contracts/token/ERC1155/ERC1155.sol', }; c.addParent(ERC1155, [uri]); @@ -115,8 +103,8 @@ function addBase(c: ContractBuilder, uri: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC1155Pausable = { - name: "ERC1155Pausable", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol", + name: 'ERC1155Pausable', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol', }; c.addParent(ERC1155Pausable); c.addOverride(ERC1155Pausable, functions._update); @@ -126,27 +114,27 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC1155Burnable", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol", + name: 'ERC1155Burnable', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol', }); } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, "MINTER", "minter"); - requireAccessControl(c, functions.mintBatch, access, "MINTER", "minter"); - c.addFunctionCode("_mint(account, id, amount, data);", functions.mint); - c.addFunctionCode("_mintBatch(to, ids, amounts, data);", functions.mintBatch); + requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); + requireAccessControl(c, functions.mintBatch, access, 'MINTER', 'minter'); + c.addFunctionCode('_mint(account, id, amount, data);', functions.mint); + c.addFunctionCode('_mintBatch(to, ids, amounts, data);', functions.mintBatch); } function addSetUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.setURI, access, "URI_SETTER", undefined); - c.addFunctionCode("_setURI(newuri);", functions.setURI); + requireAccessControl(c, functions.setURI, access, 'URI_SETTER', undefined); + c.addFunctionCode('_setURI(newuri);', functions.setURI); } function addSupply(c: ContractBuilder) { const ERC1155Supply = { - name: "ERC1155Supply", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol", + name: 'ERC1155Supply', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol', }; c.addParent(ERC1155Supply); c.addOverride(ERC1155Supply, functions._update); @@ -154,37 +142,37 @@ function addSupply(c: ContractBuilder) { const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "ids", type: "uint256[] memory" }, - { name: "values", type: "uint256[] memory" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'ids', type: 'uint256[] memory' }, + { name: 'values', type: 'uint256[] memory' }, ], }, setURI: { - kind: "public" as const, - args: [{ name: "newuri", type: "string memory" }], + kind: 'public' as const, + args: [{ name: 'newuri', type: 'string memory' }], }, mint: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "account", type: "address" }, - { name: "id", type: "uint256" }, - { name: "amount", type: "uint256" }, - { name: "data", type: "bytes memory" }, + { name: 'account', type: 'address' }, + { name: 'id', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + { name: 'data', type: 'bytes memory' }, ], }, mintBatch: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "to", type: "address" }, - { name: "ids", type: "uint256[] memory" }, - { name: "amounts", type: "uint256[] memory" }, - { name: "data", type: "bytes memory" }, + { name: 'to', type: 'address' }, + { name: 'ids', type: 'uint256[] memory' }, + { name: 'amounts', type: 'uint256[] memory' }, + { name: 'data', type: 'bytes memory' }, ], }, }); diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 4a1cbb586..84409b78d 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc20 } from "."; +import test from 'ava'; +import { erc20 } from '.'; -import { buildERC20, ERC20Options } from "./erc20"; -import { printContract } from "./print"; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import { printContract } from './print'; function testERC20(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, (t) => { + test(title, t => { t.is( erc20.print(opts), printContract( buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -33,131 +34,131 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { }); } -testERC20("basic erc20", {}); +testERC20('basic erc20', {}); -testERC20("erc20 burnable", { +testERC20('erc20 burnable', { burnable: true, }); -testERC20("erc20 pausable", { +testERC20('erc20 pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 pausable with roles", { +testERC20('erc20 pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 pausable with managed", { +testERC20('erc20 pausable with managed', { pausable: true, - access: "managed", + access: 'managed', }); -testERC20("erc20 burnable pausable", { +testERC20('erc20 burnable pausable', { burnable: true, pausable: true, }); -testERC20("erc20 preminted", { - premint: "1000", +testERC20('erc20 preminted', { + premint: '1000', }); -testERC20("erc20 premint of 0", { - premint: "0", +testERC20('erc20 premint of 0', { + premint: '0', }); -testERC20("erc20 mintable", { +testERC20('erc20 mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 mintable with roles", { +testERC20('erc20 mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 permit", { +testERC20('erc20 permit', { permit: true, }); -testERC20("erc20 votes", { +testERC20('erc20 votes', { votes: true, }); -testERC20("erc20 votes + blocknumber", { - votes: "blocknumber", +testERC20('erc20 votes + blocknumber', { + votes: 'blocknumber', }); -testERC20("erc20 votes + timestamp", { - votes: "timestamp", +testERC20('erc20 votes + timestamp', { + votes: 'timestamp', }); -testERC20("erc20 flashmint", { +testERC20('erc20 flashmint', { flashmint: true, }); -testERC20("erc20 full upgradeable transparent", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable transparent', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC20("erc20 full upgradeable uups", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable uups', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC20("erc20 full upgradeable uups managed", { - premint: "2000", - access: "managed", +testERC20('erc20 full upgradeable uups managed', { + premint: '2000', + access: 'managed', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("erc20 API default"); +testAPIEquivalence('erc20 API default'); -testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("erc20 API full upgradeable", { - name: "CustomToken", - symbol: "CTK", - premint: "2000", - access: "roles", +testAPIEquivalence('erc20 API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("erc20 API assert defaults", async (t) => { +test('erc20 API assert defaults', async t => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test("erc20 API isAccessControlRequired", async (t) => { +test('erc20 API isAccessControlRequired', async t => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(erc20.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(erc20.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 8a2643e09..3b0f3419b 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -1,20 +1,15 @@ -import { ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; -import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; +import type { ClockMode } from './set-clock-mode'; +import { clockModeDefault, setClockMode } from './set-clock-mode'; export interface ERC20Options extends CommonOptions { name: string; @@ -33,11 +28,11 @@ export interface ERC20Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', burnable: false, pausable: false, - premint: "0", + premint: '0', mintable: false, permit: true, votes: false, @@ -66,7 +61,7 @@ export function printERC20(opts: ERC20Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === "uups"; + return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; } export function buildERC20(opts: ERC20Options): ContractBuilder { @@ -117,8 +112,8 @@ export function buildERC20(opts: ERC20Options): ContractBuilder { function addBase(c: ContractBuilder, name: string, symbol: string) { const ERC20 = { - name: "ERC20", - path: "@openzeppelin/contracts/token/ERC20/ERC20.sol", + name: 'ERC20', + path: '@openzeppelin/contracts/token/ERC20/ERC20.sol', }; c.addParent(ERC20, [name, symbol]); @@ -128,8 +123,8 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC20Pausable = { - name: "ERC20Pausable", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol", + name: 'ERC20Pausable', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', }; c.addParent(ERC20Pausable); c.addOverride(ERC20Pausable, functions._update); @@ -139,8 +134,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC20Burnable", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol", + name: 'ERC20Burnable', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', }); } @@ -149,55 +144,54 @@ export const premintPattern = /^(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addPremint(c: ContractBuilder, amount: string) { const m = amount.match(premintPattern); if (m) { - const integer = m[1]?.replace(/^0+/, "") ?? ""; - const decimals = m[2]?.replace(/0+$/, "") ?? ""; + const integer = m[1]?.replace(/^0+/, '') ?? ''; + const decimals = m[2]?.replace(/0+$/, '') ?? ''; const exponent = Number(m[3] ?? 0); if (Number(integer + decimals) > 0) { const decimalPlace = decimals.length - exponent; - const zeroes = new Array(Math.max(0, -decimalPlace)).fill("0").join(""); + const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); const units = integer + decimals + zeroes; - const exp = - decimalPlace <= 0 ? "decimals()" : `(decimals() - ${decimalPlace})`; - c.addConstructorArgument({ type: "address", name: "recipient" }); + const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; + c.addConstructorArgument({ type: 'address', name: 'recipient' }); c.addConstructorCode(`_mint(recipient, ${units} * 10 ** ${exp});`); } } } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, "MINTER", "minter"); - c.addFunctionCode("_mint(to, amount);", functions.mint); + requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); + c.addFunctionCode('_mint(to, amount);', functions.mint); } function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { - name: "ERC20Permit", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol", + name: 'ERC20Permit', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol', }; c.addParent(ERC20Permit, [name]); c.addOverride(ERC20Permit, functions.nonces); } function addVotes(c: ContractBuilder, clockMode: ClockMode) { - if (!c.parents.some((p) => p.contract.name === "ERC20Permit")) { - throw new Error("Missing ERC20Permit requirement for ERC20Votes"); + if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) { + throw new Error('Missing ERC20Permit requirement for ERC20Votes'); } const ERC20Votes = { - name: "ERC20Votes", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol", + name: 'ERC20Votes', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol', }; c.addParent(ERC20Votes); c.addOverride(ERC20Votes, functions._update); c.addImportOnly({ - name: "Nonces", - path: "@openzeppelin/contracts/utils/Nonces.sol", + name: 'Nonces', + path: '@openzeppelin/contracts/utils/Nonces.sol', }); c.addOverride( { - name: "Nonces", + name: 'Nonces', }, functions.nonces, ); @@ -207,58 +201,58 @@ function addVotes(c: ContractBuilder, clockMode: ClockMode) { function addFlashMint(c: ContractBuilder) { c.addParent({ - name: "ERC20FlashMint", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol", + name: 'ERC20FlashMint', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol', }); } export const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "value", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, ], }, _approve: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "owner", type: "address" }, - { name: "spender", type: "address" }, - { name: "value", type: "uint256" }, - { name: "emitEvent", type: "bool" }, + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'emitEvent', type: 'bool' }, ], }, mint: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }, pause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, unpause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, snapshot: { - kind: "public" as const, + kind: 'public' as const, args: [], }, nonces: { - kind: "public" as const, - args: [{ name: "owner", type: "address" }], - returns: ["uint256"], - mutability: "view" as const, + kind: 'public' as const, + args: [{ name: 'owner', type: 'address' }], + returns: ['uint256'], + mutability: 'view' as const, }, }); diff --git a/packages/core/solidity/src/erc721.test.ts b/packages/core/solidity/src/erc721.test.ts index 96b608678..1b26718e0 100644 --- a/packages/core/solidity/src/erc721.test.ts +++ b/packages/core/solidity/src/erc721.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc721 } from "."; +import test from 'ava'; +import { erc721 } from '.'; -import { buildERC721, ERC721Options } from "./erc721"; -import { printContract } from "./print"; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import { printContract } from './print'; function testERC721(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC721(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, (t) => { + test(title, t => { t.is( erc721.print(opts), printContract( buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -33,126 +34,125 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { }); } -testERC721("basic", {}); +testERC721('basic', {}); -testERC721("base uri", { - baseUri: - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testERC721('base uri', { + baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', }); -testERC721("enumerable", { +testERC721('enumerable', { enumerable: true, }); -testERC721("uri storage", { +testERC721('uri storage', { uriStorage: true, }); -testERC721("mintable + uri storage", { +testERC721('mintable + uri storage', { mintable: true, uriStorage: true, }); -testERC721("mintable + uri storage + incremental", { +testERC721('mintable + uri storage + incremental', { mintable: true, uriStorage: true, incremental: true, }); -testERC721("burnable", { +testERC721('burnable', { burnable: true, }); -testERC721("burnable + uri storage", { +testERC721('burnable + uri storage', { uriStorage: true, burnable: true, }); -testERC721("pausable", { +testERC721('pausable', { pausable: true, }); -testERC721("mintable", { +testERC721('mintable', { mintable: true, }); -testERC721("mintable + roles", { +testERC721('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC721("mintable + managed", { +testERC721('mintable + managed', { mintable: true, - access: "managed", + access: 'managed', }); -testERC721("mintable + incremental", { +testERC721('mintable + incremental', { mintable: true, incremental: true, }); -testERC721("votes", { +testERC721('votes', { votes: true, }); -testERC721("votes + blocknumber", { - votes: "blocknumber", +testERC721('votes + blocknumber', { + votes: 'blocknumber', }); -testERC721("votes + timestamp", { - votes: "timestamp", +testERC721('votes + timestamp', { + votes: 'timestamp', }); -testERC721("full upgradeable transparent", { +testERC721('full upgradeable transparent', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC721("full upgradeable uups", { +testERC721('full upgradeable uups', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC721("full upgradeable uups + managed", { +testERC721('full upgradeable uups + managed', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", - access: "managed", + upgradeable: 'uups', + access: 'managed', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("API full upgradeable", { - name: "CustomToken", - symbol: "CTK", +testAPIEquivalence('API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(erc721.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(erc721.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/erc721.ts b/packages/core/solidity/src/erc721.ts index 123bc91f4..cfa285274 100644 --- a/packages/core/solidity/src/erc721.ts +++ b/packages/core/solidity/src/erc721.ts @@ -1,21 +1,17 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { supportsInterface } from "./common-functions"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; -import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { supportsInterface } from './common-functions'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; +import type { ClockMode } from './set-clock-mode'; +import { clockModeDefault, setClockMode } from './set-clock-mode'; export interface ERC721Options extends CommonOptions { name: string; @@ -35,9 +31,9 @@ export interface ERC721Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - symbol: "MTK", - baseUri: "", + name: 'MyToken', + symbol: 'MTK', + baseUri: '', enumerable: false, uriStorage: false, burnable: false, @@ -70,7 +66,7 @@ export function printERC721(opts: ERC721Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === "uups"; + return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; } export function buildERC721(opts: ERC721Options): Contract { @@ -120,8 +116,8 @@ export function buildERC721(opts: ERC721Options): Contract { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC721Pausable = { - name: "ERC721Pausable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol", + name: 'ERC721Pausable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol', }; c.addParent(ERC721Pausable); c.addOverride(ERC721Pausable, functions._update); @@ -130,8 +126,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { } const ERC721 = { - name: "ERC721", - path: "@openzeppelin/contracts/token/ERC721/ERC721.sol", + name: 'ERC721', + path: '@openzeppelin/contracts/token/ERC721/ERC721.sol', }; function addBase(c: ContractBuilder, name: string, symbol: string) { @@ -150,8 +146,8 @@ function addBaseURI(c: ContractBuilder, baseUri: string) { function addEnumerable(c: ContractBuilder) { const ERC721Enumerable = { - name: "ERC721Enumerable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol", + name: 'ERC721Enumerable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol', }; c.addParent(ERC721Enumerable); @@ -162,8 +158,8 @@ function addEnumerable(c: ContractBuilder) { function addURIStorage(c: ContractBuilder) { const ERC721URIStorage = { - name: "ERC721URIStorage", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol", + name: 'ERC721URIStorage', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol', }; c.addParent(ERC721URIStorage); @@ -173,43 +169,38 @@ function addURIStorage(c: ContractBuilder) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC721Burnable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol", + name: 'ERC721Burnable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol', }); } -function addMintable( - c: ContractBuilder, - access: Access, - incremental = false, - uriStorage = false, -) { +function addMintable(c: ContractBuilder, access: Access, incremental = false, uriStorage = false) { const fn = getMintFunction(incremental, uriStorage); - requireAccessControl(c, fn, access, "MINTER", "minter"); + requireAccessControl(c, fn, access, 'MINTER', 'minter'); if (incremental) { - c.addVariable("uint256 private _nextTokenId;"); - c.addFunctionCode("uint256 tokenId = _nextTokenId++;", fn); - c.addFunctionCode("_safeMint(to, tokenId);", fn); + c.addVariable('uint256 private _nextTokenId;'); + c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn); + c.addFunctionCode('_safeMint(to, tokenId);', fn); } else { - c.addFunctionCode("_safeMint(to, tokenId);", fn); + c.addFunctionCode('_safeMint(to, tokenId);', fn); } if (uriStorage) { - c.addFunctionCode("_setTokenURI(tokenId, uri);", fn); + c.addFunctionCode('_setTokenURI(tokenId, uri);', fn); } } function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const EIP712 = { - name: "EIP712", - path: "@openzeppelin/contracts/utils/cryptography/EIP712.sol", + name: 'EIP712', + path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol', }; - c.addParent(EIP712, [name, "1"]); + c.addParent(EIP712, [name, '1']); const ERC721Votes = { - name: "ERC721Votes", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol", + name: 'ERC721Votes', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol', }; c.addParent(ERC721Votes); @@ -221,51 +212,51 @@ function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "to", type: "address" }, - { name: "tokenId", type: "uint256" }, - { name: "auth", type: "address" }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'auth', type: 'address' }, ], - returns: ["address"], + returns: ['address'], }, tokenURI: { - kind: "public" as const, - args: [{ name: "tokenId", type: "uint256" }], - returns: ["string memory"], - mutability: "view" as const, + kind: 'public' as const, + args: [{ name: 'tokenId', type: 'uint256' }], + returns: ['string memory'], + mutability: 'view' as const, }, _baseURI: { - kind: "internal" as const, + kind: 'internal' as const, args: [], - returns: ["string memory"], - mutability: "pure" as const, + returns: ['string memory'], + mutability: 'pure' as const, }, _increaseBalance: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "account", type: "address" }, - { name: "value", type: "uint128" }, + { name: 'account', type: 'address' }, + { name: 'value', type: 'uint128' }, ], }, }); function getMintFunction(incremental: boolean, uriStorage: boolean) { const fn = { - name: "safeMint", - kind: "public" as const, - args: [{ name: "to", type: "address" }], + name: 'safeMint', + kind: 'public' as const, + args: [{ name: 'to', type: 'address' }], }; if (!incremental) { - fn.args.push({ name: "tokenId", type: "uint256" }); + fn.args.push({ name: 'tokenId', type: 'uint256' }); } if (uriStorage) { - fn.args.push({ name: "uri", type: "string memory" }); + fn.args.push({ name: 'uri', type: 'string memory' }); } return fn; diff --git a/packages/core/solidity/src/error.ts b/packages/core/solidity/src/error.ts index 2235dfd68..96eb23489 100644 --- a/packages/core/solidity/src/error.ts +++ b/packages/core/solidity/src/error.ts @@ -2,6 +2,6 @@ export type OptionsErrorMessages = { [prop in string]?: string }; export class OptionsError extends Error { constructor(readonly messages: OptionsErrorMessages) { - super("Invalid options for Governor"); + super('Invalid options for Governor'); } } diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index c4b282064..b66e733cf 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -4,9 +4,7 @@ type Alternatives = { [k in keyof B]: B[k][number]; }; -export function* generateAlternatives( - blueprint: B, -): Generator> { +export function* generateAlternatives(blueprint: B): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, values, @@ -15,9 +13,7 @@ export function* generateAlternatives( })); for (; !done(); advance()) { - yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]), - ) as Alternatives; + yield Object.fromEntries(entries.map(e => [e.key, e.values[e.current % e.limit]])) as Alternatives; } function done() { diff --git a/packages/core/solidity/src/generate/custom.ts b/packages/core/solidity/src/generate/custom.ts index 884032068..40207666a 100644 --- a/packages/core/solidity/src/generate/custom.ts +++ b/packages/core/solidity/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from "../custom"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { CustomOptions } from '../custom'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyContract"], + name: ['MyContract'], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/solidity/src/generate/erc1155.ts b/packages/core/solidity/src/generate/erc1155.ts index d340602a9..fdb9c09fe 100644 --- a/packages/core/solidity/src/generate/erc1155.ts +++ b/packages/core/solidity/src/generate/erc1155.ts @@ -1,14 +1,14 @@ -import type { ERC1155Options } from "../erc1155"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC1155Options } from '../erc1155'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - uri: ["https://example.com/"], + name: ['MyToken'], + uri: ['https://example.com/'], burnable: booleans, pausable: booleans, mintable: booleans, diff --git a/packages/core/solidity/src/generate/erc20.ts b/packages/core/solidity/src/generate/erc20.ts index 5c3d6eb48..3db65e95f 100644 --- a/packages/core/solidity/src/generate/erc20.ts +++ b/packages/core/solidity/src/generate/erc20.ts @@ -1,22 +1,22 @@ -import type { ERC20Options } from "../erc20"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC20Options } from '../erc20'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], + name: ['MyToken'], + symbol: ['MTK'], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ["1"], + premint: ['1'], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/solidity/src/generate/erc721.ts b/packages/core/solidity/src/generate/erc721.ts index 06d1ca530..1f91b7c30 100644 --- a/packages/core/solidity/src/generate/erc721.ts +++ b/packages/core/solidity/src/generate/erc721.ts @@ -1,16 +1,16 @@ -import type { ERC721Options } from "../erc721"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC721Options } from '../erc721'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + symbol: ['MTK'], + baseUri: ['https://example.com/'], enumerable: booleans, uriStorage: booleans, burnable: booleans, diff --git a/packages/core/solidity/src/generate/governor.ts b/packages/core/solidity/src/generate/governor.ts index 66679caae..765f4e69f 100644 --- a/packages/core/solidity/src/generate/governor.ts +++ b/packages/core/solidity/src/generate/governor.ts @@ -1,27 +1,23 @@ -import { - defaults, - GovernorOptions, - timelockOptions, - votesOptions, -} from "../governor"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { GovernorOptions } from '../governor'; +import { defaults, timelockOptions, votesOptions } from '../governor'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyGovernor"], - delay: ["1 week"], - period: ["1 week"], + name: ['MyGovernor'], + delay: ['1 week'], + period: ['1 week'], blockTime: [defaults.blockTime], - proposalThreshold: ["0", "1000"], + proposalThreshold: ['0', '1000'], decimals: [18], - quorumMode: ["percent", "absolute"] as const, + quorumMode: ['percent', 'absolute'] as const, quorumPercent: [4], - quorumAbsolute: ["1000"], + quorumAbsolute: ['1000'], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, @@ -32,8 +28,6 @@ const blueprint = { info: infoOptions, }; -export function* generateGovernorOptions(): Generator< - Required -> { +export function* generateGovernorOptions(): Generator> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/solidity/src/generate/sources.ts b/packages/core/solidity/src/generate/sources.ts index 3ee3ad00c..b25f6c444 100644 --- a/packages/core/solidity/src/generate/sources.ts +++ b/packages/core/solidity/src/generate/sources.ts @@ -1,63 +1,64 @@ -import { promises as fs } from "fs"; -import path from "path"; -import crypto from "crypto"; - -import { generateERC20Options } from "./erc20"; -import { generateERC721Options } from "./erc721"; -import { generateERC1155Options } from "./erc1155"; -import { generateStablecoinOptions } from "./stablecoin"; -import { generateGovernorOptions } from "./governor"; -import { generateCustomOptions } from "./custom"; -import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; -import { printContract } from "../print"; -import { OptionsError } from "../error"; -import { findCover } from "../utils/find-cover"; -import type { Contract } from "../contract"; - -type Subset = "all" | "minimal-cover"; +import { promises as fs } from 'fs'; +import path from 'path'; +import crypto from 'crypto'; + +import { generateERC20Options } from './erc20'; +import { generateERC721Options } from './erc721'; +import { generateERC1155Options } from './erc1155'; +import { generateStablecoinOptions } from './stablecoin'; +import { generateGovernorOptions } from './governor'; +import { generateCustomOptions } from './custom'; +import type { GenericOptions, KindedOptions } from '../build-generic'; +import { buildGeneric } from '../build-generic'; +import { printContract } from '../print'; +import { OptionsError } from '../error'; +import { findCover } from '../utils/find-cover'; +import type { Contract } from '../contract'; + +type Subset = 'all' | 'minimal-cover'; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === "ERC20") { + if (!kind || kind === 'ERC20') { for (const kindOpts of generateERC20Options()) { - yield { kind: "ERC20", ...kindOpts }; + yield { kind: 'ERC20', ...kindOpts }; } } - if (!kind || kind === "ERC721") { + if (!kind || kind === 'ERC721') { for (const kindOpts of generateERC721Options()) { - yield { kind: "ERC721", ...kindOpts }; + yield { kind: 'ERC721', ...kindOpts }; } } - if (!kind || kind === "ERC1155") { + if (!kind || kind === 'ERC1155') { for (const kindOpts of generateERC1155Options()) { - yield { kind: "ERC1155", ...kindOpts }; + yield { kind: 'ERC1155', ...kindOpts }; } } - if (!kind || kind === "Stablecoin") { + if (!kind || kind === 'Stablecoin') { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: "Stablecoin", ...kindOpts }; + yield { kind: 'Stablecoin', ...kindOpts }; } } - if (!kind || kind === "RealWorldAsset") { + if (!kind || kind === 'RealWorldAsset') { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: "RealWorldAsset", ...kindOpts }; + yield { kind: 'RealWorldAsset', ...kindOpts }; } } - if (!kind || kind === "Governor") { + if (!kind || kind === 'Governor') { for (const kindOpts of generateGovernorOptions()) { - yield { kind: "Governor", ...kindOpts }; + yield { kind: 'Governor', ...kindOpts }; } } - if (!kind || kind === "Custom") { + if (!kind || kind === 'Custom') { for (const kindOpts of generateCustomOptions()) { - yield { kind: "Custom", ...kindOpts }; + yield { kind: 'Custom', ...kindOpts }; } } } @@ -72,18 +73,11 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset( - subset: Subset, - kind?: Kind, -): GeneratedContract[] { +function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { - const id = crypto - .createHash("sha1") - .update(JSON.stringify(options)) - .digest() - .toString("hex"); + const id = crypto.createHash('sha1').update(JSON.stringify(options)).digest().toString('hex'); try { const contract = buildGeneric(options); @@ -97,42 +91,34 @@ function generateContractSubset( } } - if (subset === "all") { + if (subset === 'all') { return contracts; } else { - const getParents = (c: GeneratedContract) => - c.contract.parents.map((p) => p.contract.path); + const getParents = (c: GeneratedContract) => c.contract.parents.map(p => p.contract.path); return [ ...findCover( - contracts.filter((c) => c.options.upgradeable), + contracts.filter(c => c.options.upgradeable), getParents, ), ...findCover( - contracts.filter((c) => !c.options.upgradeable), + contracts.filter(c => !c.options.upgradeable), getParents, ), ]; } } -export function* generateSources( - subset: Subset, - kind?: Kind, -): Generator { +export function* generateSources(subset: Subset, kind?: Kind): Generator { for (const c of generateContractSubset(subset, kind)) { const source = printContract(c.contract); yield { ...c, source }; } } -export async function writeGeneratedSources( - dir: string, - subset: Subset, - kind?: Kind, -): Promise { +export async function writeGeneratedSources(dir: string, subset: Subset, kind?: Kind): Promise { await fs.mkdir(dir, { recursive: true }); for (const { id, source } of generateSources(subset, kind)) { - await fs.writeFile(path.format({ dir, name: id, ext: ".sol" }), source); + await fs.writeFile(path.format({ dir, name: id, ext: '.sol' }), source); } } diff --git a/packages/core/solidity/src/generate/stablecoin.ts b/packages/core/solidity/src/generate/stablecoin.ts index e20841856..8cbcd1adc 100644 --- a/packages/core/solidity/src/generate/stablecoin.ts +++ b/packages/core/solidity/src/generate/stablecoin.ts @@ -1,32 +1,30 @@ -import type { StablecoinOptions } from "../stablecoin"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { StablecoinOptions } from '../stablecoin'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyStablecoin"], - symbol: ["MST"], + name: ['MyStablecoin'], + symbol: ['MST'], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - limitations: [false, "allowlist", "blocklist"] as const, + limitations: [false, 'allowlist', 'blocklist'] as const, votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ["1"], + premint: ['1'], custodian: booleans, access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateStablecoinOptions(): Generator< - Required -> { +export function* generateStablecoinOptions(): Generator> { for (const opts of generateAlternatives(blueprint)) { yield { ...opts, upgradeable: false }; } diff --git a/packages/core/solidity/src/get-imports.test.ts b/packages/core/solidity/src/get-imports.test.ts index 089ce3678..d6f1509bb 100644 --- a/packages/core/solidity/src/get-imports.test.ts +++ b/packages/core/solidity/src/get-imports.test.ts @@ -1,29 +1,29 @@ -import test from "ava"; +import test from 'ava'; -import { getImports } from "./get-imports"; -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { generateSources } from "./generate/sources"; -import { buildGeneric } from "./build-generic"; +import { getImports } from './get-imports'; +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { generateSources } from './generate/sources'; +import { buildGeneric } from './build-generic'; -test("erc20 basic", (t) => { - const c = buildERC20({ name: "MyToken", symbol: "MTK", permit: false }); +test('erc20 basic', t => { + const c = buildERC20({ name: 'MyToken', symbol: 'MTK', permit: false }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/token/ERC20/ERC20.sol", - "@openzeppelin/contracts/token/ERC20/IERC20.sol", - "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol", - "@openzeppelin/contracts/utils/Context.sol", + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC20/ERC20.sol', + '@openzeppelin/contracts/token/ERC20/IERC20.sol', + '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol', + '@openzeppelin/contracts/utils/Context.sol', ]); }); -test("erc721 auto increment", (t) => { +test('erc721 auto increment', t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', mintable: true, incremental: true, }); @@ -31,65 +31,65 @@ test("erc721 auto increment", (t) => { const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts/access/Ownable.sol", - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/token/ERC721/ERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", - "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", - "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", - "@openzeppelin/contracts/utils/Context.sol", - "@openzeppelin/contracts/utils/Panic.sol", - "@openzeppelin/contracts/utils/Strings.sol", - "@openzeppelin/contracts/utils/introspection/ERC165.sol", - "@openzeppelin/contracts/utils/introspection/IERC165.sol", - "@openzeppelin/contracts/utils/math/Math.sol", - "@openzeppelin/contracts/utils/math/SafeCast.sol", - "@openzeppelin/contracts/utils/math/SignedMath.sol", + '@openzeppelin/contracts/access/Ownable.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC721/ERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Context.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/ERC165.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', ]); }); -test("erc721 auto increment uups", (t) => { +test('erc721 auto increment uups', t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', mintable: true, incremental: true, - upgradeable: "uups", + upgradeable: 'uups', }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", - "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol", - "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol", - "@openzeppelin/contracts/interfaces/IERC1967.sol", - "@openzeppelin/contracts/interfaces/draft-IERC1822.sol", - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol", - "@openzeppelin/contracts/proxy/beacon/IBeacon.sol", - "@openzeppelin/contracts/token/ERC721/IERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", - "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", - "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", - "@openzeppelin/contracts/utils/Address.sol", - "@openzeppelin/contracts/utils/Errors.sol", - "@openzeppelin/contracts/utils/Panic.sol", - "@openzeppelin/contracts/utils/StorageSlot.sol", - "@openzeppelin/contracts/utils/Strings.sol", - "@openzeppelin/contracts/utils/introspection/IERC165.sol", - "@openzeppelin/contracts/utils/math/Math.sol", - "@openzeppelin/contracts/utils/math/SafeCast.sol", - "@openzeppelin/contracts/utils/math/SignedMath.sol", + '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol', + '@openzeppelin/contracts/interfaces/IERC1967.sol', + '@openzeppelin/contracts/interfaces/draft-IERC1822.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol', + '@openzeppelin/contracts/proxy/beacon/IBeacon.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Address.sol', + '@openzeppelin/contracts/utils/Errors.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/StorageSlot.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', ]); }); -test("can get imports for all combinations", (t) => { - for (const { options } of generateSources("all")) { +test('can get imports for all combinations', t => { + for (const { options } of generateSources('all')) { const c = buildGeneric(options); getImports(c); } diff --git a/packages/core/solidity/src/get-imports.ts b/packages/core/solidity/src/get-imports.ts index 4a5ca111b..c7e390907 100644 --- a/packages/core/solidity/src/get-imports.ts +++ b/packages/core/solidity/src/get-imports.ts @@ -1,8 +1,8 @@ -import type { Contract } from "./contract"; -import { reachable } from "./utils/transitive-closure"; +import type { Contract } from './contract'; +import { reachable } from './utils/transitive-closure'; -import contracts from "../openzeppelin-contracts"; -import { withHelpers } from "./options"; +import contracts from '../openzeppelin-contracts'; +import { withHelpers } from './options'; export interface SolcInputSources { [source: string]: { @@ -24,10 +24,10 @@ export function getImports(c: Contract): SolcInputSources { const result: SolcInputSources = {}; - const fileName = c.name + ".sol"; + const fileName = c.name + '.sol'; const dependencies = { - [fileName]: c.imports.map((i) => transformImport(i).path), + [fileName]: c.imports.map(i => transformImport(i).path), ...contracts.dependencies, }; diff --git a/packages/core/solidity/src/governor.test.ts b/packages/core/solidity/src/governor.test.ts index b22c706d0..8b84b468c 100644 --- a/packages/core/solidity/src/governor.test.ts +++ b/packages/core/solidity/src/governor.test.ts @@ -1,15 +1,16 @@ -import test from "ava"; -import { governor } from "."; +import test from 'ava'; +import { governor } from '.'; -import { buildGovernor, GovernorOptions } from "./governor"; -import { printContract } from "./print"; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import { printContract } from './print'; function testGovernor(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildGovernor({ - name: "MyGovernor", - delay: "1 week", - period: "1 week", + name: 'MyGovernor', + delay: '1 week', + period: '1 week', settings: false, ...opts, }); @@ -21,14 +22,14 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, (t) => { + test(title, t => { t.is( governor.print(opts), printContract( buildGovernor({ - name: "MyGovernor", - delay: "1 day", - period: "1 week", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', ...opts, }), ), @@ -36,131 +37,131 @@ function testAPIEquivalence(title: string, opts?: GovernorOptions) { }); } -testGovernor("governor with proposal threshold", { - proposalThreshold: "1", +testGovernor('governor with proposal threshold', { + proposalThreshold: '1', }); -testGovernor("governor with custom block time", { +testGovernor('governor with custom block time', { blockTime: 6, }); -testGovernor("governor with custom decimals", { +testGovernor('governor with custom decimals', { decimals: 6, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "1", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '1', }); -testGovernor("governor with 0 decimals", { +testGovernor('governor with 0 decimals', { decimals: 0, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "1", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '1', }); -testGovernor("governor with settings", { +testGovernor('governor with settings', { settings: true, - proposalThreshold: "1", + proposalThreshold: '1', }); -testGovernor("governor with storage", { +testGovernor('governor with storage', { storage: true, }); -testGovernor("governor with erc20votes", { - votes: "erc20votes", +testGovernor('governor with erc20votes', { + votes: 'erc20votes', }); -testGovernor("governor with erc721votes", { - votes: "erc721votes", +testGovernor('governor with erc721votes', { + votes: 'erc721votes', }); -testGovernor("governor with erc721votes omit decimals", { - votes: "erc721votes", +testGovernor('governor with erc721votes omit decimals', { + votes: 'erc721votes', decimals: 6, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "5", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '5', }); -testGovernor("governor with erc721votes settings omit decimals", { - votes: "erc721votes", +testGovernor('governor with erc721votes settings omit decimals', { + votes: 'erc721votes', decimals: 6, - proposalThreshold: "10", + proposalThreshold: '10', settings: true, }); -testGovernor("governor with percent quorum", { - quorumMode: "percent", +testGovernor('governor with percent quorum', { + quorumMode: 'percent', quorumPercent: 6, }); -testGovernor("governor with fractional percent quorum", { - quorumMode: "percent", +testGovernor('governor with fractional percent quorum', { + quorumMode: 'percent', quorumPercent: 0.5, }); -testGovernor("governor with openzeppelin timelock", { - timelock: "openzeppelin", +testGovernor('governor with openzeppelin timelock', { + timelock: 'openzeppelin', }); -testGovernor("governor with compound timelock", { - timelock: "compound", +testGovernor('governor with compound timelock', { + timelock: 'compound', }); -testGovernor("governor with blocknumber, updatable settings", { - clockMode: "blocknumber", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with blocknumber, updatable settings', { + clockMode: 'blocknumber', + delay: '1 day', + period: '2 weeks', settings: true, }); -testGovernor("governor with blocknumber, non-updatable settings", { - clockMode: "blocknumber", - delay: "1 block", - period: "2 weeks", +testGovernor('governor with blocknumber, non-updatable settings', { + clockMode: 'blocknumber', + delay: '1 block', + period: '2 weeks', settings: false, }); -testGovernor("governor with timestamp clock mode, updatable settings", { - clockMode: "timestamp", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with timestamp clock mode, updatable settings', { + clockMode: 'timestamp', + delay: '1 day', + period: '2 weeks', settings: true, }); -testGovernor("governor with timestamp clock mode, non-updatable settings", { - clockMode: "timestamp", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with timestamp clock mode, non-updatable settings', { + clockMode: 'timestamp', + delay: '1 day', + period: '2 weeks', settings: false, }); -testGovernor("governor with erc20votes, upgradable", { - votes: "erc20votes", - upgradeable: "uups", +testGovernor('governor with erc20votes, upgradable', { + votes: 'erc20votes', + upgradeable: 'uups', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { - name: "CustomGovernor", - delay: "2 weeks", - period: "2 week", +testAPIEquivalence('API basic', { + name: 'CustomGovernor', + delay: '2 weeks', + period: '2 week', }); -testAPIEquivalence("API basic upgradeable", { - name: "CustomGovernor", - delay: "2 weeks", - period: "2 week", - upgradeable: "uups", +testAPIEquivalence('API basic upgradeable', { + name: 'CustomGovernor', + delay: '2 weeks', + period: '2 week', + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(governor.print(governor.defaults), governor.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is(governor.isAccessControlRequired({ upgradeable: "uups" }), true); +test('API isAccessControlRequired', async t => { + t.is(governor.isAccessControlRequired({ upgradeable: 'uups' }), true); t.is(governor.isAccessControlRequired({}), false); }); diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 4e32d38b3..2ffaa90e1 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -1,33 +1,31 @@ -import { supportsInterface } from "./common-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { ContractBuilder, Contract } from "./contract"; -import { OptionsError } from "./error"; -import { setAccessControl } from "./set-access-control"; -import { printContract } from "./print"; -import { setInfo } from "./set-info"; -import { setUpgradeable } from "./set-upgradeable"; -import { defineFunctions } from "./utils/define-functions"; -import { durationToBlocks, durationToTimestamp } from "./utils/duration"; -import { clockModeDefault, type ClockMode } from "./set-clock-mode"; +import { supportsInterface } from './common-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { OptionsError } from './error'; +import { setAccessControl } from './set-access-control'; +import { printContract } from './print'; +import { setInfo } from './set-info'; +import { setUpgradeable } from './set-upgradeable'; +import { defineFunctions } from './utils/define-functions'; +import { durationToBlocks, durationToTimestamp } from './utils/duration'; +import { clockModeDefault, type ClockMode } from './set-clock-mode'; export const defaults: Required = { - name: "MyGovernor", - delay: "1 day", - period: "1 week", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', - votes: "erc20votes", + votes: 'erc20votes', clockMode: clockModeDefault, - timelock: "openzeppelin", + timelock: 'openzeppelin', blockTime: 12, decimals: 18, - proposalThreshold: "0", - quorumMode: "percent", + proposalThreshold: '0', + quorumMode: 'percent', quorumPercent: 4, - quorumAbsolute: "", + quorumAbsolute: '', storage: false, settings: true, @@ -36,10 +34,10 @@ export const defaults: Required = { info: commonDefaults.info, } as const; -export const votesOptions = ["erc20votes", "erc721votes"] as const; +export const votesOptions = ['erc20votes', 'erc721votes'] as const; export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, "openzeppelin", "compound"] as const; +export const timelockOptions = [false, 'openzeppelin', 'compound'] as const; export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { @@ -53,7 +51,7 @@ export interface GovernorOptions extends CommonOptions { blockTime?: number; proposalThreshold?: string; decimals?: number; - quorumMode?: "percent" | "absolute"; + quorumMode?: 'percent' | 'absolute'; quorumPercent?: number; quorumAbsolute?: string; votes?: VotesOptions; @@ -63,10 +61,8 @@ export interface GovernorOptions extends CommonOptions { settings?: boolean; } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return opts.upgradeable === "uups"; +export function isAccessControlRequired(opts: Partial): boolean { + return opts.upgradeable === 'uups'; } function withDefaults(opts: GovernorOptions): Required { @@ -111,8 +107,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { function addBase(c: ContractBuilder, { name }: GovernorOptions) { const Governor = { - name: "Governor", - path: "@openzeppelin/contracts/governance/Governor.sol", + name: 'Governor', + path: '@openzeppelin/contracts/governance/Governor.sol', }; c.addParent(Governor, [name]); c.addOverride(Governor, functions.votingDelay); @@ -133,28 +129,26 @@ function addBase(c: ContractBuilder, { name }: GovernorOptions) { function addSettings(c: ContractBuilder, allOpts: Required) { if (allOpts.settings) { const GovernorSettings = { - name: "GovernorSettings", - path: "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol", + name: 'GovernorSettings', + path: '@openzeppelin/contracts/governance/extensions/GovernorSettings.sol', }; c.addParent(GovernorSettings, [ getVotingDelay(allOpts), getVotingPeriod(allOpts), { lit: getProposalThreshold(allOpts) }, ]); - c.addOverride(GovernorSettings, functions.votingDelay, "view"); - c.addOverride(GovernorSettings, functions.votingPeriod, "view"); - c.addOverride(GovernorSettings, functions.proposalThreshold, "view"); + c.addOverride(GovernorSettings, functions.votingDelay, 'view'); + c.addOverride(GovernorSettings, functions.votingPeriod, 'view'); + c.addOverride(GovernorSettings, functions.proposalThreshold, 'view'); } else { setVotingParameters(c, allOpts); setProposalThreshold(c, allOpts); } } -function getVotingDelay( - opts: Required, -): { lit: string } | { note: string; value: number } { +function getVotingDelay(opts: Required): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return { lit: durationToTimestamp(opts.delay), }; @@ -175,11 +169,9 @@ function getVotingDelay( } } -function getVotingPeriod( - opts: Required, -): { lit: string } | { note: string; value: number } { +function getVotingPeriod(opts: Required): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return { lit: durationToTimestamp(opts.period), }; @@ -203,62 +195,42 @@ function getVotingPeriod( function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: "Not a valid number", + decimals: 'Not a valid number', }); } } -function getProposalThreshold({ - proposalThreshold, - decimals, - votes, -}: Required): string { +function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): string { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: "Not a valid number", + proposalThreshold: 'Not a valid number', }); } - if ( - /^0+$/.test(proposalThreshold) || - decimals === 0 || - votes === "erc721votes" - ) { + if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { return proposalThreshold; } else { return `${proposalThreshold}e${decimals}`; } } -function setVotingParameters( - c: ContractBuilder, - opts: Required, -) { +function setVotingParameters(c: ContractBuilder, opts: Required) { const delayBlocks = getVotingDelay(opts); - if ("lit" in delayBlocks) { + if ('lit' in delayBlocks) { c.setFunctionBody([`return ${delayBlocks.lit};`], functions.votingDelay); } else { - c.setFunctionBody( - [`return ${delayBlocks.value}; // ${delayBlocks.note}`], - functions.votingDelay, - ); + c.setFunctionBody([`return ${delayBlocks.value}; // ${delayBlocks.note}`], functions.votingDelay); } const periodBlocks = getVotingPeriod(opts); - if ("lit" in periodBlocks) { + if ('lit' in periodBlocks) { c.setFunctionBody([`return ${periodBlocks.lit};`], functions.votingPeriod); } else { - c.setFunctionBody( - [`return ${periodBlocks.value}; // ${periodBlocks.note}`], - functions.votingPeriod, - ); + c.setFunctionBody([`return ${periodBlocks.value}; // ${periodBlocks.note}`], functions.votingPeriod); } } -function setProposalThreshold( - c: ContractBuilder, - opts: Required, -) { +function setProposalThreshold(c: ContractBuilder, opts: Required) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -269,22 +241,22 @@ function setProposalThreshold( function addCounting(c: ContractBuilder) { c.addParent({ - name: "GovernorCountingSimple", - path: "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol", + name: 'GovernorCountingSimple', + path: '@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol', }); } function addVotes(c: ContractBuilder) { - const tokenArg = "_token"; + const tokenArg = '_token'; c.addImportOnly({ - name: "IVotes", + name: 'IVotes', path: `@openzeppelin/contracts/governance/utils/IVotes.sol`, transpiled: false, }); c.addConstructorArgument({ type: { - name: "IVotes", + name: 'IVotes', transpiled: false, }, name: tokenArg, @@ -292,7 +264,7 @@ function addVotes(c: ContractBuilder) { c.addParent( { - name: "GovernorVotes", + name: 'GovernorVotes', path: `@openzeppelin/contracts/governance/extensions/GovernorVotes.sol`, }, [{ lit: tokenArg }], @@ -302,67 +274,62 @@ function addVotes(c: ContractBuilder) { export const numberPattern = /^(?!$)(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addQuorum(c: ContractBuilder, opts: Required) { - if (opts.quorumMode === "percent") { + if (opts.quorumMode === 'percent') { if (opts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } - const { quorumFractionNumerator, quorumFractionDenominator } = - getQuorumFractionComponents(opts.quorumPercent); + const { quorumFractionNumerator, quorumFractionDenominator } = getQuorumFractionComponents(opts.quorumPercent); const GovernorVotesQuorumFraction = { - name: "GovernorVotesQuorumFraction", - path: "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol", + name: 'GovernorVotesQuorumFraction', + path: '@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol', }; if (quorumFractionDenominator !== undefined) { c.addOverride(GovernorVotesQuorumFraction, functions.quorumDenominator); - c.setFunctionBody( - [`return ${quorumFractionDenominator};`], - functions.quorumDenominator, - "pure", - ); + c.setFunctionBody([`return ${quorumFractionDenominator};`], functions.quorumDenominator, 'pure'); } c.addParent(GovernorVotesQuorumFraction, [quorumFractionNumerator]); c.addOverride(GovernorVotesQuorumFraction, functions.quorum); - } else if (opts.quorumMode === "absolute") { + } else if (opts.quorumMode === 'absolute') { if (!numberPattern.test(opts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: "Not a valid number", + quorumAbsolute: 'Not a valid number', }); } const returnStatement = - opts.decimals === 0 || opts.votes === "erc721votes" + opts.decimals === 0 || opts.votes === 'erc721votes' ? `return ${opts.quorumAbsolute};` : `return ${opts.quorumAbsolute}e${opts.decimals};`; - c.setFunctionBody([returnStatement], functions.quorum, "pure"); + c.setFunctionBody([returnStatement], functions.quorum, 'pure'); } } const timelockModules = { openzeppelin: { timelockType: { - name: "TimelockController", + name: 'TimelockController', path: `@openzeppelin/contracts/governance/TimelockController.sol`, }, timelockParent: { - name: "GovernorTimelockControl", + name: 'GovernorTimelockControl', path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol`, }, }, compound: { timelockType: { - name: "ICompoundTimelock", + name: 'ICompoundTimelock', path: `@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol`, transpiled: false, }, timelockParent: { - name: "GovernorTimelockCompound", + name: 'GovernorTimelockCompound', path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol`, }, }, @@ -375,37 +342,32 @@ function getQuorumFractionComponents(quorumPercent: number): { let quorumFractionNumerator = quorumPercent; let quorumFractionDenominator = undefined; - const quorumPercentSegments = quorumPercent.toString().split("."); + const quorumPercentSegments = quorumPercent.toString().split('.'); if (quorumPercentSegments.length > 2) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } else if ( quorumPercentSegments.length == 2 && quorumPercentSegments[0] !== undefined && quorumPercentSegments[1] !== undefined ) { - quorumFractionNumerator = parseInt( - quorumPercentSegments[0].concat(quorumPercentSegments[1]), - ); + quorumFractionNumerator = parseInt(quorumPercentSegments[0].concat(quorumPercentSegments[1])); const decimals = quorumPercentSegments[1].length; - quorumFractionDenominator = "100"; + quorumFractionDenominator = '100'; while (quorumFractionDenominator.length < decimals + 3) { - quorumFractionDenominator += "0"; + quorumFractionDenominator += '0'; } } return { quorumFractionNumerator, quorumFractionDenominator }; } -function addTimelock( - c: ContractBuilder, - { timelock }: Required, -) { +function addTimelock(c: ContractBuilder, { timelock }: Required) { if (timelock === false) { return; } - const timelockArg = "_timelock"; + const timelockArg = '_timelock'; const { timelockType, timelockParent } = timelockModules[timelock]; c.addImportOnly(timelockType); @@ -426,8 +388,8 @@ function addTimelock( function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { if (storage) { const GovernorStorage = { - name: "GovernorStorage", - path: "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol", + name: 'GovernorStorage', + path: '@openzeppelin/contracts/governance/extensions/GovernorStorage.sol', }; c.addParent(GovernorStorage); c.addOverride(GovernorStorage, functions._propose); @@ -437,102 +399,102 @@ function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { const functions = defineFunctions({ votingDelay: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, votingPeriod: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, proposalThreshold: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, proposalNeedsQueuing: { - args: [{ name: "proposalId", type: "uint256" }], - returns: ["bool"], - kind: "public", - mutability: "view", + args: [{ name: 'proposalId', type: 'uint256' }], + returns: ['bool'], + kind: 'public', + mutability: 'view', }, quorum: { - args: [{ name: "blockNumber", type: "uint256" }], - returns: ["uint256"], - kind: "public", - mutability: "view", + args: [{ name: 'blockNumber', type: 'uint256' }], + returns: ['uint256'], + kind: 'public', + mutability: 'view', }, quorumDenominator: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "view", + returns: ['uint256'], + kind: 'public', + mutability: 'view', }, propose: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "description", type: "string memory" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'description', type: 'string memory' }, ], - returns: ["uint256"], - kind: "public", + returns: ['uint256'], + kind: 'public', }, _propose: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "description", type: "string memory" }, - { name: "proposer", type: "address" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'description', type: 'string memory' }, + { name: 'proposer', type: 'address' }, ], - returns: ["uint256"], - kind: "internal", + returns: ['uint256'], + kind: 'internal', }, _queueOperations: { args: [ - { name: "proposalId", type: "uint256" }, - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'proposalId', type: 'uint256' }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - kind: "internal", - returns: ["uint48"], + kind: 'internal', + returns: ['uint48'], }, _executeOperations: { args: [ - { name: "proposalId", type: "uint256" }, - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'proposalId', type: 'uint256' }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - kind: "internal", + kind: 'internal', }, _cancel: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - returns: ["uint256"], - kind: "internal", + returns: ['uint256'], + kind: 'internal', }, state: { - args: [{ name: "proposalId", type: "uint256" }], - returns: ["ProposalState"], - kind: "public", - mutability: "view", + args: [{ name: 'proposalId', type: 'uint256' }], + returns: ['ProposalState'], + kind: 'public', + mutability: 'view', }, _executor: { args: [], - returns: ["address"], - kind: "internal", - mutability: "view", + returns: ['address'], + kind: 'internal', + mutability: 'view', }, }); diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index 2cfc6b1ee..4b4bba141 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -1,32 +1,24 @@ -export type { GenericOptions, KindedOptions } from "./build-generic"; -export { buildGeneric } from "./build-generic"; +export type { GenericOptions, KindedOptions } from './build-generic'; +export { buildGeneric } from './build-generic'; -export type { Contract } from "./contract"; -export { ContractBuilder } from "./contract"; +export type { Contract } from './contract'; +export { ContractBuilder } from './contract'; -export { printContract } from "./print"; +export { printContract } from './print'; -export type { Access } from "./set-access-control"; -export type { Upgradeable } from "./set-upgradeable"; -export type { Info } from "./set-info"; +export type { Access } from './set-access-control'; +export type { Upgradeable } from './set-upgradeable'; +export type { Info } from './set-info'; -export { premintPattern } from "./erc20"; -export { defaults as infoDefaults } from "./set-info"; +export { premintPattern } from './erc20'; +export { defaults as infoDefaults } from './set-info'; -export type { OptionsErrorMessages } from "./error"; -export { OptionsError } from "./error"; +export type { OptionsErrorMessages } from './error'; +export { OptionsError } from './error'; -export type { Kind } from "./kind"; -export { sanitizeKind } from "./kind"; +export type { Kind } from './kind'; +export { sanitizeKind } from './kind'; -export { - erc20, - erc721, - erc1155, - stablecoin, - realWorldAsset, - governor, - custom, -} from "./api"; +export { erc20, erc721, erc1155, stablecoin, realWorldAsset, governor, custom } from './api'; -export { compatibleContractsSemver } from "./utils/version"; +export { compatibleContractsSemver } from './utils/version'; diff --git a/packages/core/solidity/src/infer-transpiled.test.ts b/packages/core/solidity/src/infer-transpiled.test.ts index 262d80660..cccffeeab 100644 --- a/packages/core/solidity/src/infer-transpiled.test.ts +++ b/packages/core/solidity/src/infer-transpiled.test.ts @@ -1,12 +1,12 @@ -import test from "ava"; -import { inferTranspiled } from "./infer-transpiled"; +import test from 'ava'; +import { inferTranspiled } from './infer-transpiled'; -test("infer transpiled", (t) => { - t.true(inferTranspiled({ name: "Foo" })); - t.true(inferTranspiled({ name: "Foo", transpiled: true })); - t.false(inferTranspiled({ name: "Foo", transpiled: false })); +test('infer transpiled', t => { + t.true(inferTranspiled({ name: 'Foo' })); + t.true(inferTranspiled({ name: 'Foo', transpiled: true })); + t.false(inferTranspiled({ name: 'Foo', transpiled: false })); - t.false(inferTranspiled({ name: "IFoo" })); - t.true(inferTranspiled({ name: "IFoo", transpiled: true })); - t.false(inferTranspiled({ name: "IFoo", transpiled: false })); + t.false(inferTranspiled({ name: 'IFoo' })); + t.true(inferTranspiled({ name: 'IFoo', transpiled: true })); + t.false(inferTranspiled({ name: 'IFoo', transpiled: false })); }); diff --git a/packages/core/solidity/src/infer-transpiled.ts b/packages/core/solidity/src/infer-transpiled.ts index 9089099e1..f338ea6a4 100644 --- a/packages/core/solidity/src/infer-transpiled.ts +++ b/packages/core/solidity/src/infer-transpiled.ts @@ -1,4 +1,4 @@ -import type { ReferencedContract } from "./contract"; +import type { ReferencedContract } from './contract'; export function inferTranspiled(c: ReferencedContract): boolean { return c.transpiled ?? !/^I[A-Z]/.test(c.name); diff --git a/packages/core/solidity/src/kind.ts b/packages/core/solidity/src/kind.ts index 844bfddde..26a775c4a 100644 --- a/packages/core/solidity/src/kind.ts +++ b/packages/core/solidity/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from "./build-generic"; +import type { GenericOptions } from './build-generic'; -export type Kind = GenericOptions["kind"]; +export type Kind = GenericOptions['kind']; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === "string") { - const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); + if (typeof kind === 'string') { + const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return "ERC20"; + return 'ERC20'; } function isKind(value: Kind | T): value is Kind { switch (value) { - case "ERC20": - case "ERC1155": - case "ERC721": - case "Stablecoin": - case "RealWorldAsset": - case "Governor": - case "Custom": + case 'ERC20': + case 'ERC1155': + case 'ERC721': + case 'Stablecoin': + case 'RealWorldAsset': + case 'Governor': + case 'Custom': return true; default: { diff --git a/packages/core/solidity/src/options.ts b/packages/core/solidity/src/options.ts index 7ae76a616..6e800715f 100644 --- a/packages/core/solidity/src/options.ts +++ b/packages/core/solidity/src/options.ts @@ -1,13 +1,13 @@ -import path from "path"; +import path from 'path'; -import type { Contract, ReferencedContract, ImportContract } from "./contract"; -import { inferTranspiled } from "./infer-transpiled"; +import type { Contract, ReferencedContract, ImportContract } from './contract'; +import { inferTranspiled } from './infer-transpiled'; const upgradeableName = (n: string) => { - if (n === "Initializable") { + if (n === 'Initializable') { return n; } else { - return n.replace(/(Upgradeable)?(?=\.|$)/, "Upgradeable"); + return n.replace(/(Upgradeable)?(?=\.|$)/, 'Upgradeable'); } }; @@ -19,10 +19,7 @@ const upgradeableImport = (p: ImportContract): ImportContract => { name: upgradeableName(p.name), // Contract name path: path.posix.format({ ext, - dir: dir.replace( - /^@openzeppelin\/contracts/, - "@openzeppelin/contracts-upgradeable", - ), + dir: dir.replace(/^@openzeppelin\/contracts/, '@openzeppelin/contracts-upgradeable'), name: upgradeableName(name), // Solidity file name }), }; @@ -40,15 +37,12 @@ export interface Helpers extends Required { export function withHelpers(contract: Contract, opts: Options = {}): Helpers { const contractUpgradeable = contract.upgradeable; const transformName = (n: ReferencedContract) => - contractUpgradeable && inferTranspiled(n) - ? upgradeableName(n.name) - : n.name; + contractUpgradeable && inferTranspiled(n) ? upgradeableName(n.name) : n.name; return { upgradeable: contractUpgradeable, transformName, - transformImport: (p1) => { - const p2 = - contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; + transformImport: p1 => { + const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; return opts.transformImport?.(p2) ?? p2; }, }; diff --git a/packages/core/solidity/src/print-versioned.ts b/packages/core/solidity/src/print-versioned.ts index 4f2a6a59d..d4bde0aad 100644 --- a/packages/core/solidity/src/print-versioned.ts +++ b/packages/core/solidity/src/print-versioned.ts @@ -1,16 +1,13 @@ -import contracts from "../openzeppelin-contracts"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; +import contracts from '../openzeppelin-contracts'; +import type { Contract } from './contract'; +import { printContract } from './print'; export function printContractVersioned(contract: Contract): string { return printContract(contract, { - transformImport: (p) => { + transformImport: p => { return { ...p, - path: p.path.replace( - /^@openzeppelin\/contracts(-upgradeable)?/, - `$&@${contracts.version}`, - ), + path: p.path.replace(/^@openzeppelin\/contracts(-upgradeable)?/, `$&@${contracts.version}`), }; }, }); diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index 0868529dc..88d9ad432 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -6,23 +6,23 @@ import type { Value, NatspecTag, ImportContract, -} from "./contract"; -import { Options, Helpers, withHelpers } from "./options"; +} from './contract'; +import type { Options, Helpers } from './options'; +import { withHelpers } from './options'; -import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; -import { mapValues } from "./utils/map-values"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import { inferTranspiled } from "./infer-transpiled"; -import { compatibleContractsSemver } from "./utils/version"; +import type { Lines } from './utils/format-lines'; +import { formatLines, spaceBetween } from './utils/format-lines'; +import { mapValues } from './utils/map-values'; +import SOLIDITY_VERSION from './solidity-version.json'; +import { inferTranspiled } from './infer-transpiled'; +import { compatibleContractsSemver } from './utils/version'; export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); - const fns = mapValues(sortedFunctions(contract), (fns) => - fns.map((fn) => printFunction(fn, helpers)), - ); + const fns = mapValues(sortedFunctions(contract), fns => fns.map(fn => printFunction(fn, helpers))); - const hasOverrides = fns.override.some((l) => l.length > 0); + const hasOverrides = fns.override.some(l => l.length > 0); return formatLines( ...spaceBetween( @@ -36,20 +36,14 @@ export function printContract(contract: Contract, opts?: Options): string { [ ...printNatspecTags(contract.natspecTags), - [ - `contract ${contract.name}`, - ...printInheritance(contract, helpers), - "{", - ].join(" "), + [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '), spaceBetween( contract.variables, printConstructor(contract, helpers), ...fns.code, ...fns.modifiers, - hasOverrides - ? [`// The following functions are overrides required by Solidity.`] - : [], + hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], ...fns.override, ), @@ -59,40 +53,29 @@ export function printContract(contract: Contract, opts?: Options): string { ); } -function printInheritance( - contract: Contract, - { transformName }: Helpers, -): [] | [string] { +function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] { if (contract.parents.length > 0) { - return [ - "is " + contract.parents.map((p) => transformName(p.contract)).join(", "), - ]; + return ['is ' + contract.parents.map(p => transformName(p.contract)).join(', ')]; } else { return []; } } function printConstructor(contract: Contract, helpers: Helpers): Lines[] { - const hasParentParams = contract.parents.some((p) => p.params.length > 0); + const hasParentParams = contract.parents.some(p => p.params.length > 0); const hasConstructorCode = contract.constructorCode.length > 0; const parentsWithInitializers = contract.parents.filter(hasInitializer); - if ( - hasParentParams || - hasConstructorCode || - (helpers.upgradeable && parentsWithInitializers.length > 0) - ) { - const parents = parentsWithInitializers.flatMap((p) => - printParentConstructor(p, helpers), - ); - const modifiers = helpers.upgradeable ? ["initializer public"] : parents; - const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); + if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) { + const parents = parentsWithInitializers.flatMap(p => printParentConstructor(p, helpers)); + const modifiers = helpers.upgradeable ? ['initializer public'] : parents; + const args = contract.constructorArgs.map(a => printArgument(a, helpers)); const body = helpers.upgradeable ? spaceBetween( - parents.map((p) => p + ";"), + parents.map(p => p + ';'), contract.constructorCode, ) : contract.constructorCode; - const head = helpers.upgradeable ? "function initialize" : "constructor"; + const head = helpers.upgradeable ? 'function initialize' : 'constructor'; const constructor = printFunction2([], head, args, modifiers, body); if (!helpers.upgradeable) { return constructor; @@ -107,23 +90,20 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { } const DISABLE_INITIALIZERS = [ - "/// @custom:oz-upgrades-unsafe-allow constructor", - "constructor() {", - ["_disableInitializers();"], - "}", + '/// @custom:oz-upgrades-unsafe-allow constructor', + 'constructor() {', + ['_disableInitializers();'], + '}', ]; function hasInitializer(parent: Parent) { // CAUTION // This list is validated by compilation of SafetyCheck.sol. // Always keep this list and that file in sync. - return !["Initializable"].includes(parent.contract.name); + return !['Initializable'].includes(parent.contract.name); } -type SortedFunctions = Record< - "code" | "modifiers" | "override", - ContractFunction[] ->; +type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>; // Functions with code first, then those with modifiers, then the rest function sortedFunctions(contract: Contract): SortedFunctions { @@ -142,29 +122,26 @@ function sortedFunctions(contract: Contract): SortedFunctions { return fns; } -function printParentConstructor( - { contract, params }: Parent, - helpers: Helpers, -): [] | [string] { +function printParentConstructor({ contract, params }: Parent, helpers: Helpers): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; if (useTranspiled || params.length > 0) { - return [fn + "(" + params.map(printValue).join(", ") + ")"]; + return [fn + '(' + params.map(printValue).join(', ') + ')']; } else { return []; } } export function printValue(value: Value): string { - if (typeof value === "object") { - if ("lit" in value) { + if (typeof value === 'object') { + if ('lit' in value) { return value.lit; - } else if ("note" in value) { + } else if ('note' in value) { return `${printValue(value.value)} /* ${value.note} */`; } else { - throw Error("Unknown value type"); + throw Error('Unknown value type'); } - } else if (typeof value === "number") { + } else if (typeof value === 'number') { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { @@ -178,47 +155,38 @@ export function printValue(value: Value): string { function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { const { transformName } = helpers; - if ( - fn.override.size <= 1 && - fn.modifiers.length === 0 && - fn.code.length === 0 && - !fn.final - ) { + if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) { return []; } const modifiers: string[] = [fn.kind, ...fn.modifiers]; - if (fn.mutability !== "nonpayable") { + if (fn.mutability !== 'nonpayable') { modifiers.splice(1, 0, fn.mutability); } if (fn.override.size === 1) { modifiers.push(`override`); } else if (fn.override.size > 1) { - modifiers.push( - `override(${[...fn.override].map(transformName).join(", ")})`, - ); + modifiers.push(`override(${[...fn.override].map(transformName).join(', ')})`); } if (fn.returns?.length) { - modifiers.push(`returns (${fn.returns.join(", ")})`); + modifiers.push(`returns (${fn.returns.join(', ')})`); } const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { - const superCall = `super.${fn.name}(${fn.args - .map((a) => a.name) - .join(", ")});`; - code.push(fn.returns?.length ? "return " + superCall : superCall); + const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; + code.push(fn.returns?.length ? 'return ' + superCall : superCall); } if (modifiers.length + fn.code.length > 1) { return printFunction2( fn.comments, - "function " + fn.name, - fn.args.map((a) => printArgument(a, helpers)), + 'function ' + fn.name, + fn.args.map(a => printArgument(a, helpers)), modifiers, code, ); @@ -238,33 +206,26 @@ function printFunction2( ): Lines[] { const fn: Lines[] = [...comments]; - const headingLength = [kindedName, ...args, ...modifiers] - .map((s) => s.length) - .reduce((a, b) => a + b); + const headingLength = [kindedName, ...args, ...modifiers].map(s => s.length).reduce((a, b) => a + b); - const braces = code.length > 0 ? "{" : "{}"; + const braces = code.length > 0 ? '{' : '{}'; if (headingLength <= 72) { - fn.push( - [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" "), - ); + fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' ')); } else { - fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); + fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces); } if (code.length > 0) { - fn.push(code, "}"); + fn.push(code, '}'); } return fn; } -function printArgument( - arg: FunctionArgument, - { transformName }: Helpers, -): string { +function printArgument(arg: FunctionArgument, { transformName }: Helpers): string { let type: string; - if (typeof arg.type === "string") { + if (typeof arg.type === 'string') { if (/^[A-Z]/.test(arg.type)) { // eslint-disable-next-line @typescript-eslint/no-unused-expressions `Type ${arg.type} is not a primitive type. Define it as a ContractReference`; @@ -274,7 +235,7 @@ function printArgument( type = transformName(arg.type); } - return [type, arg.name].join(" "); + return [type, arg.name].join(' '); } function printNatspecTags(tags: NatspecTag[]): string[] { @@ -290,11 +251,9 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { }); const lines: string[] = []; - imports.map((p) => { + imports.map(p => { const importContract = helpers.transformImport(p); - lines.push( - `import {${importContract.name}} from "${importContract.path}";`, - ); + lines.push(`import {${importContract.name}} from "${importContract.path}";`); }); return lines; diff --git a/packages/core/solidity/src/scripts/prepare.ts b/packages/core/solidity/src/scripts/prepare.ts index 50974bc2d..e65e08d9b 100644 --- a/packages/core/solidity/src/scripts/prepare.ts +++ b/packages/core/solidity/src/scripts/prepare.ts @@ -1,50 +1,44 @@ -import { promises as fs } from "fs"; -import path from "path"; -import hre from "hardhat"; -import type { BuildInfo } from "hardhat/types"; -import { findAll } from "solidity-ast/utils"; -import { rimraf } from "rimraf"; -import { version } from "@openzeppelin/contracts/package.json"; - -import type { OpenZeppelinContracts } from "../../openzeppelin-contracts"; -import { writeGeneratedSources } from "../generate/sources"; -import { mapValues } from "../utils/map-values"; -import { transitiveClosure } from "../utils/transitive-closure"; +import { promises as fs } from 'fs'; +import path from 'path'; +import hre from 'hardhat'; +import type { BuildInfo } from 'hardhat/types'; +import { findAll } from 'solidity-ast/utils'; +import { rimraf } from 'rimraf'; +import { version } from '@openzeppelin/contracts/package.json'; + +import type { OpenZeppelinContracts } from '../../openzeppelin-contracts'; +import { writeGeneratedSources } from '../generate/sources'; +import { mapValues } from '../utils/map-values'; +import { transitiveClosure } from '../utils/transitive-closure'; async function main() { - const generatedSourcesPath = path.join(hre.config.paths.sources, "generated"); + const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated'); await rimraf(generatedSourcesPath); - await writeGeneratedSources(generatedSourcesPath, "minimal-cover"); - await hre.run("compile"); + await writeGeneratedSources(generatedSourcesPath, 'minimal-cover'); + await hre.run('compile'); const dependencies: Record> = {}; const sources: Record = {}; for (const buildInfoPath of await hre.artifacts.getBuildInfoPaths()) { - const buildInfo: BuildInfo = JSON.parse( - await fs.readFile(buildInfoPath, "utf8"), - ); + const buildInfo: BuildInfo = JSON.parse(await fs.readFile(buildInfoPath, 'utf8')); - for (const [sourceFile, { ast }] of Object.entries( - buildInfo.output.sources, - )) { + for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) { if ( - sourceFile.startsWith("@openzeppelin/contracts") || - sourceFile.startsWith("@openzeppelin/community-contracts") + sourceFile.startsWith('@openzeppelin/contracts') || + sourceFile.startsWith('@openzeppelin/community-contracts') ) { const sourceDependencies = (dependencies[sourceFile] ??= new Set()); - for (const imp of findAll("ImportDirective", ast)) { + for (const imp of findAll('ImportDirective', ast)) { sourceDependencies.add(imp.absolutePath); } } } - for (const [sourceFile, { content }] of Object.entries( - buildInfo.input.sources, - )) { + for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) { if ( - sourceFile.startsWith("@openzeppelin/contracts") || - sourceFile.startsWith("@openzeppelin/community-contracts") + sourceFile.startsWith('@openzeppelin/contracts') || + sourceFile.startsWith('@openzeppelin/community-contracts') ) { sources[sourceFile] = content; } @@ -54,18 +48,13 @@ async function main() { const contracts: OpenZeppelinContracts = { version, sources, - dependencies: mapValues(transitiveClosure(dependencies), (d) => - Array.from(d), - ), + dependencies: mapValues(transitiveClosure(dependencies), d => Array.from(d)), }; - await fs.writeFile( - "openzeppelin-contracts.json", - JSON.stringify(contracts, null, 2), - ); + await fs.writeFile('openzeppelin-contracts.json', JSON.stringify(contracts, null, 2)); } -main().catch((e) => { +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/packages/core/solidity/src/set-access-control.ts b/packages/core/solidity/src/set-access-control.ts index 0147594f2..a4779231d 100644 --- a/packages/core/solidity/src/set-access-control.ts +++ b/packages/core/solidity/src/set-access-control.ts @@ -1,7 +1,7 @@ -import type { ContractBuilder, BaseFunction } from "./contract"; -import { supportsInterface } from "./common-functions"; +import type { ContractBuilder, BaseFunction } from './contract'; +import { supportsInterface } from './common-functions'; -export const accessOptions = [false, "ownable", "roles", "managed"] as const; +export const accessOptions = [false, 'ownable', 'roles', 'managed'] as const; export type Access = (typeof accessOptions)[number]; @@ -10,31 +10,31 @@ export type Access = (typeof accessOptions)[number]; */ export function setAccessControl(c: ContractBuilder, access: Access) { switch (access) { - case "ownable": { - if (c.addParent(parents.Ownable, [{ lit: "initialOwner" }])) { + case 'ownable': { + if (c.addParent(parents.Ownable, [{ lit: 'initialOwner' }])) { c.addConstructorArgument({ - type: "address", - name: "initialOwner", + type: 'address', + name: 'initialOwner', }); } break; } - case "roles": { + case 'roles': { if (c.addParent(parents.AccessControl)) { c.addConstructorArgument({ - type: "address", - name: "defaultAdmin", + type: 'address', + name: 'defaultAdmin', }); - c.addConstructorCode("_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);"); + c.addConstructorCode('_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);'); } c.addOverride(parents.AccessControl, supportsInterface); break; } - case "managed": { - if (c.addParent(parents.AccessManaged, [{ lit: "initialAuthority" }])) { + case 'managed': { + if (c.addParent(parents.AccessManaged, [{ lit: 'initialAuthority' }])) { c.addConstructorArgument({ - type: "address", - name: "initialAuthority", + type: 'address', + name: 'initialAuthority', }); } break; @@ -53,30 +53,28 @@ export function requireAccessControl( roleOwner: string | undefined, ) { if (access === false) { - access = "ownable"; + access = 'ownable'; } setAccessControl(c, access); switch (access) { - case "ownable": { - c.addModifier("onlyOwner", fn); + case 'ownable': { + c.addModifier('onlyOwner', fn); break; } - case "roles": { - const roleId = roleIdPrefix + "_ROLE"; - const addedConstant = c.addVariable( - `bytes32 public constant ${roleId} = keccak256("${roleId}");`, - ); + case 'roles': { + const roleId = roleIdPrefix + '_ROLE'; + const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); if (roleOwner && addedConstant) { - c.addConstructorArgument({ type: "address", name: roleOwner }); + c.addConstructorArgument({ type: 'address', name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } c.addModifier(`onlyRole(${roleId})`, fn); break; } - case "managed": { - c.addModifier("restricted", fn); + case 'managed': { + c.addModifier('restricted', fn); break; } } @@ -84,15 +82,15 @@ export function requireAccessControl( const parents = { Ownable: { - name: "Ownable", - path: "@openzeppelin/contracts/access/Ownable.sol", + name: 'Ownable', + path: '@openzeppelin/contracts/access/Ownable.sol', }, AccessControl: { - name: "AccessControl", - path: "@openzeppelin/contracts/access/AccessControl.sol", + name: 'AccessControl', + path: '@openzeppelin/contracts/access/AccessControl.sol', }, AccessManaged: { - name: "AccessManaged", - path: "@openzeppelin/contracts/access/manager/AccessManaged.sol", + name: 'AccessManaged', + path: '@openzeppelin/contracts/access/manager/AccessManaged.sol', }, }; diff --git a/packages/core/solidity/src/set-clock-mode.ts b/packages/core/solidity/src/set-clock-mode.ts index 3569b1dfe..189a952cc 100644 --- a/packages/core/solidity/src/set-clock-mode.ts +++ b/packages/core/solidity/src/set-clock-mode.ts @@ -1,39 +1,32 @@ -import type { ContractBuilder, ReferencedContract } from "./contract"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder, ReferencedContract } from './contract'; +import { defineFunctions } from './utils/define-functions'; -export const clockModeOptions = ["blocknumber", "timestamp"] as const; -export const clockModeDefault = "blocknumber" as const; +export const clockModeOptions = ['blocknumber', 'timestamp'] as const; +export const clockModeDefault = 'blocknumber' as const; export type ClockMode = (typeof clockModeOptions)[number]; const functions = defineFunctions({ clock: { - kind: "public" as const, + kind: 'public' as const, args: [], - returns: ["uint48"], - mutability: "view" as const, + returns: ['uint48'], + mutability: 'view' as const, }, CLOCK_MODE: { - kind: "public" as const, + kind: 'public' as const, args: [], - returns: ["string memory"], - mutability: "pure" as const, + returns: ['string memory'], + mutability: 'pure' as const, }, }); -export function setClockMode( - c: ContractBuilder, - parent: ReferencedContract, - votes: ClockMode, -) { - if (votes === "timestamp") { +export function setClockMode(c: ContractBuilder, parent: ReferencedContract, votes: ClockMode) { + if (votes === 'timestamp') { c.addOverride(parent, functions.clock); - c.setFunctionBody(["return uint48(block.timestamp);"], functions.clock); + c.setFunctionBody(['return uint48(block.timestamp);'], functions.clock); - c.setFunctionComments( - ["// solhint-disable-next-line func-name-mixedcase"], - functions.CLOCK_MODE, - ); + c.setFunctionComments(['// solhint-disable-next-line func-name-mixedcase'], functions.CLOCK_MODE); c.addOverride(parent, functions.CLOCK_MODE); c.setFunctionBody(['return "mode=timestamp";'], functions.CLOCK_MODE); } diff --git a/packages/core/solidity/src/set-info.ts b/packages/core/solidity/src/set-info.ts index 9821cd29a..61e3713f4 100644 --- a/packages/core/solidity/src/set-info.ts +++ b/packages/core/solidity/src/set-info.ts @@ -1,13 +1,10 @@ -import type { ContractBuilder } from "./contract"; +import type { ContractBuilder } from './contract'; export const TAG_SECURITY_CONTACT = `@custom:security-contact`; -export const infoOptions = [ - {}, - { securityContact: "security@example.com", license: "WTFPL" }, -] as const; +export const infoOptions = [{}, { securityContact: 'security@example.com', license: 'WTFPL' }] as const; -export const defaults: Info = { license: "MIT" }; +export const defaults: Info = { license: 'MIT' }; export type Info = { securityContact?: string; diff --git a/packages/core/solidity/src/set-upgradeable.ts b/packages/core/solidity/src/set-upgradeable.ts index 71073d743..ff6502805 100644 --- a/packages/core/solidity/src/set-upgradeable.ts +++ b/packages/core/solidity/src/set-upgradeable.ts @@ -1,16 +1,13 @@ -import type { ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; -export const upgradeableOptions = [false, "transparent", "uups"] as const; +export const upgradeableOptions = [false, 'transparent', 'uups'] as const; export type Upgradeable = (typeof upgradeableOptions)[number]; -export function setUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - access: Access, -) { +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { if (upgradeable === false) { return; } @@ -18,25 +15,19 @@ export function setUpgradeable( c.upgradeable = true; c.addParent({ - name: "Initializable", - path: "@openzeppelin/contracts/proxy/utils/Initializable.sol", + name: 'Initializable', + path: '@openzeppelin/contracts/proxy/utils/Initializable.sol', }); switch (upgradeable) { - case "transparent": + case 'transparent': break; - case "uups": { - requireAccessControl( - c, - functions._authorizeUpgrade, - access, - "UPGRADER", - "upgrader", - ); + case 'uups': { + requireAccessControl(c, functions._authorizeUpgrade, access, 'UPGRADER', 'upgrader'); const UUPSUpgradeable = { - name: "UUPSUpgradeable", - path: "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol", + name: 'UUPSUpgradeable', + path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol', }; c.addParent(UUPSUpgradeable); c.addOverride(UUPSUpgradeable, functions._authorizeUpgrade); @@ -46,14 +37,14 @@ export function setUpgradeable( default: { const _: never = upgradeable; - throw new Error("Unknown value for `upgradeable`"); + throw new Error('Unknown value for `upgradeable`'); } } } const functions = defineFunctions({ _authorizeUpgrade: { - args: [{ name: "newImplementation", type: "address" }], - kind: "internal", + args: [{ name: 'newImplementation', type: 'address' }], + kind: 'internal', }, }); diff --git a/packages/core/solidity/src/stablecoin.test.ts b/packages/core/solidity/src/stablecoin.test.ts index 35d205e0c..b5a837863 100644 --- a/packages/core/solidity/src/stablecoin.test.ts +++ b/packages/core/solidity/src/stablecoin.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { stablecoin } from "."; +import test from 'ava'; +import { stablecoin } from '.'; -import { buildStablecoin, StablecoinOptions } from "./stablecoin"; -import { printContract } from "./print"; +import type { StablecoinOptions } from './stablecoin'; +import { buildStablecoin } from './stablecoin'; +import { printContract } from './print'; function testStablecoin(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildStablecoin({ - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testStablecoin(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: StablecoinOptions) { - test(title, (t) => { + test(title, t => { t.is( stablecoin.print(opts), printContract( buildStablecoin({ - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', ...opts, }), ), @@ -33,111 +34,111 @@ function testAPIEquivalence(title: string, opts?: StablecoinOptions) { }); } -testStablecoin("basic stablecoin", {}); +testStablecoin('basic stablecoin', {}); -testStablecoin("stablecoin burnable", { +testStablecoin('stablecoin burnable', { burnable: true, }); -testStablecoin("stablecoin pausable", { +testStablecoin('stablecoin pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testStablecoin("stablecoin pausable with roles", { +testStablecoin('stablecoin pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testStablecoin("stablecoin pausable with managed", { +testStablecoin('stablecoin pausable with managed', { pausable: true, - access: "managed", + access: 'managed', }); -testStablecoin("stablecoin burnable pausable", { +testStablecoin('stablecoin burnable pausable', { burnable: true, pausable: true, }); -testStablecoin("stablecoin preminted", { - premint: "1000", +testStablecoin('stablecoin preminted', { + premint: '1000', }); -testStablecoin("stablecoin premint of 0", { - premint: "0", +testStablecoin('stablecoin premint of 0', { + premint: '0', }); -testStablecoin("stablecoin mintable", { +testStablecoin('stablecoin mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testStablecoin("stablecoin mintable with roles", { +testStablecoin('stablecoin mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testStablecoin("stablecoin permit", { +testStablecoin('stablecoin permit', { permit: true, }); -testStablecoin("stablecoin custodian", { +testStablecoin('stablecoin custodian', { custodian: true, }); -testStablecoin("stablecoin allowlist", { - limitations: "allowlist", +testStablecoin('stablecoin allowlist', { + limitations: 'allowlist', }); -testStablecoin("stablecoin blocklist", { - limitations: "blocklist", +testStablecoin('stablecoin blocklist', { + limitations: 'blocklist', }); -testStablecoin("stablecoin votes", { +testStablecoin('stablecoin votes', { votes: true, }); -testStablecoin("stablecoin votes + blocknumber", { - votes: "blocknumber", +testStablecoin('stablecoin votes + blocknumber', { + votes: 'blocknumber', }); -testStablecoin("stablecoin votes + timestamp", { - votes: "timestamp", +testStablecoin('stablecoin votes + timestamp', { + votes: 'timestamp', }); -testStablecoin("stablecoin flashmint", { +testStablecoin('stablecoin flashmint', { flashmint: true, }); -testAPIEquivalence("stablecoin API default"); +testAPIEquivalence('stablecoin API default'); -testAPIEquivalence("stablecoin API basic", { - name: "CustomStablecoin", - symbol: "CST", +testAPIEquivalence('stablecoin API basic', { + name: 'CustomStablecoin', + symbol: 'CST', }); -testAPIEquivalence("stablecoin API full", { - name: "CustomStablecoin", - symbol: "CST", - premint: "2000", - access: "roles", +testAPIEquivalence('stablecoin API full', { + name: 'CustomStablecoin', + symbol: 'CST', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - limitations: "allowlist", + limitations: 'allowlist', custodian: true, }); -test("stablecoin API assert defaults", async (t) => { +test('stablecoin API assert defaults', async t => { t.is(stablecoin.print(stablecoin.defaults), stablecoin.print()); }); -test("stablecoin API isAccessControlRequired", async (t) => { +test('stablecoin API isAccessControlRequired', async t => { t.is(stablecoin.isAccessControlRequired({ mintable: true }), true); t.is(stablecoin.isAccessControlRequired({ pausable: true }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: "allowlist" }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: "blocklist" }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: 'allowlist' }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: 'blocklist' }), true); }); diff --git a/packages/core/solidity/src/stablecoin.ts b/packages/core/solidity/src/stablecoin.ts index af0dec852..7510d6755 100644 --- a/packages/core/solidity/src/stablecoin.ts +++ b/packages/core/solidity/src/stablecoin.ts @@ -1,28 +1,25 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; -import { printContract } from "./print"; +import type { Contract, ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; +import { printContract } from './print'; +import type { ERC20Options } from './erc20'; import { buildERC20, - ERC20Options, defaults as erc20defaults, withDefaults as withERC20Defaults, functions as erc20functions, -} from "./erc20"; +} from './erc20'; export interface StablecoinOptions extends ERC20Options { - limitations?: false | "allowlist" | "blocklist"; + limitations?: false | 'allowlist' | 'blocklist'; custodian?: boolean; } export const defaults: Required = { ...erc20defaults, - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', limitations: false, custodian: false, } as const; @@ -41,16 +38,8 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string { return printContract(buildStablecoin(opts)); } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return ( - opts.mintable || - opts.limitations !== false || - opts.custodian || - opts.pausable || - opts.upgradeable === "uups" - ); +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups'; } export function buildStablecoin(opts: StablecoinOptions): Contract { @@ -72,15 +61,11 @@ export function buildStablecoin(opts: StablecoinOptions): Contract { return c; } -function addLimitations( - c: ContractBuilder, - access: Access, - mode: boolean | "allowlist" | "blocklist", -) { - const type = mode === "allowlist"; +function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') { + const type = mode === 'allowlist'; const ERC20Limitation = { - name: type ? "ERC20Allowlist" : "ERC20Blocklist", - path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? "ERC20Allowlist" : "ERC20Blocklist"}.sol`, + name: type ? 'ERC20Allowlist' : 'ERC20Blocklist', + path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`, }; c.addParent(ERC20Limitation); @@ -91,20 +76,17 @@ function addLimitations( ? [functions.allowUser, functions.disallowUser] : [functions.blockUser, functions.unblockUser]; - requireAccessControl(c, addFn, access, "LIMITER", "limiter"); - c.addFunctionCode(`_${type ? "allowUser" : "blockUser"}(user);`, addFn); + requireAccessControl(c, addFn, access, 'LIMITER', 'limiter'); + c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn); - requireAccessControl(c, removeFn, access, "LIMITER", "limiter"); - c.addFunctionCode( - `_${type ? "disallowUser" : "unblockUser"}(user);`, - removeFn, - ); + requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter'); + c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn); } function addCustodian(c: ContractBuilder, access: Access) { const ERC20Custodian = { - name: "ERC20Custodian", - path: "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol", + name: 'ERC20Custodian', + path: '@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol', }; c.addParent(ERC20Custodian); @@ -112,35 +94,30 @@ function addCustodian(c: ContractBuilder, access: Access) { c.addOverride(ERC20Custodian, functions._isCustodian); if (access === false) { - access = "ownable"; + access = 'ownable'; } setAccessControl(c, access); switch (access) { - case "ownable": { + case 'ownable': { c.setFunctionBody([`return user == owner();`], functions._isCustodian); break; } - case "roles": { - const roleOwner = "custodian"; - const roleId = "CUSTODIAN_ROLE"; - const addedConstant = c.addVariable( - `bytes32 public constant ${roleId} = keccak256("${roleId}");`, - ); + case 'roles': { + const roleOwner = 'custodian'; + const roleId = 'CUSTODIAN_ROLE'; + const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); if (roleOwner && addedConstant) { - c.addConstructorArgument({ type: "address", name: roleOwner }); + c.addConstructorArgument({ type: 'address', name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } - c.setFunctionBody( - [`return hasRole(CUSTODIAN_ROLE, user);`], - functions._isCustodian, - ); + c.setFunctionBody([`return hasRole(CUSTODIAN_ROLE, user);`], functions._isCustodian); break; } - case "managed": { + case 'managed': { c.addImportOnly({ - name: "AuthorityUtils", + name: 'AuthorityUtils', path: `@openzeppelin/contracts/access/manager/AuthorityUtils.sol`, }); const logic = [ @@ -157,30 +134,30 @@ const functions = { ...erc20functions, ...defineFunctions({ _isCustodian: { - kind: "internal" as const, - args: [{ name: "user", type: "address" }], - returns: ["bool"], - mutability: "view" as const, + kind: 'internal' as const, + args: [{ name: 'user', type: 'address' }], + returns: ['bool'], + mutability: 'view' as const, }, allowUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, disallowUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, blockUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, unblockUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, }), }; diff --git a/packages/core/solidity/src/test.ts b/packages/core/solidity/src/test.ts index e0e7211cb..1d7105f86 100644 --- a/packages/core/solidity/src/test.ts +++ b/packages/core/solidity/src/test.ts @@ -1,11 +1,12 @@ -import { promises as fs } from "fs"; -import _test, { TestFn, ExecutionContext } from "ava"; -import hre from "hardhat"; -import path from "path"; +import { promises as fs } from 'fs'; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import hre from 'hardhat'; +import path from 'path'; -import { generateSources, writeGeneratedSources } from "./generate/sources"; -import type { GenericOptions, KindedOptions } from "./build-generic"; -import { custom, erc1155, stablecoin, erc20, erc721, governor } from "./api"; +import { generateSources, writeGeneratedSources } from './generate/sources'; +import type { GenericOptions, KindedOptions } from './build-generic'; +import { custom, erc1155, stablecoin, erc20, erc721, governor } from './api'; interface Context { generatedSourcesPath: string; @@ -13,37 +14,34 @@ interface Context { const test = _test as TestFn; -test.serial("erc20 result compiles", async (t) => { - await testCompile(t, "ERC20"); +test.serial('erc20 result compiles', async t => { + await testCompile(t, 'ERC20'); }); -test.serial("erc721 result compiles", async (t) => { - await testCompile(t, "ERC721"); +test.serial('erc721 result compiles', async t => { + await testCompile(t, 'ERC721'); }); -test.serial("erc1155 result compiles", async (t) => { - await testCompile(t, "ERC1155"); +test.serial('erc1155 result compiles', async t => { + await testCompile(t, 'ERC1155'); }); -test.serial("stablecoin result compiles", async (t) => { - await testCompile(t, "Stablecoin"); +test.serial('stablecoin result compiles', async t => { + await testCompile(t, 'Stablecoin'); }); -test.serial("governor result compiles", async (t) => { - await testCompile(t, "Governor"); +test.serial('governor result compiles', async t => { + await testCompile(t, 'Governor'); }); -test.serial("custom result compiles", async (t) => { - await testCompile(t, "Custom"); +test.serial('custom result compiles', async t => { + await testCompile(t, 'Custom'); }); -async function testCompile( - t: ExecutionContext, - kind: keyof KindedOptions, -) { +async function testCompile(t: ExecutionContext, kind: keyof KindedOptions) { const generatedSourcesPath = path.join(hre.config.paths.sources, `generated`); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, "all", kind); + await writeGeneratedSources(generatedSourcesPath, 'all', kind); // We only want to check that contracts compile and we don't care about any // of the outputs. Setting empty outputSelection causes compilation to go a @@ -52,48 +50,40 @@ async function testCompile( settings.outputSelection = {}; } - await hre.run("compile"); + await hre.run('compile'); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return erc20.isAccessControlRequired(opts); - case "ERC721": + case 'ERC721': return erc721.isAccessControlRequired(opts); - case "ERC1155": + case 'ERC1155': return erc1155.isAccessControlRequired(opts); - case "Stablecoin": + case 'Stablecoin': return stablecoin.isAccessControlRequired(opts); - case "RealWorldAsset": + case 'RealWorldAsset': return stablecoin.isAccessControlRequired(opts); - case "Governor": + case 'Governor': return governor.isAccessControlRequired(opts); - case "Custom": + case 'Custom': return custom.isAccessControlRequired(opts); default: - throw new Error("No such kind"); + throw new Error('No such kind'); } } -test("is access control required", async (t) => { - for (const contract of generateSources("all")) { +test('is access control required', async t => { + for (const contract of generateSources('all')) { const regexOwnable = /import.*Ownable(Upgradeable)?.sol.*/gm; if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); } else { - t.notRegex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); } } } diff --git a/packages/core/solidity/src/utils/define-functions.ts b/packages/core/solidity/src/utils/define-functions.ts index c05316bce..3c89e6c76 100644 --- a/packages/core/solidity/src/utils/define-functions.ts +++ b/packages/core/solidity/src/utils/define-functions.ts @@ -1,18 +1,9 @@ -import type { BaseFunction } from "../contract"; +import type { BaseFunction } from '../contract'; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; -export function defineFunctions( - fns: Record, -): Record; +export function defineFunctions(fns: Record): Record; -export function defineFunctions( - fns: Record, -): Record { - return Object.fromEntries( - Object.entries(fns).map(([name, fn]) => [ - name, - Object.assign({ name }, fn), - ]), - ); +export function defineFunctions(fns: Record): Record { + return Object.fromEntries(Object.entries(fns).map(([name, fn]) => [name, Object.assign({ name }, fn)])); } diff --git a/packages/core/solidity/src/utils/duration.ts b/packages/core/solidity/src/utils/duration.ts index 32c273fff..f74ff1c1d 100644 --- a/packages/core/solidity/src/utils/duration.ts +++ b/packages/core/solidity/src/utils/duration.ts @@ -1,17 +1,6 @@ -const durationUnits = [ - "block", - "second", - "minute", - "hour", - "day", - "week", - "month", - "year", -] as const; +const durationUnits = ['block', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; type DurationUnit = (typeof durationUnits)[number]; -export const durationPattern = new RegExp( - `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, -); +export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); const second = 1; const minute = 60 * second; @@ -26,15 +15,15 @@ export function durationToBlocks(duration: string, blockTime: number): number { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = parseFloat(match[1]!); const unit = match[2]! as DurationUnit; - if (unit === "block") { + if (unit === 'block') { if (!Number.isInteger(value)) { - throw new Error("Invalid number of blocks"); + throw new Error('Invalid number of blocks'); } return value; @@ -48,15 +37,15 @@ export function durationToTimestamp(duration: string): string { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = match[1]!; const unit = match[2]! as DurationUnit; // unit must be a Solidity supported time unit - if (unit === "block" || unit === "month" || unit === "year") { - throw new Error("Invalid unit for timestamp"); + if (unit === 'block' || unit === 'month' || unit === 'year') { + throw new Error('Invalid unit for timestamp'); } return `${value} ${unit}s`; diff --git a/packages/core/solidity/src/utils/find-cover.ts b/packages/core/solidity/src/utils/find-cover.ts index 0cc7f0bb6..939ed9240 100644 --- a/packages/core/solidity/src/utils/find-cover.ts +++ b/packages/core/solidity/src/utils/find-cover.ts @@ -1,14 +1,11 @@ -import { sortedBy } from "./sorted-by"; +import { sortedBy } from './sorted-by'; // Greedy approximation of minimum set cover. -export function findCover( - sets: T[], - getElements: (set: T) => unknown[], -): T[] { +export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { const sortedSets = sortedBy( - sets.map((set) => ({ set, elems: getElements(set) })), - (s) => -s.elems.length, + sets.map(set => ({ set, elems: getElements(set) })), + s => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/solidity/src/utils/format-lines.ts b/packages/core/solidity/src/utils/format-lines.ts index a0ab46e87..6ab9aa445 100644 --- a/packages/core/solidity/src/utils/format-lines.ts +++ b/packages/core/solidity/src/utils/format-lines.ts @@ -1,37 +1,30 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol("whitespace"); +const whitespace = Symbol('whitespace'); export function formatLines(...lines: Lines[]): string { return formatLinesWithSpaces(4, ...lines); } -export function formatLinesWithSpaces( - spacesPerIndent: number, - ...lines: Lines[] -): string { - return [...indentEach(0, lines, spacesPerIndent)].join("\n") + "\n"; +export function formatLinesWithSpaces(spacesPerIndent: number, ...lines: Lines[]): string { + return [...indentEach(0, lines, spacesPerIndent)].join('\n') + '\n'; } -function* indentEach( - indent: number, - lines: Lines[], - spacesPerIndent: number, -): Generator { +function* indentEach(indent: number, lines: Lines[], spacesPerIndent: number): Generator { for (const line of lines) { if (line === whitespace) { - yield ""; + yield ''; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line, spacesPerIndent); } else { - yield " ".repeat(indent * spacesPerIndent) + line; + yield ' '.repeat(indent * spacesPerIndent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter((l) => l.length > 0) - .flatMap((l) => [whitespace, ...l]) + .filter(l => l.length > 0) + .flatMap(l => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index b177c69a4..1e42bad36 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,8 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function mapValues( - obj: Record, - fn: (val: V) => W, -): Record { +export function mapValues(obj: Record, fn: (val: V) => W): Record { const res = {} as Record; for (const key in obj) { res[key] = fn(obj[key]); diff --git a/packages/core/solidity/src/utils/to-identifier.test.ts b/packages/core/solidity/src/utils/to-identifier.test.ts index 870f062ff..6af3b3a11 100644 --- a/packages/core/solidity/src/utils/to-identifier.test.ts +++ b/packages/core/solidity/src/utils/to-identifier.test.ts @@ -1,20 +1,20 @@ -import test from "ava"; +import test from 'ava'; -import { toIdentifier } from "./to-identifier"; +import { toIdentifier } from './to-identifier'; -test("unmodified", (t) => { - t.is(toIdentifier("abc"), "abc"); +test('unmodified', t => { + t.is(toIdentifier('abc'), 'abc'); }); -test("remove leading specials", (t) => { - t.is(toIdentifier("--abc"), "abc"); +test('remove leading specials', t => { + t.is(toIdentifier('--abc'), 'abc'); }); -test("remove specials and upcase next char", (t) => { - t.is(toIdentifier("abc-def"), "abcDef"); - t.is(toIdentifier("abc--def"), "abcDef"); +test('remove specials and upcase next char', t => { + t.is(toIdentifier('abc-def'), 'abcDef'); + t.is(toIdentifier('abc--def'), 'abcDef'); }); -test("capitalize", (t) => { - t.is(toIdentifier("abc", true), "Abc"); +test('capitalize', t => { + t.is(toIdentifier('abc', true), 'Abc'); }); diff --git a/packages/core/solidity/src/utils/to-identifier.ts b/packages/core/solidity/src/utils/to-identifier.ts index eb49e670f..70e7c9310 100644 --- a/packages/core/solidity/src/utils/to-identifier.ts +++ b/packages/core/solidity/src/utils/to-identifier.ts @@ -1,8 +1,8 @@ export function toIdentifier(str: string, capitalize = false): string { return str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/^[^a-zA-Z$_]+/, "") - .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/^[^a-zA-Z$_]+/, '') + .replace(/^(.)/, c => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w$]+(.?)/g, (_, c) => c.toUpperCase()); } diff --git a/packages/core/solidity/src/utils/transitive-closure.ts b/packages/core/solidity/src/utils/transitive-closure.ts index 1b56e79fc..f2e04ba2d 100644 --- a/packages/core/solidity/src/utils/transitive-closure.ts +++ b/packages/core/solidity/src/utils/transitive-closure.ts @@ -1,8 +1,6 @@ type T = string; -export function transitiveClosure( - obj: Record>, -): Record> { +export function transitiveClosure(obj: Record>): Record> { const closure = {} as Record>; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/version.test.ts b/packages/core/solidity/src/utils/version.test.ts index bc114df8c..5a90bc410 100644 --- a/packages/core/solidity/src/utils/version.test.ts +++ b/packages/core/solidity/src/utils/version.test.ts @@ -1,11 +1,11 @@ -import test from "ava"; +import test from 'ava'; -import semver from "semver"; +import semver from 'semver'; -import { compatibleContractsSemver } from "./version"; -import contracts from "../../openzeppelin-contracts"; +import { compatibleContractsSemver } from './version'; +import contracts from '../../openzeppelin-contracts'; -test("installed contracts satisfies compatible range", (t) => { +test('installed contracts satisfies compatible range', t => { t.true( semver.satisfies(contracts.version, compatibleContractsSemver), `Installed contracts version ${contracts.version} does not satisfy compatible range ${compatibleContractsSemver}. diff --git a/packages/core/solidity/src/utils/version.ts b/packages/core/solidity/src/utils/version.ts index 31bf323b7..3daddc218 100644 --- a/packages/core/solidity/src/utils/version.ts +++ b/packages/core/solidity/src/utils/version.ts @@ -1,4 +1,4 @@ /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = "^5.0.0"; +export const compatibleContractsSemver = '^5.0.0'; diff --git a/packages/core/solidity/src/zip-foundry.test.ts b/packages/core/solidity/src/zip-foundry.test.ts index ae1a640fc..c1214fa24 100644 --- a/packages/core/solidity/src/zip-foundry.test.ts +++ b/packages/core/solidity/src/zip-foundry.test.ts @@ -1,21 +1,22 @@ -import _test, { TestFn, ExecutionContext } from "ava"; - -import { zipFoundry } from "./zip-foundry"; - -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { buildERC1155 } from "./erc1155"; -import { buildCustom } from "./custom"; -import { promises as fs } from "fs"; -import path from "path"; -import os from "os"; -import util from "util"; -import child from "child_process"; -import type { Contract } from "./contract"; -import { rimraf } from "rimraf"; -import type { JSZipObject } from "jszip"; -import type JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; + +import { zipFoundry } from './zip-foundry'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import { buildCustom } from './custom'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; +import util from 'util'; +import child from 'child_process'; +import type { Contract } from './contract'; +import { rimraf } from 'rimraf'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; interface Context { tempFolder: string; @@ -23,23 +24,21 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async (t) => { - t.context.tempFolder = await fs.mkdtemp( - path.join(os.tmpdir(), "openzeppelin-wizard-"), - ); +test.beforeEach(async t => { + t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); }); -test.afterEach.always(async (t) => { +test.afterEach.always(async t => { await rimraf(t.context.tempFolder); }); -test.serial("erc20 full", async (t) => { +test.serial('erc20 full', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - premint: "2000", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, @@ -51,74 +50,70 @@ test.serial("erc20 full", async (t) => { await runTest(c, t, opts); }); -test.serial("erc20 uups, roles", async (t) => { +test.serial('erc20 uups, roles', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', + access: 'roles', }; const c = buildERC20(opts); await runTest(c, t, opts); }); -test.serial("erc721 uups, ownable", async (t) => { +test.serial('erc721 uups, ownable', async t => { const opts: GenericOptions = { - kind: "ERC721", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", - access: "ownable", + kind: 'ERC721', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', + access: 'ownable', }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial("erc1155 basic", async (t) => { +test.serial('erc1155 basic', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("erc1155 transparent, ownable", async (t) => { +test.serial('erc1155 transparent, ownable', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", - upgradeable: "transparent", - access: "ownable", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', + upgradeable: 'transparent', + access: 'ownable', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("custom basic", async (t) => { - const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; +test.serial('custom basic', async t => { + const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial("custom transparent, managed", async (t) => { +test.serial('custom transparent, managed', async t => { const opts: GenericOptions = { - kind: "Custom", - name: "My Contract", - upgradeable: "transparent", - access: "managed", + kind: 'Custom', + name: 'My Contract', + upgradeable: 'transparent', + access: 'managed', }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest( - c: Contract, - t: ExecutionContext, - opts: GenericOptions, -) { +async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { const zip = await zipFoundry(c, opts); assertLayout(zip, c, t); @@ -128,25 +123,21 @@ async function runTest( function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { const sorted = Object.values(zip.files) - .map((f) => f.name) + .map(f => f.name) .sort(); t.deepEqual(sorted, [ - "README.md", - "script/", + 'README.md', + 'script/', `script/${c.name}.s.sol`, - "setup.sh", - "src/", + 'setup.sh', + 'src/', `src/${c.name}.sol`, - "test/", + 'test/', `test/${c.name}.t.sol`, ]); } -async function extractAndRunPackage( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -156,22 +147,16 @@ async function extractAndRunPackage( if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile( - path.join(tempFolder, item.name), - await asString(item), - ); + await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); } } - const setGitUser = - 'git init && git config user.email "test@test.test" && git config user.name "Test"'; - const setup = "bash setup.sh"; - const test = "forge test" + (c.upgradeable ? " --force" : ""); - const script = - `forge script script/${c.name}.s.sol` + (c.upgradeable ? " --force" : ""); + const setGitUser = 'git init && git config user.email "test@test.test" && git config user.name "Test"'; + const setup = 'bash setup.sh'; + const test = 'forge test' + (c.upgradeable ? ' --force' : ''); + const script = `forge script script/${c.name}.s.sol` + (c.upgradeable ? ' --force' : ''); - const exec = (cmd: string) => - util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: "" } }); + const exec = (cmd: string) => util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: '' } }); const command = `cd "${tempFolder}" && ${setGitUser} && ${setup} && ${test} && ${script}`; const result = await exec(command); @@ -190,13 +175,8 @@ async function extractAndRunPackage( t.regex(rerunResult.stdout, /Foundry project already initialized\./); } -async function assertContents( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { - const normalizeVersion = (text: string) => - text.replace(/\bv\d+\.\d+\.\d+\b/g, "vX.Y.Z"); +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { + const normalizeVersion = (text: string) => text.replace(/\bv\d+\.\d+\.\d+\b/g, 'vX.Y.Z'); const contentComparison = [ normalizeVersion(await getItemString(zip, `setup.sh`)), @@ -218,5 +198,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async("arraybuffer")).toString(); + return Buffer.from(await item.async('arraybuffer')).toString(); } diff --git a/packages/core/solidity/src/zip-foundry.ts b/packages/core/solidity/src/zip-foundry.ts index 4a0e5c8af..81850a8d0 100644 --- a/packages/core/solidity/src/zip-foundry.ts +++ b/packages/core/solidity/src/zip-foundry.ts @@ -1,34 +1,23 @@ -import JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import contracts from "../openzeppelin-contracts"; -import { - formatLinesWithSpaces, - Lines, - spaceBetween, -} from "./utils/format-lines"; +import JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; +import type { Contract } from './contract'; +import { printContract } from './print'; +import SOLIDITY_VERSION from './solidity-version.json'; +import contracts from '../openzeppelin-contracts'; +import type { Lines } from './utils/format-lines'; +import { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; function getHeader(c: Contract) { - return [ - `// SPDX-License-Identifier: ${c.license}`, - `pragma solidity ^${SOLIDITY_VERSION};`, - ]; + return [`// SPDX-License-Identifier: ${c.license}`, `pragma solidity ^${SOLIDITY_VERSION};`]; } const test = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getHeader(c), getImports(c), getTestCase(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getHeader(c), getImports(c), getTestCase(c))); function getImports(c: Contract) { const result = ['import {Test} from "forge-std/Test.sol";']; if (c.upgradeable) { - result.push( - 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', - ); + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -40,55 +29,39 @@ const test = (c: Contract, opts?: GenericOptions) => { `contract ${c.name}Test is Test {`, spaceBetween( [`${c.name} public instance;`], - [ - "function setUp() public {", - getAddressVariables(c, args), - getDeploymentCode(c, args), - "}", - ], + ['function setUp() public {', getAddressVariables(c, args), getDeploymentCode(c, args), '}'], getContractSpecificTestFunction(), ), - "}", + '}', ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === "transparent") { + if (opts?.upgradeable === 'transparent') { return [ `address proxy = Upgrades.deployTransparentProxy(`, - [ - `"${c.name}.sol",`, - `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `initialOwner,`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `instance = ${c.name}(proxy);`, ]; } else { return [ `address proxy = Upgrades.deployUUPSProxy(`, - [ - `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `instance = ${c.name}(proxy);`, ]; } } else { - return [`instance = new ${c.name}(${args.join(", ")});`]; + return [`instance = new ${c.name}(${args.join(', ')});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; let i = 1; // private key index starts from 1 since it must be non-zero - if ( - c.upgradeable && - opts?.upgradeable === "transparent" && - !args.includes("initialOwner") - ) { + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { vars.push(`address initialOwner = vm.addr(${i++});`); } for (const arg of args) { @@ -100,31 +73,19 @@ const test = (c: Contract, opts?: GenericOptions) => { function getContractSpecificTestFunction(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case "ERC20": - case "ERC721": - return [ - "function testName() public view {", - [`assertEq(instance.name(), "${opts.name}");`], - "}", - ]; - - case "ERC1155": - return [ - "function testUri() public view {", - [`assertEq(instance.uri(0), "${opts.uri}");`], - "}", - ]; - - case "Governor": - case "Custom": - return [ - "function testSomething() public {", - ["// Add your test here"], - "}", - ]; + case 'ERC20': + case 'ERC721': + return ['function testName() public view {', [`assertEq(instance.name(), "${opts.name}");`], '}']; + + case 'ERC1155': + return ['function testUri() public view {', [`assertEq(instance.uri(0), "${opts.uri}");`], '}']; + + case 'Governor': + case 'Custom': + return ['function testSomething() public {', ['// Add your test here'], '}']; default: - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } return []; @@ -134,7 +95,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === "address") { + if (constructorArg.type === 'address') { args.push(constructorArg.name); } } @@ -142,20 +103,12 @@ function getAddressArgs(c: Contract): string[] { } const script = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getHeader(c), getImports(c), getScript(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getHeader(c), getImports(c), getScript(c))); function getImports(c: Contract) { - const result = [ - 'import {Script} from "forge-std/Script.sol";', - 'import {console} from "forge-std/console.sol";', - ]; + const result = ['import {Script} from "forge-std/Script.sol";', 'import {console} from "forge-std/console.sol";']; if (c.upgradeable) { - result.push( - 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', - ); + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -164,65 +117,48 @@ const script = (c: Contract, opts?: GenericOptions) => { function getScript(c: Contract) { const args = getAddressArgs(c); const deploymentLines = [ - "vm.startBroadcast();", + 'vm.startBroadcast();', ...getAddressVariables(c, args), ...getDeploymentCode(c, args), - `console.log("${c.upgradeable ? "Proxy" : "Contract"} deployed to %s", address(instance));`, - "vm.stopBroadcast();", + `console.log("${c.upgradeable ? 'Proxy' : 'Contract'} deployed to %s", address(instance));`, + 'vm.stopBroadcast();', ]; return [ `contract ${c.name}Script is Script {`, spaceBetween( - ["function setUp() public {}"], - [ - "function run() public {", - args.length > 0 - ? addTodoAndCommentOut(deploymentLines) - : deploymentLines, - "}", - ], + ['function setUp() public {}'], + ['function run() public {', args.length > 0 ? addTodoAndCommentOut(deploymentLines) : deploymentLines, '}'], ), - "}", + '}', ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === "transparent") { + if (opts?.upgradeable === 'transparent') { return [ `address proxy = Upgrades.deployTransparentProxy(`, - [ - `"${c.name}.sol",`, - `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `initialOwner,`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `${c.name} instance = ${c.name}(proxy);`, ]; } else { return [ `address proxy = Upgrades.deployUUPSProxy(`, - [ - `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `${c.name} instance = ${c.name}(proxy);`, ]; } } else { - return [`${c.name} instance = new ${c.name}(${args.join(", ")});`]; + return [`${c.name} instance = new ${c.name}(${args.join(', ')});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; - if ( - c.upgradeable && - opts?.upgradeable === "transparent" && - !args.includes("initialOwner") - ) { - vars.push("address initialOwner = ;"); + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + vars.push('address initialOwner = ;'); } for (const arg of args) { vars.push(`address ${arg} = ;`); @@ -232,10 +168,10 @@ const script = (c: Contract, opts?: GenericOptions) => { function addTodoAndCommentOut(lines: Lines[]) { return [ - "// TODO: Set addresses for the variables below, then uncomment the following section:", - "/*", + '// TODO: Set addresses for the variables below, then uncomment the following section:', + '/*', ...lines, - "*/", + '*/', ]; } }; @@ -341,7 +277,7 @@ bash setup.sh ## Testing the contract \`\`\` -forge test${c.upgradeable ? " --force" : ""} +forge test${c.upgradeable ? ' --force' : ''} \`\`\` ## Deploying the contract @@ -349,7 +285,7 @@ forge test${c.upgradeable ? " --force" : ""} You can simulate a deployment by running the script: \`\`\` -forge script script/${c.name}.s.sol${c.upgradeable ? " --force" : ""} +forge script script/${c.name}.s.sol${c.upgradeable ? ' --force' : ''} \`\`\` See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information. @@ -361,8 +297,8 @@ export async function zipFoundry(c: Contract, opts?: GenericOptions) { zip.file(`src/${c.name}.sol`, printContract(c)); zip.file(`test/${c.name}.t.sol`, test(c, opts)); zip.file(`script/${c.name}.s.sol`, script(c, opts)); - zip.file("setup.sh", setupSh(c)); - zip.file("README.md", readme(c)); + zip.file('setup.sh', setupSh(c)); + zip.file('README.md', readme(c)); return zip; } diff --git a/packages/core/solidity/src/zip-hardhat.test.ts b/packages/core/solidity/src/zip-hardhat.test.ts index 30c1380f8..5e08d7010 100644 --- a/packages/core/solidity/src/zip-hardhat.test.ts +++ b/packages/core/solidity/src/zip-hardhat.test.ts @@ -1,21 +1,22 @@ -import _test, { TestFn, ExecutionContext } from "ava"; - -import { zipHardhat } from "./zip-hardhat"; - -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { buildERC1155 } from "./erc1155"; -import { buildCustom } from "./custom"; -import { promises as fs } from "fs"; -import path from "path"; -import os from "os"; -import util from "util"; -import child from "child_process"; -import type { Contract } from "./contract"; -import { rimraf } from "rimraf"; -import type { JSZipObject } from "jszip"; -import type JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; + +import { zipHardhat } from './zip-hardhat'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import { buildCustom } from './custom'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; +import util from 'util'; +import child from 'child_process'; +import type { Contract } from './contract'; +import { rimraf } from 'rimraf'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; interface Context { tempFolder: string; @@ -23,23 +24,21 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async (t) => { - t.context.tempFolder = await fs.mkdtemp( - path.join(os.tmpdir(), "openzeppelin-wizard-"), - ); +test.beforeEach(async t => { + t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); }); -test.afterEach.always(async (t) => { +test.afterEach.always(async t => { await rimraf(t.context.tempFolder); }); -test.serial("erc20 full", async (t) => { +test.serial('erc20 full', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - premint: "2000", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, @@ -51,48 +50,44 @@ test.serial("erc20 full", async (t) => { await runTest(c, t, opts); }); -test.serial("erc721 upgradeable", async (t) => { +test.serial('erc721 upgradeable', async t => { const opts: GenericOptions = { - kind: "ERC721", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", + kind: 'ERC721', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial("erc1155 basic", async (t) => { +test.serial('erc1155 basic', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("custom basic", async (t) => { - const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; +test.serial('custom basic', async t => { + const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial("custom upgradeable", async (t) => { +test.serial('custom upgradeable', async t => { const opts: GenericOptions = { - kind: "Custom", - name: "My Contract", - upgradeable: "transparent", + kind: 'Custom', + name: 'My Contract', + upgradeable: 'transparent', }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest( - c: Contract, - t: ExecutionContext, - opts: GenericOptions, -) { +async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { const zip = await zipHardhat(c, opts); assertLayout(zip, c, t); @@ -102,29 +97,25 @@ async function runTest( function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { const sorted = Object.values(zip.files) - .map((f) => f.name) + .map(f => f.name) .sort(); t.deepEqual(sorted, [ - ".gitignore", - "README.md", - "contracts/", + '.gitignore', + 'README.md', + 'contracts/', `contracts/${c.name}.sol`, - "hardhat.config.ts", - "package-lock.json", - "package.json", - "scripts/", - "scripts/deploy.ts", - "test/", - "test/test.ts", - "tsconfig.json", + 'hardhat.config.ts', + 'package-lock.json', + 'package.json', + 'scripts/', + 'scripts/deploy.ts', + 'test/', + 'test/test.ts', + 'tsconfig.json', ]); } -async function extractAndRunPackage( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -134,17 +125,14 @@ async function extractAndRunPackage( if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile( - path.join(tempFolder, item.name), - await asString(item), - ); + await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); } } let command = `cd "${tempFolder}" && npm install && npm test`; if (c.constructorArgs === undefined) { // only test deploying the contract if there are no constructor args needed - command += " && npx hardhat run scripts/deploy.ts"; + command += ' && npx hardhat run scripts/deploy.ts'; } const exec = util.promisify(child.exec); @@ -156,17 +144,13 @@ async function extractAndRunPackage( } } -async function assertContents( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { const contentComparison = [ await getItemString(zip, `contracts/${c.name}.sol`), - await getItemString(zip, "hardhat.config.ts"), - await getItemString(zip, "package.json"), - await getItemString(zip, "scripts/deploy.ts"), - await getItemString(zip, "test/test.ts"), + await getItemString(zip, 'hardhat.config.ts'), + await getItemString(zip, 'package.json'), + await getItemString(zip, 'scripts/deploy.ts'), + await getItemString(zip, 'test/test.ts'), ]; t.snapshot(contentComparison); @@ -181,5 +165,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async("arraybuffer")).toString(); + return Buffer.from(await item.async('arraybuffer')).toString(); } diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index f9092b67f..893f9cf79 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -1,18 +1,15 @@ -import JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import { - formatLinesWithSpaces, - Lines, - spaceBetween, -} from "./utils/format-lines"; +import JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; +import type { Contract } from './contract'; +import { printContract } from './print'; +import SOLIDITY_VERSION from './solidity-version.json'; +import type { Lines } from './utils/format-lines'; +import { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; const hardhatConfig = (upgradeable: boolean) => `\ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; -${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ""} +${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ''} const config: HardhatUserConfig = { solidity: { @@ -56,10 +53,7 @@ artifacts `; const test = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getImports(c), getTestCase(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getImports(c), getTestCase(c))); function getTestCase(c: Contract) { const args = getAddressArgs(c); @@ -68,45 +62,37 @@ const test = (c: Contract, opts?: GenericOptions) => { [ 'it("Test contract", async function () {', spaceBetween( - [ - `const ContractFactory = await ethers.getContractFactory("${c.name}");`, - ], + [`const ContractFactory = await ethers.getContractFactory("${c.name}");`], getAddressVariables(args), - [ - `const instance = await ${getDeploymentCall(c, args)};`, - "await instance.waitForDeployment();", - ], + [`const instance = await ${getDeploymentCall(c, args)};`, 'await instance.waitForDeployment();'], getExpects(), ), - "});", + '});', ], - "});", + '});', ]; } function getImports(c: Contract) { - return [ - 'import { expect } from "chai";', - `import { ${getHardhatPlugins(c).join(", ")} } from "hardhat";`, - ]; + return ['import { expect } from "chai";', `import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";`]; } function getExpects(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case "ERC20": - case "ERC721": + case 'ERC20': + case 'ERC721': return [`expect(await instance.name()).to.equal("${opts.name}");`]; - case "ERC1155": + case 'ERC1155': return [`expect(await instance.uri(0)).to.equal("${opts.uri}");`]; - case "Governor": - case "Custom": + case 'Governor': + case 'Custom': break; default: - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } return []; @@ -115,9 +101,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressVariables(args: string[]): Lines[] { const vars = []; for (let i = 0; i < args.length; i++) { - vars.push( - `const ${args[i]} = (await ethers.getSigners())[${i}].address;`, - ); + vars.push(`const ${args[i]} = (await ethers.getSigners())[${i}].address;`); } return vars; } @@ -126,7 +110,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === "address") { + if (constructorArg.type === 'address') { args.push(constructorArg.name); } } @@ -135,29 +119,23 @@ function getAddressArgs(c: Contract): string[] { function getDeploymentCall(c: Contract, args: string[]): string { return c.upgradeable - ? `upgrades.deployProxy(ContractFactory, [${args.join(", ")}])` - : `ContractFactory.deploy(${args.join(", ")})`; + ? `upgrades.deployProxy(ContractFactory, [${args.join(', ')}])` + : `ContractFactory.deploy(${args.join(', ')})`; } const script = (c: Contract) => { const args = getAddressArgs(c); return `\ -import { ${getHardhatPlugins(c).join(", ")} } from "hardhat"; +import { ${getHardhatPlugins(c).join(', ')} } from "hardhat"; async function main() { const ContractFactory = await ethers.getContractFactory("${c.name}"); - ${ - args.length > 0 - ? "// TODO: Set addresses for the contract arguments below" - : "" - } + ${args.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''} const instance = await ${getDeploymentCall(c, args)}; await instance.waitForDeployment(); - console.log(\`${ - c.upgradeable ? "Proxy" : "Contract" - } deployed to \${await instance.getAddress()}\`); + console.log(\`${c.upgradeable ? 'Proxy' : 'Contract'} deployed to \${await instance.getAddress()}\`); } // We recommend this pattern to be able to use async/await everywhere @@ -196,9 +174,9 @@ npx hardhat run --network scripts/deploy.ts `; function getHardhatPlugins(c: Contract) { - const plugins = ["ethers"]; + const plugins = ['ethers']; if (c.upgradeable) { - plugins.push("upgrades"); + plugins.push('upgrades'); } return plugins; } @@ -207,24 +185,24 @@ export async function zipHardhat(c: Contract, opts?: GenericOptions) { const zip = new JSZip(); const { default: packageJson } = c.upgradeable - ? await import("./environments/hardhat/upgradeable/package.json") - : await import("./environments/hardhat/package.json"); + ? await import('./environments/hardhat/upgradeable/package.json') + : await import('./environments/hardhat/package.json'); packageJson.license = c.license; const { default: packageLock } = c.upgradeable - ? await import("./environments/hardhat/upgradeable/package-lock.json") - : await import("./environments/hardhat/package-lock.json"); - packageLock.packages[""].license = c.license; + ? await import('./environments/hardhat/upgradeable/package-lock.json') + : await import('./environments/hardhat/package-lock.json'); + packageLock.packages[''].license = c.license; zip.file(`contracts/${c.name}.sol`, printContract(c)); - zip.file("test/test.ts", test(c, opts)); - zip.file("scripts/deploy.ts", script(c)); - zip.file(".gitignore", gitIgnore); - zip.file("hardhat.config.ts", hardhatConfig(c.upgradeable)); - zip.file("package.json", JSON.stringify(packageJson, null, 2)); + zip.file('test/test.ts', test(c, opts)); + zip.file('scripts/deploy.ts', script(c)); + zip.file('.gitignore', gitIgnore); + zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable)); + zip.file('package.json', JSON.stringify(packageJson, null, 2)); zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2)); - zip.file("README.md", readme); - zip.file("tsconfig.json", tsConfig); + zip.file('README.md', readme); + zip.file('tsconfig.json', tsConfig); return zip; } diff --git a/packages/core/solidity/zip-env-foundry.js b/packages/core/solidity/zip-env-foundry.js index 5cfb903a7..836eb4d8e 100644 --- a/packages/core/solidity/zip-env-foundry.js +++ b/packages/core/solidity/zip-env-foundry.js @@ -1 +1 @@ -module.exports = require("./dist/zip-foundry"); +module.exports = require('./dist/zip-foundry'); diff --git a/packages/core/solidity/zip-env-foundry.ts b/packages/core/solidity/zip-env-foundry.ts index 7b167f759..b28db5f46 100644 --- a/packages/core/solidity/zip-env-foundry.ts +++ b/packages/core/solidity/zip-env-foundry.ts @@ -1 +1 @@ -export * from "./src/zip-foundry"; +export * from './src/zip-foundry'; diff --git a/packages/core/solidity/zip-env-hardhat.js b/packages/core/solidity/zip-env-hardhat.js index 5ce54ca53..2d13c70df 100644 --- a/packages/core/solidity/zip-env-hardhat.js +++ b/packages/core/solidity/zip-env-hardhat.js @@ -1 +1 @@ -module.exports = require("./dist/zip-hardhat"); +module.exports = require('./dist/zip-hardhat'); diff --git a/packages/core/solidity/zip-env-hardhat.ts b/packages/core/solidity/zip-env-hardhat.ts index 3934d6d7f..8786bfeae 100644 --- a/packages/core/solidity/zip-env-hardhat.ts +++ b/packages/core/solidity/zip-env-hardhat.ts @@ -1 +1 @@ -export * from "./src/zip-hardhat"; +export * from './src/zip-hardhat'; diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index 938e5370d..2332bea44 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -1,5 +1,5 @@ -import OpenAI from "https://esm.sh/openai@4.11.0"; -import { OpenAIStream, StreamingTextResponse } from "https://esm.sh/ai@2.2.16"; +import OpenAI from 'https://esm.sh/openai@4.11.0'; +import { OpenAIStream, StreamingTextResponse } from 'https://esm.sh/ai@2.2.16'; import { erc20Function, erc721Function, @@ -8,19 +8,19 @@ import { realWorldAssetFunction, governorFunction, customFunction, -} from "../src/solidity/wiz-functions.ts"; -import { Redis } from "https://esm.sh/@upstash/redis@1.25.1"; +} from '../src/solidity/wiz-functions.ts'; +import { Redis } from 'https://esm.sh/@upstash/redis@1.25.1'; export default async (req: Request) => { try { const data = await req.json(); - const apiKey = Deno.env.get("OPENAI_API_KEY"); + const apiKey = Deno.env.get('OPENAI_API_KEY'); - const redisUrl = Deno.env.get("REDIS_URL"); - const redisToken = Deno.env.get("REDIS_TOKEN"); + const redisUrl = Deno.env.get('REDIS_URL'); + const redisToken = Deno.env.get('REDIS_TOKEN'); if (!redisUrl || !redisToken) { - throw new Error("missing redis credentials"); + throw new Error('missing redis credentials'); } const redis = new Redis({ @@ -32,15 +32,13 @@ export default async (req: Request) => { apiKey: apiKey, }); - const validatedMessages = data.messages.filter( - (message: { role: string; content: string }) => { - return message.content.length < 500; - }, - ); + const validatedMessages = data.messages.filter((message: { role: string; content: string }) => { + return message.content.length < 500; + }); const messages = [ { - role: "system", + role: 'system', content: ` You are a smart contract assistant built by OpenZeppelin to help users using OpenZeppelin Contracts Wizard. The current options are ${JSON.stringify(data.currentOpts)}. @@ -51,7 +49,7 @@ export default async (req: Request) => { ]; const response = await openai.chat.completions.create({ - model: "gpt-4-1106-preview", + model: 'gpt-4-1106-preview', messages, functions: [ erc20Function, @@ -77,7 +75,7 @@ export default async (req: Request) => { ...messages, { content: completion, - role: "assistant", + role: 'assistant', }, ], }; @@ -91,9 +89,9 @@ export default async (req: Request) => { }); return new StreamingTextResponse(stream); } catch (e) { - console.error("Could not retrieve results:", e); + console.error('Could not retrieve results:', e); return Response.json({ - error: "Could not retrieve results.", + error: 'Could not retrieve results.', }); } }; diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js index 0b17b641f..01dfd298e 100644 --- a/packages/ui/postcss.config.js +++ b/packages/ui/postcss.config.js @@ -1,6 +1,6 @@ -const nesting = require("tailwindcss/nesting"); -const tailwindcss = require("tailwindcss"); -const autoprefixer = require("autoprefixer"); +const nesting = require('tailwindcss/nesting'); +const tailwindcss = require('tailwindcss'); +const autoprefixer = require('autoprefixer'); module.exports = { plugins: [nesting, tailwindcss, autoprefixer], diff --git a/packages/ui/rollup.config.mjs b/packages/ui/rollup.config.mjs index 65f0d7635..e920328fd 100644 --- a/packages/ui/rollup.config.mjs +++ b/packages/ui/rollup.config.mjs @@ -1,27 +1,27 @@ -import svelte from "rollup-plugin-svelte"; -import commonjs from "@rollup/plugin-commonjs"; -import json from "@rollup/plugin-json"; -import resolve from "@rollup/plugin-node-resolve"; -import replace from "@rollup/plugin-replace"; -import alias from "@rollup/plugin-alias"; -import livereload from "rollup-plugin-livereload"; -import { terser } from "rollup-plugin-terser"; -import typescript from "@rollup/plugin-typescript"; -import styles from "rollup-plugin-styles"; -import proc from "child_process"; -import events from "events"; -import serve from "./rollup.server.mjs"; +import svelte from 'rollup-plugin-svelte'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import replace from '@rollup/plugin-replace'; +import alias from '@rollup/plugin-alias'; +import livereload from 'rollup-plugin-livereload'; +import { terser } from 'rollup-plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import styles from 'rollup-plugin-styles'; +import proc from 'child_process'; +import events from 'events'; +import serve from './rollup.server.mjs'; const production = !process.env.ROLLUP_WATCH; -process.env.NODE_ENV = production ? "production" : "development"; +process.env.NODE_ENV = production ? 'production' : 'development'; // Watch the `public` directory and refresh the // browser on changes when not in production const livereloader = !production && livereload({ - watch: "public", + watch: 'public', port: 35731, }); @@ -31,10 +31,10 @@ function onStartRun(cmd, ...args) { async buildStart() { if (ran) return; const child = proc.spawn(cmd, args, { - stdio: "inherit", - shell: process.platform == "win32", + stdio: 'inherit', + shell: process.platform == 'win32', }); - const [code, signal] = await events.once(child, "exit"); + const [code, signal] = await events.once(child, 'exit'); if (code || signal) { throw new Error(`Command \`${cmd}\` failed`); } @@ -46,37 +46,37 @@ function onStartRun(cmd, ...args) { /** @type import('rollup').RollupOptions */ export default [ { - input: "src/standalone.js", + input: 'src/standalone.js', output: { - dir: "public/build", - assetFileNames: "[name][extname]", + dir: 'public/build', + assetFileNames: '[name][extname]', sourcemap: true, }, onwarn(warning, warn) { - if (warning.code !== "EMPTY_BUNDLE") { + if (warning.code !== 'EMPTY_BUNDLE') { warn(warning); } }, plugins: [ styles({ - include: "src/standalone.css", - mode: ["extract"], + include: 'src/standalone.css', + mode: ['extract'], url: false, sourceMap: true, }), ], }, { - input: "src/embed.ts", + input: 'src/embed.ts', output: { sourcemap: true, - format: "iife", - name: "embed", - file: "public/build/embed.js", + format: 'iife', + name: 'embed', + file: 'public/build/embed.js', }, plugins: [ typescript({ - include: ["src/**/*.ts"], + include: ['src/**/*.ts'], sourceMap: true, inlineSources: true, }), @@ -90,45 +90,44 @@ export default [ }, { preserveEntrySignatures: false, - input: "src/main.ts", + input: 'src/main.ts', output: { sourcemap: true, - format: "es", - dir: "public/build", - chunkFileNames: "[name].js", - assetFileNames: "[name][extname]", + format: 'es', + dir: 'public/build', + chunkFileNames: '[name].js', + assetFileNames: '[name][extname]', }, plugins: [ // Generate openzeppelin-contracts.js data file - onStartRun(..."yarn --cwd ../core/solidity prepare".split(" ")), + onStartRun(...'yarn --cwd ../core/solidity prepare'.split(' ')), - svelte(await import("./svelte.config.js")), + svelte(await import('./svelte.config.js')), styles({ - mode: ["extract", "bundle.css"], + mode: ['extract', 'bundle.css'], sourceMap: true, }), alias({ entries: { - path: "path-browserify", - "highlight.js/lib/languages/python": - "../../node_modules/highlight.js/lib/languages/python.js", + path: 'path-browserify', + 'highlight.js/lib/languages/python': '../../node_modules/highlight.js/lib/languages/python.js', }, }), resolve({ browser: true, - dedupe: ["svelte"], - mainFields: ["ts:main", "module", "main"], + dedupe: ['svelte'], + mainFields: ['ts:main', 'module', 'main'], preferBuiltins: false, }), replace({ preventAssignment: true, - include: "../../**/node_modules/**/*", - "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), - "process.env.NODE_DEBUG": JSON.stringify(process.env.NODE_DEBUG), + include: '../../**/node_modules/**/*', + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), }), json(), @@ -136,7 +135,7 @@ export default [ commonjs(), typescript({ - include: ["src/**/*.ts", "../core/*/src/**/*.ts"], + include: ['src/**/*.ts', '../core/*/src/**/*.ts'], sourceMap: true, inlineSources: true, }), diff --git a/packages/ui/rollup.server.mjs b/packages/ui/rollup.server.mjs index e456b3d2e..ff2266018 100644 --- a/packages/ui/rollup.server.mjs +++ b/packages/ui/rollup.server.mjs @@ -1,4 +1,4 @@ -import proc from "child_process"; +import proc from 'child_process'; const state = (global.ROLLUP_SERVER = global.ROLLUP_SERVER || { server: undefined, @@ -12,13 +12,13 @@ export default function serve() { return { writeBundle() { if (state.server) return; - state.server = proc.spawn("npm", ["run", "start", "--", "--dev"], { - stdio: ["ignore", "inherit", "inherit"], + state.server = proc.spawn('npm', ['run', 'start', '--', '--dev'], { + stdio: ['ignore', 'inherit', 'inherit'], shell: true, }); - process.on("SIGTERM", toExit); - process.on("exit", toExit); + process.on('SIGTERM', toExit); + process.on('exit', toExit); }, }; } diff --git a/packages/ui/src/cairo/highlightjs.ts b/packages/ui/src/cairo/highlightjs.ts index cfc81aa15..eb4e7c319 100644 --- a/packages/ui/src/cairo/highlightjs.ts +++ b/packages/ui/src/cairo/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from "highlight.js/lib/core"; +import hljs from 'highlight.js/lib/core'; // @ts-expect-error missing type declaration -import hljsDefineCairo from "highlightjs-cairo"; +import hljsDefineCairo from 'highlightjs-cairo'; hljsDefineCairo(hljs); export default hljs; diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index 6f3becaf5..a83929d2e 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -1,39 +1,33 @@ /* eslint-disable no-useless-escape */ -import { contractsVersionTag } from "@openzeppelin/wizard-cairo/src"; +import { contractsVersionTag } from '@openzeppelin/wizard-cairo/src'; export function injectHyperlinks(code: string) { - const importRegex = - /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; + const importRegex = /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; let result = code; let match = importRegex.exec(code); while (match != null) { const [line, libraryPrefix, libraryPath, suffix] = match; - if ( - line !== undefined && - libraryPrefix !== undefined && - libraryPath !== undefined && - suffix !== undefined - ) { + if (line !== undefined && libraryPrefix !== undefined && libraryPath !== undefined && suffix !== undefined) { const githubPrefix = `https://github.com/OpenZeppelin/cairo-contracts/blob/${contractsVersionTag}/packages/`; - const libraryPathSegments = libraryPath.split("::"); - libraryPathSegments.splice(1, 0, "src"); + const libraryPathSegments = libraryPath.split('::'); + libraryPathSegments.splice(1, 0, 'src'); if (libraryPathSegments !== undefined && libraryPathSegments.length > 0) { let replacement; - if (suffix === "::{") { + if (suffix === '::{') { // Multiple components are imported, so remove components and link to the parent .cairo file replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported // If a mapping exists, link to the mapped file, otherwise remove the component and link to the parent .cairo file const componentName = suffix.substring(2, suffix.length); const mapping = componentMappings[componentName]; - const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; + const urlSuffix = mapping ? `/${mapping}.cairo` : '.cairo'; replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } @@ -46,6 +40,6 @@ export function injectHyperlinks(code: string) { } const componentMappings: { [key: string]: string } = { - AccountComponent: "account", - UpgradeableComponent: "upgradeable", + AccountComponent: 'account', + UpgradeableComponent: 'upgradeable', } as const; diff --git a/packages/ui/src/common/error-tooltip.ts b/packages/ui/src/common/error-tooltip.ts index cd6b96741..804eb5be0 100644 --- a/packages/ui/src/common/error-tooltip.ts +++ b/packages/ui/src/common/error-tooltip.ts @@ -1,13 +1,13 @@ -import tippy from "tippy.js"; +import tippy from 'tippy.js'; -const klass = "has-error"; +const klass = 'has-error'; export function error(node: HTMLElement, content?: string) { let shown = false; const t = tippy(node, { - placement: "right", - theme: "light-red border", + placement: 'right', + theme: 'light-red border', showOnCreate: false, onShow: () => { shown = true; diff --git a/packages/ui/src/common/post-config.ts b/packages/ui/src/common/post-config.ts index 365cd3c80..b917f6d40 100644 --- a/packages/ui/src/common/post-config.ts +++ b/packages/ui/src/common/post-config.ts @@ -1,5 +1,5 @@ -import type { GenericOptions as SolidityOptions } from "@openzeppelin/wizard"; -import type { GenericOptions as CairoOptions } from "@openzeppelin/wizard-cairo"; +import type { GenericOptions as SolidityOptions } from '@openzeppelin/wizard'; +import type { GenericOptions as CairoOptions } from '@openzeppelin/wizard-cairo'; declare global { interface Window { @@ -7,21 +7,15 @@ declare global { } } -export type Action = - | "copy" - | "remix" - | "download-file" - | "download-hardhat" - | "download-foundry" - | "defender"; -export type Language = "solidity" | "cairo" | "stylus" | "stellar"; +export type Action = 'copy' | 'remix' | 'download-file' | 'download-hardhat' | 'download-foundry' | 'defender'; +export type Language = 'solidity' | 'cairo' | 'stylus' | 'stellar'; export async function postConfig( opts: Required | Required, action: Action, language: Language, ) { - window.gtag?.("event", "wizard_action", { + window.gtag?.('event', 'wizard_action', { ...opts, action, wizard_lang: language, diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index c63bba812..c5ddb81d9 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -1,47 +1,41 @@ -import type { SolcInputSources } from "@openzeppelin/wizard/get-imports"; +import type { SolcInputSources } from '@openzeppelin/wizard/get-imports'; -export type Message = - | ResizeMessage - | TabChangeMessage - | UnsupportedVersionMessage - | DefenderDeployMessage; +export type Message = ResizeMessage | TabChangeMessage | UnsupportedVersionMessage | DefenderDeployMessage; export interface ResizeMessage { - kind: "oz-wizard-resize"; + kind: 'oz-wizard-resize'; height: number; } export interface TabChangeMessage { - kind: "oz-wizard-tab-change"; + kind: 'oz-wizard-tab-change'; tab: string; } export interface UnsupportedVersionMessage { - kind: "oz-wizard-unsupported-version"; + kind: 'oz-wizard-unsupported-version'; } export interface DefenderDeployMessage { - kind: "oz-wizard-defender-deploy"; + kind: 'oz-wizard-defender-deploy'; sources: SolcInputSources; } export function postMessage(msg: Message) { if (parent) { - parent.postMessage(msg, "*"); + parent.postMessage(msg, '*'); } } -export function postMessageToIframe(id: "defender-deploy", msg: Message) { - const iframe: HTMLIFrameElement | null = document.getElementById( - id, - ) as HTMLIFrameElement; +export function postMessageToIframe(id: 'defender-deploy', msg: Message) { + const iframe: HTMLIFrameElement | null = document.getElementById(id) as HTMLIFrameElement; if (iframe) { - iframe.contentWindow?.postMessage(msg, "*"); + iframe.contentWindow?.postMessage(msg, '*'); // in case the iframe is still loading, waits // a second to fully load and tries again iframe.onload = () => { setTimeout(() => { - iframe?.contentWindow?.postMessage(msg, "*"); + iframe?.contentWindow?.postMessage(msg, '*'); }, 1000); }; } diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index 52b9097ed..6326f1d18 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -1,33 +1,26 @@ export function resizeToFit(node: HTMLInputElement) { const resize = () => { - if (node.value === "") { + if (node.value === '') { return; } const style = window.getComputedStyle(node); const textWidth = measureTextWidth(node.value, style); const minWidth = measureTextWidth(node.placeholder, style); - const padding = [ - "padding-left", - "padding-right", - "border-left-width", - "border-right-width", - ].map((p) => style.getPropertyValue(p)); - const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( - " + ", - )})`; - node.style.setProperty("width", result); + const padding = ['padding-left', 'padding-right', 'border-left-width', 'border-right-width'].map(p => + style.getPropertyValue(p), + ); + const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join(' + ')})`; + node.style.setProperty('width', result); }; resize(); - node.addEventListener("input", resize); + node.addEventListener('input', resize); } function measureTextWidth(text: string, style: CSSStyleDeclaration): string { - const font = ["font-size", "font-family"] - .map((p) => style.getPropertyValue(p)) - .join(" "); - const ctx = document.createElement("canvas").getContext("2d")!; + const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); + const ctx = document.createElement('canvas').getContext('2d')!; ctx.font = font; - return ctx.measureText(text).width + "px"; + return ctx.measureText(text).width + 'px'; } diff --git a/packages/ui/src/embed.ts b/packages/ui/src/embed.ts index 0ea4c6d52..2ac1e629d 100644 --- a/packages/ui/src/embed.ts +++ b/packages/ui/src/embed.ts @@ -1,7 +1,7 @@ -import type { Message } from "./common/post-message"; +import type { Message } from './common/post-message'; -if (!document.currentScript || !("src" in document.currentScript)) { - throw new Error("Unknown script URL"); +if (!document.currentScript || !('src' in document.currentScript)) { + throw new Error('Unknown script URL'); } const currentScript = new URL(document.currentScript.src); @@ -9,61 +9,59 @@ const currentScript = new URL(document.currentScript.src); const iframes = new WeakMap(); let unsupportedVersion: boolean = false; -const unsupportedVersionFrameHeight = "auto"; +const unsupportedVersionFrameHeight = 'auto'; -window.addEventListener("message", function (e: MessageEvent) { +window.addEventListener('message', function (e: MessageEvent) { if (e.source) { - if (e.data.kind === "oz-wizard-unsupported-version") { + if (e.data.kind === 'oz-wizard-unsupported-version') { unsupportedVersion = true; const iframe = iframes.get(e.source); if (iframe) { iframe.style.height = unsupportedVersionFrameHeight; } - } else if (e.data.kind === "oz-wizard-resize") { + } else if (e.data.kind === 'oz-wizard-resize') { const iframe = iframes.get(e.source); if (iframe) { - iframe.style.height = unsupportedVersion - ? unsupportedVersionFrameHeight - : "calc(100vh - 100px)"; + iframe.style.height = unsupportedVersion ? unsupportedVersionFrameHeight : 'calc(100vh - 100px)'; } } } }); onDOMContentLoaded(function () { - const wizards = document.querySelectorAll("oz-wizard"); + const wizards = document.querySelectorAll('oz-wizard'); for (const w of wizards) { - w.style.display = "block"; + w.style.display = 'block'; - const src = new URL("embed", currentScript.origin); + const src = new URL('embed', currentScript.origin); - setSearchParam(w, src.searchParams, "data-lang", "lang"); - setSearchParam(w, src.searchParams, "data-tab", "tab"); - setSearchParam(w, src.searchParams, "version", "version"); + setSearchParam(w, src.searchParams, 'data-lang', 'lang'); + setSearchParam(w, src.searchParams, 'data-tab', 'tab'); + setSearchParam(w, src.searchParams, 'version', 'version'); - const sync = w.getAttribute("data-sync-url"); + const sync = w.getAttribute('data-sync-url'); - if (sync === "fragment") { + if (sync === 'fragment') { // Uses format: #tab&key=value&key=value... - const fragments = window.location.hash.replace("#", "").split("&"); + const fragments = window.location.hash.replace('#', '').split('&'); for (const fragment of fragments) { - const [key, value] = fragment.split("=", 2); + const [key, value] = fragment.split('=', 2); if (key && value) { src.searchParams.set(key, value); } else { - src.searchParams.set("tab", fragment); + src.searchParams.set('tab', fragment); } } } - const iframe = document.createElement("iframe"); + const iframe = document.createElement('iframe'); iframe.src = src.toString(); - iframe.style.display = "block"; - iframe.style.border = "0"; - iframe.style.width = "100%"; - iframe.style.height = "calc(100vh - 100px)"; - iframe.allow = "clipboard-write"; + iframe.style.display = 'block'; + iframe.style.border = '0'; + iframe.style.width = '100%'; + iframe.style.height = 'calc(100vh - 100px)'; + iframe.allow = 'clipboard-write'; w.appendChild(iframe); @@ -71,9 +69,9 @@ onDOMContentLoaded(function () { iframes.set(iframe.contentWindow, iframe); } - if (sync === "fragment") { - window.addEventListener("message", (e: MessageEvent) => { - if (e.source && e.data.kind === "oz-wizard-tab-change") { + if (sync === 'fragment') { + window.addEventListener('message', (e: MessageEvent) => { + if (e.source && e.data.kind === 'oz-wizard-tab-change') { if (iframe === iframes.get(e.source)) { window.location.hash = e.data.tab; } @@ -83,12 +81,7 @@ onDOMContentLoaded(function () { } }); -function setSearchParam( - w: HTMLElement, - searchParams: URLSearchParams, - dataParam: string, - param: string, -) { +function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam: string, param: string) { const value = w.getAttribute(dataParam) ?? w.getAttribute(param); if (value) { searchParams.set(param, value); @@ -96,8 +89,8 @@ function setSearchParam( } function onDOMContentLoaded(callback: () => void) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", callback); + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback); } else { callback(); } diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index e8afa5c09..346c3d8ea 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -1,18 +1,18 @@ -import "./common/styles/global.css"; +import './common/styles/global.css'; -import type {} from "svelte"; -import SolidityApp from "./solidity/App.svelte"; -import CairoApp from "./cairo/App.svelte"; -import { postMessage } from "./common/post-message"; -import UnsupportedVersion from "./common/UnsupportedVersion.svelte"; -import semver from "semver"; -import { compatibleContractsSemver as compatibleSolidityContractsSemver } from "@openzeppelin/wizard"; -import { compatibleContractsSemver as compatibleCairoContractsSemver } from "@openzeppelin/wizard-cairo"; -import type { InitialOptions } from "./common/initial-options.ts"; +import type {} from 'svelte'; +import SolidityApp from './solidity/App.svelte'; +import CairoApp from './cairo/App.svelte'; +import { postMessage } from './common/post-message'; +import UnsupportedVersion from './common/UnsupportedVersion.svelte'; +import semver from 'semver'; +import { compatibleContractsSemver as compatibleSolidityContractsSemver } from '@openzeppelin/wizard'; +import { compatibleContractsSemver as compatibleCairoContractsSemver } from '@openzeppelin/wizard-cairo'; +import type { InitialOptions } from './common/initial-options.ts'; function postResize() { const { height } = document.documentElement.getBoundingClientRect(); - postMessage({ kind: "oz-wizard-resize", height }); + postMessage({ kind: 'oz-wizard-resize', height }); } window.onload = postResize; @@ -22,40 +22,34 @@ resizeObserver.observe(document.body); const params = new URLSearchParams(window.location.search); -const initialTab = params.get("tab") ?? undefined; -const lang = params.get("lang") ?? undefined; -const requestedVersion = params.get("version") ?? undefined; +const initialTab = params.get('tab') ?? undefined; +const lang = params.get('lang') ?? undefined; +const requestedVersion = params.get('version') ?? undefined; const initialOpts: InitialOptions = { - name: params.get("name") ?? undefined, - symbol: params.get("symbol") ?? undefined, - premint: params.get("premint") ?? undefined, + name: params.get('name') ?? undefined, + symbol: params.get('symbol') ?? undefined, + premint: params.get('premint') ?? undefined, }; -const compatibleVersionSemver = - lang === "cairo" - ? compatibleCairoContractsSemver - : compatibleSolidityContractsSemver; +const compatibleVersionSemver = lang === 'cairo' ? compatibleCairoContractsSemver : compatibleSolidityContractsSemver; let app; -if ( - requestedVersion && - !semver.satisfies(requestedVersion, compatibleVersionSemver) -) { - postMessage({ kind: "oz-wizard-unsupported-version" }); +if (requestedVersion && !semver.satisfies(requestedVersion, compatibleVersionSemver)) { + postMessage({ kind: 'oz-wizard-unsupported-version' }); app = new UnsupportedVersion({ target: document.body, props: { requestedVersion, compatibleVersionSemver }, }); } else { switch (lang) { - case "cairo": + case 'cairo': app = new CairoApp({ target: document.body, props: { initialTab, initialOpts }, }); break; - case "solidity": + case 'solidity': default: app = new SolidityApp({ target: document.body, @@ -65,8 +59,8 @@ if ( } } -app.$on("tab-change", (e: CustomEvent) => { - postMessage({ kind: "oz-wizard-tab-change", tab: e.detail.toLowerCase() }); +app.$on('tab-change', (e: CustomEvent) => { + postMessage({ kind: 'oz-wizard-tab-change', tab: e.detail.toLowerCase() }); }); export default app; diff --git a/packages/ui/src/solidity/highlightjs.ts b/packages/ui/src/solidity/highlightjs.ts index b387baaa6..5772decd1 100644 --- a/packages/ui/src/solidity/highlightjs.ts +++ b/packages/ui/src/solidity/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from "highlight.js/lib/core"; +import hljs from 'highlight.js/lib/core'; // @ts-expect-error missing type declaration file -import hljsDefineSolidity from "highlightjs-solidity"; +import hljsDefineSolidity from 'highlightjs-solidity'; hljsDefineSolidity(hljs); export default hljs; diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 8f5b9305c..d8ae1ef13 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -1,4 +1,4 @@ -import { version as contractsVersion } from "@openzeppelin/contracts/package.json"; +import { version as contractsVersion } from '@openzeppelin/contracts/package.json'; export function injectHyperlinks(code: string) { // We are modifying HTML, so use HTML escaped chars. The pattern excludes paths that include /../ in the URL. diff --git a/packages/ui/src/solidity/remix.ts b/packages/ui/src/solidity/remix.ts index ee479a157..a43087074 100644 --- a/packages/ui/src/solidity/remix.ts +++ b/packages/ui/src/solidity/remix.ts @@ -1,8 +1,8 @@ export function remixURL(code: string, upgradeable = false): URL { - const remix = new URL("https://remix.ethereum.org"); - remix.searchParams.set("code", btoa(code).replace(/=*$/, "")); + const remix = new URL('https://remix.ethereum.org'); + remix.searchParams.set('code', btoa(code).replace(/=*$/, '')); if (upgradeable) { - remix.searchParams.set("deployProxy", upgradeable.toString()); + remix.searchParams.set('deployProxy', upgradeable.toString()); } return remix; } diff --git a/packages/ui/src/solidity/wiz-functions.ts b/packages/ui/src/solidity/wiz-functions.ts index be6877bd3..0b0c9efab 100644 --- a/packages/ui/src/solidity/wiz-functions.ts +++ b/packages/ui/src/solidity/wiz-functions.ts @@ -1,31 +1,31 @@ const commonOptions = { // 'false' gets converted to false access: { - type: "string", - enum: ["false", "ownable", "roles", "managed"], + type: 'string', + enum: ['false', 'ownable', 'roles', 'managed'], description: - "The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.", + 'The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.', }, // 'false' gets converted to false upgradeable: { - type: "string", - enum: ["false", "transparent", "uups"], + type: 'string', + enum: ['false', 'transparent', 'uups'], description: - "Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.", + 'Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.', }, info: { - type: "object", - description: "Metadata about the contract and author", + type: 'object', + description: 'Metadata about the contract and author', properties: { securityContact: { - type: "string", + type: 'string', description: - "Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.", + 'Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.', }, license: { - type: "string", + type: 'string', description: 'The license used by the contract, default is "MIT"', }, }, @@ -33,260 +33,249 @@ const commonOptions = { }; const repeatedOptions = { - name: { type: "string", description: "The name of the contract" }, - symbol: { type: "string", description: "The short symbol for the token" }, + name: { type: 'string', description: 'The name of the contract' }, + symbol: { type: 'string', description: 'The short symbol for the token' }, burnable: { - type: "boolean", - description: "Whether token holders will be able to destroy their tokens", + type: 'boolean', + description: 'Whether token holders will be able to destroy their tokens', }, pausable: { - type: "boolean", + type: 'boolean', description: - "Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.", + 'Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.', }, mintable: { - type: "boolean", - description: - "Whether privileged accounts will be able to create more supply or emit more tokens", + type: 'boolean', + description: 'Whether privileged accounts will be able to create more supply or emit more tokens', }, }; export const erc20Function = { - name: "erc20", - description: "Make a fungible token per the ERC-20 standard", + name: 'erc20', + description: 'Make a fungible token per the ERC-20 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, premint: { - type: "number", - description: "The number of tokens to premint for the deployer.", + type: 'number', + description: 'The number of tokens to premint for the deployer.', }, mintable: repeatedOptions.mintable, permit: { - type: "boolean", + type: 'boolean', description: - "Whether without paying gas, token holders will be able to allow third parties to transfer from their account.", + 'Whether without paying gas, token holders will be able to allow third parties to transfer from their account.', }, // 'false' gets converted to false votes: { - type: "string", - enum: ["false", "blocknumber", "timestamp"], + type: 'string', + enum: ['false', 'blocknumber', 'timestamp'], description: - "Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + 'Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.', }, flashmint: { - type: "boolean", + type: 'boolean', description: "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", }, ...commonOptions, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const erc721Function = { - name: "erc721", - description: "Make a non-fungible token per the ERC-721 standard", + name: 'erc721', + description: 'Make a non-fungible token per the ERC-721 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, - baseUri: { type: "string", description: "A base uri for the token" }, + baseUri: { type: 'string', description: 'A base uri for the token' }, enumerable: { - type: "boolean", + type: 'boolean', description: - "Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.", + 'Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.', }, uriStorage: { - type: "boolean", - description: "Allows updating token URIs for individual token IDs", + type: 'boolean', + description: 'Allows updating token URIs for individual token IDs', }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, incremental: { - type: "boolean", - description: - "Whether new tokens will be automatically assigned an incremental id", + type: 'boolean', + description: 'Whether new tokens will be automatically assigned an incremental id', }, // 'false' gets converted to false votes: { - type: "string", - enum: ["false", "blocknumber", "timestamp"], + type: 'string', + enum: ['false', 'blocknumber', 'timestamp'], description: - "Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + 'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.', }, ...commonOptions, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const erc1155Function = { - name: "erc1155", - description: "Make a non-fungible token per the ERC-1155 standard", + name: 'erc1155', + description: 'Make a non-fungible token per the ERC-1155 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, uri: { - type: "string", + type: 'string', description: - "The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.", + 'The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.', }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, supply: { - type: "boolean", - description: "Whether to keep track of total supply of tokens", + type: 'boolean', + description: 'Whether to keep track of total supply of tokens', }, updatableUri: { - type: "boolean", - description: - "Whether privileged accounts will be able to set a new URI for all token types", + type: 'boolean', + description: 'Whether privileged accounts will be able to set a new URI for all token types', }, ...commonOptions, }, - required: ["name", "uri"], + required: ['name', 'uri'], }, }; export const stablecoinFunction = { - name: "stablecoin", + name: 'stablecoin', description: - "Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", + 'Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', parameters: { - type: "object", + type: 'object', properties: { ...erc20Function.parameters.properties, custodian: { - type: "boolean", + type: 'boolean', description: - "Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.", + 'Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.', }, // 'false' gets converted to false limitations: { - type: "string", - enum: ["false", "allowlist", "blocklist"], + type: 'string', + enum: ['false', 'allowlist', 'blocklist'], description: - "Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.", + 'Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.', }, upgradeable: { - type: "string", - enum: ["false"], - description: - "Upgradeability is not yet available for features that use @openzeppelin/community-contracts", + type: 'string', + enum: ['false'], + description: 'Upgradeability is not yet available for features that use @openzeppelin/community-contracts', }, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const realWorldAssetFunction = { - name: "realworldasset", + name: 'realworldasset', description: - "Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", + 'Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', parameters: stablecoinFunction.parameters, }; export const governorFunction = { - name: "governor", - description: "Make a contract to implement governance, such as for a DAO", + name: 'governor', + description: 'Make a contract to implement governance, such as for a DAO', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, delay: { - type: "string", - description: - 'The delay since proposal is created until voting starts, default is "1 day"', + type: 'string', + description: 'The delay since proposal is created until voting starts, default is "1 day"', }, period: { - type: "string", - description: - 'The length of period during which people can cast their vote, default is "1 week"', + type: 'string', + description: 'The length of period during which people can cast their vote, default is "1 week"', }, blockTime: { - type: "number", - description: "The number of seconds assumed for a block, default is 12", + type: 'number', + description: 'The number of seconds assumed for a block, default is 12', }, // gets converted to a string to follow the API proposalThreshold: { - type: "number", - description: - "Minimum number of votes an account must have to create a proposal, default is 0.", + type: 'number', + description: 'Minimum number of votes an account must have to create a proposal, default is 0.', }, decimals: { - type: "number", + type: 'number', description: - "The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)", + 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)', }, quorumMode: { - type: "string", - enum: ["percent", "absolute"], - description: "The type of quorum mode to use", + type: 'string', + enum: ['percent', 'absolute'], + description: 'The type of quorum mode to use', }, quorumPercent: { - type: "number", - description: - "The percent required, in cases of quorumMode equals percent", + type: 'number', + description: 'The percent required, in cases of quorumMode equals percent', }, // gets converted to a string to follow the API quorumAbsolute: { - type: "number", - description: - "The absolute quorum required, in cases of quorumMode equals absolute", + type: 'number', + description: 'The absolute quorum required, in cases of quorumMode equals absolute', }, votes: { - type: "string", - enum: ["erc20votes", "erc721votes"], - description: "The type of voting to use", + type: 'string', + enum: ['erc20votes', 'erc721votes'], + description: 'The type of voting to use', }, clockMode: { - type: "string", - enum: ["blocknumber", "timestamp"], + type: 'string', + enum: ['blocknumber', 'timestamp'], description: - "The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.", + 'The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.', }, // 'false' gets converted to false timelock: { - type: "string", - enum: ["false", "openzeppelin", "compound"], - description: "The type of timelock to use", + type: 'string', + enum: ['false', 'openzeppelin', 'compound'], + description: 'The type of timelock to use', }, storage: { - type: "boolean", - description: - "Enable storage of proposal details and enumerability of proposals", + type: 'boolean', + description: 'Enable storage of proposal details and enumerability of proposals', }, settings: { - type: "boolean", - description: - "Allow governance to update voting settings (delay, period, proposal threshold)", + type: 'boolean', + description: 'Allow governance to update voting settings (delay, period, proposal threshold)', }, ...commonOptions, }, - required: ["name", "delay", "period"], + required: ['name', 'delay', 'period'], }, }; export const customFunction = { - name: "custom", - description: "Make a custom smart contract", + name: 'custom', + description: 'Make a custom smart contract', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, pausable: repeatedOptions.pausable, ...commonOptions, }, - required: ["name"], + required: ['name'], }, }; diff --git a/packages/ui/src/standalone.js b/packages/ui/src/standalone.js index 66b97fc1d..fe423caf7 100644 --- a/packages/ui/src/standalone.js +++ b/packages/ui/src/standalone.js @@ -1,3 +1,3 @@ // Used as Rollup entry point to preprocess CSS for wizard.openzeppelin.com -import "./standalone.css"; +import './standalone.css'; diff --git a/packages/ui/svelte.config.js b/packages/ui/svelte.config.js index f043d9dd9..dc6f828f6 100644 --- a/packages/ui/svelte.config.js +++ b/packages/ui/svelte.config.js @@ -1,4 +1,4 @@ -const sveltePreprocess = require("svelte-preprocess"); +const sveltePreprocess = require('svelte-preprocess'); const production = !process.env.ROLLUP_WATCH; diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js index cafb69e25..579a11b5e 100644 --- a/packages/ui/tailwind.config.js +++ b/packages/ui/tailwind.config.js @@ -1,36 +1,36 @@ module.exports = { content: [ - "./src/**/*.{html,svelte}", + './src/**/*.{html,svelte}', // Using glob patterns results in infinite loop - "./public/index.html", - "./public/cairo.html", - "./public/stylus.html", - "./public/stellar.html", - "./public/embed.html", + './public/index.html', + './public/cairo.html', + './public/stylus.html', + './public/stellar.html', + './public/embed.html', ], theme: { extend: { keyframes: { - "fade-in": { - "0%": { opacity: "0" }, - "100%": { opacity: "1" }, + 'fade-in': { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, }, - "fade-up": { - "0%": { opacity: "0", transform: "translateY(1rem)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, + 'fade-up': { + '0%': { opacity: '0', transform: 'translateY(1rem)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, }, - "fade-down": { - "0%": { opacity: "0", transform: "translateY(-1rem)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, + 'fade-down': { + '0%': { opacity: '0', transform: 'translateY(-1rem)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, }, }, animation: { - "fade-in": "fade-in 0.3s ease-out", - "fade-up": "fade-up 0.2s ease-out", - "fade-down": "fade-down 0.5s ease-out", - "spin-slow": "spin 2s linear infinite", + 'fade-in': 'fade-in 0.3s ease-out', + 'fade-up': 'fade-up 0.2s ease-out', + 'fade-down': 'fade-down 0.5s ease-out', + 'spin-slow': 'spin 2s linear infinite', }, }, }, diff --git a/scripts/bump-changelog.js b/scripts/bump-changelog.js index de6f39a3d..fb756d7a2 100644 --- a/scripts/bump-changelog.js +++ b/scripts/bump-changelog.js @@ -1,22 +1,19 @@ #!/usr/bin/env node -const { version } = require(process.cwd() + "/package.json"); -const [date] = new Date().toISOString().split("T"); +const { version } = require(process.cwd() + '/package.json'); +const [date] = new Date().toISOString().split('T'); -const fs = require("fs"); -const changelog = fs.readFileSync("CHANGELOG.md", "utf8"); +const fs = require('fs'); +const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); const unreleased = /^## Unreleased$/im; if (!unreleased.test(changelog)) { - console.error("Missing changelog entry"); + console.error('Missing changelog entry'); process.exit(1); } -fs.writeFileSync( - "CHANGELOG.md", - changelog.replace(unreleased, `## ${version} (${date})`), -); +fs.writeFileSync('CHANGELOG.md', changelog.replace(unreleased, `## ${version} (${date})`)); -const proc = require("child_process"); -proc.execSync("git add CHANGELOG.md", { stdio: "inherit" }); +const proc = require('child_process'); +proc.execSync('git add CHANGELOG.md', { stdio: 'inherit' }); From 3cd0b593a9ffc2a6236bfa9ea5477e9ab3248962 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:03:46 -0500 Subject: [PATCH 04/16] Add lint step in ci action --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b5ab7d6c..f429b8720 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,8 @@ jobs: - name: Check Svelte run: yarn svelte-check working-directory: packages/ui + - name: Run linter + run: yarn lint - name: Run tests run: yarn test working-directory: packages/core/${{matrix.package}} From 31f0c7800ae219f2e0cc3795c19c512546b60878 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:10:57 -0500 Subject: [PATCH 05/16] resolve prettier conflict --- .vscode/settings.json | 15 + eslint.config.mjs | 62 ++ package.json | 11 +- packages/core/cairo/package.json | 2 +- packages/core/cairo/src/build-generic.ts | 47 +- packages/core/cairo/src/erc1155.ts | 258 ++++--- packages/core/cairo/src/erc20.test.ts | 203 ++--- packages/core/cairo/src/erc20.ts | 203 ++--- packages/core/cairo/src/erc721.test.ts | 210 +++--- packages/core/cairo/src/generate/sources.ts | 132 ++-- packages/core/cairo/src/print.ts | 256 ++++--- .../cairo/src/scripts/update-scarb-project.ts | 48 +- packages/core/cairo/src/test.ts | 95 ++- .../cairo/src/utils/convert-strings.test.ts | 126 ++-- packages/core/cairo/src/vesting.ts | 171 +++-- packages/core/solidity/src/build-generic.ts | 47 +- packages/core/solidity/src/contract.ts | 82 +- packages/core/solidity/src/erc20.ts | 138 ++-- .../solidity/src/generate/alternatives.ts | 6 +- packages/core/solidity/src/governor.ts | 356 +++++---- packages/core/solidity/src/print.ts | 204 ++--- .../core/solidity/src/utils/map-values.ts | 3 +- packages/core/solidity/src/zip-hardhat.ts | 92 ++- packages/ui/api/ai.ts | 98 ++- packages/ui/src/cairo/highlightjs.ts | 6 +- packages/ui/src/cairo/inject-hyperlinks.ts | 31 +- packages/ui/src/common/error-tooltip.ts | 12 +- packages/ui/src/common/post-message.ts | 30 +- packages/ui/src/common/resize-to-fit.ts | 26 +- packages/ui/src/main.ts | 74 +- packages/ui/src/solidity/highlightjs.ts | 6 +- yarn.lock | 707 +++++++++++++++++- 32 files changed, 2477 insertions(+), 1280 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 eslint.config.mjs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..07fa5d824 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + }, +} \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..7a17e8293 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,62 @@ +// @ts-check + +import eslint from "@eslint/js"; +import prettierRecommended from "eslint-plugin-prettier/recommended"; +import unicornPlugin from "eslint-plugin-unicorn"; +import typescriptEslint from "typescript-eslint"; + +export default typescriptEslint.config( + eslint.configs.recommended, + typescriptEslint.configs.strict, + prettierRecommended, + { + plugins: { + unicorn: unicornPlugin, + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-non-null-assertion": "warn", + }, + }, + { + ignores: [ + "node_modules/", + "*.sol", + "packages/*/node_modules/", + "packages/**/dist/", + "packages/**/build/", + ], + }, + { + files: ["**/*.config.js"], + languageOptions: { + sourceType: "commonjs", + }, + }, + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + }, + rules: { + "@typescript-eslint/*": "off", + }, + }, + { + files: ["**/*.mjs", "**/*.js"], + languageOptions: { + sourceType: "commonjs", + globals: { + process: "readonly", + global: "readonly", + console: "readonly", + }, + }, + rules: { + "@typescript-eslint/no-require-imports": "off", + }, + } +); diff --git a/package.json b/package.json index c6c359d09..4b9b661e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "root", "private": true, "scripts": { - "prepare": "wsrun -m prepare" + "prepare": "wsrun -m prepare", + "lint": "eslint" }, "workspaces": { "packages": [ @@ -15,6 +16,14 @@ ] }, "devDependencies": { + "@eslint/js": "^9.20.0", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-unicorn": "^57.0.0", + "prettier": "^3.5.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", "wsrun": "^5.2.4" } } diff --git a/packages/core/cairo/package.json b/packages/core/cairo/package.json index 659340299..41a8a4bad 100644 --- a/packages/core/cairo/package.json +++ b/packages/core/cairo/package.json @@ -31,4 +31,4 @@ "typescript": "^5.0.0", "semver": "^7.6.0" } -} +} \ No newline at end of file diff --git a/packages/core/cairo/src/build-generic.ts b/packages/core/cairo/src/build-generic.ts index fc04c30dc..abfab3d16 100644 --- a/packages/core/cairo/src/build-generic.ts +++ b/packages/core/cairo/src/build-generic.ts @@ -1,48 +1,49 @@ -import { ERC20Options, buildERC20 } from './erc20'; -import { ERC721Options, buildERC721 } from './erc721'; -import { ERC1155Options, buildERC1155 } from './erc1155'; -import { CustomOptions, buildCustom } from './custom'; -import { AccountOptions, buildAccount } from './account'; -import { GovernorOptions, buildGovernor } from './governor'; -import { VestingOptions, buildVesting } from './vesting'; +import { ERC20Options, buildERC20 } from "./erc20"; +import { ERC721Options, buildERC721 } from "./erc721"; +import { ERC1155Options, buildERC1155 } from "./erc1155"; +import { CustomOptions, buildCustom } from "./custom"; +import { AccountOptions, buildAccount } from "./account"; +import { GovernorOptions, buildGovernor } from "./governor"; +import { VestingOptions, buildVesting } from "./vesting"; export interface KindedOptions { - ERC20: { kind: 'ERC20' } & ERC20Options; - ERC721: { kind: 'ERC721' } & ERC721Options; - ERC1155: { kind: 'ERC1155' } & ERC1155Options; - Account: { kind: 'Account' } & AccountOptions; - Governor: { kind: 'Governor' } & GovernorOptions; - Vesting: { kind: 'Vesting' } & VestingOptions; - Custom: { kind: 'Custom' } & CustomOptions; + ERC20: { kind: "ERC20" } & ERC20Options; + ERC721: { kind: "ERC721" } & ERC721Options; + ERC1155: { kind: "ERC1155" } & ERC1155Options; + Account: { kind: "Account" } & AccountOptions; + Governor: { kind: "Governor" } & GovernorOptions; + Vesting: { kind: "Vesting" } & VestingOptions; + Custom: { kind: "Custom" } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions) { switch (opts.kind) { - case 'ERC20': + case "ERC20": return buildERC20(opts); - case 'ERC721': + case "ERC721": return buildERC721(opts); - case 'ERC1155': + case "ERC1155": return buildERC1155(opts); - case 'Account': + case "Account": return buildAccount(opts); - case 'Governor': + case "Governor": return buildGovernor(opts); - case 'Vesting': + case "Vesting": return buildVesting(opts); - case 'Custom': + case "Custom": return buildCustom(opts); - default: + default: { const _: never = opts; - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); + } } } diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index d17cb8f3c..7d5eda31a 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -1,21 +1,33 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { addSRC5Component } from './common-components'; -import { externalTrait } from './external-trait'; -import { toByteArray } from './utils/convert-strings'; -import { RoyaltyInfoOptions, setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { addSRC5Component } from "./common-components"; +import { externalTrait } from "./external-trait"; +import { toByteArray } from "./utils/convert-strings"; +import { + RoyaltyInfoOptions, + setRoyaltyInfo, + defaults as royaltyInfoDefaults, +} from "./set-royalty-info"; export const defaults: Required = { - name: 'MyToken', - baseUri: '', + name: "MyToken", + baseUri: "", burnable: false, pausable: false, mintable: false, @@ -23,7 +35,7 @@ export const defaults: Required = { royaltyInfo: royaltyInfoDefaults, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC1155(opts: ERC1155Options = defaults): string { @@ -52,8 +64,16 @@ function withDefaults(opts: ERC1155Options): Required { }; } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.updatableUri !== false || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; +export function isAccessControlRequired( + opts: Partial +): boolean { + return ( + opts.mintable === true || + opts.pausable === true || + opts.updatableUri !== false || + opts.upgradeable === true || + opts.royaltyInfo?.enabled === true + ); } export function buildERC1155(opts: ERC1155Options): Contract { @@ -84,7 +104,7 @@ export function buildERC1155(opts: ERC1155Options): Contract { setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); - + addHooks(c, allOpts); return c; @@ -94,92 +114,112 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable; if (usesCustomHooks) { const hooksTrait = { - name: 'ERC1155HooksImpl', - of: 'ERC1155Component::ERC1155HooksTrait', + name: "ERC1155HooksImpl", + of: "ERC1155Component::ERC1155HooksTrait", tags: [], priority: 1, }; c.addImplementedTrait(hooksTrait); - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); c.addFunction(hooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: `ERC1155Component::ComponentState` }, - { name: 'from', type: 'ContractAddress' }, - { name: 'to', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, + { + name: "ref self", + type: `ERC1155Component::ComponentState`, + }, + { name: "from", type: "ContractAddress" }, + { name: "to", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, ], code: [ - 'let contract_state = self.get_contract()', - 'contract_state.pausable.assert_not_paused()', + "let contract_state = self.get_contract()", + "contract_state.pausable.assert_not_paused()", ], }); } else { - c.addUseClause('openzeppelin::token::erc1155', 'ERC1155HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc1155", "ERC1155HooksEmptyImpl"); } } function addERC1155Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC1155Component, { - name: 'ERC1155MixinImpl', - value: 'ERC1155Component::ERC1155MixinImpl', + name: "ERC1155MixinImpl", + value: "ERC1155Component::ERC1155MixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } function addBase(c: ContractBuilder, baseUri: string) { - c.addComponent( - components.ERC1155Component, - [ - baseUri, - ], - true, - ); + c.addComponent(components.ERC1155Component, [baseUri], true); } function addBurnable(c: ContractBuilder) { - c.addUseClause('starknet', 'ContractAddress'); - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("starknet", "ContractAddress"); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); c.addFunction(externalTrait, functions.batch_burn); c.addFunction(externalTrait, functions.batchBurn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); - requireAccessControl(c, externalTrait, functions.batch_mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.mint, + access, + "MINTER", + "minter" + ); + requireAccessControl( + c, + externalTrait, + functions.batch_mint, + access, + "MINTER", + "minter" + ); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. c.addFunction(externalTrait, functions.batchMint); } function addSetBaseUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, externalTrait, functions.set_base_uri, access, 'URI_SETTER', 'uri_setter'); + requireAccessControl( + c, + externalTrait, + functions.set_base_uri, + access, + "URI_SETTER", + "uri_setter" + ); // Camel case version of set_base_uri. Access control is already set on set_base_uri. c.addFunction(externalTrait, functions.setBaseUri); } -const components = defineComponents( { +const components = defineComponents({ ERC1155Component: { - path: 'openzeppelin::token::erc1155', + path: "openzeppelin::token::erc1155", substorage: { - name: 'erc1155', - type: 'ERC1155Component::Storage', + name: "erc1155", + type: "ERC1155Component::Storage", }, event: { - name: 'ERC1155Event', - type: 'ERC1155Component::Event', + name: "ERC1155Event", + type: "ERC1155Component::Event", }, - impls: [{ - name: 'ERC1155InternalImpl', - embed: false, - value: 'ERC1155Component::InternalImpl', - }], + impls: [ + { + name: "ERC1155InternalImpl", + embed: false, + value: "ERC1155Component::InternalImpl", + }, + ], }, }); @@ -187,96 +227,82 @@ const functions = defineFunctions({ burn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'value', type: 'u256' }, + { name: "account", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "value", type: "u256" }, ], code: [ - 'let caller = get_caller_address();', - 'if account != caller {', - ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', - '}', - 'self.erc1155.burn(account, token_id, value);' - ] + "let caller = get_caller_address();", + "if account != caller {", + " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", + "}", + "self.erc1155.burn(account, token_id, value);", + ], }, batch_burn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, ], code: [ - 'let caller = get_caller_address();', - 'if account != caller {', - ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', - '}', - 'self.erc1155.batch_burn(account, token_ids, values);' - ] + "let caller = get_caller_address();", + "if account != caller {", + " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", + "}", + "self.erc1155.batch_burn(account, token_ids, values);", + ], }, batchBurn: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'tokenIds', type: 'Span' }, - { name: 'values', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "tokenIds", type: "Span" }, + { name: "values", type: "Span" }, ], - code: [ - 'self.batch_burn(account, tokenIds, values);' - ] + code: ["self.batch_burn(account, tokenIds, values);"], }, mint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'value', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "value", type: "u256" }, + { name: "data", type: "Span" }, ], code: [ - 'self.erc1155.mint_with_acceptance_check(account, token_id, value, data);', - ] + "self.erc1155.mint_with_acceptance_check(account, token_id, value, data);", + ], }, batch_mint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'token_ids', type: 'Span' }, - { name: 'values', type: 'Span' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "token_ids", type: "Span" }, + { name: "values", type: "Span" }, + { name: "data", type: "Span" }, ], code: [ - 'self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);', - ] + "self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);", + ], }, batchMint: { args: [ getSelfArg(), - { name: 'account', type: 'ContractAddress' }, - { name: 'tokenIds', type: 'Span' }, - { name: 'values', type: 'Span' }, - { name: 'data', type: 'Span' }, + { name: "account", type: "ContractAddress" }, + { name: "tokenIds", type: "Span" }, + { name: "values", type: "Span" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.batch_mint(account, tokenIds, values, data);', - ] + code: ["self.batch_mint(account, tokenIds, values, data);"], }, set_base_uri: { - args: [ - getSelfArg(), - { name: 'base_uri', type: 'ByteArray' }, - ], - code: [ - 'self.erc1155._set_base_uri(base_uri);' - ] + args: [getSelfArg(), { name: "base_uri", type: "ByteArray" }], + code: ["self.erc1155._set_base_uri(base_uri);"], }, setBaseUri: { - args: [ - getSelfArg(), - { name: 'baseUri', type: 'ByteArray' }, - ], - code: [ - 'self.set_base_uri(baseUri);' - ] + args: [getSelfArg(), { name: "baseUri", type: "ByteArray" }], + code: ["self.set_base_uri(baseUri);"], }, }); diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 69f4cdd3d..479c598f4 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; +import test from "ava"; -import { buildERC20, ERC20Options, getInitialSupply } from './erc20'; -import { printContract } from './print'; +import { buildERC20, ERC20Options, getInitialSupply } from "./erc20"; +import { printContract } from "./print"; -import { erc20, OptionsError } from '.'; +import { erc20, OptionsError } from "."; function testERC20(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC20({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -20,170 +20,185 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, t => { - t.is(erc20.print(opts), printContract(buildERC20({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); + test(title, (t) => { + t.is( + erc20.print(opts), + printContract( + buildERC20({ + name: "MyToken", + symbol: "MTK", + ...opts, + }) + ) + ); }); } -testERC20('basic erc20, non-upgradeable', { +testERC20("basic erc20, non-upgradeable", { upgradeable: false, }); -testERC20('basic erc20', {}); +testERC20("basic erc20", {}); -testERC20('erc20 burnable', { +testERC20("erc20 burnable", { burnable: true, }); -testERC20('erc20 pausable', { +testERC20("erc20 pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 pausable with roles', { +testERC20("erc20 pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 burnable pausable', { +testERC20("erc20 burnable pausable", { burnable: true, pausable: true, }); -testERC20('erc20 preminted', { - premint: '1000', +testERC20("erc20 preminted", { + premint: "1000", }); -testERC20('erc20 premint of 0', { - premint: '0', +testERC20("erc20 premint of 0", { + premint: "0", }); -testERC20('erc20 mintable', { +testERC20("erc20 mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 mintable with roles', { +testERC20("erc20 mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 votes', { +testERC20("erc20 votes", { votes: true, - appName: 'MY_DAPP_NAME', + appName: "MY_DAPP_NAME", }); -testERC20('erc20 votes, version', { +testERC20("erc20 votes, version", { votes: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', -}); - -test('erc20 votes, no name', async t => { - let error = t.throws(() => buildERC20({ - name: 'MyToken', - symbol: 'MTK', - votes: true, - })); - t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); -}); - -test('erc20 votes, empty version', async t => { - let error = t.throws(() => buildERC20({ - name: 'MyToken', - symbol: 'MTK', - votes: true, - appName: 'MY_DAPP_NAME', - appVersion: '', // avoids default value of v1 - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -testERC20('erc20 votes, non-upgradeable', { + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", +}); + +test("erc20 votes, no name", async (t) => { + const error = t.throws(() => + buildERC20({ + name: "MyToken", + symbol: "MTK", + votes: true, + }) + ); + t.is( + (error as OptionsError).messages.appName, + "Application Name is required when Votes are enabled" + ); +}); + +test("erc20 votes, empty version", async (t) => { + const error = t.throws(() => + buildERC20({ + name: "MyToken", + symbol: "MTK", + votes: true, + appName: "MY_DAPP_NAME", + appVersion: "", // avoids default value of v1 + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +testERC20("erc20 votes, non-upgradeable", { votes: true, - appName: 'MY_DAPP_NAME', + appName: "MY_DAPP_NAME", upgradeable: false, }); -testERC20('erc20 full, non-upgradeable', { - premint: '2000', - access: 'ownable', +testERC20("erc20 full, non-upgradeable", { + premint: "2000", + access: "ownable", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: false, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testERC20('erc20 full upgradeable', { - premint: '2000', - access: 'ownable', +testERC20("erc20 full upgradeable", { + premint: "2000", + access: "ownable", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testERC20('erc20 full upgradeable with roles', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable with roles", { + premint: "2000", + access: "roles", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -testAPIEquivalence('erc20 API default'); +testAPIEquivalence("erc20 API default"); -testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('erc20 API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', - premint: '2000', - access: 'roles', +testAPIEquivalence("erc20 API full upgradeable", { + name: "CustomToken", + symbol: "CTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: "MY_DAPP_NAME", + appVersion: "MY_DAPP_VERSION", }); -test('erc20 API assert defaults', async t => { +test("erc20 API assert defaults", async (t) => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test('erc20 API isAccessControlRequired', async t => { +test("erc20 API isAccessControlRequired", async (t) => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); t.is(erc20.isAccessControlRequired({ upgradeable: true }), true); }); -test('erc20 getInitialSupply', async t => { - t.is(getInitialSupply('1000', 18), '1000000000000000000000'); - t.is(getInitialSupply('1000.1', 18), '1000100000000000000000'); - t.is(getInitialSupply('.1', 18), '100000000000000000'); - t.is(getInitialSupply('.01', 2), '1'); +test("erc20 getInitialSupply", async (t) => { + t.is(getInitialSupply("1000", 18), "1000000000000000000000"); + t.is(getInitialSupply("1000.1", 18), "1000100000000000000000"); + t.is(getInitialSupply(".1", 18), "100000000000000000"); + t.is(getInitialSupply(".01", 2), "1"); - let error = t.throws(() => getInitialSupply('.01', 1)); + let error = t.throws(() => getInitialSupply(".01", 1)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, 'Too many decimals'); + t.is((error as OptionsError).messages.premint, "Too many decimals"); - error = t.throws(() => getInitialSupply('1.1.1', 18)); + error = t.throws(() => getInitialSupply("1.1.1", 18)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, 'Not a valid number'); -}); \ No newline at end of file + t.is((error as OptionsError).messages.premint, "Not a valid number"); +}); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 57cfe8d86..8dbe4cd14 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -1,32 +1,39 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { OptionsError } from './error'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { externalTrait } from './external-trait'; -import { toByteArray, toFelt252, toUint } from './utils/convert-strings'; -import { addVotesComponent } from './common-components'; - +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { OptionsError } from "./error"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { externalTrait } from "./external-trait"; +import { toByteArray, toFelt252, toUint } from "./utils/convert-strings"; +import { addVotesComponent } from "./common-components"; export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", burnable: false, pausable: false, - premint: '0', + premint: "0", mintable: false, votes: false, - appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: 'v1', + appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: "v1", access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC20(opts: ERC20Options = defaults): string { @@ -55,12 +62,16 @@ function withDefaults(opts: ERC20Options): Required { mintable: opts.mintable ?? defaults.mintable, votes: opts.votes ?? defaults.votes, appName: opts.appName ?? defaults.appName, - appVersion: opts.appVersion ?? defaults.appVersion + appVersion: opts.appVersion ?? defaults.appVersion, }; } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; + return ( + opts.mintable === true || + opts.pausable === true || + opts.upgradeable === true + ); } export function buildERC20(opts: ERC20Options): Contract { @@ -100,8 +111,8 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable || allOpts.votes; if (usesCustomHooks) { const hooksTrait = { - name: 'ERC20HooksImpl', - of: 'ERC20Component::ERC20HooksTrait', + name: "ERC20HooksImpl", + of: "ERC20Component::ERC20HooksTrait", tags: [], priority: 1, }; @@ -109,99 +120,103 @@ function addHooks(c: ContractBuilder, allOpts: Required) { if (allOpts.pausable) { const beforeUpdateFn = c.addFunction(hooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: 'ERC20Component::ComponentState' }, - { name: 'from', type: 'ContractAddress' }, - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' }, + { + name: "ref self", + type: "ERC20Component::ComponentState", + }, + { name: "from", type: "ContractAddress" }, + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], code: [], }); beforeUpdateFn.code.push( - 'let contract_state = self.get_contract();', - 'contract_state.pausable.assert_not_paused();', + "let contract_state = self.get_contract();", + "contract_state.pausable.assert_not_paused();" ); } if (allOpts.votes) { if (!allOpts.appName) { throw new OptionsError({ - appName: 'Application Name is required when Votes are enabled', + appName: "Application Name is required when Votes are enabled", }); } if (!allOpts.appVersion) { throw new OptionsError({ - appVersion: 'Application Version is required when Votes are enabled', + appVersion: "Application Version is required when Votes are enabled", }); } addVotesComponent( c, - toFelt252(allOpts.appName, 'appName'), - toFelt252(allOpts.appVersion, 'appVersion'), - 'SNIP12 Metadata', + toFelt252(allOpts.appName, "appName"), + toFelt252(allOpts.appVersion, "appVersion"), + "SNIP12 Metadata" ); const afterUpdateFn = c.addFunction(hooksTrait, { - name: 'after_update', + name: "after_update", args: [ - { name: 'ref self', type: 'ERC20Component::ComponentState' }, - { name: 'from', type: 'ContractAddress' }, - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' }, + { + name: "ref self", + type: "ERC20Component::ComponentState", + }, + { name: "from", type: "ContractAddress" }, + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], code: [], }); afterUpdateFn.code.push( - 'let mut contract_state = self.get_contract_mut();', - 'contract_state.votes.transfer_voting_units(from, recipient, amount);', + "let mut contract_state = self.get_contract_mut();", + "contract_state.votes.transfer_voting_units(from, recipient, amount);" ); } } else { - c.addUseClause('openzeppelin::token::erc20', 'ERC20HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc20", "ERC20HooksEmptyImpl"); } } function addERC20Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC20Component, { - name: 'ERC20MixinImpl', - value: 'ERC20Component::ERC20MixinImpl', + name: "ERC20MixinImpl", + value: "ERC20Component::ERC20MixinImpl", }); } function addBase(c: ContractBuilder, name: string, symbol: string) { - c.addComponent( - components.ERC20Component, - [ - name, symbol - ], - true, - ); + c.addComponent(components.ERC20Component, [name, symbol], true); } function addBurnable(c: ContractBuilder) { - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); } export const premintPattern = /^(\d*\.?\d*)$/; function addPremint(c: ContractBuilder, amount: string) { - if (amount !== undefined && amount !== '0') { + if (amount !== undefined && amount !== "0") { if (!premintPattern.test(amount)) { throw new OptionsError({ - premint: 'Not a valid number', + premint: "Not a valid number", }); } - const premintAbsolute = toUint(getInitialSupply(amount, 18), 'premint', 'u256'); + const premintAbsolute = toUint( + getInitialSupply(amount, 18), + "premint", + "u256" + ); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name:'recipient', type:'ContractAddress' }); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: "recipient", type: "ContractAddress" }); c.addConstructorCode(`self.erc20.mint(recipient, ${premintAbsolute})`); } } @@ -219,76 +234,78 @@ export function getInitialSupply(premint: string, decimals: number): string { const premintSegments = premint.split("."); if (premintSegments.length > 2) { throw new OptionsError({ - premint: 'Not a valid number', + premint: "Not a valid number", }); } else { - let firstSegment = premintSegments[0] ?? ''; - let lastSegment = premintSegments[1] ?? ''; + const firstSegment = premintSegments[0] ?? ""; + let lastSegment = premintSegments[1] ?? ""; if (decimals > lastSegment.length) { try { lastSegment += "0".repeat(decimals - lastSegment.length); - } catch (e) { + } catch { // .repeat gives an error if decimals number is too large throw new OptionsError({ - premint: 'Decimals number too large', + premint: "Decimals number too large", }); } } else if (decimals < lastSegment.length) { throw new OptionsError({ - premint: 'Too many decimals', + premint: "Too many decimals", }); } // concat segments without leading zeros - result = firstSegment.concat(lastSegment).replace(/^0+/, ''); + result = firstSegment.concat(lastSegment).replace(/^0+/, ""); } if (result.length === 0) { - result = '0'; + result = "0"; } return result; } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.mint, + access, + "MINTER", + "minter" + ); } -const components = defineComponents( { +const components = defineComponents({ ERC20Component: { - path: 'openzeppelin::token::erc20', + path: "openzeppelin::token::erc20", substorage: { - name: 'erc20', - type: 'ERC20Component::Storage', + name: "erc20", + type: "ERC20Component::Storage", }, event: { - name: 'ERC20Event', - type: 'ERC20Component::Event', + name: "ERC20Event", + type: "ERC20Component::Event", }, - impls: [{ - name: 'ERC20InternalImpl', - embed: false, - value: 'ERC20Component::InternalImpl', - }], + impls: [ + { + name: "ERC20InternalImpl", + embed: false, + value: "ERC20Component::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ burn: { - args: [ - getSelfArg(), - { name: 'value', type: 'u256' } - ], - code: [ - 'self.erc20.burn(get_caller_address(), value);' - ] + args: [getSelfArg(), { name: "value", type: "u256" }], + code: ["self.erc20.burn(get_caller_address(), value);"], }, mint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'amount', type: 'u256' } + { name: "recipient", type: "ContractAddress" }, + { name: "amount", type: "u256" }, ], - code: [ - 'self.erc20.mint(recipient, amount);' - ] + code: ["self.erc20.mint(recipient, amount);"], }, }); diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index 6608368fa..c2819b953 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -1,18 +1,19 @@ -import test from 'ava'; +import test from "ava"; -import { buildERC721, ERC721Options } from './erc721'; -import { printContract } from './print'; -import { royaltyInfoOptions } from './set-royalty-info'; +import { buildERC721, ERC721Options } from "./erc721"; +import { printContract } from "./print"; +import { royaltyInfoOptions } from "./set-royalty-info"; -import { erc721, OptionsError } from '.'; +import { erc721, OptionsError } from "."; -const NAME = 'MyToken'; -const CUSTOM_NAME = 'CustomToken'; -const SYMBOL = 'MTK'; -const CUSTOM_SYMBOL = 'CTK'; -const APP_NAME = 'MY_DAPP_NAME'; -const APP_VERSION = 'MY_DAPP_VERSION'; -const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; +const NAME = "MyToken"; +const CUSTOM_NAME = "CustomToken"; +const SYMBOL = "MTK"; +const CUSTOM_SYMBOL = "CTK"; +const APP_NAME = "MY_DAPP_NAME"; +const APP_VERSION = "MY_DAPP_VERSION"; +const BASE_URI = + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; const allFeaturesON: Partial = { mintable: true, @@ -23,11 +24,11 @@ const allFeaturesON: Partial = { votes: true, appName: APP_NAME, appVersion: APP_VERSION, - upgradeable: true + upgradeable: true, } as const; function testERC721(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC721({ name: NAME, symbol: SYMBOL, @@ -40,152 +41,187 @@ function testERC721(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, t => { - t.is(erc721.print(opts), printContract(buildERC721({ - name: NAME, - symbol: SYMBOL, - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC721Options) { + test(title, (t) => { + t.is( + erc721.print(opts), + printContract( + buildERC721({ + name: NAME, + symbol: SYMBOL, + ...opts, + }) + ) + ); }); } -testERC721('basic non-upgradeable', { +testERC721("basic non-upgradeable", { upgradeable: false, }); -testERC721('basic', {}); +testERC721("basic", {}); -testERC721('base uri', { +testERC721("base uri", { baseUri: BASE_URI, }); -testERC721('burnable', { +testERC721("burnable", { burnable: true, }); -testERC721('pausable', { +testERC721("pausable", { pausable: true, }); -testERC721('mintable', { +testERC721("mintable", { mintable: true, }); -testERC721('enumerable', { +testERC721("enumerable", { enumerable: true, }); -testERC721('pausable + enumerable', { +testERC721("pausable + enumerable", { pausable: true, enumerable: true, }); -testERC721('mintable + roles', { +testERC721("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC721('royalty info disabled', { - royaltyInfo: royaltyInfoOptions.disabled +testERC721("royalty info disabled", { + royaltyInfo: royaltyInfoOptions.disabled, }); -testERC721('royalty info enabled default + ownable', { +testERC721("royalty info enabled default + ownable", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'ownable' + access: "ownable", }); -testERC721('royalty info enabled default + roles', { +testERC721("royalty info enabled default + roles", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'roles' + access: "roles", }); -testERC721('royalty info enabled custom + ownable', { +testERC721("royalty info enabled custom + ownable", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'ownable' + access: "ownable", }); -testERC721('royalty info enabled custom + roles', { +testERC721("royalty info enabled custom + roles", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'roles' + access: "roles", }); -testERC721('full non-upgradeable', { +testERC721("full non-upgradeable", { ...allFeaturesON, upgradeable: false, }); -testERC721('erc721 votes', { +testERC721("erc721 votes", { votes: true, appName: APP_NAME, }); -testERC721('erc721 votes, version', { +testERC721("erc721 votes, version", { votes: true, appName: APP_NAME, appVersion: APP_VERSION, }); -test('erc721 votes, no name', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - })); - t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); -}); - -test('erc721 votes, no version', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - appName: APP_NAME, - appVersion: '' - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -test('erc721 votes, empty version', async t => { - let error = t.throws(() => buildERC721({ - name: NAME, - symbol: SYMBOL, - votes: true, - appName: APP_NAME, - appVersion: '', // avoids default value of v1 - })); - t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); -}); - -testERC721('erc721 votes, non-upgradeable', { +test("erc721 votes, no name", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + }) + ); + t.is( + (error as OptionsError).messages.appName, + "Application Name is required when Votes are enabled" + ); +}); + +test("erc721 votes, no version", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + appName: APP_NAME, + appVersion: "", + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +test("erc721 votes, empty version", async (t) => { + const error = t.throws(() => + buildERC721({ + name: NAME, + symbol: SYMBOL, + votes: true, + appName: APP_NAME, + appVersion: "", // avoids default value of v1 + }) + ); + t.is( + (error as OptionsError).messages.appVersion, + "Application Version is required when Votes are enabled" + ); +}); + +testERC721("erc721 votes, non-upgradeable", { votes: true, appName: APP_NAME, upgradeable: false, }); -testERC721('full upgradeable', allFeaturesON); +testERC721("full upgradeable", allFeaturesON); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); +testAPIEquivalence("API basic", { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); -testAPIEquivalence('API full upgradeable', { +testAPIEquivalence("API full upgradeable", { ...allFeaturesON, name: CUSTOM_NAME, - symbol: CUSTOM_SYMBOL + symbol: CUSTOM_SYMBOL, }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); t.is(erc721.isAccessControlRequired({ upgradeable: true }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); - t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.disabled }), false); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledDefault, + }), + true + ); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledCustom, + }), + true + ); + t.is( + erc721.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.disabled, + }), + false + ); t.is(erc721.isAccessControlRequired({ burnable: true }), false); t.is(erc721.isAccessControlRequired({ enumerable: true }), false); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index 5be28487c..67867b287 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -1,64 +1,64 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import crypto from 'crypto'; - -import { generateERC20Options } from './erc20'; -import { generateERC721Options } from './erc721'; -import { generateERC1155Options } from './erc1155'; -import { generateAccountOptions } from './account'; -import { generateCustomOptions } from './custom'; -import { generateGovernorOptions } from './governor'; -import { generateVestingOptions } from './vesting'; -import { buildGeneric, GenericOptions, KindedOptions } from '../build-generic'; -import { printContract } from '../print'; -import { OptionsError } from '../error'; -import { findCover } from '../utils/find-cover'; -import type { Contract } from '../contract'; - -type Subset = 'all' | 'minimal-cover'; +import { promises as fs } from "fs"; +import path from "path"; +import crypto from "crypto"; + +import { generateERC20Options } from "./erc20"; +import { generateERC721Options } from "./erc721"; +import { generateERC1155Options } from "./erc1155"; +import { generateAccountOptions } from "./account"; +import { generateCustomOptions } from "./custom"; +import { generateGovernorOptions } from "./governor"; +import { generateVestingOptions } from "./vesting"; +import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; +import { printContract } from "../print"; +import { OptionsError } from "../error"; +import { findCover } from "../utils/find-cover"; +import type { Contract } from "../contract"; + +type Subset = "all" | "minimal-cover"; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === 'ERC20') { + if (!kind || kind === "ERC20") { for (const kindOpts of generateERC20Options()) { - yield { kind: 'ERC20', ...kindOpts }; + yield { kind: "ERC20", ...kindOpts }; } } - if (!kind || kind === 'ERC721') { + if (!kind || kind === "ERC721") { for (const kindOpts of generateERC721Options()) { - yield { kind: 'ERC721', ...kindOpts }; + yield { kind: "ERC721", ...kindOpts }; } } - if (!kind || kind === 'ERC1155') { + if (!kind || kind === "ERC1155") { for (const kindOpts of generateERC1155Options()) { - yield { kind: 'ERC1155', ...kindOpts }; + yield { kind: "ERC1155", ...kindOpts }; } } - if (!kind || kind === 'Account') { + if (!kind || kind === "Account") { for (const kindOpts of generateAccountOptions()) { - yield { kind: 'Account', ...kindOpts }; + yield { kind: "Account", ...kindOpts }; } } - if (!kind || kind === 'Custom') { + if (!kind || kind === "Custom") { for (const kindOpts of generateCustomOptions()) { - yield { kind: 'Custom', ...kindOpts }; + yield { kind: "Custom", ...kindOpts }; } } - if (!kind || kind === 'Governor') { + if (!kind || kind === "Governor") { for (const kindOpts of generateGovernorOptions()) { - yield { kind: 'Governor', ...kindOpts }; + yield { kind: "Governor", ...kindOpts }; } } - if (!kind || kind === 'Vesting') { + if (!kind || kind === "Vesting") { for (const kindOpts of generateVestingOptions()) { - yield { kind: 'Vesting', ...kindOpts }; + yield { kind: "Vesting", ...kindOpts }; } } } @@ -73,15 +73,18 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { +function generateContractSubset( + subset: Subset, + kind?: Kind +): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { const id = crypto - .createHash('sha1') + .createHash("sha1") .update(JSON.stringify(options)) .digest() - .toString('hex'); + .toString("hex"); try { const contract = buildGeneric(options); @@ -95,36 +98,48 @@ function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[ } } - if (subset === 'all') { + if (subset === "all") { return contracts; } else { - const getParents = (c: GeneratedContract) => c.contract.components.map(p => p.path); + const getParents = (c: GeneratedContract) => + c.contract.components.map((p) => p.path); function filterByUpgradeableSetTo(isUpgradeable: boolean) { return (c: GeneratedContract) => { switch (c.options.kind) { - case 'Vesting': + case "Vesting": return isUpgradeable === false; - case 'Account': - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Governor': - case 'Custom': + case "Account": + case "ERC20": + case "ERC721": + case "ERC1155": + case "Governor": + case "Custom": return c.options.upgradeable === isUpgradeable; - default: + default: { const _: never = c.options; - throw new Error('Unknown kind'); + throw new Error("Unknown kind"); + } } - } + }; } return [ - ...findCover(contracts.filter(filterByUpgradeableSetTo(true)), getParents), - ...findCover(contracts.filter(filterByUpgradeableSetTo(false)), getParents), + ...findCover( + contracts.filter(filterByUpgradeableSetTo(true)), + getParents + ), + ...findCover( + contracts.filter(filterByUpgradeableSetTo(false)), + getParents + ), ]; } } -export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Kind): Generator { +export function* generateSources( + subset: Subset, + uniqueName?: boolean, + kind?: Kind +): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { if (uniqueName) { @@ -135,13 +150,22 @@ export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Ki } } -export async function writeGeneratedSources(dir: string, subset: Subset, uniqueName?: boolean, kind?: Kind): Promise { +export async function writeGeneratedSources( + dir: string, + subset: Subset, + uniqueName?: boolean, + kind?: Kind +): Promise { await fs.mkdir(dir, { recursive: true }); - let contractNames = []; + const contractNames = []; - for (const { id, contract, source } of generateSources(subset, uniqueName, kind)) { + for (const { id, contract, source } of generateSources( + subset, + uniqueName, + kind + )) { const name = uniqueName ? contract.name : id; - await fs.writeFile(path.format({ dir, name, ext: '.cairo' }), source); + await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); contractNames.push(name); } diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index d946c1a26..71f5dec38 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -1,16 +1,27 @@ -import type { Contract, Component, Argument, Value, Impl, ContractFunction, ImplementedTrait, UseClause, } from './contract'; - -import { formatLines, spaceBetween, Lines } from './utils/format-lines'; -import { getSelfArg } from './common-options'; -import { compatibleContractsSemver } from './utils/version'; - -const DEFAULT_SECTION = '1. with no section'; -const STANDALONE_IMPORTS_GROUP = 'Standalone Imports'; +import type { + Contract, + Component, + Argument, + Value, + Impl, + ContractFunction, + ImplementedTrait, + UseClause, +} from "./contract"; + +import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; +import { getSelfArg } from "./common-options"; +import { compatibleContractsSemver } from "./utils/version"; + +const DEFAULT_SECTION = "1. with no section"; +const STANDALONE_IMPORTS_GROUP = "Standalone Imports"; const MAX_USE_CLAUSE_LINE_LENGTH = 90; const TAB = "\t"; export function printContract(contract: Contract): string { - const contractAttribute = contract.account ? '#[starknet::contract(account)]' : '#[starknet::contract]' + const contractAttribute = contract.account + ? "#[starknet::contract(account)]" + : "#[starknet::contract]"; return formatLines( ...spaceBetween( [ @@ -29,20 +40,24 @@ export function printContract(contract: Contract): string { printStorage(contract), printEvents(contract), printConstructor(contract), - printImplementedTraits(contract), + printImplementedTraits(contract) ), `}`, - ], - ), + ] + ) ); } function withSemicolons(lines: string[]): string[] { - return lines.map(line => line.endsWith(';') ? line : line + ';'); + return lines.map((line) => (line.endsWith(";") ? line : line + ";")); } function printSuperVariables(contract: Contract): string[] { - return withSemicolons(contract.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); + return withSemicolons( + contract.superVariables.map( + (v) => `const ${v.name}: ${v.type} = ${v.value}` + ) + ); } function printUseClauses(contract: Contract): Lines[] { @@ -50,30 +65,46 @@ function printUseClauses(contract: Contract): Lines[] { // group by containerPath const grouped = useClauses.reduce( - (result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => { + ( + result: { [containerPath: string]: UseClause[] }, + useClause: UseClause + ) => { if (useClause.groupable) { - (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause); + (result[useClause.containerPath] = + result[useClause.containerPath] || []).push(useClause); } else { - (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); + (result[STANDALONE_IMPORTS_GROUP] = + result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); } return result; - }, {}); + }, + {} + ); - const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName)); - return lines.flatMap(line => splitLongUseClauseLine(line.toString())); + const lines = Object.entries(grouped).flatMap(([groupName, group]) => + getLinesFromUseClausesGroup(group, groupName) + ); + return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); } -function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] { +function getLinesFromUseClausesGroup( + group: UseClause[], + groupName: string +): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { - lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`); + lines.push( + `use ${useClause.containerPath}::${nameWithAlias(useClause)};` + ); } } else { if (group.length == 1) { lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`); } else if (group.length > 1) { - let names = group.map((useClause) => nameWithAlias(useClause)).join(', '); + const names = group + .map((useClause) => nameWithAlias(useClause)) + .join(", "); lines.push(`use ${groupName}::{${names}};`); } } @@ -81,18 +112,20 @@ function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lin } function nameWithAlias(useClause: UseClause): string { - return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name; + return useClause.alias + ? `${useClause.name} as ${useClause.alias}` + : useClause.name; } // TODO: remove this when we can use a formatting js library function splitLongUseClauseLine(line: string): Lines[] { const lines = []; - const containsBraces = line.indexOf('{') !== -1; + const containsBraces = line.indexOf("{") !== -1; if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) { // split at the first brace - lines.push(line.slice(0, line.indexOf('{') + 1)); - lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2))); + lines.push(line.slice(0, line.indexOf("{") + 1)); + lines.push(...splitLongLineInner(line.slice(line.indexOf("{") + 1, -2))); lines.push("};"); } else { lines.push(line); @@ -104,7 +137,7 @@ function splitLongLineInner(line: string): Lines[] { const lines = []; if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) { const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH); - const lastCommaIndex = max_accessible_string.lastIndexOf(','); + const lastCommaIndex = max_accessible_string.lastIndexOf(","); if (lastCommaIndex !== -1) { lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1)); lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2))); @@ -134,11 +167,17 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value};` + ); } else if (commented) { - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}` + ); } else { - lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); + lines.push( + `const ${constant.name}: ${constant.type} = ${constant.value};` + ); } } return lines; @@ -147,35 +186,39 @@ function printConstants(contract: Contract): Lines[] { function printComponentDeclarations(contract: Contract): Lines[] { const lines = []; for (const component of contract.components) { - lines.push(`component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});`); + lines.push( + `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});` + ); } return lines; } function printImpls(contract: Contract): Lines[] { - const impls = contract.components.flatMap(c => c.impls); + const impls = contract.components.flatMap((c) => c.impls); // group by section const grouped = impls.reduce( - (result: { [section: string]: Impl[] }, current:Impl) => { + (result: { [section: string]: Impl[] }, current: Impl) => { // default section depends on embed // embed defaults to true const embed = current.embed ?? true; - const section = current.section ?? (embed ? 'External' : 'Internal'); + const section = current.section ?? (embed ? "External" : "Internal"); (result[section] = result[section] || []).push(current); return result; - }, {}); - - const sections = Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])).map( - ([section, impls]) => printSection(section, impls as Impl[]), + }, + {} ); + + const sections = Object.entries(grouped) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([section, impls]) => printSection(section, impls as Impl[])); return spaceBetween(...sections); } function printSection(section: string, impls: Impl[]): Lines[] { const lines = []; lines.push(`// ${section}`); - impls.map(impl => lines.push(...printImpl(impl))); + impls.map((impl) => lines.push(...printImpl(impl))); return lines; } @@ -183,7 +226,7 @@ function printImpl(impl: Impl): Lines[] { const lines = []; // embed is optional, default to true if (impl.embed ?? true) { - lines.push('#[abi(embed_v0)]'); + lines.push("#[abi(embed_v0)]"); } lines.push(`impl ${impl.name} = ${impl.value};`); return lines; @@ -192,31 +235,33 @@ function printImpl(impl: Impl): Lines[] { function printStorage(contract: Contract): (string | string[])[] { const lines = []; // storage is required regardless of whether there are components - lines.push('#[storage]'); - lines.push('struct Storage {'); + lines.push("#[storage]"); + lines.push("struct Storage {"); const storageLines = []; for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); - storageLines.push(`${component.substorage.name}: ${component.substorage.type},`); + storageLines.push( + `${component.substorage.name}: ${component.substorage.type},` + ); } lines.push(storageLines); - lines.push('}'); + lines.push("}"); return lines; } function printEvents(contract: Contract): (string | string[])[] { const lines = []; if (contract.components.length > 0) { - lines.push('#[event]'); - lines.push('#[derive(Drop, starknet::Event)]'); - lines.push('enum Event {') + lines.push("#[event]"); + lines.push("#[derive(Drop, starknet::Event)]"); + lines.push("enum Event {"); const eventLines = []; for (const component of contract.components) { - eventLines.push('#[flat]'); + eventLines.push("#[flat]"); eventLines.push(`${component.event.name}: ${component.event.type},`); } lines.push(eventLines); - lines.push('}'); + lines.push("}"); } return lines; } @@ -235,31 +280,41 @@ function printImplementedTraits(contract: Contract): Lines[] { // group by section const grouped = sortedTraits.reduce( - (result: { [section: string]: ImplementedTrait[] }, current:ImplementedTrait) => { + ( + result: { [section: string]: ImplementedTrait[] }, + current: ImplementedTrait + ) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); return result; - }, {}); - - const sections = Object.entries(grouped).sort((a, b) => a[0].localeCompare(b[0])).map( - ([section, impls]) => printImplementedTraitsSection(section, impls as ImplementedTrait[]), + }, + {} ); + const sections = Object.entries(grouped) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([section, impls]) => + printImplementedTraitsSection(section, impls as ImplementedTrait[]) + ); + return spaceBetween(...sections); } -function printImplementedTraitsSection(section: string, impls: ImplementedTrait[]): Lines[] { +function printImplementedTraitsSection( + section: string, + impls: ImplementedTrait[] +): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; if (!isDefaultSection) { - lines.push('//'); + lines.push("//"); lines.push(`// ${section}`); - lines.push('//'); + lines.push("//"); } impls.forEach((trait, index) => { if (index > 0 || !isDefaultSection) { - lines.push(''); + lines.push(""); } lines.push(...printImplementedTrait(trait)); }); @@ -268,32 +323,36 @@ function printImplementedTraitsSection(section: string, impls: ImplementedTrait[ function printImplementedTrait(trait: ImplementedTrait): Lines[] { const implLines = []; - implLines.push(...trait.tags.map(t => `#[${t}]`)); + implLines.push(...trait.tags.map((t) => `#[${t}]`)); implLines.push(`impl ${trait.name} of ${trait.of} {`); const superVars = withSemicolons( - trait.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`) + trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`) ); implLines.push(superVars); - const fns = trait.functions.map(fn => printFunction(fn)); + const fns = trait.functions.map((fn) => printFunction(fn)); implLines.push(spaceBetween(...fns)); - implLines.push('}'); + implLines.push("}"); return implLines; } function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; - const args = fn.args.map(a => printArgument(a)); + const args = fn.args.map((a) => printArgument(a)); const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code; for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; - const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; + const shouldEndWithSemicolon = + i < codeLines.length - 1 || fn.returns === undefined; if (line !== undefined && line.length > 0) { - if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { - codeLines[i] += ';'; - } else if (!shouldEndWithSemicolon && line.endsWith(';')) { + if ( + shouldEndWithSemicolon && + !["{", "}", ";"].includes(line.charAt(line.length - 1)) + ) { + codeLines[i] += ";"; + } else if (!shouldEndWithSemicolon && line.endsWith(";")) { codeLines[i] = line.slice(0, line.length - 1); } } @@ -303,28 +362,30 @@ function printFunction(fn: ContractFunction): Lines[] { } function printConstructor(contract: Contract): Lines[] { - const hasInitializers = contract.components.some(p => p.initializer !== undefined); + const hasInitializers = contract.components.some( + (p) => p.initializer !== undefined + ); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { const parents = contract.components .filter(hasInitializer) - .flatMap(p => printParentConstructor(p)); - const tag = 'constructor'; - const head = 'fn constructor'; - const args = [ getSelfArg(), ...contract.constructorArgs ]; + .flatMap((p) => printParentConstructor(p)); + const tag = "constructor"; + const head = "fn constructor"; + const args = [getSelfArg(), ...contract.constructorArgs]; const body = spaceBetween( - withSemicolons(parents), - withSemicolons(contract.constructorCode), - ); + withSemicolons(parents), + withSemicolons(contract.constructorCode) + ); const constructor = printFunction2( head, - args.map(a => printArgument(a)), + args.map((a) => printArgument(a)), tag, undefined, undefined, - body, + body ); return constructor; } else { @@ -333,37 +394,40 @@ function printConstructor(contract: Contract): Lines[] { } function hasInitializer(parent: Component): boolean { - return parent.initializer !== undefined && parent.substorage?.name !== undefined; + return ( + parent.initializer !== undefined && parent.substorage?.name !== undefined + ); } -function printParentConstructor({ substorage, initializer }: Component): [] | [string] { +function printParentConstructor({ + substorage, + initializer, +}: Component): [] | [string] { if (initializer === undefined || substorage?.name === undefined) { return []; } const fn = `self.${substorage.name}.initializer`; - return [ - fn + '(' + initializer.params.map(printValue).join(', ') + ')', - ]; + return [fn + "(" + initializer.params.map(printValue).join(", ") + ")"]; } export function printValue(value: Value): string { - if (typeof value === 'object') { - if ('lit' in value) { + if (typeof value === "object") { + if ("lit" in value) { return value.lit; - } else if ('note' in value) { + } else if ("note" in value) { // TODO: add /* ${value.note} */ after lsp is fixed return `${printValue(value.value)}`; } else { - throw Error('Unknown value type'); + throw Error("Unknown value type"); } - } else if (typeof value === 'number') { + } else if (typeof value === "number") { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { throw new Error(`Number not representable (${value})`); } - } else if (typeof value === 'bigint') { - return `${value}` + } else if (typeof value === "bigint") { + return `${value}`; } else { return `"${value}"`; } @@ -388,20 +452,20 @@ function printFunction2( let accum = `${kindedName}(`; if (args.length > 0) { - const formattedArgs = args.join(', '); + const formattedArgs = args.join(", "); if (formattedArgs.length > 80) { fn.push(accum); - accum = ''; + accum = ""; // print each arg in a separate line - fn.push(args.map(arg => `${arg},`)); + fn.push(args.map((arg) => `${arg},`)); } else { accum += `${formattedArgs}`; } } - accum += ')'; + accum += ")"; if (returns === undefined) { - accum += ' {'; + accum += " {"; } else { accum += ` -> ${returns} {`; } @@ -411,7 +475,7 @@ function printFunction2( if (returnLine !== undefined) { fn.push([returnLine]); } - fn.push('}'); + fn.push("}"); return fn; } diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index 022e74759..53a626d05 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -1,15 +1,24 @@ -import { promises as fs } from 'fs'; -import path from 'path'; +import { promises as fs } from "fs"; +import path from "path"; -import { writeGeneratedSources } from '../generate/sources'; -import { contractsVersion, edition, cairoVersion, scarbVersion } from '../utils/version'; +import { writeGeneratedSources } from "../generate/sources"; +import { + contractsVersion, + edition, + cairoVersion, + scarbVersion, +} from "../utils/version"; export async function updateScarbProject() { - const generatedSourcesPath = path.join('test_project', 'src'); + const generatedSourcesPath = path.join("test_project", "src"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); // Generate the contracts source code - const contractNames = await writeGeneratedSources(generatedSourcesPath, 'all', true); + const contractNames = await writeGeneratedSources( + generatedSourcesPath, + "all", + true + ); // Generate lib.cairo file writeLibCairo(contractNames); @@ -19,23 +28,32 @@ export async function updateScarbProject() { } async function writeLibCairo(contractNames: string[]) { - const libCairoPath = path.join('test_project/src', 'lib.cairo'); - const libCairo = contractNames.map(name => `pub mod ${name};\n`).join(''); + const libCairoPath = path.join("test_project/src", "lib.cairo"); + const libCairo = contractNames.map((name) => `pub mod ${name};\n`).join(""); await fs.writeFile(libCairoPath, libCairo); } async function updateScarbToml() { - const scarbTomlPath = path.join('test_project', 'Scarb.toml'); + const scarbTomlPath = path.join("test_project", "Scarb.toml"); - let currentContent = await fs.readFile(scarbTomlPath, 'utf8'); - let updatedContent = currentContent + const currentContent = await fs.readFile(scarbTomlPath, "utf8"); + const updatedContent = currentContent .replace(/edition = "\w+"/, `edition = "${edition}"`) - .replace(/cairo-version = "\d+\.\d+\.\d+"/, `cairo-version = "${cairoVersion}"`) - .replace(/scarb-version = "\d+\.\d+\.\d+"/, `scarb-version = "${scarbVersion}"`) + .replace( + /cairo-version = "\d+\.\d+\.\d+"/, + `cairo-version = "${cairoVersion}"` + ) + .replace( + /scarb-version = "\d+\.\d+\.\d+"/, + `scarb-version = "${scarbVersion}"` + ) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) - .replace(/openzeppelin = "\d+\.\d+\.\d+"/, `openzeppelin = "${contractsVersion}"`); + .replace( + /openzeppelin = "\d+\.\d+\.\d+"/, + `openzeppelin = "${contractsVersion}"` + ); - await fs.writeFile(scarbTomlPath, updatedContent, 'utf8'); + await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); } updateScarbProject(); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index 967e17ece..e34e31247 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -1,88 +1,101 @@ -import { promises as fs } from 'fs'; -import os from 'os'; -import _test, { TestFn, ExecutionContext } from 'ava'; -import path from 'path'; +import { promises as fs } from "fs"; +import os from "os"; +import _test, { TestFn, ExecutionContext } from "ava"; +import path from "path"; -import { generateSources, writeGeneratedSources } from './generate/sources'; -import type { GenericOptions, KindedOptions } from './build-generic'; -import { custom, erc20, erc721, erc1155 } from './api'; +import { generateSources, writeGeneratedSources } from "./generate/sources"; +import type { GenericOptions, KindedOptions } from "./build-generic"; +import { custom, erc20, erc721, erc1155 } from "./api"; interface Context { - generatedSourcesPath: string + generatedSourcesPath: string; } const test = _test as TestFn; -test.serial('erc20 result generated', async t => { - await testGenerate(t, 'ERC20'); +test.serial("erc20 result generated", async (t) => { + await testGenerate(t, "ERC20"); }); -test.serial('erc721 result generated', async t => { - await testGenerate(t, 'ERC721'); +test.serial("erc721 result generated", async (t) => { + await testGenerate(t, "ERC721"); }); -test.serial('erc1155 result generated', async t => { - await testGenerate(t, 'ERC1155'); +test.serial("erc1155 result generated", async (t) => { + await testGenerate(t, "ERC1155"); }); -test.serial('account result generated', async t => { - await testGenerate(t, 'Account'); +test.serial("account result generated", async (t) => { + await testGenerate(t, "Account"); }); -test.serial('custom result generated', async t => { - await testGenerate(t, 'Custom'); +test.serial("custom result generated", async (t) => { + await testGenerate(t, "Custom"); }); -async function testGenerate(t: ExecutionContext, kind: keyof KindedOptions) { - const generatedSourcesPath = path.join(os.tmpdir(), 'oz-wizard-cairo'); +async function testGenerate( + t: ExecutionContext, + kind: keyof KindedOptions +) { + const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, 'all', true, kind); + await writeGeneratedSources(generatedSourcesPath, "all", true, kind); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { - switch(opts.kind) { - case 'ERC20': + switch (opts.kind) { + case "ERC20": return erc20.isAccessControlRequired(opts); - case 'ERC721': + case "ERC721": return erc721.isAccessControlRequired(opts); - case 'ERC1155': + case "ERC1155": return erc1155.isAccessControlRequired(opts); - case 'Account': + case "Account": throw new Error("Not applicable for accounts"); - case 'Custom': + case "Custom": return custom.isAccessControlRequired(opts); default: throw new Error("No such kind"); } } -test('is access control required', async t => { - for (const contract of generateSources('all')) { - const regexOwnable = /(use openzeppelin::access::ownable::OwnableComponent)/gm; +test("is access control required", async (t) => { + for (const contract of generateSources("all")) { + const regexOwnable = + /(use openzeppelin::access::ownable::OwnableComponent)/gm; switch (contract.options.kind) { - case 'Account': - case 'Governor': - case 'Vesting': + case "Account": + case "Governor": + case "Vesting": // These contracts have no access control option break; - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Custom': + case "ERC20": + case "ERC721": + case "ERC1155": + case "Custom": if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.regex( + contract.source, + regexOwnable, + JSON.stringify(contract.options) + ); } else { - t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.notRegex( + contract.source, + regexOwnable, + JSON.stringify(contract.options) + ); } } break; - default: + default: { const _: never = contract.options; - throw new Error('Unknown kind'); + throw new Error("Unknown kind"); + } } } }); diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index 8a1421343..2bab75c9d 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -1,99 +1,119 @@ -import test from 'ava'; +import test from "ava"; -import { toIdentifier, toByteArray, toFelt252 } from './convert-strings'; -import { OptionsError } from '../error'; +import { toIdentifier, toByteArray, toFelt252 } from "./convert-strings"; +import { OptionsError } from "../error"; -test('identifier - unmodified', t => { - t.is(toIdentifier('abc'), 'abc'); +test("identifier - unmodified", (t) => { + t.is(toIdentifier("abc"), "abc"); }); -test('identifier - remove leading specials', t => { - t.is(toIdentifier('--abc'), 'abc'); +test("identifier - remove leading specials", (t) => { + t.is(toIdentifier("--abc"), "abc"); }); -test('identifier - remove specials and upcase next char', t => { - t.is(toIdentifier('abc-def'), 'abcDef'); - t.is(toIdentifier('abc--def'), 'abcDef'); +test("identifier - remove specials and upcase next char", (t) => { + t.is(toIdentifier("abc-def"), "abcDef"); + t.is(toIdentifier("abc--def"), "abcDef"); }); -test('identifier - capitalize', t => { - t.is(toIdentifier('abc', true), 'Abc'); +test("identifier - capitalize", (t) => { + t.is(toIdentifier("abc", true), "Abc"); }); -test('identifier - remove accents', t => { - t.is(toIdentifier('ábc'), 'abc'); +test("identifier - remove accents", (t) => { + t.is(toIdentifier("ábc"), "abc"); }); -test('identifier - underscores', t => { - t.is(toIdentifier('_abc_'), '_abc_'); +test("identifier - underscores", (t) => { + t.is(toIdentifier("_abc_"), "_abc_"); }); -test('identifier - remove starting numbers', t => { - t.is(toIdentifier('123abc456'), 'abc456'); +test("identifier - remove starting numbers", (t) => { + t.is(toIdentifier("123abc456"), "abc456"); }); -test('identifier - empty string', t => { - let error = t.throws(() => toIdentifier(''), { instanceOf: OptionsError }); - t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); +test("identifier - empty string", (t) => { + const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); + t.is( + error.messages.name, + "Identifier is empty or does not have valid characters" + ); }); -test('identifier - no valid chars', t => { - let error = t.throws(() => toIdentifier('123'), { instanceOf: OptionsError }); - t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); +test("identifier - no valid chars", (t) => { + const error = t.throws(() => toIdentifier("123"), { + instanceOf: OptionsError, + }); + t.is( + error.messages.name, + "Identifier is empty or does not have valid characters" + ); }); -test('toByteArray - unmodified', t => { - t.is(toByteArray('abc'), 'abc'); +test("toByteArray - unmodified", (t) => { + t.is(toByteArray("abc"), "abc"); }); -test('toByteArray - remove accents', t => { - t.is(toByteArray('ábc'), 'abc'); +test("toByteArray - remove accents", (t) => { + t.is(toByteArray("ábc"), "abc"); }); -test('toByteArray - remove non-ascii-printable characters', t => { - t.is(toByteArray('abc😀'), 'abc'); +test("toByteArray - remove non-ascii-printable characters", (t) => { + t.is(toByteArray("abc😀"), "abc"); }); -test('toByteArray - escape double quote', t => { - t.is(toByteArray("abc\"def"), "abc\\\"def"); +test("toByteArray - escape double quote", (t) => { + t.is(toByteArray('abc"def'), 'abc\\"def'); }); -test('toByteArray - does not escape single quote', t => { +test("toByteArray - does not escape single quote", (t) => { t.is(toByteArray("abc'def"), "abc'def"); }); -test('toByteArray - escape backslash', t => { - t.is(toByteArray('abc\\def'), 'abc\\\\def'); +test("toByteArray - escape backslash", (t) => { + t.is(toByteArray("abc\\def"), "abc\\\\def"); }); -test('more than 31 characters', t => { - t.is(toByteArray('A234567890123456789012345678901'), 'A234567890123456789012345678901'); - t.is(toByteArray('A2345678901234567890123456789012'), 'A2345678901234567890123456789012'); +test("more than 31 characters", (t) => { + t.is( + toByteArray("A234567890123456789012345678901"), + "A234567890123456789012345678901" + ); + t.is( + toByteArray("A2345678901234567890123456789012"), + "A2345678901234567890123456789012" + ); }); -test('toFelt252 - unmodified', t => { - t.is(toFelt252('abc', 'foo'), 'abc'); +test("toFelt252 - unmodified", (t) => { + t.is(toFelt252("abc", "foo"), "abc"); }); -test('toFelt252 - remove accents', t => { - t.is(toFelt252('ábc', 'foo'), 'abc'); +test("toFelt252 - remove accents", (t) => { + t.is(toFelt252("ábc", "foo"), "abc"); }); -test('toFelt252 - remove non-ascii-printable characters', t => { - t.is(toFelt252('abc😀', 'foo'), 'abc'); +test("toFelt252 - remove non-ascii-printable characters", (t) => { + t.is(toFelt252("abc😀", "foo"), "abc"); }); -test('toFelt252 - escape single quote', t => { - t.is(toFelt252("abc'def", 'foo'), "abc\\'def"); +test("toFelt252 - escape single quote", (t) => { + t.is(toFelt252("abc'def", "foo"), "abc\\'def"); }); -test('toFelt252 - escape backslash', t => { - t.is(toFelt252('abc\\def', 'foo'), 'abc\\\\def'); +test("toFelt252 - escape backslash", (t) => { + t.is(toFelt252("abc\\def", "foo"), "abc\\\\def"); }); -test('toFelt252 - max 31 characters', t => { - t.is(toFelt252('A234567890123456789012345678901', 'foo'), 'A234567890123456789012345678901'); +test("toFelt252 - max 31 characters", (t) => { + t.is( + toFelt252("A234567890123456789012345678901", "foo"), + "A234567890123456789012345678901" + ); - let error = t.throws(() => toFelt252('A2345678901234567890123456789012', 'foo'), { instanceOf: OptionsError }); - t.is(error.messages.foo, 'String is longer than 31 characters'); -}); \ No newline at end of file + const error = t.throws( + () => toFelt252("A2345678901234567890123456789012", "foo"), + { instanceOf: OptionsError } + ); + t.is(error.messages.foo, "String is longer than 31 characters"); +}); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 51c6512f2..372fde3b2 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -1,23 +1,23 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { contractDefaults as commonDefaults } from './common-options'; -import { setAccessControl } from './set-access-control'; -import { setUpgradeable } from './set-upgradeable'; -import { Info, setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { printContract } from './print'; -import { OptionsError } from './error'; -import { durationToTimestamp } from './utils/duration'; -import { toUint, validateUint } from './utils/convert-strings'; - -export type VestingSchedule = 'linear' | 'custom'; +import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { setAccessControl } from "./set-access-control"; +import { setUpgradeable } from "./set-upgradeable"; +import { Info, setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { printContract } from "./print"; +import { OptionsError } from "./error"; +import { durationToTimestamp } from "./utils/duration"; +import { toUint, validateUint } from "./utils/convert-strings"; + +export type VestingSchedule = "linear" | "custom"; export const defaults: Required = { - name: 'VestingWallet', - startDate: '', - duration: '0 day', - cliffDuration: '0 day', - schedule: 'custom', - info: commonDefaults.info + name: "VestingWallet", + startDate: "", + duration: "0 day", + cliffDuration: "0 day", + schedule: "custom", + info: commonDefaults.info, } as const; export function printVesting(opts: VestingOptions = defaults): string { @@ -40,7 +40,7 @@ function withDefaults(opts: VestingOptions): Required { duration: opts.duration ?? defaults.duration, cliffDuration: opts.cliffDuration ?? defaults.cliffDuration, schedule: opts.schedule ?? defaults.schedule, - info: opts.info ?? defaults.info + info: opts.info ?? defaults.info, }; } @@ -53,7 +53,7 @@ export function buildVesting(opts: VestingOptions): Contract { setInfo(c, allOpts.info); // Vesting component depends on Ownable component - const access = 'ownable'; + const access = "ownable"; setAccessControl(c, access); // Must be non-upgradable to guarantee vesting according to the schedule @@ -63,97 +63,107 @@ export function buildVesting(opts: VestingOptions): Contract { } function addBase(c: ContractBuilder, opts: VestingOptions) { - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); const startDate = getVestingStart(opts); const totalDuration = getVestingDuration(opts); const cliffDuration = getCliffDuration(opts); validateDurations(totalDuration, cliffDuration); if (startDate !== undefined) { c.addConstant({ - name: 'START', - type: 'u64', + name: "START", + type: "u64", value: startDate.timestampInSec.toString(), comment: startDate.formattedDate, - inlineComment: true + inlineComment: true, }); } else { c.addConstant({ - name: 'START', - type: 'u64', - value: '0' + name: "START", + type: "u64", + value: "0", }); } c.addConstant({ - name: 'DURATION', - type: 'u64', + name: "DURATION", + type: "u64", value: totalDuration.toString(), comment: opts.duration, - inlineComment: true + inlineComment: true, }); c.addConstant({ - name: 'CLIFF_DURATION', - type: 'u64', + name: "CLIFF_DURATION", + type: "u64", value: cliffDuration.toString(), comment: opts.cliffDuration, - inlineComment: true + inlineComment: true, }); - const initParams = [{ lit: 'START' }, { lit: 'DURATION' }, { lit: 'CLIFF_DURATION' }]; + const initParams = [ + { lit: "START" }, + { lit: "DURATION" }, + { lit: "CLIFF_DURATION" }, + ]; c.addComponent(components.VestingComponent, initParams, true); } function addSchedule(c: ContractBuilder, opts: VestingOptions) { switch (opts.schedule) { - case 'linear': - c.addUseClause('openzeppelin::finance::vesting', 'LinearVestingSchedule'); + case "linear": + c.addUseClause("openzeppelin::finance::vesting", "LinearVestingSchedule"); return; - case 'custom': + case "custom": { const scheduleTrait: BaseImplementedTrait = { name: `VestingSchedule`, - of: 'VestingComponent::VestingScheduleTrait', + of: "VestingComponent::VestingScheduleTrait", tags: [], priority: 0, }; c.addImplementedTrait(scheduleTrait); c.addFunction(scheduleTrait, { - name: 'calculate_vested_amount', - returns: 'u256', + name: "calculate_vested_amount", + returns: "u256", args: [ - { name: 'self', type: `@VestingComponent::ComponentState` }, - { name: 'token', type: 'ContractAddress' }, - { name: 'total_allocation', type: 'u256' }, - { name: 'timestamp', type: 'u64' }, - { name: 'start', type: 'u64' }, - { name: 'duration', type: 'u64' }, - { name: 'cliff', type: 'u64' } + { + name: "self", + type: `@VestingComponent::ComponentState`, + }, + { name: "token", type: "ContractAddress" }, + { name: "total_allocation", type: "u256" }, + { name: "timestamp", type: "u64" }, + { name: "start", type: "u64" }, + { name: "duration", type: "u64" }, + { name: "cliff", type: "u64" }, ], code: [ - '// TODO: Must be implemented according to the desired vesting schedule', - '0', + "// TODO: Must be implemented according to the desired vesting schedule", + "0", ], }); return; + } } } -function getVestingStart(opts: VestingOptions): { timestampInSec: bigint, formattedDate: string } | undefined { - if (opts.startDate === '' || opts.startDate === 'NaN') { +function getVestingStart( + opts: VestingOptions +): { timestampInSec: bigint; formattedDate: string } | undefined { + if (opts.startDate === "" || opts.startDate === "NaN") { return undefined; } const startDate = new Date(`${opts.startDate}Z`); const timestampInMillis = startDate.getTime(); const timestampInSec = toUint( Math.floor(timestampInMillis / 1000), - 'startDate', - 'u64' + "startDate", + "u64" ); - const formattedDate = startDate.toLocaleString('en-GB', { - day: '2-digit', - month: 'short', - year: 'numeric', - hour: '2-digit', - minute: '2-digit', + const formattedDate = startDate.toLocaleString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", hour12: false, - timeZone: 'UTC' + timeZone: "UTC", }); return { timestampInSec, formattedDate: `${formattedDate} (UTC)` }; } @@ -164,7 +174,7 @@ function getVestingDuration(opts: VestingOptions): number { } catch (e) { if (e instanceof Error) { throw new OptionsError({ - duration: e.message + duration: e.message, }); } else { throw e; @@ -178,7 +188,7 @@ function getCliffDuration(opts: VestingOptions): number { } catch (e) { if (e instanceof Error) { throw new OptionsError({ - cliffDuration: e.message + cliffDuration: e.message, }); } else { throw e; @@ -187,33 +197,36 @@ function getCliffDuration(opts: VestingOptions): number { } function validateDurations(duration: number, cliffDuration: number): void { - validateUint(duration, 'duration', 'u64'); - validateUint(cliffDuration, 'cliffDuration', 'u64'); + validateUint(duration, "duration", "u64"); + validateUint(cliffDuration, "cliffDuration", "u64"); if (cliffDuration > duration) { throw new OptionsError({ - cliffDuration: `Cliff duration must be less than or equal to the total duration` + cliffDuration: `Cliff duration must be less than or equal to the total duration`, }); } } const components = defineComponents({ VestingComponent: { - path: 'openzeppelin::finance::vesting', + path: "openzeppelin::finance::vesting", substorage: { - name: 'vesting', - type: 'VestingComponent::Storage', + name: "vesting", + type: "VestingComponent::Storage", }, event: { - name: 'VestingEvent', - type: 'VestingComponent::Event', + name: "VestingEvent", + type: "VestingComponent::Event", }, - impls: [{ - name: 'VestingImpl', - value: 'VestingComponent::VestingImpl' - }, { - name: 'VestingInternalImpl', - embed: false, - value: 'VestingComponent::InternalImpl', - }], - } + impls: [ + { + name: "VestingImpl", + value: "VestingComponent::VestingImpl", + }, + { + name: "VestingInternalImpl", + embed: false, + value: "VestingComponent::InternalImpl", + }, + ], + }, }); diff --git a/packages/core/solidity/src/build-generic.ts b/packages/core/solidity/src/build-generic.ts index e0b9084da..8487be2e3 100644 --- a/packages/core/solidity/src/build-generic.ts +++ b/packages/core/solidity/src/build-generic.ts @@ -1,48 +1,49 @@ -import { CustomOptions, buildCustom } from './custom'; -import { ERC20Options, buildERC20 } from './erc20'; -import { ERC721Options, buildERC721 } from './erc721'; -import { ERC1155Options, buildERC1155 } from './erc1155'; -import { StablecoinOptions, buildStablecoin } from './stablecoin'; -import { GovernorOptions, buildGovernor } from './governor'; -import { Contract } from './contract'; +import { CustomOptions, buildCustom } from "./custom"; +import { ERC20Options, buildERC20 } from "./erc20"; +import { ERC721Options, buildERC721 } from "./erc721"; +import { ERC1155Options, buildERC1155 } from "./erc1155"; +import { StablecoinOptions, buildStablecoin } from "./stablecoin"; +import { GovernorOptions, buildGovernor } from "./governor"; +import { Contract } from "./contract"; export interface KindedOptions { - ERC20: { kind: 'ERC20' } & ERC20Options; - ERC721: { kind: 'ERC721' } & ERC721Options; - ERC1155: { kind: 'ERC1155' } & ERC1155Options; - Stablecoin: { kind: 'Stablecoin' } & StablecoinOptions; - RealWorldAsset: { kind: 'RealWorldAsset' } & StablecoinOptions; - Governor: { kind: 'Governor' } & GovernorOptions; - Custom: { kind: 'Custom' } & CustomOptions; + ERC20: { kind: "ERC20" } & ERC20Options; + ERC721: { kind: "ERC721" } & ERC721Options; + ERC1155: { kind: "ERC1155" } & ERC1155Options; + Stablecoin: { kind: "Stablecoin" } & StablecoinOptions; + RealWorldAsset: { kind: "RealWorldAsset" } & StablecoinOptions; + Governor: { kind: "Governor" } & GovernorOptions; + Custom: { kind: "Custom" } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions): Contract { switch (opts.kind) { - case 'ERC20': + case "ERC20": return buildERC20(opts); - case 'ERC721': + case "ERC721": return buildERC721(opts); - case 'ERC1155': + case "ERC1155": return buildERC1155(opts); - case 'Stablecoin': + case "Stablecoin": return buildStablecoin(opts); - case 'RealWorldAsset': + case "RealWorldAsset": return buildStablecoin(opts); - case 'Governor': + case "Governor": return buildGovernor(opts); - case 'Custom': + case "Custom": return buildCustom(opts); - default: + default: { const _: never = opts; - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); + } } } diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index e7142d54f..7ac998d20 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from './utils/to-identifier'; +import { toIdentifier } from "./utils/to-identifier"; export interface Contract { name: string; @@ -13,7 +13,11 @@ export interface Contract { upgradeable: boolean; } -export type Value = string | number | { lit: string } | { note: string, value: Value }; +export type Value = + | string + | number + | { lit: string } + | { note: string; value: Value }; export interface Parent { contract: ImportContract; @@ -52,13 +56,16 @@ export interface ContractFunction extends BaseFunction { comments: string[]; } -export type FunctionKind = 'internal' | 'public'; -export type FunctionMutability = typeof mutabilityRank[number]; +export type FunctionKind = "internal" | "public"; +export type FunctionMutability = (typeof mutabilityRank)[number]; // Order is important -const mutabilityRank = ['pure', 'view', 'nonpayable', 'payable'] as const; +const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; -function maxMutability(a: FunctionMutability, b: FunctionMutability): FunctionMutability { +function maxMutability( + a: FunctionMutability, + b: FunctionMutability +): FunctionMutability { return mutabilityRank[ Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) ]!; @@ -76,7 +83,7 @@ export interface NatspecTag { export class ContractBuilder implements Contract { readonly name: string; - license: string = 'MIT'; + license: string = "MIT"; upgradeable = false; readonly using: Using[] = []; @@ -94,21 +101,23 @@ export class ContractBuilder implements Contract { } get parents(): Parent[] { - return [...this.parentMap.values()].filter(p => !p.importOnly).sort((a, b) => { - if (a.contract.name === 'Initializable') { - return -1; - } else if (b.contract.name === 'Initializable') { - return 1; - } else { - return 0; - } - }); + return [...this.parentMap.values()] + .filter((p) => !p.importOnly) + .sort((a, b) => { + if (a.contract.name === "Initializable") { + return -1; + } else if (b.contract.name === "Initializable") { + return 1; + } else { + return 0; + } + }); } get imports(): ImportContract[] { return [ - ...[...this.parentMap.values()].map(p => p.contract), - ...this.using.map(u => u.library), + ...[...this.parentMap.values()].map((p) => p.contract), + ...this.using.map((u) => u.library), ]; } @@ -128,11 +137,19 @@ export class ContractBuilder implements Contract { addImportOnly(contract: ImportContract): boolean { const present = this.parentMap.has(contract.name); - this.parentMap.set(contract.name, { contract, params: [], importOnly: true }); + this.parentMap.set(contract.name, { + contract, + params: [], + importOnly: true, + }); return !present; } - addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) { + addOverride( + parent: ReferencedContract, + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); fn.override.add(parent); if (mutability) { @@ -146,12 +163,19 @@ export class ContractBuilder implements Contract { } addNatspecTag(key: string, value: string) { - if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) throw new Error(`Invalid natspec key: ${key}`); + // eslint-disable-next-line no-useless-escape + if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) + throw new Error(`Invalid natspec key: ${key}`); this.natspecTags.push({ key, value }); } private addFunction(baseFn: BaseFunction): ContractFunction { - const signature = [baseFn.name, '(', ...baseFn.args.map(a => a.name), ')'].join(''); + const signature = [ + baseFn.name, + "(", + ...baseFn.args.map((a) => a.name), + ")", + ].join(""); const got = this.functionMap.get(signature); if (got !== undefined) { return got; @@ -160,7 +184,7 @@ export class ContractBuilder implements Contract { override: new Set(), modifiers: [], code: [], - mutability: 'nonpayable', + mutability: "nonpayable", final: false, comments: [], ...baseFn, @@ -178,7 +202,11 @@ export class ContractBuilder implements Contract { this.constructorCode.push(code); } - addFunctionCode(code: string, baseFn: BaseFunction, mutability?: FunctionMutability) { + addFunctionCode( + code: string, + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); if (fn.final) { throw new Error(`Function ${baseFn.name} is already finalized`); @@ -189,7 +217,11 @@ export class ContractBuilder implements Contract { } } - setFunctionBody(code: string[], baseFn: BaseFunction, mutability?: FunctionMutability) { + setFunctionBody( + code: string[], + baseFn: BaseFunction, + mutability?: FunctionMutability + ) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { throw new Error(`Function ${baseFn.name} has additional code`); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 09847179b..ce0b9e49c 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -1,12 +1,20 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; -import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode'; +import { ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; +import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; export interface ERC20Options extends CommonOptions { name: string; @@ -25,11 +33,11 @@ export interface ERC20Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", burnable: false, pausable: false, - premint: '0', + premint: "0", mintable: false, permit: true, votes: false, @@ -58,7 +66,7 @@ export function printERC20(opts: ERC20Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; + return opts.mintable || opts.pausable || opts.upgradeable === "uups"; } export function buildERC20(opts: ERC20Options): ContractBuilder { @@ -109,13 +117,10 @@ export function buildERC20(opts: ERC20Options): ContractBuilder { function addBase(c: ContractBuilder, name: string, symbol: string) { const ERC20 = { - name: 'ERC20', - path: '@openzeppelin/contracts/token/ERC20/ERC20.sol', + name: "ERC20", + path: "@openzeppelin/contracts/token/ERC20/ERC20.sol", }; - c.addParent( - ERC20, - [name, symbol], - ); + c.addParent(ERC20, [name, symbol]); c.addOverride(ERC20, functions._update); c.addOverride(ERC20, functions._approve); // allows override from stablecoin @@ -123,8 +128,8 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC20Pausable = { - name: 'ERC20Pausable', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', + name: "ERC20Pausable", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol", }; c.addParent(ERC20Pausable); c.addOverride(ERC20Pausable, functions._update); @@ -134,8 +139,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC20Burnable', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', + name: "ERC20Burnable", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol", }); } @@ -144,115 +149,116 @@ export const premintPattern = /^(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addPremint(c: ContractBuilder, amount: string) { const m = amount.match(premintPattern); if (m) { - const integer = m[1]?.replace(/^0+/, '') ?? ''; - const decimals = m[2]?.replace(/0+$/, '') ?? ''; + const integer = m[1]?.replace(/^0+/, "") ?? ""; + const decimals = m[2]?.replace(/0+$/, "") ?? ""; const exponent = Number(m[3] ?? 0); if (Number(integer + decimals) > 0) { const decimalPlace = decimals.length - exponent; - const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); + const zeroes = new Array(Math.max(0, -decimalPlace)).fill("0").join(""); const units = integer + decimals + zeroes; - const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; - c.addConstructorArgument({type: 'address', name: 'recipient'}); + const exp = + decimalPlace <= 0 ? "decimals()" : `(decimals() - ${decimalPlace})`; + c.addConstructorArgument({ type: "address", name: "recipient" }); c.addConstructorCode(`_mint(recipient, ${units} * 10 ** ${exp});`); } } } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); - c.addFunctionCode('_mint(to, amount);', functions.mint); + requireAccessControl(c, functions.mint, access, "MINTER", "minter"); + c.addFunctionCode("_mint(to, amount);", functions.mint); } function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { - name: 'ERC20Permit', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol', + name: "ERC20Permit", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol", }; c.addParent(ERC20Permit, [name]); c.addOverride(ERC20Permit, functions.nonces); - } function addVotes(c: ContractBuilder, clockMode: ClockMode) { - if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) { - throw new Error('Missing ERC20Permit requirement for ERC20Votes'); + if (!c.parents.some((p) => p.contract.name === "ERC20Permit")) { + throw new Error("Missing ERC20Permit requirement for ERC20Votes"); } const ERC20Votes = { - name: 'ERC20Votes', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol', + name: "ERC20Votes", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol", }; c.addParent(ERC20Votes); c.addOverride(ERC20Votes, functions._update); c.addImportOnly({ - name: 'Nonces', - path: '@openzeppelin/contracts/utils/Nonces.sol', + name: "Nonces", + path: "@openzeppelin/contracts/utils/Nonces.sol", }); - c.addOverride({ - name: 'Nonces', - }, functions.nonces); + c.addOverride( + { + name: "Nonces", + }, + functions.nonces + ); setClockMode(c, ERC20Votes, clockMode); } function addFlashMint(c: ContractBuilder) { c.addParent({ - name: 'ERC20FlashMint', - path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol', + name: "ERC20FlashMint", + path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol", }); } export const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "value", type: "uint256" }, ], }, _approve: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'owner', type: 'address' }, - { name: 'spender', type: 'address' }, - { name: 'value', type: 'uint256' }, - { name: 'emitEvent', type: 'bool' }, + { name: "owner", type: "address" }, + { name: "spender", type: "address" }, + { name: "value", type: "uint256" }, + { name: "emitEvent", type: "bool" }, ], }, mint: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }, pause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, unpause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, snapshot: { - kind: 'public' as const, + kind: "public" as const, args: [], }, nonces: { - kind: 'public' as const, - args: [ - { name: 'owner', type: 'address' }, - ], - returns: ['uint256'], - mutability: 'view' as const, - } + kind: "public" as const, + args: [{ name: "owner", type: "address" }], + returns: ["uint256"], + mutability: "view" as const, + }, }); diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index e26603539..e37ac9562 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -1,5 +1,3 @@ -import { mapValues } from "../utils/map-values"; - type Blueprint = Record; type Alternatives = { @@ -7,7 +5,7 @@ type Alternatives = { }; export function* generateAlternatives( - blueprint: B, + blueprint: B ): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, @@ -18,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map(e => [e.key, e.values[e.current % e.limit]]), + entries.map((e) => [e.key, e.values[e.current % e.limit]]) ) as Alternatives; } diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 4591cdfdb..5f61a21e2 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -1,42 +1,46 @@ import { supportsInterface } from "./common-functions"; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from "./common-options"; -import { ContractBuilder, Contract, Value } from "./contract"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { ContractBuilder, Contract } from "./contract"; import { OptionsError } from "./error"; import { setAccessControl } from "./set-access-control"; import { printContract } from "./print"; import { setInfo } from "./set-info"; import { setUpgradeable } from "./set-upgradeable"; -import { defineFunctions } from './utils/define-functions'; +import { defineFunctions } from "./utils/define-functions"; import { durationToBlocks, durationToTimestamp } from "./utils/duration"; import { clockModeDefault, type ClockMode } from "./set-clock-mode"; export const defaults: Required = { - name: 'MyGovernor', - delay: '1 day', - period: '1 week', + name: "MyGovernor", + delay: "1 day", + period: "1 week", - votes: 'erc20votes', + votes: "erc20votes", clockMode: clockModeDefault, - timelock: 'openzeppelin', + timelock: "openzeppelin", blockTime: 12, decimals: 18, - proposalThreshold: '0', - quorumMode: 'percent', + proposalThreshold: "0", + quorumMode: "percent", quorumPercent: 4, - quorumAbsolute: '', + quorumAbsolute: "", storage: false, settings: true, - + access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; -export const votesOptions = ['erc20votes', 'erc721votes'] as const; -export type VotesOptions = typeof votesOptions[number]; +export const votesOptions = ["erc20votes", "erc721votes"] as const; +export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, 'openzeppelin', 'compound'] as const; -export type TimelockOptions = typeof timelockOptions[number]; +export const timelockOptions = [false, "openzeppelin", "compound"] as const; +export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { return printContract(buildGovernor(opts)); @@ -49,7 +53,7 @@ export interface GovernorOptions extends CommonOptions { blockTime?: number; proposalThreshold?: string; decimals?: number; - quorumMode?: 'percent' | 'absolute'; + quorumMode?: "percent" | "absolute"; quorumPercent?: number; quorumAbsolute?: string; votes?: VotesOptions; @@ -59,8 +63,10 @@ export interface GovernorOptions extends CommonOptions { settings?: boolean; } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial +): boolean { + return opts.upgradeable === "uups"; } function withDefaults(opts: GovernorOptions): Required { @@ -77,7 +83,7 @@ function withDefaults(opts: GovernorOptions): Required { quorumMode: opts.quorumMode ?? defaults.quorumMode, votes: opts.votes ?? defaults.votes, clockMode: opts.clockMode ?? defaults.clockMode, - timelock: opts.timelock ?? defaults.timelock + timelock: opts.timelock ?? defaults.timelock, }; } @@ -105,8 +111,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { function addBase(c: ContractBuilder, { name }: GovernorOptions) { const Governor = { - name: 'Governor', - path: '@openzeppelin/contracts/governance/Governor.sol', + name: "Governor", + path: "@openzeppelin/contracts/governance/Governor.sol", }; c.addParent(Governor, [name]); c.addOverride(Governor, functions.votingDelay); @@ -127,36 +133,35 @@ function addBase(c: ContractBuilder, { name }: GovernorOptions) { function addSettings(c: ContractBuilder, allOpts: Required) { if (allOpts.settings) { const GovernorSettings = { - name: 'GovernorSettings', - path: '@openzeppelin/contracts/governance/extensions/GovernorSettings.sol', + name: "GovernorSettings", + path: "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol", }; - c.addParent( - GovernorSettings, - [ - getVotingDelay(allOpts), - getVotingPeriod(allOpts), - { lit: getProposalThreshold(allOpts) }, - ], - ); - c.addOverride(GovernorSettings, functions.votingDelay, 'view'); - c.addOverride(GovernorSettings, functions.votingPeriod, 'view'); - c.addOverride(GovernorSettings, functions.proposalThreshold, 'view'); + c.addParent(GovernorSettings, [ + getVotingDelay(allOpts), + getVotingPeriod(allOpts), + { lit: getProposalThreshold(allOpts) }, + ]); + c.addOverride(GovernorSettings, functions.votingDelay, "view"); + c.addOverride(GovernorSettings, functions.votingPeriod, "view"); + c.addOverride(GovernorSettings, functions.proposalThreshold, "view"); } else { setVotingParameters(c, allOpts); setProposalThreshold(c, allOpts); } } -function getVotingDelay(opts: Required): { lit: string } | { note: string, value: number } { +function getVotingDelay( + opts: Required +): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === 'timestamp') { + if (opts.clockMode === "timestamp") { return { lit: durationToTimestamp(opts.delay), }; } else { return { value: durationToBlocks(opts.delay, opts.blockTime), - note: opts.delay + note: opts.delay, }; } } catch (e) { @@ -170,16 +175,18 @@ function getVotingDelay(opts: Required): { lit: string } | { no } } -function getVotingPeriod(opts: Required): { lit: string } | { note: string, value: number } { +function getVotingPeriod( + opts: Required +): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === 'timestamp') { + if (opts.clockMode === "timestamp") { return { lit: durationToTimestamp(opts.period), }; } else { return { value: durationToBlocks(opts.period, opts.blockTime), - note: opts.period + note: opts.period, }; } } catch (e) { @@ -196,42 +203,62 @@ function getVotingPeriod(opts: Required): { lit: string } | { n function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: 'Not a valid number', + decimals: "Not a valid number", }); } } -function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): string { +function getProposalThreshold({ + proposalThreshold, + decimals, + votes, +}: Required): string { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: 'Not a valid number', + proposalThreshold: "Not a valid number", }); } - if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { + if ( + /^0+$/.test(proposalThreshold) || + decimals === 0 || + votes === "erc721votes" + ) { return proposalThreshold; } else { return `${proposalThreshold}e${decimals}`; } } -function setVotingParameters(c: ContractBuilder, opts: Required) { +function setVotingParameters( + c: ContractBuilder, + opts: Required +) { const delayBlocks = getVotingDelay(opts); - if ('lit' in delayBlocks) { + if ("lit" in delayBlocks) { c.setFunctionBody([`return ${delayBlocks.lit};`], functions.votingDelay); } else { - c.setFunctionBody([`return ${delayBlocks.value}; // ${delayBlocks.note}`], functions.votingDelay); + c.setFunctionBody( + [`return ${delayBlocks.value}; // ${delayBlocks.note}`], + functions.votingDelay + ); } const periodBlocks = getVotingPeriod(opts); - if ('lit' in periodBlocks) { + if ("lit" in periodBlocks) { c.setFunctionBody([`return ${periodBlocks.lit};`], functions.votingPeriod); } else { - c.setFunctionBody([`return ${periodBlocks.value}; // ${periodBlocks.note}`], functions.votingPeriod); + c.setFunctionBody( + [`return ${periodBlocks.value}; // ${periodBlocks.note}`], + functions.votingPeriod + ); } } -function setProposalThreshold(c: ContractBuilder, opts: Required) { +function setProposalThreshold( + c: ContractBuilder, + opts: Required +) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -242,128 +269,143 @@ function setProposalThreshold(c: ContractBuilder, opts: Required) { - if (opts.quorumMode === 'percent') { + if (opts.quorumMode === "percent") { if (opts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); } - let { quorumFractionNumerator, quorumFractionDenominator } = getQuorumFractionComponents(opts.quorumPercent); + const { quorumFractionNumerator, quorumFractionDenominator } = + getQuorumFractionComponents(opts.quorumPercent); const GovernorVotesQuorumFraction = { - name: 'GovernorVotesQuorumFraction', - path: '@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol', + name: "GovernorVotesQuorumFraction", + path: "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol", }; if (quorumFractionDenominator !== undefined) { c.addOverride(GovernorVotesQuorumFraction, functions.quorumDenominator); - c.setFunctionBody([ - `return ${quorumFractionDenominator};` - ], functions.quorumDenominator, 'pure'); + c.setFunctionBody( + [`return ${quorumFractionDenominator};`], + functions.quorumDenominator, + "pure" + ); } c.addParent(GovernorVotesQuorumFraction, [quorumFractionNumerator]); c.addOverride(GovernorVotesQuorumFraction, functions.quorum); - } - - else if (opts.quorumMode === 'absolute') { + } else if (opts.quorumMode === "absolute") { if (!numberPattern.test(opts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: 'Not a valid number', + quorumAbsolute: "Not a valid number", }); } - let returnStatement = (opts.decimals === 0 || opts.votes === 'erc721votes') ? - `return ${opts.quorumAbsolute};` : - `return ${opts.quorumAbsolute}e${opts.decimals};`; + const returnStatement = + opts.decimals === 0 || opts.votes === "erc721votes" + ? `return ${opts.quorumAbsolute};` + : `return ${opts.quorumAbsolute}e${opts.decimals};`; - c.setFunctionBody([ - returnStatement, - ], functions.quorum, 'pure'); + c.setFunctionBody([returnStatement], functions.quorum, "pure"); } } const timelockModules = { openzeppelin: { timelockType: { - name: 'TimelockController', + name: "TimelockController", path: `@openzeppelin/contracts/governance/TimelockController.sol`, }, timelockParent: { - name: 'GovernorTimelockControl', + name: "GovernorTimelockControl", path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol`, - } + }, }, compound: { timelockType: { - name: 'ICompoundTimelock', + name: "ICompoundTimelock", path: `@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol`, transpiled: false, }, timelockParent: { - name: 'GovernorTimelockCompound', + name: "GovernorTimelockCompound", path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol`, - } + }, }, } as const; -function getQuorumFractionComponents(quorumPercent: number): {quorumFractionNumerator: number, quorumFractionDenominator: string | undefined} { +function getQuorumFractionComponents(quorumPercent: number): { + quorumFractionNumerator: number; + quorumFractionDenominator: string | undefined; +} { let quorumFractionNumerator = quorumPercent; let quorumFractionDenominator = undefined; const quorumPercentSegments = quorumPercent.toString().split("."); if (quorumPercentSegments.length > 2) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); - } else if (quorumPercentSegments.length == 2 && quorumPercentSegments[0] !== undefined && quorumPercentSegments[1] !== undefined) { - quorumFractionNumerator = parseInt(quorumPercentSegments[0].concat(quorumPercentSegments[1])); + } else if ( + quorumPercentSegments.length == 2 && + quorumPercentSegments[0] !== undefined && + quorumPercentSegments[1] !== undefined + ) { + quorumFractionNumerator = parseInt( + quorumPercentSegments[0].concat(quorumPercentSegments[1]) + ); const decimals = quorumPercentSegments[1].length; - quorumFractionDenominator = '100'; + quorumFractionDenominator = "100"; while (quorumFractionDenominator.length < decimals + 3) { - quorumFractionDenominator += '0'; + quorumFractionDenominator += "0"; } } return { quorumFractionNumerator, quorumFractionDenominator }; } -function addTimelock(c: ContractBuilder, { timelock }: Required) { +function addTimelock( + c: ContractBuilder, + { timelock }: Required +) { if (timelock === false) { return; } - const timelockArg = '_timelock'; + const timelockArg = "_timelock"; const { timelockType, timelockParent } = timelockModules[timelock]; c.addImportOnly(timelockType); @@ -384,8 +426,8 @@ function addTimelock(c: ContractBuilder, { timelock }: Required function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { if (storage) { const GovernorStorage = { - name: 'GovernorStorage', - path: '@openzeppelin/contracts/governance/extensions/GovernorStorage.sol', + name: "GovernorStorage", + path: "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol", }; c.addParent(GovernorStorage); c.addOverride(GovernorStorage, functions._propose); @@ -395,108 +437,102 @@ function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { const functions = defineFunctions({ votingDelay: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, votingPeriod: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, proposalThreshold: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'pure', + returns: ["uint256"], + kind: "public", + mutability: "pure", }, proposalNeedsQueuing: { - args: [ - { name: 'proposalId', type: 'uint256' }, - ], - returns: ['bool'], - kind: 'public', - mutability: 'view', + args: [{ name: "proposalId", type: "uint256" }], + returns: ["bool"], + kind: "public", + mutability: "view", }, quorum: { - args: [ - { name: 'blockNumber', type: 'uint256' }, - ], - returns: ['uint256'], - kind: 'public', - mutability: 'view', + args: [{ name: "blockNumber", type: "uint256" }], + returns: ["uint256"], + kind: "public", + mutability: "view", }, quorumDenominator: { args: [], - returns: ['uint256'], - kind: 'public', - mutability: 'view', + returns: ["uint256"], + kind: "public", + mutability: "view", }, propose: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'description', type: 'string memory' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "description", type: "string memory" }, ], - returns: ['uint256'], - kind: 'public', + returns: ["uint256"], + kind: "public", }, _propose: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'description', type: 'string memory' }, - { name: 'proposer', type: 'address' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "description", type: "string memory" }, + { name: "proposer", type: "address" }, ], - returns: ['uint256'], - kind: 'internal', + returns: ["uint256"], + kind: "internal", }, _queueOperations: { args: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "proposalId", type: "uint256" }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - kind: 'internal', - returns: ['uint48'], + kind: "internal", + returns: ["uint48"], }, _executeOperations: { args: [ - { name: 'proposalId', type: 'uint256' }, - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "proposalId", type: "uint256" }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - kind: 'internal', + kind: "internal", }, _cancel: { args: [ - { name: 'targets', type: 'address[] memory' }, - { name: 'values', type: 'uint256[] memory' }, - { name: 'calldatas', type: 'bytes[] memory' }, - { name: 'descriptionHash', type: 'bytes32' }, + { name: "targets", type: "address[] memory" }, + { name: "values", type: "uint256[] memory" }, + { name: "calldatas", type: "bytes[] memory" }, + { name: "descriptionHash", type: "bytes32" }, ], - returns: ['uint256'], - kind: 'internal', + returns: ["uint256"], + kind: "internal", }, state: { - args: [ - { name: 'proposalId', type: 'uint256' }, - ], - returns: ['ProposalState'], - kind: 'public', - mutability: 'view', + args: [{ name: "proposalId", type: "uint256" }], + returns: ["ProposalState"], + kind: "public", + mutability: "view", }, _executor: { args: [], - returns: ['address'], - kind: 'internal', - mutability: 'view', + returns: ["address"], + kind: "internal", + mutability: "view", }, }); diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index 0cdc4d111..3db246060 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -1,21 +1,28 @@ -import type { Contract, Parent, ContractFunction, FunctionArgument, Value, NatspecTag, ImportContract } from './contract'; -import { Options, Helpers, withHelpers } from './options'; - -import { formatLines, spaceBetween, Lines } from './utils/format-lines'; -import { mapValues } from './utils/map-values'; -import SOLIDITY_VERSION from './solidity-version.json'; -import { inferTranspiled } from './infer-transpiled'; -import { compatibleContractsSemver } from './utils/version'; +import type { + Contract, + Parent, + ContractFunction, + FunctionArgument, + Value, + NatspecTag, + ImportContract, +} from "./contract"; +import { Options, Helpers, withHelpers } from "./options"; + +import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; +import { mapValues } from "./utils/map-values"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import { inferTranspiled } from "./infer-transpiled"; +import { compatibleContractsSemver } from "./utils/version"; export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); - const fns = mapValues( - sortedFunctions(contract), - fns => fns.map(fn => printFunction(fn, helpers)), + const fns = mapValues(sortedFunctions(contract), (fns) => + fns.map((fn) => printFunction(fn, helpers)) ); - const hasOverrides = fns.override.some(l => l.length > 0); + const hasOverrides = fns.override.some((l) => l.length > 0); return formatLines( ...spaceBetween( @@ -29,61 +36,68 @@ export function printContract(contract: Contract, opts?: Options): string { [ ...printNatspecTags(contract.natspecTags), - [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '), + [ + `contract ${contract.name}`, + ...printInheritance(contract, helpers), + "{", + ].join(" "), spaceBetween( contract.variables, printConstructor(contract, helpers), ...fns.code, ...fns.modifiers, - hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], - ...fns.override, + hasOverrides + ? [`// The following functions are overrides required by Solidity.`] + : [], + ...fns.override ), `}`, - ], - ), + ] + ) ); } -function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] { +function printInheritance( + contract: Contract, + { transformName }: Helpers +): [] | [string] { if (contract.parents.length > 0) { - return ['is ' + contract.parents.map(p => transformName(p.contract)).join(', ')]; + return [ + "is " + contract.parents.map((p) => transformName(p.contract)).join(", "), + ]; } else { return []; } } function printConstructor(contract: Contract, helpers: Helpers): Lines[] { - const hasParentParams = contract.parents.some(p => p.params.length > 0); + const hasParentParams = contract.parents.some((p) => p.params.length > 0); const hasConstructorCode = contract.constructorCode.length > 0; const parentsWithInitializers = contract.parents.filter(hasInitializer); - if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) { - const parents = parentsWithInitializers - .flatMap(p => printParentConstructor(p, helpers)); - const modifiers = helpers.upgradeable ? ['public initializer'] : parents; - const args = contract.constructorArgs.map(a => printArgument(a, helpers)); + if ( + hasParentParams || + hasConstructorCode || + (helpers.upgradeable && parentsWithInitializers.length > 0) + ) { + const parents = parentsWithInitializers.flatMap((p) => + printParentConstructor(p, helpers) + ); + const modifiers = helpers.upgradeable ? ["public initializer"] : parents; + const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); const body = helpers.upgradeable ? spaceBetween( - parents.map(p => p + ';'), - contract.constructorCode, - ) + parents.map((p) => p + ";"), + contract.constructorCode + ) : contract.constructorCode; - const head = helpers.upgradeable ? 'function initialize' : 'constructor'; - const constructor = printFunction2( - [], - head, - args, - modifiers, - body, - ); + const head = helpers.upgradeable ? "function initialize" : "constructor"; + const constructor = printFunction2([], head, args, modifiers, body); if (!helpers.upgradeable) { return constructor; } else { - return spaceBetween( - DISABLE_INITIALIZERS, - constructor, - ); + return spaceBetween(DISABLE_INITIALIZERS, constructor); } } else if (!helpers.upgradeable) { return []; @@ -92,24 +106,24 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { } } -const DISABLE_INITIALIZERS = -[ - '/// @custom:oz-upgrades-unsafe-allow constructor', - 'constructor() {', - [ - '_disableInitializers();' - ], - '}' +const DISABLE_INITIALIZERS = [ + "/// @custom:oz-upgrades-unsafe-allow constructor", + "constructor() {", + ["_disableInitializers();"], + "}", ]; function hasInitializer(parent: Parent) { // CAUTION // This list is validated by compilation of SafetyCheck.sol. // Always keep this list and that file in sync. - return !['Initializable'].includes(parent.contract.name); + return !["Initializable"].includes(parent.contract.name); } -type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>; +type SortedFunctions = Record< + "code" | "modifiers" | "override", + ContractFunction[] +>; // Functions with code first, then those with modifiers, then the rest function sortedFunctions(contract: Contract): SortedFunctions { @@ -128,28 +142,29 @@ function sortedFunctions(contract: Contract): SortedFunctions { return fns; } -function printParentConstructor({ contract, params }: Parent, helpers: Helpers): [] | [string] { +function printParentConstructor( + { contract, params }: Parent, + helpers: Helpers +): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; if (useTranspiled || params.length > 0) { - return [ - fn + '(' + params.map(printValue).join(', ') + ')', - ]; + return [fn + "(" + params.map(printValue).join(", ") + ")"]; } else { return []; } } export function printValue(value: Value): string { - if (typeof value === 'object') { - if ('lit' in value) { + if (typeof value === "object") { + if ("lit" in value) { return value.lit; - } else if ('note' in value) { + } else if ("note" in value) { return `${printValue(value.value)} /* ${value.note} */`; } else { - throw Error('Unknown value type'); + throw Error("Unknown value type"); } - } else if (typeof value === 'number') { + } else if (typeof value === "number") { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { @@ -163,41 +178,48 @@ export function printValue(value: Value): string { function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { const { transformName } = helpers; - if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) { - return [] + if ( + fn.override.size <= 1 && + fn.modifiers.length === 0 && + fn.code.length === 0 && + !fn.final + ) { + return []; } - const modifiers: string[] = [fn.kind] + const modifiers: string[] = [fn.kind]; - if (fn.mutability !== 'nonpayable') { + if (fn.mutability !== "nonpayable") { modifiers.push(fn.mutability); } - + if (fn.override.size === 1) { modifiers.push(`override`); } else if (fn.override.size > 1) { - modifiers.push(`override(${[...fn.override].map(transformName).join(', ')})`); + modifiers.push( + `override(${[...fn.override].map(transformName).join(", ")})` + ); } modifiers.push(...fn.modifiers); if (fn.returns?.length) { - modifiers.push(`returns (${fn.returns.join(', ')})`); + modifiers.push(`returns (${fn.returns.join(", ")})`); } const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { - const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; - code.push(fn.returns?.length ? 'return ' + superCall : superCall); + const superCall = `super.${fn.name}(${fn.args.map((a) => a.name).join(", ")});`; + code.push(fn.returns?.length ? "return " + superCall : superCall); } if (modifiers.length + fn.code.length > 1) { return printFunction2( fn.comments, - 'function ' + fn.name, - fn.args.map(a => printArgument(a, helpers)), + "function " + fn.name, + fn.args.map((a) => printArgument(a, helpers)), modifiers, - code, + code ); } else { return []; @@ -206,32 +228,44 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { // generic for functions and constructors // kindedName = 'function foo' or 'constructor' -function printFunction2(comments: string[], kindedName: string, args: string[], modifiers: string[], code: Lines[]): Lines[] { - const fn: Lines[] = [ ...comments ]; +function printFunction2( + comments: string[], + kindedName: string, + args: string[], + modifiers: string[], + code: Lines[] +): Lines[] { + const fn: Lines[] = [...comments]; const headingLength = [kindedName, ...args, ...modifiers] - .map(s => s.length) + .map((s) => s.length) .reduce((a, b) => a + b); - const braces = code.length > 0 ? '{' : '{}'; + const braces = code.length > 0 ? "{" : "{}"; if (headingLength <= 72) { - fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' ')); + fn.push( + [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" ") + ); } else { - fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces); + fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); } if (code.length > 0) { - fn.push(code, '}'); + fn.push(code, "}"); } return fn; } -function printArgument(arg: FunctionArgument, { transformName }: Helpers): string { +function printArgument( + arg: FunctionArgument, + { transformName }: Helpers +): string { let type: string; - if (typeof arg.type === 'string') { + if (typeof arg.type === "string") { if (/^[A-Z]/.test(arg.type)) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions `Type ${arg.type} is not a primitive type. Define it as a ContractReference`; } type = arg.type; @@ -239,7 +273,7 @@ function printArgument(arg: FunctionArgument, { transformName }: Helpers): strin type = transformName(arg.type); } - return [type, arg.name].join(' '); + return [type, arg.name].join(" "); } function printNatspecTags(tags: NatspecTag[]): string[] { @@ -255,10 +289,12 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { }); const lines: string[] = []; - imports.map(p => { + imports.map((p) => { const importContract = helpers.transformImport(p); - lines.push(`import {${importContract.name}} from "${importContract.path}";`); + lines.push( + `import {${importContract.name}} from "${importContract.path}";` + ); }); return lines; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index 56d598de3..1d3a359b2 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,6 +1,7 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function mapValues( obj: Record, - fn: (val: V) => W, + fn: (val: V) => W ): Record { const res = {} as Record; for (const key in obj) { diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index 195322586..8f09757d5 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -2,13 +2,17 @@ import JSZip from "jszip"; import type { GenericOptions } from "./build-generic"; import type { Contract } from "./contract"; import { printContract } from "./print"; -import SOLIDITY_VERSION from './solidity-version.json'; -import { formatLinesWithSpaces, Lines, spaceBetween } from "./utils/format-lines"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import { + formatLinesWithSpaces, + Lines, + spaceBetween, +} from "./utils/format-lines"; const hardhatConfig = (upgradeable: boolean) => `\ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; -${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ''} +${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ""} const config: HardhatUserConfig = { solidity: { @@ -54,10 +58,7 @@ artifacts const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getImports(c), - getTestCase(c), - ), + ...spaceBetween(getImports(c), getTestCase(c)) ); function getTestCase(c: Contract) { @@ -73,39 +74,39 @@ const test = (c: Contract, opts?: GenericOptions) => { getAddressVariables(args), [ `const instance = await ${getDeploymentCall(c, args)};`, - 'await instance.waitForDeployment();' + "await instance.waitForDeployment();", ], - getExpects(), + getExpects() ), - '});' + "});", ], - '});', + "});", ]; } function getImports(c: Contract) { return [ 'import { expect } from "chai";', - `import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";`, + `import { ${getHardhatPlugins(c).join(", ")} } from "hardhat";`, ]; } function getExpects(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case 'ERC20': - case 'ERC721': + case "ERC20": + case "ERC721": return [`expect(await instance.name()).to.equal("${opts.name}");`]; - case 'ERC1155': + case "ERC1155": return [`expect(await instance.uri(0)).to.equal("${opts.uri}");`]; - case 'Governor': - case 'Custom': + case "Governor": + case "Custom": break; default: - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); } } return []; @@ -114,7 +115,9 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressVariables(args: string[]): Lines[] { const vars = []; for (let i = 0; i < args.length; i++) { - vars.push(`const ${args[i]} = (await ethers.getSigners())[${i}].address;`); + vars.push( + `const ${args[i]} = (await ethers.getSigners())[${i}].address;` + ); } return vars; } @@ -123,7 +126,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === 'address') { + if (constructorArg.type === "address") { args.push(constructorArg.name); } } @@ -131,22 +134,30 @@ function getAddressArgs(c: Contract): string[] { } function getDeploymentCall(c: Contract, args: string[]): string { - return c.upgradeable ? `upgrades.deployProxy(ContractFactory, [${args.join(', ')}])` : `ContractFactory.deploy(${args.join(', ')})`; + return c.upgradeable + ? `upgrades.deployProxy(ContractFactory, [${args.join(", ")}])` + : `ContractFactory.deploy(${args.join(", ")})`; } const script = (c: Contract) => { const args = getAddressArgs(c); return `\ -import { ${getHardhatPlugins(c).join(', ')} } from "hardhat"; +import { ${getHardhatPlugins(c).join(", ")} } from "hardhat"; async function main() { const ContractFactory = await ethers.getContractFactory("${c.name}"); - ${args.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''} + ${ + args.length > 0 + ? "// TODO: Set addresses for the contract arguments below" + : "" + } const instance = await ${getDeploymentCall(c, args)}; await instance.waitForDeployment(); - console.log(\`${c.upgradeable ? 'Proxy' : 'Contract'} deployed to \${await instance.getAddress()}\`); + console.log(\`${ + c.upgradeable ? "Proxy" : "Contract" + } deployed to \${await instance.getAddress()}\`); } // We recommend this pattern to be able to use async/await everywhere @@ -155,7 +166,8 @@ main().catch((error) => { console.error(error); process.exitCode = 1; }); -`}; +`; +}; const readme = `\ # Sample Hardhat Project @@ -184,9 +196,9 @@ npx hardhat run --network scripts/deploy.ts `; function getHardhatPlugins(c: Contract) { - let plugins = ['ethers']; + const plugins = ["ethers"]; if (c.upgradeable) { - plugins.push('upgrades'); + plugins.push("upgrades"); } return plugins; } @@ -194,21 +206,25 @@ function getHardhatPlugins(c: Contract) { export async function zipHardhat(c: Contract, opts?: GenericOptions) { const zip = new JSZip(); - const { default: packageJson } = c.upgradeable ? await import("./environments/hardhat/upgradeable/package.json") : await import("./environments/hardhat/package.json"); + const { default: packageJson } = c.upgradeable + ? await import("./environments/hardhat/upgradeable/package.json") + : await import("./environments/hardhat/package.json"); packageJson.license = c.license; - const { default: packageLock } = c.upgradeable ? await import("./environments/hardhat/upgradeable/package-lock.json") : await import("./environments/hardhat/package-lock.json"); - packageLock.packages[''].license = c.license; + const { default: packageLock } = c.upgradeable + ? await import("./environments/hardhat/upgradeable/package-lock.json") + : await import("./environments/hardhat/package-lock.json"); + packageLock.packages[""].license = c.license; zip.file(`contracts/${c.name}.sol`, printContract(c)); - zip.file('test/test.ts', test(c, opts)); - zip.file('scripts/deploy.ts', script(c)); - zip.file('.gitignore', gitIgnore); - zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable)); - zip.file('package.json', JSON.stringify(packageJson, null, 2)); + zip.file("test/test.ts", test(c, opts)); + zip.file("scripts/deploy.ts", script(c)); + zip.file(".gitignore", gitIgnore); + zip.file("hardhat.config.ts", hardhatConfig(c.upgradeable)); + zip.file("package.json", JSON.stringify(packageJson, null, 2)); zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2)); - zip.file('README.md', readme); - zip.file('tsconfig.json', tsConfig); + zip.file("README.md", readme); + zip.file("tsconfig.json", tsConfig); return zip; -} \ No newline at end of file +} diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index 90fff1255..fd010e506 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -1,54 +1,75 @@ -import OpenAI from 'https://esm.sh/openai@4.11.0' -import { OpenAIStream, StreamingTextResponse } from 'https://esm.sh/ai@2.2.16' -import { erc20Function, erc721Function, erc1155Function, stablecoinFunction, realWorldAssetFunction, governorFunction, customFunction } from '../src/solidity/wiz-functions.ts' -import { Redis } from 'https://esm.sh/@upstash/redis@1.25.1' +import OpenAI from "https://esm.sh/openai@4.11.0"; +import { OpenAIStream, StreamingTextResponse } from "https://esm.sh/ai@2.2.16"; +import { + erc20Function, + erc721Function, + erc1155Function, + stablecoinFunction, + realWorldAssetFunction, + governorFunction, + customFunction, +} from "../src/solidity/wiz-functions.ts"; +import { Redis } from "https://esm.sh/@upstash/redis@1.25.1"; export default async (req: Request) => { try { - const data = await req.json() - const apiKey = Deno.env.get('OPENAI_API_KEY') + const data = await req.json(); + const apiKey = Deno.env.get("OPENAI_API_KEY"); - const redisUrl = Deno.env.get('REDIS_URL') - const redisToken = Deno.env.get('REDIS_TOKEN') + const redisUrl = Deno.env.get("REDIS_URL"); + const redisToken = Deno.env.get("REDIS_TOKEN"); - if (!redisUrl || !redisToken) { throw new Error('missing redis credentials') } + if (!redisUrl || !redisToken) { + throw new Error("missing redis credentials"); + } const redis = new Redis({ - url: redisUrl, + url: redisUrl, token: redisToken, - }) + }); const openai = new OpenAI({ - apiKey: apiKey - }) + apiKey: apiKey, + }); - const validatedMessages = data.messages.filter((message: { role: string, content: string }) => { - return message.content.length < 500 - }) + const validatedMessages = data.messages.filter( + (message: { role: string; content: string }) => { + return message.content.length < 500; + } + ); - const messages = [{ - role: 'system', - content: ` + const messages = [ + { + role: "system", + content: ` You are a smart contract assistant built by OpenZeppelin to help users using OpenZeppelin Contracts Wizard. The current options are ${JSON.stringify(data.currentOpts)}. Please be kind and concise. Keep responses to <100 words. - `.trim() - }, ...validatedMessages] + `.trim(), + }, + ...validatedMessages, + ]; const response = await openai.chat.completions.create({ - model: 'gpt-4-1106-preview', + model: "gpt-4-1106-preview", messages, functions: [ - erc20Function, erc721Function, erc1155Function, stablecoinFunction, realWorldAssetFunction, governorFunction, customFunction + erc20Function, + erc721Function, + erc1155Function, + stablecoinFunction, + realWorldAssetFunction, + governorFunction, + customFunction, ], temperature: 0.7, - stream: true - }) + stream: true, + }); const stream = OpenAIStream(response, { async onCompletion(completion) { - const id = data.chatId - const updatedAt = Date.now() + const id = data.chatId; + const updatedAt = Date.now(); const payload = { id, updatedAt, @@ -56,24 +77,23 @@ export default async (req: Request) => { ...messages, { content: completion, - role: 'assistant' - } - ] - } - const exists = await redis.exists(`chat:${id}`) + role: "assistant", + }, + ], + }; + const exists = await redis.exists(`chat:${id}`); if (!exists) { - // @ts-ignore redis types seem to require [key: string] - payload.createdAt = updatedAt + // @ts-expect-error redis types seem to require [key: string] + payload.createdAt = updatedAt; } - await redis.hset(`chat:${id}`, payload) - } + await redis.hset(`chat:${id}`, payload); + }, }); return new StreamingTextResponse(stream); - } catch (e) { console.error("Could not retrieve results:", e); return Response.json({ - error: 'Could not retrieve results.' + error: "Could not retrieve results.", }); } -} +}; diff --git a/packages/ui/src/cairo/highlightjs.ts b/packages/ui/src/cairo/highlightjs.ts index c94c01084..cfc81aa15 100644 --- a/packages/ui/src/cairo/highlightjs.ts +++ b/packages/ui/src/cairo/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from 'highlight.js/lib/core'; +import hljs from "highlight.js/lib/core"; -// @ts-ignore -import hljsDefineCairo from 'highlightjs-cairo'; +// @ts-expect-error missing type declaration +import hljsDefineCairo from "highlightjs-cairo"; hljsDefineCairo(hljs); export default hljs; diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index d48acfdd3..0db04cc5d 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -1,29 +1,40 @@ +/* eslint-disable no-useless-escape */ import { contractsVersionTag } from "@openzeppelin/wizard-cairo/src"; export function injectHyperlinks(code: string) { - const importRegex = /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g + const importRegex = + /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; let result = code; let match = importRegex.exec(code); while (match != null) { const [line, libraryPrefix, libraryPath, suffix] = match; - if (line !== undefined && libraryPrefix !== undefined && libraryPath !== undefined && suffix !== undefined) { + if ( + line !== undefined && + libraryPrefix !== undefined && + libraryPath !== undefined && + suffix !== undefined + ) { const githubPrefix = `https://github.com/OpenZeppelin/cairo-contracts/blob/${contractsVersionTag}/packages/`; - let libraryPathSegments = libraryPath.split('::'); - libraryPathSegments.splice(1, 0, 'src'); + const libraryPathSegments = libraryPath.split("::"); + libraryPathSegments.splice(1, 0, "src"); if (libraryPathSegments !== undefined && libraryPathSegments.length > 0) { let replacement; - if (suffix === '::{') { + if (suffix === "::{") { // Multiple components are imported, so remove components and link to the parent .cairo file - replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link + replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported // If a mapping exists, link to the mapped file, otherwise remove the component and link to the parent .cairo file const componentName = suffix.substring(2, suffix.length); const mapping = componentMappings[componentName]; - const urlSuffix = mapping ? `/${mapping}.cairo` : '.cairo'; - replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link + const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; + replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } result = result.replace(line, replacement); @@ -35,6 +46,6 @@ export function injectHyperlinks(code: string) { } const componentMappings: { [key: string]: string } = { - 'AccountComponent': 'account', - 'UpgradeableComponent': 'upgradeable', + AccountComponent: "account", + UpgradeableComponent: "upgradeable", } as const; diff --git a/packages/ui/src/common/error-tooltip.ts b/packages/ui/src/common/error-tooltip.ts index 79b546a88..cd6b96741 100644 --- a/packages/ui/src/common/error-tooltip.ts +++ b/packages/ui/src/common/error-tooltip.ts @@ -1,15 +1,17 @@ -import tippy, { Props } from 'tippy.js'; +import tippy from "tippy.js"; -const klass = 'has-error'; +const klass = "has-error"; export function error(node: HTMLElement, content?: string) { let shown = false; const t = tippy(node, { - placement: 'right', - theme: 'light-red border', + placement: "right", + theme: "light-red border", showOnCreate: false, - onShow: () => { shown = true; }, + onShow: () => { + shown = true; + }, }); t.disable(); diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index d621a7053..6f9a3a3f0 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -1,42 +1,48 @@ -import type { SolcInputSources } from '@openzeppelin/wizard/get-imports'; +import type { SolcInputSources } from "@openzeppelin/wizard/get-imports"; -export type Message = ResizeMessage | TabChangeMessage | UnsupportedVersionMessage | DefenderDeployMessage; +export type Message = + | ResizeMessage + | TabChangeMessage + | UnsupportedVersionMessage + | DefenderDeployMessage; export interface ResizeMessage { - kind: 'oz-wizard-resize'; + kind: "oz-wizard-resize"; height: number; } export interface TabChangeMessage { - kind: 'oz-wizard-tab-change'; + kind: "oz-wizard-tab-change"; tab: string; } export interface UnsupportedVersionMessage { - kind: 'oz-wizard-unsupported-version'; + kind: "oz-wizard-unsupported-version"; } export interface DefenderDeployMessage { - kind: 'oz-wizard-defender-deploy'; + kind: "oz-wizard-defender-deploy"; sources: SolcInputSources; } export function postMessage(msg: Message) { if (parent) { - parent.postMessage(msg, '*'); + parent.postMessage(msg, "*"); } } -export function postMessageToIframe(id: 'defender-deploy', msg: Message) { - var iframe: HTMLIFrameElement | null = document.getElementById(id) as HTMLIFrameElement; +export function postMessageToIframe(id: "defender-deploy", msg: Message) { + const iframe: HTMLIFrameElement | null = document.getElementById( + id + ) as HTMLIFrameElement; if (iframe) { - iframe.contentWindow?.postMessage(msg, '*'); + iframe.contentWindow?.postMessage(msg, "*"); // in case the iframe is still loading, waits // a second to fully load and tries again iframe.onload = () => { setTimeout(() => { - iframe?.contentWindow?.postMessage(msg, '*'); + iframe?.contentWindow?.postMessage(msg, "*"); }, 1000); - } + }; } } diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index f24613e88..6e8e7ce8e 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -1,25 +1,33 @@ export function resizeToFit(node: HTMLInputElement) { const resize = () => { - if (node.value === '') { + if (node.value === "") { return; } const style = window.getComputedStyle(node); - const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); const textWidth = measureTextWidth(node.value, style); const minWidth = measureTextWidth(node.placeholder, style); - const padding = ['padding-left', 'padding-right', 'border-left-width', 'border-right-width'].map(p => style.getPropertyValue(p)); - const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join(' + ')})`; - node.style.setProperty('width', result); + const padding = [ + "padding-left", + "padding-right", + "border-left-width", + "border-right-width", + ].map((p) => style.getPropertyValue(p)); + const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( + " + " + )})`; + node.style.setProperty("width", result); }; resize(); - node.addEventListener('input', resize); + node.addEventListener("input", resize); } function measureTextWidth(text: string, style: CSSStyleDeclaration): string { - const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); - const ctx = document.createElement('canvas').getContext('2d')!; + const font = ["font-size", "font-family"] + .map((p) => style.getPropertyValue(p)) + .join(" "); + const ctx = document.createElement("canvas").getContext("2d")!; ctx.font = font; - return ctx.measureText(text).width + 'px'; + return ctx.measureText(text).width + "px"; } diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index a85ab8a81..e8afa5c09 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -1,18 +1,18 @@ -import './common/styles/global.css'; - -import type {} from 'svelte'; -import SolidityApp from './solidity/App.svelte'; -import CairoApp from './cairo/App.svelte'; -import { postMessage } from './common/post-message'; -import UnsupportedVersion from './common/UnsupportedVersion.svelte'; -import semver from 'semver'; -import { compatibleContractsSemver as compatibleSolidityContractsSemver } from '@openzeppelin/wizard'; -import { compatibleContractsSemver as compatibleCairoContractsSemver } from '@openzeppelin/wizard-cairo'; -import type { InitialOptions } from './common/initial-options.ts'; +import "./common/styles/global.css"; + +import type {} from "svelte"; +import SolidityApp from "./solidity/App.svelte"; +import CairoApp from "./cairo/App.svelte"; +import { postMessage } from "./common/post-message"; +import UnsupportedVersion from "./common/UnsupportedVersion.svelte"; +import semver from "semver"; +import { compatibleContractsSemver as compatibleSolidityContractsSemver } from "@openzeppelin/wizard"; +import { compatibleContractsSemver as compatibleCairoContractsSemver } from "@openzeppelin/wizard-cairo"; +import type { InitialOptions } from "./common/initial-options.ts"; function postResize() { const { height } = document.documentElement.getBoundingClientRect(); - postMessage({ kind: 'oz-wizard-resize', height }); + postMessage({ kind: "oz-wizard-resize", height }); } window.onload = postResize; @@ -22,37 +22,51 @@ resizeObserver.observe(document.body); const params = new URLSearchParams(window.location.search); -const initialTab = params.get('tab') ?? undefined; -const lang = params.get('lang') ?? undefined; -const requestedVersion = params.get('version') ?? undefined; +const initialTab = params.get("tab") ?? undefined; +const lang = params.get("lang") ?? undefined; +const requestedVersion = params.get("version") ?? undefined; const initialOpts: InitialOptions = { - name: params.get('name') ?? undefined, - symbol: params.get('symbol') ?? undefined, - premint: params.get('premint') ?? undefined, -} + name: params.get("name") ?? undefined, + symbol: params.get("symbol") ?? undefined, + premint: params.get("premint") ?? undefined, +}; -let compatibleVersionSemver = lang === 'cairo' ? compatibleCairoContractsSemver : compatibleSolidityContractsSemver; +const compatibleVersionSemver = + lang === "cairo" + ? compatibleCairoContractsSemver + : compatibleSolidityContractsSemver; let app; -if (requestedVersion && !semver.satisfies(requestedVersion, compatibleVersionSemver)) { - postMessage({ kind: 'oz-wizard-unsupported-version' }); - app = new UnsupportedVersion({ target: document.body, props: { requestedVersion, compatibleVersionSemver }}); +if ( + requestedVersion && + !semver.satisfies(requestedVersion, compatibleVersionSemver) +) { + postMessage({ kind: "oz-wizard-unsupported-version" }); + app = new UnsupportedVersion({ + target: document.body, + props: { requestedVersion, compatibleVersionSemver }, + }); } else { switch (lang) { - case 'cairo': - app = new CairoApp({ target: document.body, props: { initialTab, initialOpts } }); + case "cairo": + app = new CairoApp({ + target: document.body, + props: { initialTab, initialOpts }, + }); break; - case 'solidity': + case "solidity": default: - app = new SolidityApp({ target: document.body, props: { initialTab, initialOpts } }); + app = new SolidityApp({ + target: document.body, + props: { initialTab, initialOpts }, + }); break; } } -app.$on('tab-change', (e: CustomEvent) => { - postMessage({ kind: 'oz-wizard-tab-change', tab: e.detail.toLowerCase() }); +app.$on("tab-change", (e: CustomEvent) => { + postMessage({ kind: "oz-wizard-tab-change", tab: e.detail.toLowerCase() }); }); export default app; - diff --git a/packages/ui/src/solidity/highlightjs.ts b/packages/ui/src/solidity/highlightjs.ts index bbe81e6f6..b387baaa6 100644 --- a/packages/ui/src/solidity/highlightjs.ts +++ b/packages/ui/src/solidity/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from 'highlight.js/lib/core'; +import hljs from "highlight.js/lib/core"; -// @ts-ignore -import hljsDefineSolidity from 'highlightjs-solidity'; +// @ts-expect-error missing type declaration file +import hljsDefineSolidity from "highlightjs-solidity"; hljsDefineSolidity(hljs); export default hljs; diff --git a/yarn.lock b/yarn.lock index 0c990aae4..437ec947e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,7 +7,7 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.22.13": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -28,6 +28,67 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0", "@eslint-community/eslint-utils@^4.4.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.19.0": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa" + integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/core@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12" + integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" + integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.20.0", "@eslint/js@^9.20.0": + version "9.20.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4" + integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.5": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz#a30084164a4ced1efb6ec31d3d04f581cb8929c0" + integrity sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA== + dependencies: + "@eslint/core" "^0.11.0" + levn "^0.4.1" + "@ethersproject/abi@^5.1.2": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -210,6 +271,34 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.1": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" + integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -484,6 +573,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" @@ -794,7 +888,7 @@ dependencies: cssnano "*" -"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0": +"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== @@ -824,6 +918,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/lru-cache@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.1.tgz#c48c2e27b65d2a153b19bfc1a317e30872e01eef" @@ -843,6 +942,11 @@ dependencies: undici-types "~5.26.4" +"@types/normalize-package-data@^2.4.3": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -894,6 +998,87 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz#d104c2a6212304c649105b18af2c110b4a1dd4ae" + integrity sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/type-utils" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/parser@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.1.tgz#67965c2d2ddd7eadb2f094c395695db8334ef9a2" + integrity sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ== + dependencies: + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/typescript-estree" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz#1e1e76ec4560aa85077ab36deb9b2bead4ae124e" + integrity sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q== + dependencies: + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + +"@typescript-eslint/type-utils@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz#99113e1df63d1571309d87eef68967344c78dd65" + integrity sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw== + dependencies: + "@typescript-eslint/typescript-estree" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + debug "^4.3.4" + ts-api-utils "^2.0.1" + +"@typescript-eslint/types@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.1.tgz#8777a024f3afc4ace5e48f9a804309c6dd38f95a" + integrity sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A== + +"@typescript-eslint/typescript-estree@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz#3bb479401f8bd471b3c6dd3db89e7256977c54db" + integrity sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg== + dependencies: + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/visitor-keys" "8.24.1" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.1.tgz#08d14eac33cfb3456feeee5a275b8ad3349e52ed" + integrity sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.24.1" + "@typescript-eslint/types" "8.24.1" + "@typescript-eslint/typescript-estree" "8.24.1" + +"@typescript-eslint/visitor-keys@8.24.1": + version "8.24.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz#8bdfe47a89195344b34eb21ef61251562148202b" + integrity sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg== + dependencies: + "@typescript-eslint/types" "8.24.1" + eslint-visitor-keys "^4.2.0" + "@vercel/nft@^0.27.5": version "0.27.6" resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.27.6.tgz#4fffb7ad4ffdb70698213cfd67596be5e9cdfb6c" @@ -922,6 +1107,11 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^8.1.1, acorn-walk@^8.3.4: version "8.3.4" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" @@ -929,7 +1119,7 @@ acorn-walk@^8.1.1, acorn-walk@^8.3.4: dependencies: acorn "^8.11.0" -acorn@^8.11.0, acorn@^8.13.0, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.8.2: +acorn@^8.11.0, acorn@^8.13.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.6.0, acorn@^8.8.2: version "8.14.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== @@ -954,6 +1144,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-align@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" @@ -1262,6 +1462,16 @@ browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.23.3: node-releases "^2.0.18" update-browserslist-db "^1.1.1" +browserslist@^4.24.3: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -1293,6 +1503,11 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== +builtin-modules@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-4.0.0.tgz#348db54ec0e6b197494423d26845f1674025ee46" + integrity sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -1349,6 +1564,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001669: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== +caniuse-lite@^1.0.30001688: + version "1.0.30001700" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6" + integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ== + cbor@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.2.tgz#536b4f2d544411e70ec2b19a2453f10f83cd9fdb" @@ -1365,7 +1585,7 @@ chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1415,7 +1635,7 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^4.0.0: +ci-info@^4.0.0, ci-info@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== @@ -1433,6 +1653,13 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.4" safe-buffer "^5.2.1" +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -1593,6 +1820,13 @@ cookie@^0.4.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +core-js-compat@^3.40.0: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38" + integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ== + dependencies: + browserslist "^4.24.3" + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -1648,7 +1882,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1856,6 +2090,13 @@ debug@4, debug@^4.1.1, debug@^4.3.5, debug@^4.3.7: dependencies: ms "^2.1.3" +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1871,6 +2112,11 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -1990,6 +2236,11 @@ electron-to-chromium@^1.5.41: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz#8289468414b0b0b3e9180ef619a763555debe612" integrity sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg== +electron-to-chromium@^1.5.73: + version "1.5.102" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f" + integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q== + elliptic@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -2120,17 +2371,138 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== +eslint-config-prettier@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz#fbb03bfc8db0651df9ce4e8b7150d11c5fe3addf" + integrity sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw== + +eslint-plugin-prettier@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz#c4af01691a6fa9905207f0fbba0d7bea0902cce5" + integrity sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.9.1" + +eslint-plugin-unicorn@^57.0.0: + version "57.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-57.0.0.tgz#4ae27a31e65b1a0307c09cb957f5de36b1773575" + integrity sha512-zUYYa6zfNdTeG9BISWDlcLmz16c+2Ck2o5ZDHh0UzXJz3DEP7xjmlVDTzbyV0W+XksgZ0q37WEWzN2D2Ze+g9Q== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + "@eslint-community/eslint-utils" "^4.4.1" + ci-info "^4.1.0" + clean-regexp "^1.0.0" + core-js-compat "^3.40.0" + esquery "^1.6.0" + globals "^15.15.0" + indent-string "^5.0.0" + is-builtin-module "^4.0.0" + jsesc "^3.1.0" + pluralize "^8.0.0" + read-package-up "^11.0.0" + regexp-tree "^0.1.27" + regjsparser "^0.12.0" + semver "^7.7.1" + strip-indent "^4.0.0" + +eslint-scope@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" + integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.20.1: + version "9.20.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6" + integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.19.0" + "@eslint/core" "^0.11.0" + "@eslint/eslintrc" "^3.2.0" + "@eslint/js" "9.20.0" + "@eslint/plugin-kit" "^0.2.5" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.1" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.2.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.5.0, esquery@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + estree-walker@2.0.2, estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== -esutils@^2.0.3: +esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -2221,7 +2593,12 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -fast-diff@^1.2.0: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2, fast-diff@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== @@ -2237,6 +2614,16 @@ fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2256,6 +2643,13 @@ figures@^6.1.0: dependencies: is-unicode-supported "^2.0.0" +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + file-saver@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" @@ -2298,11 +2692,24 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + follow-redirects@^1.12.1: version "1.15.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" @@ -2475,6 +2882,16 @@ glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.15.0: + version "15.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== + globby@^14.0.2: version "14.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-14.0.2.tgz#06554a54ccfe9264e5a9ff8eded46aa1e306482f" @@ -2499,6 +2916,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + hardhat@^2.1.1: version "2.22.16" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.16.tgz#6cf3413f63b14770f863f35452da891ac2bd50cb" @@ -2641,6 +3063,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" + integrity sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w== + dependencies: + lru-cache "^10.0.1" + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -2677,7 +3106,7 @@ ignore-by-default@^2.1.0: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-2.1.0.tgz#c0e0de1a99b6065bdc93315a6f728867981464db" integrity sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw== -ignore@^5.2.4: +ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -2715,6 +3144,11 @@ indent-string@^5.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== +index-to-position@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/index-to-position/-/index-to-position-0.1.2.tgz#e11bfe995ca4d8eddb1ec43274488f3c201a7f09" + integrity sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2760,6 +3194,13 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" +is-builtin-module@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-4.0.0.tgz#dfbf2080dad42d28af2bde71df7e4bc3f1dee6c0" + integrity sha512-rWP3AMAalQSesXO8gleROyL2iKU73SX5Er66losQn9rWOWL4Gef0a/xOEOVqjWGMuR2vHG3FJ8UUmT700O8oFg== + dependencies: + builtin-modules "^4.0.0" + is-callable@^1.1.3: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -2799,7 +3240,7 @@ is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2937,11 +3378,36 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsesc@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json-stream-stringify@^3.1.4: version "3.1.6" resolved "https://registry.yarnpkg.com/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz#ebe32193876fb99d4ec9f612389a8d8e2b5d54d4" @@ -2982,11 +3448,26 @@ keccak@^3.0.0, keccak@^3.0.2: node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kleur@^4.1.4: version "4.1.5" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lie@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" @@ -3054,6 +3535,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -3072,7 +3558,7 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -lru-cache@^10.2.0: +lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -3186,7 +3672,7 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== -min-indent@^1.0.0: +min-indent@^1.0.0, min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== @@ -3201,7 +3687,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3326,6 +3812,11 @@ nanoid@^3.3.7: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -3358,6 +3849,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + nofilter@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" @@ -3370,6 +3866,15 @@ nopt@^5.0.0: dependencies: abbrev "1" +normalize-package-data@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-6.0.2.tgz#a7bc22167fe24025412bcff0a9651eb768b03506" + integrity sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g== + dependencies: + hosted-git-info "^7.0.0" + semver "^7.3.5" + validate-npm-package-license "^3.0.4" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -3431,6 +3936,18 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + "opts@>= 1.2.0": version "2.0.2" resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.2.tgz#a17e189fbbfee171da559edd8a42423bc5993ce1" @@ -3541,6 +4058,15 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-json@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.1.0.tgz#91cdc7728004e955af9cb734de5684733b24a717" + integrity sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA== + dependencies: + "@babel/code-frame" "^7.22.13" + index-to-position "^0.1.2" + type-fest "^4.7.1" + parse-ms@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-4.0.0.tgz#c0c058edd47c2a590151a718990533fd62803df4" @@ -3642,6 +4168,11 @@ plur@^5.1.0: dependencies: irregular-plurals "^3.3.0" +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -4139,6 +4670,23 @@ postcss@^8.2.8, postcss@^8.4.47, postcss@^8.4.5: picocolors "^1.1.1" source-map-js "^1.2.1" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7" + integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw== + pretty-ms@^9.1.0: version "9.2.0" resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-9.2.0.tgz#e14c0aad6493b69ed63114442a84133d7e560ef0" @@ -4159,6 +4707,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + query-string@^7.1.0: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -4198,6 +4751,26 @@ read-cache@^1.0.0: dependencies: pify "^2.3.0" +read-package-up@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/read-package-up/-/read-package-up-11.0.0.tgz#71fb879fdaac0e16891e6e666df22de24a48d5ba" + integrity sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ== + dependencies: + find-up-simple "^1.0.0" + read-pkg "^9.0.0" + type-fest "^4.6.0" + +read-pkg@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-9.0.1.tgz#b1b81fb15104f5dbb121b6bbdee9bbc9739f569b" + integrity sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA== + dependencies: + "@types/normalize-package-data" "^2.4.3" + normalize-package-data "^6.0.0" + parse-json "^8.0.0" + type-fest "^4.6.0" + unicorn-magic "^0.1.0" + readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -4232,6 +4805,18 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +regexp-tree@^0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4470,6 +5055,11 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -4642,6 +5232,32 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.21" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz#6d6e980c9df2b6fc905343a3b2d702a6239536c3" + integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -4794,6 +5410,13 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853" + integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA== + dependencies: + min-indent "^1.0.1" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -4918,6 +5541,14 @@ svgo@^3.3.2: csso "^5.0.5" picocolors "^1.0.0" +synckit@^0.9.1: + version "0.9.2" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62" + integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + tailwindcss@^3.0.15: version "3.4.15" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.15.tgz#04808bf4bf1424b105047d19e7d4bfab368044a9" @@ -5051,6 +5682,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +ts-api-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd" + integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" @@ -5080,7 +5716,7 @@ tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.3.1: +tslib@^2.0.0, tslib@^2.3.1, tslib@^2.6.2: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -5100,6 +5736,13 @@ tweetnacl@^1.0.3: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" @@ -5120,7 +5763,21 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== -typescript@^5.0.0: +type-fest@^4.6.0, type-fest@^4.7.1: + version "4.35.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.35.0.tgz#007ed74d65c2ca0fb3b564b3dc8170d5c872d665" + integrity sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A== + +typescript-eslint@^8.24.1: + version "8.24.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.24.1.tgz#ce85d791392608a2a9f80c4b2530a214d16a2a47" + integrity sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.24.1" + "@typescript-eslint/parser" "8.24.1" + "@typescript-eslint/utils" "8.24.1" + +typescript@^5.0.0, typescript@^5.7.3: version "5.7.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== @@ -5175,6 +5832,13 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -5201,6 +5865,14 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validate-npm-package-license@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -5263,6 +5935,11 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + workerpool@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" From 365421ba52a4f977394177e931d67695dac39944 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Thu, 20 Feb 2025 20:05:13 -0500 Subject: [PATCH 06/16] After running with prettier --- eslint.config.mjs | 2 +- packages/core/cairo/ava.config.js | 12 +- packages/core/cairo/src/account.test.ts | 145 ++--- packages/core/cairo/src/account.ts | 160 +++--- packages/core/cairo/src/add-pausable.ts | 75 +-- packages/core/cairo/src/api.ts | 84 ++- packages/core/cairo/src/common-components.ts | 102 ++-- packages/core/cairo/src/common-options.ts | 18 +- packages/core/cairo/src/contract.test.ts | 139 +++-- packages/core/cairo/src/contract.ts | 73 ++- packages/core/cairo/src/custom.test.ts | 63 +- packages/core/cairo/src/custom.ts | 24 +- packages/core/cairo/src/erc1155.test.ts | 129 +++-- packages/core/cairo/src/erc1155.ts | 8 +- packages/core/cairo/src/erc20.test.ts | 12 +- packages/core/cairo/src/erc20.ts | 10 +- packages/core/cairo/src/erc721.test.ts | 22 +- packages/core/cairo/src/erc721.ts | 251 ++++---- packages/core/cairo/src/external-trait.ts | 13 +- packages/core/cairo/src/generate/account.ts | 10 +- .../core/cairo/src/generate/alternatives.ts | 2 +- packages/core/cairo/src/generate/custom.ts | 12 +- packages/core/cairo/src/generate/erc1155.ts | 22 +- packages/core/cairo/src/generate/erc20.ts | 22 +- packages/core/cairo/src/generate/erc721.ts | 28 +- packages/core/cairo/src/generate/governor.ts | 33 +- packages/core/cairo/src/generate/sources.ts | 12 +- packages/core/cairo/src/generate/vesting.ts | 18 +- packages/core/cairo/src/governor.test.ts | 207 +++---- packages/core/cairo/src/governor.ts | 539 ++++++++++-------- packages/core/cairo/src/index.ts | 68 ++- packages/core/cairo/src/kind.ts | 25 +- packages/core/cairo/src/print.ts | 50 +- .../cairo/src/scripts/update-scarb-project.ts | 8 +- packages/core/cairo/src/set-access-control.ts | 155 +++-- packages/core/cairo/src/set-info.ts | 6 +- packages/core/cairo/src/set-royalty-info.ts | 124 ++-- packages/core/cairo/src/set-upgradeable.ts | 119 ++-- packages/core/cairo/src/test.ts | 6 +- .../cairo/src/utils/convert-strings.test.ts | 12 +- .../core/cairo/src/utils/convert-strings.ts | 63 +- .../core/cairo/src/utils/define-components.ts | 4 +- .../core/cairo/src/utils/define-functions.ts | 4 +- packages/core/cairo/src/utils/duration.ts | 18 +- packages/core/cairo/src/utils/find-cover.ts | 11 +- packages/core/cairo/src/utils/format-lines.ts | 12 +- packages/core/cairo/src/utils/version.test.ts | 14 +- packages/core/cairo/src/utils/version.ts | 10 +- packages/core/cairo/src/vesting.test.ts | 110 ++-- packages/core/cairo/src/vesting.ts | 4 +- packages/core/solidity/ava.config.js | 12 +- packages/core/solidity/get-imports.d.ts | 2 +- packages/core/solidity/get-imports.js | 2 +- packages/core/solidity/hardhat.config.js | 43 +- packages/core/solidity/print-versioned.js | 2 +- packages/core/solidity/print-versioned.ts | 2 +- packages/core/solidity/src/add-pausable.ts | 30 +- packages/core/solidity/src/api.ts | 82 ++- .../core/solidity/src/common-functions.ts | 14 +- packages/core/solidity/src/common-options.ts | 4 +- packages/core/solidity/src/contract.test.ts | 165 +++--- packages/core/solidity/src/contract.ts | 8 +- packages/core/solidity/src/custom.test.ts | 89 +-- packages/core/solidity/src/custom.ts | 23 +- packages/core/solidity/src/erc1155.test.ts | 122 ++-- packages/core/solidity/src/erc1155.ts | 107 ++-- packages/core/solidity/src/erc20.test.ts | 131 +++-- packages/core/solidity/src/erc20.ts | 2 +- packages/core/solidity/src/erc721.test.ts | 112 ++-- packages/core/solidity/src/erc721.ts | 127 +++-- .../solidity/src/generate/alternatives.ts | 4 +- packages/core/solidity/src/generate/custom.ts | 12 +- .../core/solidity/src/generate/erc1155.ts | 14 +- packages/core/solidity/src/generate/erc20.ts | 20 +- packages/core/solidity/src/generate/erc721.ts | 20 +- .../core/solidity/src/generate/governor.ts | 33 +- .../core/solidity/src/generate/sources.ts | 99 ++-- .../core/solidity/src/generate/stablecoin.ts | 26 +- .../core/solidity/src/get-imports.test.ts | 129 +++-- packages/core/solidity/src/get-imports.ts | 22 +- packages/core/solidity/src/governor.test.ts | 166 +++--- packages/core/solidity/src/governor.ts | 22 +- packages/core/solidity/src/index.ts | 40 +- .../solidity/src/infer-transpiled.test.ts | 20 +- .../core/solidity/src/infer-transpiled.ts | 2 +- packages/core/solidity/src/kind.ts | 25 +- packages/core/solidity/src/options.ts | 29 +- packages/core/solidity/src/print-versioned.ts | 15 +- packages/core/solidity/src/print.ts | 28 +- packages/core/solidity/src/scripts/prepare.ts | 59 +- .../core/solidity/src/set-access-control.ts | 74 +-- packages/core/solidity/src/set-clock-mode.ts | 37 +- packages/core/solidity/src/set-info.ts | 11 +- packages/core/solidity/src/set-upgradeable.ts | 45 +- packages/core/solidity/src/stablecoin.test.ts | 122 ++-- packages/core/solidity/src/stablecoin.ts | 133 +++-- packages/core/solidity/src/test.ts | 83 +-- .../solidity/src/utils/define-functions.ts | 4 +- packages/core/solidity/src/utils/duration.ts | 31 +- .../core/solidity/src/utils/find-cover.ts | 11 +- .../core/solidity/src/utils/format-lines.ts | 17 +- .../core/solidity/src/utils/map-values.ts | 2 +- .../solidity/src/utils/to-identifier.test.ts | 22 +- .../core/solidity/src/utils/to-identifier.ts | 7 +- .../solidity/src/utils/transitive-closure.ts | 4 +- .../core/solidity/src/utils/version.test.ts | 16 +- packages/core/solidity/src/utils/version.ts | 2 +- .../core/solidity/src/zip-foundry.test.ts | 160 ++++-- packages/core/solidity/src/zip-foundry.ts | 184 +++--- .../core/solidity/src/zip-hardhat.test.ts | 144 +++-- packages/core/solidity/src/zip-hardhat.ts | 6 +- packages/core/solidity/zip-env-foundry.js | 2 +- packages/core/solidity/zip-env-foundry.ts | 2 +- packages/core/solidity/zip-env-hardhat.js | 2 +- packages/core/solidity/zip-env-hardhat.ts | 2 +- packages/ui/api/ai.ts | 2 +- packages/ui/postcss.config.js | 12 +- packages/ui/rollup.config.mjs | 92 +-- packages/ui/rollup.server.mjs | 18 +- packages/ui/src/cairo/inject-hyperlinks.ts | 4 +- packages/ui/src/common/initial-options.ts | 6 +- packages/ui/src/common/post-config.ts | 29 +- packages/ui/src/common/post-message.ts | 2 +- packages/ui/src/common/resize-to-fit.ts | 2 +- packages/ui/src/embed.ts | 69 ++- packages/ui/src/solidity/inject-hyperlinks.ts | 18 +- packages/ui/src/solidity/remix.ts | 6 +- packages/ui/src/solidity/wiz-functions.ts | 313 +++++++--- packages/ui/src/standalone.js | 2 +- packages/ui/svelte.config.js | 2 +- packages/ui/tailwind.config.js | 43 +- scripts/bump-changelog.js | 19 +- 132 files changed, 3810 insertions(+), 2886 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 7a17e8293..e5715f264 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -58,5 +58,5 @@ export default typescriptEslint.config( rules: { "@typescript-eslint/no-require-imports": "off", }, - } + }, ); diff --git a/packages/core/cairo/ava.config.js b/packages/core/cairo/ava.config.js index 77a9117a9..a39075959 100644 --- a/packages/core/cairo/ava.config.js +++ b/packages/core/cairo/ava.config.js @@ -1,13 +1,9 @@ module.exports = { - extensions: ['ts'], - require: ['ts-node/register'], + extensions: ["ts"], + require: ["ts-node/register"], watchmode: { - ignoreChanges: [ - 'contracts', - 'artifacts', - 'cache', - ], + ignoreChanges: ["contracts", "artifacts", "cache"], }, - timeout: '10m', + timeout: "10m", workerThreads: false, }; diff --git a/packages/core/cairo/src/account.test.ts b/packages/core/cairo/src/account.test.ts index 217e1d813..4b4097e5c 100644 --- a/packages/core/cairo/src/account.test.ts +++ b/packages/core/cairo/src/account.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; +import test from "ava"; -import { buildAccount, AccountOptions } from './account'; -import { printContract } from './print'; +import { buildAccount, AccountOptions } from "./account"; +import { printContract } from "./print"; -import { account } from '.'; +import { account } from "."; function testAccount(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildAccount({ - name: 'MyAccount', - type: 'stark', + name: "MyAccount", + type: "stark", ...opts, }); t.snapshot(printContract(c)); @@ -17,10 +17,10 @@ function testAccount(title: string, opts: Partial) { } function testEthAccount(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildAccount({ - name: 'MyAccount', - type: 'eth', + name: "MyAccount", + type: "eth", ...opts, }); t.snapshot(printContract(c)); @@ -31,184 +31,189 @@ function testEthAccount(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: AccountOptions) { - test(title, t => { - t.is(account.print(opts), printContract(buildAccount({ - name: 'MyAccount', - type: 'stark', - declare: true, - deploy: true, - pubkey: true, - outsideExecution: true, - ...opts, - }))); + test(title, (t) => { + t.is( + account.print(opts), + printContract( + buildAccount({ + name: "MyAccount", + type: "stark", + declare: true, + deploy: true, + pubkey: true, + outsideExecution: true, + ...opts, + }), + ), + ); }); } -testAccount('default full account, mixin + upgradeable', {}); +testAccount("default full account, mixin + upgradeable", {}); -testAccount('default full account, mixin + non-upgradeable', { - upgradeable: false +testAccount("default full account, mixin + non-upgradeable", { + upgradeable: false, }); -testAccount('explicit full account, mixin + upgradeable', { - name: 'MyAccount', - type: 'stark', +testAccount("explicit full account, mixin + upgradeable", { + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: true + upgradeable: true, }); -testAccount('explicit full account, mixin + non-upgradeable', { - name: 'MyAccount', - type: 'stark', +testAccount("explicit full account, mixin + non-upgradeable", { + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: false + upgradeable: false, }); -testAccount('basic account, upgradeable', { +testAccount("basic account, upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testAccount('basic account, non-upgradeable', { +testAccount("basic account, non-upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, - upgradeable: false + upgradeable: false, }); -testAccount('account outside execution', { +testAccount("account outside execution", { deploy: false, pubkey: false, declare: false, }); -testAccount('account declarer', { +testAccount("account declarer", { deploy: false, pubkey: false, outsideExecution: false, }); -testAccount('account deployable', { +testAccount("account deployable", { declare: false, pubkey: false, outsideExecution: false, }); -testAccount('account public key', { +testAccount("account public key", { declare: false, deploy: false, outsideExecution: false, }); -testAccount('account declarer, deployable', { +testAccount("account declarer, deployable", { pubkey: false, outsideExecution: false, }); -testAccount('account declarer, public key', { +testAccount("account declarer, public key", { deploy: false, outsideExecution: false, }); -testAccount('account deployable, public key', { +testAccount("account deployable, public key", { declare: false, outsideExecution: false, }); -testEthAccount('default full ethAccount, mixin + upgradeable', {}); +testEthAccount("default full ethAccount, mixin + upgradeable", {}); -testEthAccount('default full ethAccount, mixin + non-upgradeable', { - upgradeable: false +testEthAccount("default full ethAccount, mixin + non-upgradeable", { + upgradeable: false, }); -testEthAccount('explicit full ethAccount, mixin + upgradeable', { - name: 'MyAccount', - type: 'eth', +testEthAccount("explicit full ethAccount, mixin + upgradeable", { + name: "MyAccount", + type: "eth", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: true + upgradeable: true, }); -testEthAccount('explicit full ethAccount, mixin + non-upgradeable', { - name: 'MyAccount', - type: 'eth', +testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { + name: "MyAccount", + type: "eth", declare: true, deploy: true, pubkey: true, outsideExecution: true, - upgradeable: false + upgradeable: false, }); -testEthAccount('basic ethAccount, upgradeable', { +testEthAccount("basic ethAccount, upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount('basic ethAccount, non-upgradeable', { +testEthAccount("basic ethAccount, non-upgradeable", { declare: false, deploy: false, pubkey: false, outsideExecution: false, - upgradeable: false + upgradeable: false, }); -testEthAccount('ethAccount outside execution', { +testEthAccount("ethAccount outside execution", { deploy: false, pubkey: false, declare: false, }); -testEthAccount('ethAccount declarer', { +testEthAccount("ethAccount declarer", { deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount deployable', { +testEthAccount("ethAccount deployable", { declare: false, pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount public key', { +testEthAccount("ethAccount public key", { declare: false, deploy: false, outsideExecution: false, }); -testEthAccount('ethAccount declarer, deployable', { +testEthAccount("ethAccount declarer, deployable", { pubkey: false, outsideExecution: false, }); -testEthAccount('ethAccount declarer, public key', { +testEthAccount("ethAccount declarer, public key", { deploy: false, outsideExecution: false, }); -testEthAccount('ethAccount deployable, public key', { +testEthAccount("ethAccount deployable, public key", { declare: false, outsideExecution: false, }); -testAPIEquivalence('account API default'); +testAPIEquivalence("account API default"); -testAPIEquivalence('account API basic', { - name: 'CustomAccount', - type: 'stark', +testAPIEquivalence("account API basic", { + name: "CustomAccount", + type: "stark", declare: false, deploy: false, pubkey: false, @@ -216,9 +221,9 @@ testAPIEquivalence('account API basic', { upgradeable: false, }); -testAPIEquivalence('account API full upgradeable', { - name: 'CustomAccount', - type: 'stark', +testAPIEquivalence("account API full upgradeable", { + name: "CustomAccount", + type: "stark", declare: true, deploy: true, pubkey: true, @@ -226,6 +231,6 @@ testAPIEquivalence('account API full upgradeable', { upgradeable: true, }); -test('account API assert defaults', async t => { +test("account API assert defaults", async (t) => { t.is(account.print(account.defaults), account.print()); }); diff --git a/packages/core/cairo/src/account.ts b/packages/core/cairo/src/account.ts index 68914729e..5f1659de9 100644 --- a/packages/core/cairo/src/account.ts +++ b/packages/core/cairo/src/account.ts @@ -1,25 +1,24 @@ -import { Contract, ContractBuilder } from './contract'; -import { CommonOptions, withCommonDefaults } from './common-options'; -import { defaults as commonDefaults } from './common-options'; -import { setAccountUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { printContract } from './print'; -import { addSRC5Component } from './common-components'; - - -export const accountTypes = ['stark', 'eth'] as const; -export type Account = typeof accountTypes[number]; +import { Contract, ContractBuilder } from "./contract"; +import { CommonOptions, withCommonDefaults } from "./common-options"; +import { defaults as commonDefaults } from "./common-options"; +import { setAccountUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { printContract } from "./print"; +import { addSRC5Component } from "./common-components"; + +export const accountTypes = ["stark", "eth"] as const; +export type Account = (typeof accountTypes)[number]; export const defaults: Required = { - name: 'MyAccount', - type: 'stark', + name: "MyAccount", + type: "stark", declare: true, deploy: true, pubkey: true, outsideExecution: true, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printAccount(opts: AccountOptions = defaults): string { @@ -42,8 +41,8 @@ function withDefaults(opts: AccountOptions): Required { declare: opts.declare ?? defaults.declare, deploy: opts.deploy ?? defaults.deploy, pubkey: opts.pubkey ?? defaults.pubkey, - outsideExecution: opts.outsideExecution ?? defaults.outsideExecution - } + outsideExecution: opts.outsideExecution ?? defaults.outsideExecution, + }; } export function buildAccount(opts: AccountOptions): Contract { @@ -53,14 +52,22 @@ export function buildAccount(opts: AccountOptions): Contract { const allOpts = withDefaults(opts); switch (allOpts.type) { - case 'stark': - c.addConstructorArgument({ name: 'public_key', type: 'felt252' }); - c.addComponent(components.AccountComponent, [{ lit: 'public_key' }], true); + case "stark": + c.addConstructorArgument({ name: "public_key", type: "felt252" }); + c.addComponent( + components.AccountComponent, + [{ lit: "public_key" }], + true, + ); break; - case 'eth': - c.addUseClause('openzeppelin::account::interface', 'EthPublicKey'); - c.addConstructorArgument({ name: 'public_key', type: 'EthPublicKey' }); - c.addComponent(components.EthAccountComponent, [{ lit: 'public_key' }], true); + case "eth": + c.addUseClause("openzeppelin::account::interface", "EthPublicKey"); + c.addConstructorArgument({ name: "public_key", type: "EthPublicKey" }); + c.addComponent( + components.EthAccountComponent, + [{ lit: "public_key" }], + true, + ); break; } @@ -96,11 +103,11 @@ function addSRC6(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'SRC6Impl', + name: "SRC6Impl", value: `${baseComponent}::SRC6Impl`, }); c.addImplToComponent(componentType, { - name: 'SRC6CamelOnlyImpl', + name: "SRC6CamelOnlyImpl", value: `${baseComponent}::SRC6CamelOnlyImpl`, }); @@ -111,7 +118,7 @@ function addDeclarer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'DeclarerImpl', + name: "DeclarerImpl", value: `${baseComponent}::DeclarerImpl`, }); } @@ -120,7 +127,7 @@ function addDeployer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'DeployableImpl', + name: "DeployableImpl", value: `${baseComponent}::DeployableImpl`, }); } @@ -129,22 +136,23 @@ function addPublicKey(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: 'PublicKeyImpl', + name: "PublicKeyImpl", value: `${baseComponent}::PublicKeyImpl`, }); c.addImplToComponent(componentType, { - name: 'PublicKeyCamelImpl', + name: "PublicKeyCamelImpl", value: `${baseComponent}::PublicKeyCamelImpl`, }); } function addOutsideExecution(c: ContractBuilder) { - c.addUseClause('openzeppelin::account::extensions', 'SRC9Component'); + c.addUseClause("openzeppelin::account::extensions", "SRC9Component"); c.addComponent(components.SRC9Component, [], true); } function addAccountMixin(c: ContractBuilder, accountType: Account) { - const accountMixinImpl = accountType === 'stark' ? 'AccountMixinImpl' : 'EthAccountMixinImpl'; + const accountMixinImpl = + accountType === "stark" ? "AccountMixinImpl" : "EthAccountMixinImpl"; const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { @@ -152,65 +160,77 @@ function addAccountMixin(c: ContractBuilder, accountType: Account) { value: `${baseComponent}::${accountMixinImpl}`, }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } -function getBaseCompAndCompType(accountType: Account): [string, typeof componentType] { - const [baseComponent, componentType] = accountType === 'stark' ? ['AccountComponent', components.AccountComponent] : ['EthAccountComponent', components.EthAccountComponent]; +function getBaseCompAndCompType( + accountType: Account, +): [string, typeof componentType] { + const [baseComponent, componentType] = + accountType === "stark" + ? ["AccountComponent", components.AccountComponent] + : ["EthAccountComponent", components.EthAccountComponent]; return [baseComponent, componentType]; } -const components = defineComponents( { +const components = defineComponents({ AccountComponent: { - path: 'openzeppelin::account', + path: "openzeppelin::account", substorage: { - name: 'account', - type: 'AccountComponent::Storage', + name: "account", + type: "AccountComponent::Storage", }, event: { - name: 'AccountEvent', - type: 'AccountComponent::Event', + name: "AccountEvent", + type: "AccountComponent::Event", }, - impls: [{ - name: 'AccountInternalImpl', - embed: false, - value: 'AccountComponent::InternalImpl', - }], + impls: [ + { + name: "AccountInternalImpl", + embed: false, + value: "AccountComponent::InternalImpl", + }, + ], }, EthAccountComponent: { - path: 'openzeppelin::account::eth_account', + path: "openzeppelin::account::eth_account", substorage: { - name: 'eth_account', - type: 'EthAccountComponent::Storage', + name: "eth_account", + type: "EthAccountComponent::Storage", }, event: { - name: 'EthAccountEvent', - type: 'EthAccountComponent::Event', + name: "EthAccountEvent", + type: "EthAccountComponent::Event", }, - impls: [{ - name: 'EthAccountInternalImpl', - embed: false, - value: 'EthAccountComponent::InternalImpl', - }] + impls: [ + { + name: "EthAccountInternalImpl", + embed: false, + value: "EthAccountComponent::InternalImpl", + }, + ], }, SRC9Component: { - path: 'openzeppelin::account::extensions', + path: "openzeppelin::account::extensions", substorage: { - name: 'src9', - type: 'SRC9Component::Storage', + name: "src9", + type: "SRC9Component::Storage", }, event: { - name: 'SRC9Event', - type: 'SRC9Component::Event', + name: "SRC9Event", + type: "SRC9Component::Event", }, - impls: [{ - name: 'OutsideExecutionV2Impl', - value: 'SRC9Component::OutsideExecutionV2Impl', - }, { - name: 'OutsideExecutionInternalImpl', - embed: false, - value: 'SRC9Component::InternalImpl', - }] - } + impls: [ + { + name: "OutsideExecutionV2Impl", + value: "SRC9Component::OutsideExecutionV2Impl", + }, + { + name: "OutsideExecutionInternalImpl", + embed: false, + value: "SRC9Component::InternalImpl", + }, + ], + }, }); diff --git a/packages/core/cairo/src/add-pausable.ts b/packages/core/cairo/src/add-pausable.ts index 1e950f98e..4b33dd23b 100644 --- a/packages/core/cairo/src/add-pausable.ts +++ b/packages/core/cairo/src/add-pausable.ts @@ -1,58 +1,65 @@ -import { getSelfArg } from './common-options'; -import type { ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; -import { defineComponents } from './utils/define-components'; -import { externalTrait } from './external-trait'; +import { getSelfArg } from "./common-options"; +import type { ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; +import { defineComponents } from "./utils/define-components"; +import { externalTrait } from "./external-trait"; export function addPausable(c: ContractBuilder, access: Access) { c.addComponent(components.PausableComponent, [], false); c.addFunction(externalTrait, functions.pause); c.addFunction(externalTrait, functions.unpause); - requireAccessControl(c, externalTrait, functions.pause, access, 'PAUSER', 'pauser'); - requireAccessControl(c, externalTrait, functions.unpause, access, 'PAUSER', 'pauser'); + requireAccessControl( + c, + externalTrait, + functions.pause, + access, + "PAUSER", + "pauser", + ); + requireAccessControl( + c, + externalTrait, + functions.unpause, + access, + "PAUSER", + "pauser", + ); } -const components = defineComponents( { +const components = defineComponents({ PausableComponent: { - path: 'openzeppelin::security::pausable', + path: "openzeppelin::security::pausable", substorage: { - name: 'pausable', - type: 'PausableComponent::Storage', + name: "pausable", + type: "PausableComponent::Storage", }, event: { - name: 'PausableEvent', - type: 'PausableComponent::Event', + name: "PausableEvent", + type: "PausableComponent::Event", }, - impls: [{ - name: 'PausableImpl', - value: 'PausableComponent::PausableImpl', - }, { - name: 'PausableInternalImpl', + impls: [ + { + name: "PausableImpl", + value: "PausableComponent::PausableImpl", + }, + { + name: "PausableInternalImpl", embed: false, - value: 'PausableComponent::InternalImpl', - } + value: "PausableComponent::InternalImpl", + }, ], }, }); const functions = defineFunctions({ pause: { - args: [ - getSelfArg(), - ], - code: [ - 'self.pausable.pause()' - ] + args: [getSelfArg()], + code: ["self.pausable.pause()"], }, unpause: { - args: [ - getSelfArg(), - ], - code: [ - 'self.pausable.unpause()' - ] + args: [getSelfArg()], + code: ["self.pausable.unpause()"], }, }); - diff --git a/packages/core/cairo/src/api.ts b/packages/core/cairo/src/api.ts index a70fc497d..67bca280b 100644 --- a/packages/core/cairo/src/api.ts +++ b/packages/core/cairo/src/api.ts @@ -1,13 +1,45 @@ -import type { CommonOptions, CommonContractOptions } from './common-options'; -import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; -import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; -import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; -import { printAccount, defaults as accountDefaults, AccountOptions } from './account'; -import { printGovernor, defaults as governorDefaults, GovernorOptions } from './governor'; -import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; -import { printVesting, defaults as vestingDefaults, VestingOptions } from './vesting'; +import type { CommonOptions, CommonContractOptions } from "./common-options"; +import { + printERC20, + defaults as erc20defaults, + isAccessControlRequired as erc20IsAccessControlRequired, + ERC20Options, +} from "./erc20"; +import { + printERC721, + defaults as erc721defaults, + isAccessControlRequired as erc721IsAccessControlRequired, + ERC721Options, +} from "./erc721"; +import { + printERC1155, + defaults as erc1155defaults, + isAccessControlRequired as erc1155IsAccessControlRequired, + ERC1155Options, +} from "./erc1155"; +import { + printAccount, + defaults as accountDefaults, + AccountOptions, +} from "./account"; +import { + printGovernor, + defaults as governorDefaults, + GovernorOptions, +} from "./governor"; +import { + printCustom, + defaults as customDefaults, + isAccessControlRequired as customIsAccessControlRequired, + CustomOptions, +} from "./custom"; +import { + printVesting, + defaults as vestingDefaults, + VestingOptions, +} from "./vesting"; -export interface WizardAccountAPI{ +export interface WizardAccountAPI { /** * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. */ @@ -33,49 +65,53 @@ export interface WizardContractAPI { export interface AccessControlAPI { /** - * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the + * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. */ isAccessControlRequired: (opts: Partial) => boolean; } -export type ERC20 = WizardContractAPI & AccessControlAPI; -export type ERC721 = WizardContractAPI & AccessControlAPI; -export type ERC1155 = WizardContractAPI & AccessControlAPI; +export type ERC20 = WizardContractAPI & + AccessControlAPI; +export type ERC721 = WizardContractAPI & + AccessControlAPI; +export type ERC1155 = WizardContractAPI & + AccessControlAPI; export type Account = WizardAccountAPI; export type Governor = WizardContractAPI; export type Vesting = WizardContractAPI; -export type Custom = WizardContractAPI & AccessControlAPI; +export type Custom = WizardContractAPI & + AccessControlAPI; export const erc20: ERC20 = { print: printERC20, defaults: erc20defaults, isAccessControlRequired: erc20IsAccessControlRequired, -} +}; export const erc721: ERC721 = { print: printERC721, defaults: erc721defaults, - isAccessControlRequired: erc721IsAccessControlRequired -} + isAccessControlRequired: erc721IsAccessControlRequired, +}; export const erc1155: ERC1155 = { print: printERC1155, defaults: erc1155defaults, - isAccessControlRequired: erc1155IsAccessControlRequired -} + isAccessControlRequired: erc1155IsAccessControlRequired, +}; export const account: Account = { print: printAccount, defaults: accountDefaults, -} +}; export const governor: Governor = { print: printGovernor, defaults: governorDefaults, -} +}; export const vesting: Vesting = { print: printVesting, defaults: vestingDefaults, -} +}; export const custom: Custom = { print: printCustom, defaults: customDefaults, - isAccessControlRequired: customIsAccessControlRequired -} + isAccessControlRequired: customIsAccessControlRequired, +}; diff --git a/packages/core/cairo/src/common-components.ts b/packages/core/cairo/src/common-components.ts index a05776fdd..90615a056 100644 --- a/packages/core/cairo/src/common-components.ts +++ b/packages/core/cairo/src/common-components.ts @@ -1,88 +1,100 @@ -import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; import { defineComponents } from "./utils/define-components"; -export const tokenTypes = ['ERC20', 'ERC721'] as const; -export type Token = typeof tokenTypes[number]; +export const tokenTypes = ["ERC20", "ERC721"] as const; +export type Token = (typeof tokenTypes)[number]; -const components = defineComponents( { +const components = defineComponents({ SRC5Component: { - path: 'openzeppelin::introspection::src5', + path: "openzeppelin::introspection::src5", substorage: { - name: 'src5', - type: 'SRC5Component::Storage', + name: "src5", + type: "SRC5Component::Storage", }, event: { - name: 'SRC5Event', - type: 'SRC5Component::Event', + name: "SRC5Event", + type: "SRC5Component::Event", }, impls: [], }, VotesComponent: { - path: 'openzeppelin::governance::votes', + path: "openzeppelin::governance::votes", substorage: { - name: 'votes', - type: 'VotesComponent::Storage', + name: "votes", + type: "VotesComponent::Storage", }, event: { - name: 'VotesEvent', - type: 'VotesComponent::Event', + name: "VotesEvent", + type: "VotesComponent::Event", }, - impls: [{ - name: 'VotesInternalImpl', - embed: false, - value: 'VotesComponent::InternalImpl', - }], + impls: [ + { + name: "VotesInternalImpl", + embed: false, + value: "VotesComponent::InternalImpl", + }, + ], }, NoncesComponent: { - path: 'openzeppelin::utils::cryptography::nonces', + path: "openzeppelin::utils::cryptography::nonces", substorage: { - name: 'nonces', - type: 'NoncesComponent::Storage', + name: "nonces", + type: "NoncesComponent::Storage", }, event: { - name: 'NoncesEvent', - type: 'NoncesComponent::Event', + name: "NoncesEvent", + type: "NoncesComponent::Event", }, impls: [ { - name: 'NoncesImpl', - value: 'NoncesComponent::NoncesImpl', + name: "NoncesImpl", + value: "NoncesComponent::NoncesImpl", }, ], }, -}) +}); export function addSRC5Component(c: ContractBuilder, section?: string) { c.addComponent(components.SRC5Component, [], false); - if (!c.interfaceFlags.has('ISRC5')) { + if (!c.interfaceFlags.has("ISRC5")) { c.addImplToComponent(components.SRC5Component, { - name: 'SRC5Impl', - value: 'SRC5Component::SRC5Impl', + name: "SRC5Impl", + value: "SRC5Component::SRC5Impl", section, }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); } } -export function addVotesComponent(c: ContractBuilder, name: string, version: string, section?: string) { +export function addVotesComponent( + c: ContractBuilder, + name: string, + version: string, + section?: string, +) { addSNIP12Metadata(c, name, version, section); c.addComponent(components.NoncesComponent, [], false); c.addComponent(components.VotesComponent, [], false); c.addImplToComponent(components.VotesComponent, { - name: 'VotesImpl', + name: "VotesImpl", value: `VotesComponent::VotesImpl`, }); } -export function addSNIP12Metadata(c: ContractBuilder, name: string, version: string, section?: string) { - c.addUseClause('openzeppelin::utils::cryptography::snip12', 'SNIP12Metadata'); +export function addSNIP12Metadata( + c: ContractBuilder, + name: string, + version: string, + section?: string, +) { + c.addUseClause("openzeppelin::utils::cryptography::snip12", "SNIP12Metadata"); const SNIP12Metadata: BaseImplementedTrait = { - name: 'SNIP12MetadataImpl', - of: 'SNIP12Metadata', + name: "SNIP12MetadataImpl", + of: "SNIP12Metadata", tags: [], priority: 0, section, @@ -90,20 +102,16 @@ export function addSNIP12Metadata(c: ContractBuilder, name: string, version: str c.addImplementedTrait(SNIP12Metadata); c.addFunction(SNIP12Metadata, { - name: 'name', + name: "name", args: [], - returns: 'felt252', - code: [ - `'${name}'`, - ], + returns: "felt252", + code: [`'${name}'`], }); c.addFunction(SNIP12Metadata, { - name: 'version', + name: "version", args: [], - returns: 'felt252', - code: [ - `'${version}'`, - ], + returns: "felt252", + code: [`'${version}'`], }); } diff --git a/packages/core/cairo/src/common-options.ts b/packages/core/cairo/src/common-options.ts index 067ea317a..de54d727c 100644 --- a/packages/core/cairo/src/common-options.ts +++ b/packages/core/cairo/src/common-options.ts @@ -23,24 +23,28 @@ export interface CommonContractOptions extends CommonOptions { access?: Access; } -export function withCommonDefaults(opts: CommonOptions): Required { +export function withCommonDefaults( + opts: CommonOptions, +): Required { return { upgradeable: opts.upgradeable ?? defaults.upgradeable, info: opts.info ?? defaults.info, }; } -export function withCommonContractDefaults(opts: CommonContractOptions): Required { +export function withCommonContractDefaults( + opts: CommonContractOptions, +): Required { return { ...withCommonDefaults(opts), access: opts.access ?? contractDefaults.access, }; } -export function getSelfArg(scope: 'external' | 'view' = 'external'): Argument { - if (scope === 'view') { - return { name: 'self', type: '@ContractState' }; +export function getSelfArg(scope: "external" | "view" = "external"): Argument { + if (scope === "view") { + return { name: "self", type: "@ContractState" }; } else { - return { name: 'ref self', type: 'ContractState' }; + return { name: "ref self", type: "ContractState" }; } -} \ No newline at end of file +} diff --git a/packages/core/cairo/src/contract.test.ts b/packages/core/cairo/src/contract.test.ts index 2dcaae2aa..c6351dcc2 100644 --- a/packages/core/cairo/src/contract.test.ts +++ b/packages/core/cairo/src/contract.test.ts @@ -1,123 +1,112 @@ -import test from 'ava'; +import test from "ava"; -import { ContractBuilder, BaseFunction, BaseImplementedTrait, Component } from './contract'; -import { printContract } from './print'; +import { + ContractBuilder, + BaseFunction, + BaseImplementedTrait, + Component, +} from "./contract"; +import { printContract } from "./print"; const FOO_COMPONENT: Component = { - name: 'FooComponent', - path: 'some::path', + name: "FooComponent", + path: "some::path", substorage: { - name: 'foo', - type: 'FooComponent::Storage', + name: "foo", + type: "FooComponent::Storage", }, event: { - name: 'FooEvent', - type: 'FooComponent::Event', + name: "FooEvent", + type: "FooComponent::Event", }, - impls: [{ - name: 'FooImpl', - value: 'FooComponent::FooImpl', - }, { - name: 'FooInternalImpl', + impls: [ + { + name: "FooImpl", + value: "FooComponent::FooImpl", + }, + { + name: "FooInternalImpl", embed: false, - value: 'FooComponent::InternalImpl', - } + value: "FooComponent::InternalImpl", + }, ], }; -test('contract basics', t => { - const Foo = new ContractBuilder('Foo'); +test("contract basics", (t) => { + const Foo = new ContractBuilder("Foo"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('someFunction()'); +test("contract with constructor code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("someFunction()"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code with semicolon', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('someFunction();'); +test("contract with constructor code with semicolon", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("someFunction();"); t.snapshot(printContract(Foo)); }); -test('contract with function code before', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with function code before", (t) => { + const Foo = new ContractBuilder("Foo"); const trait: BaseImplementedTrait = { - name: 'External', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', + name: "External", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: 'someFunction', + name: "someFunction", args: [], - code: [ - 'someFunction()' - ] + code: ["someFunction()"], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, 'before()'); + Foo.addFunctionCodeBefore(trait, fn, "before()"); t.snapshot(printContract(Foo)); }); -test('contract with function code before with semicolons', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with function code before with semicolons", (t) => { + const Foo = new ContractBuilder("Foo"); const trait: BaseImplementedTrait = { - name: 'External', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', + name: "External", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: 'someFunction', + name: "someFunction", args: [], - code: [ - 'someFunction();' - ] + code: ["someFunction();"], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, 'before();'); + Foo.addFunctionCodeBefore(trait, fn, "before();"); t.snapshot(printContract(Foo)); }); -test('contract with initializer params', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with initializer params", (t) => { + const Foo = new ContractBuilder("Foo"); - Foo.addComponent( - FOO_COMPONENT, - ['param1'], - true - ); + Foo.addComponent(FOO_COMPONENT, ["param1"], true); t.snapshot(printContract(Foo)); }); -test('contract with standalone import', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addComponent( - FOO_COMPONENT, - ); - Foo.addUseClause('some::library', 'SomeLibrary'); +test("contract with standalone import", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addComponent(FOO_COMPONENT); + Foo.addUseClause("some::library", "SomeLibrary"); t.snapshot(printContract(Foo)); }); -test('contract with sorted use clauses', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addComponent( - FOO_COMPONENT, - ); - Foo.addUseClause('some::library', 'SomeLibrary'); - Foo.addUseClause('another::library', 'AnotherLibrary'); - Foo.addUseClause('another::library', 'Foo', { alias: 'Custom2' }); - Foo.addUseClause('another::library', 'Foo', { alias: 'Custom1' }); +test("contract with sorted use clauses", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addComponent(FOO_COMPONENT); + Foo.addUseClause("some::library", "SomeLibrary"); + Foo.addUseClause("another::library", "AnotherLibrary"); + Foo.addUseClause("another::library", "Foo", { alias: "Custom2" }); + Foo.addUseClause("another::library", "Foo", { alias: "Custom1" }); t.snapshot(printContract(Foo)); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/contract.ts b/packages/core/cairo/src/contract.ts index b7f77d6a6..b876ee2ee 100644 --- a/packages/core/cairo/src/contract.ts +++ b/packages/core/cairo/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from './utils/convert-strings'; +import { toIdentifier } from "./utils/convert-strings"; export interface Contract { license: string; @@ -14,7 +14,12 @@ export interface Contract { superVariables: Variable[]; } -export type Value = string | number | bigint | { lit: string } | { note: string, value: Value }; +export type Value = + | string + | number + | bigint + | { lit: string } + | { note: string; value: Value }; export interface UseClause { containerPath: string; @@ -100,7 +105,7 @@ export interface Argument { export class ContractBuilder implements Contract { readonly name: string; readonly account: boolean; - license = 'MIT'; + license = "MIT"; upgradeable = false; readonly constructorArgs: Argument[] = []; @@ -145,24 +150,41 @@ export class ContractBuilder implements Contract { return this.interfaceFlagsSet; } - addUseClause(containerPath: string, name: string, options?: { groupable?: boolean, alias?: string }): void { + addUseClause( + containerPath: string, + name: string, + options?: { groupable?: boolean; alias?: string }, + ): void { // groupable defaults to true const groupable = options?.groupable ?? true; - const alias = options?.alias ?? ''; + const alias = options?.alias ?? ""; const uniqueName = alias.length > 0 ? alias : name; const present = this.useClausesMap.has(uniqueName); if (!present) { - this.useClausesMap.set(uniqueName, { containerPath, name, groupable, alias }); + this.useClausesMap.set(uniqueName, { + containerPath, + name, + groupable, + alias, + }); } } - addComponent(component: Component, params: Value[] = [], initializable: boolean = true): boolean { + addComponent( + component: Component, + params: Value[] = [], + initializable: boolean = true, + ): boolean { this.addUseClause(component.path, component.name); const key = component.name; const present = this.componentsMap.has(key); if (!present) { const initializer = initializable ? { params } : undefined; - const cp: Component = { initializer, ...component, impls: [ ...component.impls ] }; // spread impls to deep copy from original component + const cp: Component = { + initializer, + ...component, + impls: [...component.impls], + }; // spread impls to deep copy from original component this.componentsMap.set(key, cp); } return !present; @@ -175,7 +197,7 @@ export class ContractBuilder implements Contract { throw new Error(`Component ${component.name} has not been added yet`); } - if (!c.impls.some(i => i.name === impl.name)) { + if (!c.impls.some((i) => i.name === impl.name)) { c.impls.push(impl); } } @@ -194,7 +216,7 @@ export class ContractBuilder implements Contract { return false; } else { this.superVariablesMap.set(variable.name, variable); - this.addUseClause('super', variable.name); + this.addUseClause("super", variable.name); return true; } } @@ -208,7 +230,7 @@ export class ContractBuilder implements Contract { const t: ImplementedTrait = { name: baseTrait.name, of: baseTrait.of, - tags: [ ...baseTrait.tags ], + tags: [...baseTrait.tags], superVariables: [], functions: [], section: baseTrait.section, @@ -219,18 +241,21 @@ export class ContractBuilder implements Contract { } } - addSuperVariableToTrait(baseTrait: BaseImplementedTrait, newVar: Variable): boolean { + addSuperVariableToTrait( + baseTrait: BaseImplementedTrait, + newVar: Variable, + ): boolean { const trait = this.addImplementedTrait(baseTrait); for (const existingVar of trait.superVariables) { if (existingVar.name === newVar.name) { if (existingVar.type !== newVar.type) { throw new Error( - `Tried to add duplicate super var ${newVar.name} with different type: ${newVar.type} instead of ${existingVar.type}.` + `Tried to add duplicate super var ${newVar.name} with different type: ${newVar.type} instead of ${existingVar.type}.`, ); } if (existingVar.value !== newVar.value) { throw new Error( - `Tried to add duplicate super var ${newVar.name} with different value: ${newVar.value} instead of ${existingVar.value}.` + `Tried to add duplicate super var ${newVar.name} with different value: ${newVar.value} instead of ${existingVar.value}.`, ); } return false; // No need to add, already exists @@ -240,7 +265,10 @@ export class ContractBuilder implements Contract { return true; } - addFunction(baseTrait: BaseImplementedTrait, fn: BaseFunction): ContractFunction { + addFunction( + baseTrait: BaseImplementedTrait, + fn: BaseFunction, + ): ContractFunction { const t = this.addImplementedTrait(baseTrait); const signature = this.getFunctionSignature(fn); @@ -248,7 +276,10 @@ export class ContractBuilder implements Contract { // Look for the existing function with the same signature and return it if found for (let i = 0; i < t.functions.length; i++) { const existingFn = t.functions[i]; - if (existingFn !== undefined && this.getFunctionSignature(existingFn) === signature) { + if ( + existingFn !== undefined && + this.getFunctionSignature(existingFn) === signature + ) { return existingFn; } } @@ -264,13 +295,17 @@ export class ContractBuilder implements Contract { } private getFunctionSignature(fn: BaseFunction): string { - return [fn.name, '(', ...fn.args.map(a => a.name), ')'].join(''); + return [fn.name, "(", ...fn.args.map((a) => a.name), ")"].join(""); } - addFunctionCodeBefore(baseTrait: BaseImplementedTrait, fn: BaseFunction, codeBefore: string): void { + addFunctionCodeBefore( + baseTrait: BaseImplementedTrait, + fn: BaseFunction, + codeBefore: string, + ): void { this.addImplementedTrait(baseTrait); const existingFn = this.addFunction(baseTrait, fn); - existingFn.codeBefore = [ ...existingFn.codeBefore ?? [], codeBefore ]; + existingFn.codeBefore = [...(existingFn.codeBefore ?? []), codeBefore]; } addConstructorArgument(arg: Argument): void { diff --git a/packages/core/cairo/src/custom.test.ts b/packages/core/cairo/src/custom.test.ts index ae5b28cf0..85b3fb9be 100644 --- a/packages/core/cairo/src/custom.test.ts +++ b/packages/core/cairo/src/custom.test.ts @@ -1,13 +1,13 @@ -import test from 'ava'; -import { custom } from '.'; +import test from "ava"; +import { custom } from "."; -import { buildCustom, CustomOptions } from './custom'; -import { printContract } from './print'; +import { buildCustom, CustomOptions } from "./custom"; +import { printContract } from "./print"; function testCustom(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildCustom({ - name: 'MyContract', + name: "MyContract", ...opts, }); t.snapshot(printContract(c)); @@ -17,63 +17,68 @@ function testCustom(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, t => { - t.is(custom.print(opts), printContract(buildCustom({ - name: 'MyContract', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: CustomOptions) { + test(title, (t) => { + t.is( + custom.print(opts), + printContract( + buildCustom({ + name: "MyContract", + ...opts, + }), + ), + ); }); } -testCustom('custom non-upgradeable', { +testCustom("custom non-upgradeable", { upgradeable: false, }); -testCustom('custom defaults', {}); +testCustom("custom defaults", {}); -testCustom('pausable', { +testCustom("pausable", { pausable: true, }); -testCustom('upgradeable', { +testCustom("upgradeable", { upgradeable: true, }); -testCustom('access control disabled', { +testCustom("access control disabled", { upgradeable: false, access: false, }); -testCustom('access control ownable', { - access: 'ownable', +testCustom("access control ownable", { + access: "ownable", }); -testCustom('access control roles', { - access: 'roles', +testCustom("access control roles", { + access: "roles", }); -testCustom('pausable with access control disabled', { +testCustom("pausable with access control disabled", { // API should override access to true since it is required for pausable access: false, pausable: true, upgradeable: false, }); -testAPIEquivalence('custom API default'); +testAPIEquivalence("custom API default"); -testAPIEquivalence('custom API full upgradeable', { - name: 'CustomContract', - access: 'roles', +testAPIEquivalence("custom API full upgradeable", { + name: "CustomContract", + access: "roles", pausable: true, upgradeable: true, }); -test('custom API assert defaults', async t => { +test("custom API assert defaults", async (t) => { t.is(custom.print(custom.defaults), custom.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(custom.isAccessControlRequired({ pausable: true }), true); t.is(custom.isAccessControlRequired({ upgradeable: true }), true); -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/custom.ts b/packages/core/cairo/src/custom.ts index 7f361415f..a97ea2c7a 100644 --- a/packages/core/cairo/src/custom.ts +++ b/packages/core/cairo/src/custom.ts @@ -1,18 +1,21 @@ -import { Contract, ContractBuilder } from './contract'; -import { setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { CommonContractOptions, withCommonContractDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { setAccessControl } from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { + CommonContractOptions, + withCommonContractDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; export const defaults: Required = { - name: 'MyContract', + name: "MyContract", pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printCustom(opts: CustomOptions = defaults): string { @@ -51,4 +54,3 @@ export function buildCustom(opts: CustomOptions): Contract { return c; } - diff --git a/packages/core/cairo/src/erc1155.test.ts b/packages/core/cairo/src/erc1155.test.ts index 9923de2d5..55f9b240c 100644 --- a/packages/core/cairo/src/erc1155.test.ts +++ b/packages/core/cairo/src/erc1155.test.ts @@ -1,24 +1,25 @@ -import test from 'ava'; -import { erc1155 } from '.'; +import test from "ava"; +import { erc1155 } from "."; -import { buildERC1155, ERC1155Options } from './erc1155'; -import { printContract } from './print'; -import { royaltyInfoOptions } from './set-royalty-info'; +import { buildERC1155, ERC1155Options } from "./erc1155"; +import { printContract } from "./print"; +import { royaltyInfoOptions } from "./set-royalty-info"; -const NAME = 'MyToken'; -const CUSTOM_NAME = 'CustomToken'; -const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; +const NAME = "MyToken"; +const CUSTOM_NAME = "CustomToken"; +const BASE_URI = + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; const allFeaturesON: Partial = { mintable: true, burnable: true, pausable: true, royaltyInfo: royaltyInfoOptions.enabledDefault, - upgradeable: true + upgradeable: true, } as const; function testERC1155(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC1155({ name: NAME, baseUri: BASE_URI, @@ -32,105 +33,129 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, t => { - t.is(erc1155.print(opts), printContract(buildERC1155({ - name: NAME, - baseUri: '', - ...opts, - }))); + test(title, (t) => { + t.is( + erc1155.print(opts), + printContract( + buildERC1155({ + name: NAME, + baseUri: "", + ...opts, + }), + ), + ); }); } -testERC1155('basic non-upgradeable', { +testERC1155("basic non-upgradeable", { upgradeable: false, }); -testERC1155('basic', {}); +testERC1155("basic", {}); -testERC1155('basic + roles', { - access: 'roles', +testERC1155("basic + roles", { + access: "roles", }); -testERC1155('no updatable uri', { +testERC1155("no updatable uri", { updatableUri: false, }); -testERC1155('burnable', { +testERC1155("burnable", { burnable: true, }); -testERC1155('pausable', { +testERC1155("pausable", { pausable: true, }); -testERC1155('mintable', { +testERC1155("mintable", { mintable: true, }); -testERC1155('mintable + roles', { +testERC1155("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC1155('royalty info disabled', { - royaltyInfo: royaltyInfoOptions.disabled +testERC1155("royalty info disabled", { + royaltyInfo: royaltyInfoOptions.disabled, }); -testERC1155('royalty info enabled default + ownable', { +testERC1155("royalty info enabled default + ownable", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'ownable' + access: "ownable", }); -testERC1155('royalty info enabled default + roles', { +testERC1155("royalty info enabled default + roles", { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: 'roles' + access: "roles", }); -testERC1155('royalty info enabled custom + ownable', { +testERC1155("royalty info enabled custom + ownable", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'ownable' + access: "ownable", }); -testERC1155('royalty info enabled custom + roles', { +testERC1155("royalty info enabled custom + roles", { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: 'roles' + access: "roles", }); -testERC1155('full non-upgradeable', { +testERC1155("full non-upgradeable", { ...allFeaturesON, - access: 'roles', + access: "roles", upgradeable: false, }); -testERC1155('full upgradeable', { +testERC1155("full upgradeable", { ...allFeaturesON, - access: 'roles', + access: "roles", upgradeable: true, }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: CUSTOM_NAME, baseUri: BASE_URI }); +testAPIEquivalence("API basic", { name: CUSTOM_NAME, baseUri: BASE_URI }); -testAPIEquivalence('API full upgradeable', { +testAPIEquivalence("API full upgradeable", { ...allFeaturesON, name: CUSTOM_NAME, baseUri: BASE_URI, - access: 'roles', + access: "roles", upgradeable: true, }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test('API isAccessControlRequired', async t => { - t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), true); +test("API isAccessControlRequired", async (t) => { + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); - t.is(erc1155.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); - t.is(erc1155.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); + t.is( + erc1155.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledDefault, + }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ + royaltyInfo: royaltyInfoOptions.enabledCustom, + }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: false }), false); t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default -}); \ No newline at end of file +}); diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index 7d5eda31a..59baee0c8 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -65,7 +65,7 @@ function withDefaults(opts: ERC1155Options): Required { } export function isAccessControlRequired( - opts: Partial + opts: Partial, ): boolean { return ( opts.mintable === true || @@ -173,7 +173,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.mint, access, "MINTER", - "minter" + "minter", ); requireAccessControl( c, @@ -181,7 +181,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.batch_mint, access, "MINTER", - "minter" + "minter", ); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. @@ -195,7 +195,7 @@ function addSetBaseUri(c: ContractBuilder, access: Access) { functions.set_base_uri, access, "URI_SETTER", - "uri_setter" + "uri_setter", ); // Camel case version of set_base_uri. Access control is already set on set_base_uri. diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 479c598f4..798659d7b 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -28,8 +28,8 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { name: "MyToken", symbol: "MTK", ...opts, - }) - ) + }), + ), ); }); } @@ -94,11 +94,11 @@ test("erc20 votes, no name", async (t) => { name: "MyToken", symbol: "MTK", votes: true, - }) + }), ); t.is( (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled" + "Application Name is required when Votes are enabled", ); }); @@ -110,11 +110,11 @@ test("erc20 votes, empty version", async (t) => { votes: true, appName: "MY_DAPP_NAME", appVersion: "", // avoids default value of v1 - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 8dbe4cd14..3334cd78b 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -135,7 +135,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { beforeUpdateFn.code.push( "let contract_state = self.get_contract();", - "contract_state.pausable.assert_not_paused();" + "contract_state.pausable.assert_not_paused();", ); } @@ -156,7 +156,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { c, toFelt252(allOpts.appName, "appName"), toFelt252(allOpts.appVersion, "appVersion"), - "SNIP12 Metadata" + "SNIP12 Metadata", ); const afterUpdateFn = c.addFunction(hooksTrait, { @@ -175,7 +175,7 @@ function addHooks(c: ContractBuilder, allOpts: Required) { afterUpdateFn.code.push( "let mut contract_state = self.get_contract_mut();", - "contract_state.votes.transfer_voting_units(from, recipient, amount);" + "contract_state.votes.transfer_voting_units(from, recipient, amount);", ); } } else { @@ -212,7 +212,7 @@ function addPremint(c: ContractBuilder, amount: string) { const premintAbsolute = toUint( getInitialSupply(amount, 18), "premint", - "u256" + "u256", ); c.addUseClause("starknet", "ContractAddress"); @@ -270,7 +270,7 @@ function addMintable(c: ContractBuilder, access: Access) { functions.mint, access, "MINTER", - "minter" + "minter", ); } diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index c2819b953..7f260e482 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -50,8 +50,8 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { name: NAME, symbol: SYMBOL, ...opts, - }) - ) + }), + ), ); }); } @@ -138,11 +138,11 @@ test("erc721 votes, no name", async (t) => { name: NAME, symbol: SYMBOL, votes: true, - }) + }), ); t.is( (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled" + "Application Name is required when Votes are enabled", ); }); @@ -154,11 +154,11 @@ test("erc721 votes, no version", async (t) => { votes: true, appName: APP_NAME, appVersion: "", - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); @@ -170,11 +170,11 @@ test("erc721 votes, empty version", async (t) => { votes: true, appName: APP_NAME, appVersion: "", // avoids default value of v1 - }) + }), ); t.is( (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled" + "Application Version is required when Votes are enabled", ); }); @@ -208,19 +208,19 @@ test("API isAccessControlRequired", async (t) => { erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault, }), - true + true, ); t.is( erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom, }), - true + true, ); t.is( erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.disabled, }), - false + false, ); t.is(erc721.isAccessControlRequired({ burnable: true }), false); t.is(erc721.isAccessControlRequired({ enumerable: true }), false); diff --git a/packages/core/cairo/src/erc721.ts b/packages/core/cairo/src/erc721.ts index 6b23e1c28..12f7ecd34 100644 --- a/packages/core/cairo/src/erc721.ts +++ b/packages/core/cairo/src/erc721.ts @@ -1,34 +1,46 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; -import { Access, requireAccessControl, setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { defineFunctions } from './utils/define-functions'; -import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { defineComponents } from './utils/define-components'; -import { contractDefaults as commonDefaults } from './common-options'; -import { printContract } from './print'; -import { addSRC5Component, addVotesComponent } from './common-components'; -import { externalTrait } from './external-trait'; -import { toByteArray, toFelt252 } from './utils/convert-strings'; -import { OptionsError } from './error'; -import { RoyaltyInfoOptions, setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; +import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; +import { + Access, + requireAccessControl, + setAccessControl, +} from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonContractOptions, + withCommonContractDefaults, + getSelfArg, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { defineComponents } from "./utils/define-components"; +import { contractDefaults as commonDefaults } from "./common-options"; +import { printContract } from "./print"; +import { addSRC5Component, addVotesComponent } from "./common-components"; +import { externalTrait } from "./external-trait"; +import { toByteArray, toFelt252 } from "./utils/convert-strings"; +import { OptionsError } from "./error"; +import { + RoyaltyInfoOptions, + setRoyaltyInfo, + defaults as royaltyInfoDefaults, +} from "./set-royalty-info"; export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', - baseUri: '', + name: "MyToken", + symbol: "MTK", + baseUri: "", burnable: false, pausable: false, mintable: false, enumerable: false, votes: false, royaltyInfo: royaltyInfoDefaults, - appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: 'v1', + appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: "v1", access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC721(opts: ERC721Options = defaults): string { @@ -61,12 +73,17 @@ function withDefaults(opts: ERC721Options): Required { royaltyInfo: opts.royaltyInfo ?? defaults.royaltyInfo, votes: opts.votes ?? defaults.votes, appName: opts.appName ?? defaults.appName, - appVersion: opts.appVersion ?? defaults.appVersion + appVersion: opts.appVersion ?? defaults.appVersion, }; } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; + return ( + opts.mintable === true || + opts.pausable === true || + opts.upgradeable === true || + opts.royaltyInfo?.enabled === true + ); } export function buildERC721(opts: ERC721Options): Contract { @@ -74,7 +91,12 @@ export function buildERC721(opts: ERC721Options): Contract { const allOpts = withDefaults(opts); - addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.baseUri)); + addBase( + c, + toByteArray(allOpts.name), + toByteArray(allOpts.symbol), + toByteArray(allOpts.baseUri), + ); addERC721Mixin(c); if (allOpts.pausable) { @@ -97,7 +119,7 @@ export function buildERC721(opts: ERC721Options): Contract { setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); - + addHooks(c, allOpts); return c; @@ -108,78 +130,84 @@ function addHooks(c: ContractBuilder, opts: Required) { if (usesCustomHooks) { const ERC721HooksTrait: BaseImplementedTrait = { name: `ERC721HooksImpl`, - of: 'ERC721Component::ERC721HooksTrait', + of: "ERC721Component::ERC721HooksTrait", tags: [], priority: 0, }; c.addImplementedTrait(ERC721HooksTrait); - c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause("starknet", "ContractAddress"); const requiresMutState = opts.enumerable || opts.votes; const initStateLine = requiresMutState - ? 'let mut contract_state = self.get_contract_mut()' - : 'let contract_state = self.get_contract()'; + ? "let mut contract_state = self.get_contract_mut()" + : "let contract_state = self.get_contract()"; const beforeUpdateCode = [initStateLine]; if (opts.pausable) { - beforeUpdateCode.push('contract_state.pausable.assert_not_paused()'); + beforeUpdateCode.push("contract_state.pausable.assert_not_paused()"); } if (opts.enumerable) { - beforeUpdateCode.push('contract_state.erc721_enumerable.before_update(to, token_id)'); + beforeUpdateCode.push( + "contract_state.erc721_enumerable.before_update(to, token_id)", + ); } if (opts.votes) { - if (!opts.appName) { - throw new OptionsError({ - appName: 'Application Name is required when Votes are enabled', - }); - } + if (!opts.appName) { + throw new OptionsError({ + appName: "Application Name is required when Votes are enabled", + }); + } - if (!opts.appVersion) { - throw new OptionsError({ - appVersion: 'Application Version is required when Votes are enabled', - }); - } + if (!opts.appVersion) { + throw new OptionsError({ + appVersion: "Application Version is required when Votes are enabled", + }); + } addVotesComponent( c, - toFelt252(opts.appName, 'appName'), - toFelt252(opts.appVersion, 'appVersion'), - 'SNIP12 Metadata', + toFelt252(opts.appName, "appName"), + toFelt252(opts.appVersion, "appVersion"), + "SNIP12 Metadata", + ); + beforeUpdateCode.push("let previous_owner = self._owner_of(token_id);"); + beforeUpdateCode.push( + "contract_state.votes.transfer_voting_units(previous_owner, to, 1);", ); - beforeUpdateCode.push('let previous_owner = self._owner_of(token_id);'); - beforeUpdateCode.push('contract_state.votes.transfer_voting_units(previous_owner, to, 1);'); } c.addFunction(ERC721HooksTrait, { - name: 'before_update', + name: "before_update", args: [ - { name: 'ref self', type: `ERC721Component::ComponentState` }, - { name: 'to', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'auth', type: 'ContractAddress' }, + { + name: "ref self", + type: `ERC721Component::ComponentState`, + }, + { name: "to", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "auth", type: "ContractAddress" }, ], code: beforeUpdateCode, }); } else { - c.addUseClause('openzeppelin::token::erc721', 'ERC721HooksEmptyImpl'); + c.addUseClause("openzeppelin::token::erc721", "ERC721HooksEmptyImpl"); } } function addERC721Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC721Component, { - name: 'ERC721MixinImpl', - value: 'ERC721Component::ERC721MixinImpl', + name: "ERC721MixinImpl", + value: "ERC721Component::ERC721MixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); addSRC5Component(c); } -function addBase(c: ContractBuilder, name: string, symbol: string, baseUri: string) { - c.addComponent( - components.ERC721Component, - [ - name, symbol, baseUri, - ], - true, - ); +function addBase( + c: ContractBuilder, + name: string, + symbol: string, + baseUri: string, +) { + c.addComponent(components.ERC721Component, [name, symbol, baseUri], true); } function addEnumerable(c: ContractBuilder) { @@ -187,88 +215,91 @@ function addEnumerable(c: ContractBuilder) { } function addBurnable(c: ContractBuilder) { - c.addUseClause('core::num::traits', 'Zero'); - c.addUseClause('starknet', 'get_caller_address'); + c.addUseClause("core::num::traits", "Zero"); + c.addUseClause("starknet", "get_caller_address"); c.addFunction(externalTrait, functions.burn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause('starknet', 'ContractAddress'); - requireAccessControl(c, externalTrait, functions.safe_mint, access, 'MINTER', 'minter'); + c.addUseClause("starknet", "ContractAddress"); + requireAccessControl( + c, + externalTrait, + functions.safe_mint, + access, + "MINTER", + "minter", + ); // Camel case version of safe_mint. Access control and pausable are already set on safe_mint. c.addFunction(externalTrait, functions.safeMint); } -const components = defineComponents( { +const components = defineComponents({ ERC721Component: { - path: 'openzeppelin::token::erc721', + path: "openzeppelin::token::erc721", substorage: { - name: 'erc721', - type: 'ERC721Component::Storage', + name: "erc721", + type: "ERC721Component::Storage", }, event: { - name: 'ERC721Event', - type: 'ERC721Component::Event', + name: "ERC721Event", + type: "ERC721Component::Event", }, - impls: [{ - name: 'ERC721InternalImpl', - embed: false, - value: 'ERC721Component::InternalImpl', - }], + impls: [ + { + name: "ERC721InternalImpl", + embed: false, + value: "ERC721Component::InternalImpl", + }, + ], }, ERC721EnumerableComponent: { - path: 'openzeppelin::token::erc721::extensions', + path: "openzeppelin::token::erc721::extensions", substorage: { - name: 'erc721_enumerable', - type: 'ERC721EnumerableComponent::Storage', + name: "erc721_enumerable", + type: "ERC721EnumerableComponent::Storage", }, event: { - name: 'ERC721EnumerableEvent', - type: 'ERC721EnumerableComponent::Event', + name: "ERC721EnumerableEvent", + type: "ERC721EnumerableComponent::Event", }, - impls: [{ - name: 'ERC721EnumerableImpl', - value: 'ERC721EnumerableComponent::ERC721EnumerableImpl', - }, { - name: 'ERC721EnumerableInternalImpl', - embed: false, - value: 'ERC721EnumerableComponent::InternalImpl', - }], + impls: [ + { + name: "ERC721EnumerableImpl", + value: "ERC721EnumerableComponent::ERC721EnumerableImpl", + }, + { + name: "ERC721EnumerableInternalImpl", + embed: false, + value: "ERC721EnumerableComponent::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ burn: { - args: [ - getSelfArg(), - { name: 'token_id', type: 'u256' } - ], - code: [ - 'self.erc721.update(Zero::zero(), token_id, get_caller_address());', - ] + args: [getSelfArg(), { name: "token_id", type: "u256" }], + code: ["self.erc721.update(Zero::zero(), token_id, get_caller_address());"], }, safe_mint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'token_id', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "recipient", type: "ContractAddress" }, + { name: "token_id", type: "u256" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.erc721.safe_mint(recipient, token_id, data);', - ] + code: ["self.erc721.safe_mint(recipient, token_id, data);"], }, safeMint: { args: [ getSelfArg(), - { name: 'recipient', type: 'ContractAddress' }, - { name: 'tokenId', type: 'u256' }, - { name: 'data', type: 'Span' }, + { name: "recipient", type: "ContractAddress" }, + { name: "tokenId", type: "u256" }, + { name: "data", type: "Span" }, ], - code: [ - 'self.safe_mint(recipient, tokenId, data);', - ] + code: ["self.safe_mint(recipient, tokenId, data);"], }, }); diff --git a/packages/core/cairo/src/external-trait.ts b/packages/core/cairo/src/external-trait.ts index ed7e99e45..04e671d6d 100644 --- a/packages/core/cairo/src/external-trait.ts +++ b/packages/core/cairo/src/external-trait.ts @@ -1,11 +1,8 @@ import type { BaseImplementedTrait } from "./contract"; export const externalTrait: BaseImplementedTrait = { - name: 'ExternalImpl', - of: 'ExternalTrait', - tags: [ - 'generate_trait', - 'abi(per_item)', - ], - perItemTag: 'external(v0)', -} + name: "ExternalImpl", + of: "ExternalTrait", + tags: ["generate_trait", "abi(per_item)"], + perItemTag: "external(v0)", +}; diff --git a/packages/core/cairo/src/generate/account.ts b/packages/core/cairo/src/generate/account.ts index 175ba8df9..b61bf2056 100644 --- a/packages/core/cairo/src/generate/account.ts +++ b/packages/core/cairo/src/generate/account.ts @@ -1,12 +1,12 @@ -import { accountTypes, type AccountOptions } from '../account'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { accountTypes, type AccountOptions } from "../account"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyAccount'], + name: ["MyAccount"], type: accountTypes, declare: booleans, deploy: booleans, diff --git a/packages/core/cairo/src/generate/alternatives.ts b/packages/core/cairo/src/generate/alternatives.ts index afd89ab67..c4b282064 100644 --- a/packages/core/cairo/src/generate/alternatives.ts +++ b/packages/core/cairo/src/generate/alternatives.ts @@ -16,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map(e => [e.key, e.values[e.current % e.limit]]), + entries.map((e) => [e.key, e.values[e.current % e.limit]]), ) as Alternatives; } diff --git a/packages/core/cairo/src/generate/custom.ts b/packages/core/cairo/src/generate/custom.ts index 40207666a..884032068 100644 --- a/packages/core/cairo/src/generate/custom.ts +++ b/packages/core/cairo/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from '../custom'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { CustomOptions } from "../custom"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyContract'], + name: ["MyContract"], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/cairo/src/generate/erc1155.ts b/packages/core/cairo/src/generate/erc1155.ts index 07170fcbd..9fc09c9ca 100644 --- a/packages/core/cairo/src/generate/erc1155.ts +++ b/packages/core/cairo/src/generate/erc1155.ts @@ -1,21 +1,25 @@ -import type { ERC1155Options } from '../erc1155'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { royaltyInfoOptions } from '../set-royalty-info'; -import { generateAlternatives } from './alternatives'; +import type { ERC1155Options } from "../erc1155"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { royaltyInfoOptions } from "../set-royalty-info"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + baseUri: ["https://example.com/"], burnable: booleans, pausable: booleans, mintable: booleans, updatableUri: booleans, upgradeable: upgradeableOptions, - royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], + royaltyInfo: [ + royaltyInfoOptions.disabled, + royaltyInfoOptions.enabledDefault, + royaltyInfoOptions.enabledCustom, + ], access: accessOptions, info: infoOptions, }; diff --git a/packages/core/cairo/src/generate/erc20.ts b/packages/core/cairo/src/generate/erc20.ts index 941505c7e..2f8235af1 100644 --- a/packages/core/cairo/src/generate/erc20.ts +++ b/packages/core/cairo/src/generate/erc20.ts @@ -1,24 +1,24 @@ -import type { ERC20Options } from '../erc20'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC20Options } from "../erc20"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], + name: ["MyToken"], + symbol: ["MTK"], burnable: booleans, pausable: booleans, mintable: booleans, - premint: ['1'], + premint: ["1"], votes: booleans, - appName: ['MyApp'], - appVersion: ['v1'], + appName: ["MyApp"], + appVersion: ["v1"], access: accessOptions, upgradeable: upgradeableOptions, - info: infoOptions + info: infoOptions, }; export function* generateERC20Options(): Generator> { diff --git a/packages/core/cairo/src/generate/erc721.ts b/packages/core/cairo/src/generate/erc721.ts index e78a94f15..f3003120a 100644 --- a/packages/core/cairo/src/generate/erc721.ts +++ b/packages/core/cairo/src/generate/erc721.ts @@ -1,24 +1,28 @@ -import type { ERC721Options } from '../erc721'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { royaltyInfoOptions } from '../set-royalty-info'; -import { generateAlternatives } from './alternatives'; +import type { ERC721Options } from "../erc721"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { royaltyInfoOptions } from "../set-royalty-info"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + symbol: ["MTK"], + baseUri: ["https://example.com/"], burnable: booleans, enumerable: booleans, votes: booleans, - appName: ['MyApp'], - appVersion: ['v1'], + appName: ["MyApp"], + appVersion: ["v1"], pausable: booleans, mintable: booleans, - royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], + royaltyInfo: [ + royaltyInfoOptions.disabled, + royaltyInfoOptions.enabledDefault, + royaltyInfoOptions.enabledCustom, + ], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/governor.ts b/packages/core/cairo/src/generate/governor.ts index 8ffee9aef..6d55cb762 100644 --- a/packages/core/cairo/src/generate/governor.ts +++ b/packages/core/cairo/src/generate/governor.ts @@ -1,30 +1,37 @@ -import { clockModeOptions, GovernorOptions, quorumModeOptions, timelockOptions, votesOptions } from '../governor'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { + clockModeOptions, + GovernorOptions, + quorumModeOptions, + timelockOptions, + votesOptions, +} from "../governor"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyGovernor'], - delay: ['1 day'], - period: ['1 week'], - proposalThreshold: ['1'], + name: ["MyGovernor"], + delay: ["1 day"], + period: ["1 week"], + proposalThreshold: ["1"], decimals: [18], quorumMode: quorumModeOptions, quorumPercent: [10], - quorumAbsolute: ['10'], + quorumAbsolute: ["10"], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, settings: booleans, - appName: ['Openzeppelin Governor'], - appVersion: ['v1'], + appName: ["Openzeppelin Governor"], + appVersion: ["v1"], upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateGovernorOptions(): Generator> { +export function* generateGovernorOptions(): Generator< + Required +> { yield* generateAlternatives(blueprint); } - diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index 67867b287..ab6ebd08d 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -75,7 +75,7 @@ interface GeneratedSource extends GeneratedContract { function generateContractSubset( subset: Subset, - kind?: Kind + kind?: Kind, ): GeneratedContract[] { const contracts = []; @@ -125,11 +125,11 @@ function generateContractSubset( return [ ...findCover( contracts.filter(filterByUpgradeableSetTo(true)), - getParents + getParents, ), ...findCover( contracts.filter(filterByUpgradeableSetTo(false)), - getParents + getParents, ), ]; } @@ -138,7 +138,7 @@ function generateContractSubset( export function* generateSources( subset: Subset, uniqueName?: boolean, - kind?: Kind + kind?: Kind, ): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { @@ -154,7 +154,7 @@ export async function writeGeneratedSources( dir: string, subset: Subset, uniqueName?: boolean, - kind?: Kind + kind?: Kind, ): Promise { await fs.mkdir(dir, { recursive: true }); const contractNames = []; @@ -162,7 +162,7 @@ export async function writeGeneratedSources( for (const { id, contract, source } of generateSources( subset, uniqueName, - kind + kind, )) { const name = uniqueName ? contract.name : id; await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); diff --git a/packages/core/cairo/src/generate/vesting.ts b/packages/core/cairo/src/generate/vesting.ts index 45de06143..49a34f72e 100644 --- a/packages/core/cairo/src/generate/vesting.ts +++ b/packages/core/cairo/src/generate/vesting.ts @@ -1,14 +1,14 @@ -import { infoOptions } from '../set-info'; -import type { VestingOptions } from '../vesting'; -import { generateAlternatives } from './alternatives'; +import { infoOptions } from "../set-info"; +import type { VestingOptions } from "../vesting"; +import { generateAlternatives } from "./alternatives"; const blueprint = { - name: ['MyVesting'], - startDate: ['2024-12-31T23:59'], - duration: ['90 days', '1 year'], - cliffDuration: ['0 seconds', '30 day'], - schedule: ['linear', 'custom'] as const, - info: infoOptions + name: ["MyVesting"], + startDate: ["2024-12-31T23:59"], + duration: ["90 days", "1 year"], + cliffDuration: ["0 seconds", "30 day"], + schedule: ["linear", "custom"] as const, + info: infoOptions, }; export function* generateVestingOptions(): Generator> { diff --git a/packages/core/cairo/src/governor.test.ts b/packages/core/cairo/src/governor.test.ts index e3b3f0c22..561e4dc08 100644 --- a/packages/core/cairo/src/governor.test.ts +++ b/packages/core/cairo/src/governor.test.ts @@ -1,17 +1,17 @@ -import test from 'ava'; -import { governor } from '.'; +import test from "ava"; +import { governor } from "."; -import { buildGovernor, GovernorOptions } from './governor'; -import { printContract } from './print'; +import { buildGovernor, GovernorOptions } from "./governor"; +import { printContract } from "./print"; -const NAME = 'MyGovernor'; +const NAME = "MyGovernor"; function testGovernor(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildGovernor({ name: NAME, - delay: '1 day', - period: '1 week', + delay: "1 day", + period: "1 week", ...opts, }); t.snapshot(printContract(c)); @@ -22,160 +22,165 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, t => { - t.is(governor.print(opts), printContract(buildGovernor({ - name: NAME, - delay: '1 day', - period: '1 week', - ...opts, - }))); + test(title, (t) => { + t.is( + governor.print(opts), + printContract( + buildGovernor({ + name: NAME, + delay: "1 day", + period: "1 week", + ...opts, + }), + ), + ); }); } -testGovernor('basic + upgradeable', { - upgradeable: true +testGovernor("basic + upgradeable", { + upgradeable: true, }); -testGovernor('basic non-upgradeable', { - upgradeable: false +testGovernor("basic non-upgradeable", { + upgradeable: false, }); -testGovernor('erc20 votes + timelock', { - votes: 'erc20votes', - timelock: 'openzeppelin', +testGovernor("erc20 votes + timelock", { + votes: "erc20votes", + timelock: "openzeppelin", }); -testGovernor('erc721 votes + timelock', { - votes: 'erc721votes', - timelock: 'openzeppelin', +testGovernor("erc721 votes + timelock", { + votes: "erc721votes", + timelock: "openzeppelin", }); -testGovernor('custom name', { - name: 'CustomGovernor', +testGovernor("custom name", { + name: "CustomGovernor", }); -testGovernor('custom settings', { - delay: '2 hours', - period: '1 year', - proposalThreshold: '300', +testGovernor("custom settings", { + delay: "2 hours", + period: "1 year", + proposalThreshold: "300", settings: true, }); -testGovernor('quorum mode absolute', { - quorumMode: 'absolute', - quorumAbsolute: '200', +testGovernor("quorum mode absolute", { + quorumMode: "absolute", + quorumAbsolute: "200", }); -testGovernor('quorum mode percent', { - quorumMode: 'percent', +testGovernor("quorum mode percent", { + quorumMode: "percent", quorumPercent: 40, }); -testGovernor('custom snip12 metadata', { - appName: 'Governor', - appVersion: 'v3', +testGovernor("custom snip12 metadata", { + appName: "Governor", + appVersion: "v3", }); -testGovernor('all options', { +testGovernor("all options", { name: NAME, - delay: '4 day', - period: '4 week', - proposalThreshold: '500', + delay: "4 day", + period: "4 week", + proposalThreshold: "500", decimals: 10, - quorumMode: 'absolute', + quorumMode: "absolute", quorumPercent: 50, - quorumAbsolute: '200', - votes: 'erc721votes', - clockMode: 'timestamp', - timelock: 'openzeppelin', + quorumAbsolute: "200", + votes: "erc721votes", + clockMode: "timestamp", + timelock: "openzeppelin", settings: true, - appName: 'MyApp2', - appVersion: 'v5', + appName: "MyApp2", + appVersion: "v5", upgradeable: true, }); -testAPIEquivalence('API basic + upgradeable', { +testAPIEquivalence("API basic + upgradeable", { name: NAME, - delay: '1 day', - period: '1 week', - upgradeable: true + delay: "1 day", + period: "1 week", + upgradeable: true, }); -testAPIEquivalence('API basic non-upgradeable', { +testAPIEquivalence("API basic non-upgradeable", { name: NAME, - delay: '1 day', - period: '1 week', - upgradeable: false + delay: "1 day", + period: "1 week", + upgradeable: false, }); -testAPIEquivalence('API erc20 votes + timelock', { +testAPIEquivalence("API erc20 votes + timelock", { name: NAME, - delay: '1 day', - period: '1 week', - votes: 'erc20votes', - timelock: 'openzeppelin', + delay: "1 day", + period: "1 week", + votes: "erc20votes", + timelock: "openzeppelin", }); -testAPIEquivalence('API erc721 votes + timelock', { +testAPIEquivalence("API erc721 votes + timelock", { name: NAME, - delay: '1 day', - period: '1 week', - votes: 'erc721votes', - timelock: 'openzeppelin', + delay: "1 day", + period: "1 week", + votes: "erc721votes", + timelock: "openzeppelin", }); -testAPIEquivalence('API custom name', { - name: 'CustomGovernor', - delay: '1 day', - period: '1 week', +testAPIEquivalence("API custom name", { + name: "CustomGovernor", + delay: "1 day", + period: "1 week", }); -testAPIEquivalence('API custom settings', { +testAPIEquivalence("API custom settings", { name: NAME, - delay: '2 hours', - period: '1 year', - proposalThreshold: '300', + delay: "2 hours", + period: "1 year", + proposalThreshold: "300", settings: true, }); -testAPIEquivalence('API quorum mode absolute', { +testAPIEquivalence("API quorum mode absolute", { name: NAME, - delay: '1 day', - period: '1 week', - quorumMode: 'absolute', - quorumAbsolute: '200', + delay: "1 day", + period: "1 week", + quorumMode: "absolute", + quorumAbsolute: "200", }); -testAPIEquivalence('API quorum mode percent', { +testAPIEquivalence("API quorum mode percent", { name: NAME, - delay: '1 day', - period: '1 week', - quorumMode: 'percent', + delay: "1 day", + period: "1 week", + quorumMode: "percent", quorumPercent: 40, }); -testAPIEquivalence('API custom snip12 metadata', { +testAPIEquivalence("API custom snip12 metadata", { name: NAME, - delay: '1 day', - period: '1 week', - appName: 'Governor', - appVersion: 'v3', + delay: "1 day", + period: "1 week", + appName: "Governor", + appVersion: "v3", }); -testAPIEquivalence('API all options', { +testAPIEquivalence("API all options", { name: NAME, - delay: '4 day', - period: '4 week', - proposalThreshold: '500', + delay: "4 day", + period: "4 week", + proposalThreshold: "500", decimals: 10, - quorumMode: 'absolute', + quorumMode: "absolute", quorumPercent: 50, - quorumAbsolute: '200', - votes: 'erc721votes', - clockMode: 'timestamp', - timelock: 'openzeppelin', + quorumAbsolute: "200", + votes: "erc721votes", + clockMode: "timestamp", + timelock: "openzeppelin", settings: true, - appName: 'MyApp2', - appVersion: 'v5', + appName: "MyApp2", + appVersion: "v5", upgradeable: true, }); diff --git a/packages/core/cairo/src/governor.ts b/packages/core/cairo/src/governor.ts index fac953e62..50b6c65ef 100644 --- a/packages/core/cairo/src/governor.ts +++ b/packages/core/cairo/src/governor.ts @@ -1,49 +1,52 @@ -import { contractDefaults as commonDefaults, withCommonDefaults } from './common-options'; -import type { CommonOptions } from './common-options'; +import { + contractDefaults as commonDefaults, + withCommonDefaults, +} from "./common-options"; +import type { CommonOptions } from "./common-options"; import { ContractBuilder, Contract } from "./contract"; import { OptionsError } from "./error"; import { printContract } from "./print"; import { setInfo } from "./set-info"; import { setUpgradeableGovernor } from "./set-upgradeable"; -import { defineComponents } from './utils/define-components'; -import { durationToTimestamp } from './utils/duration'; -import { addSNIP12Metadata, addSRC5Component } from './common-components'; -import { toUint } from './utils/convert-strings'; -export const clockModeOptions = ['timestamp'] as const; -export const clockModeDefault = 'timestamp' as const; -export type ClockMode = typeof clockModeOptions[number]; - -const extensionPath = 'openzeppelin::governance::governor::extensions'; -const extensionExternalSection = 'Extensions (external)'; -const extensionInternalSection = 'Extensions (internal)'; +import { defineComponents } from "./utils/define-components"; +import { durationToTimestamp } from "./utils/duration"; +import { addSNIP12Metadata, addSRC5Component } from "./common-components"; +import { toUint } from "./utils/convert-strings"; +export const clockModeOptions = ["timestamp"] as const; +export const clockModeDefault = "timestamp" as const; +export type ClockMode = (typeof clockModeOptions)[number]; + +const extensionPath = "openzeppelin::governance::governor::extensions"; +const extensionExternalSection = "Extensions (external)"; +const extensionInternalSection = "Extensions (internal)"; export const defaults: Required = { - name: 'MyGovernor', - delay: '1 day', - period: '1 week', - votes: 'erc20votes', + name: "MyGovernor", + delay: "1 day", + period: "1 week", + votes: "erc20votes", clockMode: clockModeDefault, - timelock: 'openzeppelin', + timelock: "openzeppelin", decimals: 18, - proposalThreshold: '0', - quorumMode: 'percent', + proposalThreshold: "0", + quorumMode: "percent", quorumPercent: 4, - quorumAbsolute: '', + quorumAbsolute: "", settings: true, upgradeable: commonDefaults.upgradeable, - appName: 'OpenZeppelin Governor', - appVersion: 'v1', - info: commonDefaults.info + appName: "OpenZeppelin Governor", + appVersion: "v1", + info: commonDefaults.info, } as const; -export const quorumModeOptions = ['percent', 'absolute'] as const; -export type QuorumMode = typeof quorumModeOptions[number]; +export const quorumModeOptions = ["percent", "absolute"] as const; +export type QuorumMode = (typeof quorumModeOptions)[number]; -export const votesOptions = ['erc20votes', 'erc721votes'] as const; -export type VotesOptions = typeof votesOptions[number]; +export const votesOptions = ["erc20votes", "erc721votes"] as const; +export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, 'openzeppelin'] as const; -export type TimelockOptions = typeof timelockOptions[number]; +export const timelockOptions = [false, "openzeppelin"] as const; +export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { return printContract(buildGovernor(opts)); @@ -93,8 +96,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { validateDecimals(allOpts.decimals); addBase(c, allOpts); - addSRC5Component(c, 'SRC5'); - addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, 'SNIP12 Metadata'); + addSRC5Component(c, "SRC5"); + addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, "SNIP12 Metadata"); addCounting(c, allOpts); addQuorumAndVotes(c, allOpts); addSettings(c, allOpts); @@ -105,185 +108,224 @@ export function buildGovernor(opts: GovernorOptions): Contract { return c; } -const components = defineComponents( { +const components = defineComponents({ GovernorComponent: { - path: 'openzeppelin::governance::governor', + path: "openzeppelin::governance::governor", substorage: { - name: 'governor', - type: 'GovernorComponent::Storage', + name: "governor", + type: "GovernorComponent::Storage", }, event: { - name: 'GovernorEvent', - type: 'GovernorComponent::Event', + name: "GovernorEvent", + type: "GovernorComponent::Event", }, - impls: [{ - name: 'GovernorImpl', - value: 'GovernorComponent::GovernorImpl', - section: 'Governor Core', - }], + impls: [ + { + name: "GovernorImpl", + value: "GovernorComponent::GovernorImpl", + section: "Governor Core", + }, + ], }, GovernorSettingsComponent: { path: extensionPath, substorage: { - name: 'governor_settings', - type: 'GovernorSettingsComponent::Storage', + name: "governor_settings", + type: "GovernorSettingsComponent::Storage", }, event: { - name: 'GovernorSettingsEvent', - type: 'GovernorSettingsComponent::Event', + name: "GovernorSettingsEvent", + type: "GovernorSettingsComponent::Event", }, - impls: [{ - name: 'GovernorSettingsAdminImpl', - value: 'GovernorSettingsComponent::GovernorSettingsAdminImpl', - section: extensionExternalSection, - }, { - name: 'GovernorSettingsImpl', - value: 'GovernorSettingsComponent::GovernorSettings', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorSettingsAdminImpl", + value: + "GovernorSettingsComponent::GovernorSettingsAdminImpl", + section: extensionExternalSection, + }, + { + name: "GovernorSettingsImpl", + value: "GovernorSettingsComponent::GovernorSettings", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorVotesComponent: { path: extensionPath, substorage: { - name: 'governor_votes', - type: 'GovernorVotesComponent::Storage', + name: "governor_votes", + type: "GovernorVotesComponent::Storage", }, event: { - name: 'GovernorVotesEvent', - type: 'GovernorVotesComponent::Event', + name: "GovernorVotesEvent", + type: "GovernorVotesComponent::Event", }, - impls: [{ - name: 'VotesTokenImpl', - value: 'GovernorVotesComponent::VotesTokenImpl', - section: extensionExternalSection, - }, { - name: 'GovernorVotesImpl', - value: 'GovernorVotesComponent::GovernorVotes', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "VotesTokenImpl", + value: "GovernorVotesComponent::VotesTokenImpl", + section: extensionExternalSection, + }, + { + name: "GovernorVotesImpl", + value: "GovernorVotesComponent::GovernorVotes", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorVotesQuorumFractionComponent: { path: extensionPath, substorage: { - name: 'governor_votes', - type: 'GovernorVotesQuorumFractionComponent::Storage', + name: "governor_votes", + type: "GovernorVotesQuorumFractionComponent::Storage", }, event: { - name: 'GovernorVotesEvent', - type: 'GovernorVotesQuorumFractionComponent::Event', + name: "GovernorVotesEvent", + type: "GovernorVotesQuorumFractionComponent::Event", }, - impls: [{ - name: 'GovernorQuorumImpl', - value: 'GovernorVotesQuorumFractionComponent::GovernorQuorum', - embed: false, - section: extensionInternalSection, - }, { - name: 'GovernorVotesImpl', - value: 'GovernorVotesQuorumFractionComponent::GovernorVotes', - embed: false, - section: extensionInternalSection, - }, { - name: 'QuorumFractionImpl', - value: 'GovernorVotesQuorumFractionComponent::QuorumFractionImpl', - section: extensionExternalSection, - }], + impls: [ + { + name: "GovernorQuorumImpl", + value: + "GovernorVotesQuorumFractionComponent::GovernorQuorum", + embed: false, + section: extensionInternalSection, + }, + { + name: "GovernorVotesImpl", + value: + "GovernorVotesQuorumFractionComponent::GovernorVotes", + embed: false, + section: extensionInternalSection, + }, + { + name: "QuorumFractionImpl", + value: + "GovernorVotesQuorumFractionComponent::QuorumFractionImpl", + section: extensionExternalSection, + }, + ], }, GovernorCountingSimpleComponent: { path: extensionPath, substorage: { - name: 'governor_counting', - type: 'GovernorCountingSimpleComponent::Storage', + name: "governor_counting", + type: "GovernorCountingSimpleComponent::Storage", }, event: { - name: 'GovernorCountingSimpleEvent', - type: 'GovernorCountingSimpleComponent::Event', + name: "GovernorCountingSimpleEvent", + type: "GovernorCountingSimpleComponent::Event", }, - impls: [{ - name: 'GovernorCountingSimpleImpl', - value: 'GovernorCountingSimpleComponent::GovernorCounting', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorCountingSimpleImpl", + value: + "GovernorCountingSimpleComponent::GovernorCounting", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorCoreExecutionComponent: { path: extensionPath, substorage: { - name: 'governor_execution', - type: 'GovernorCoreExecutionComponent::Storage', + name: "governor_execution", + type: "GovernorCoreExecutionComponent::Storage", }, event: { - name: 'GovernorCoreExecutionEvent', - type: 'GovernorCoreExecutionComponent::Event', + name: "GovernorCoreExecutionEvent", + type: "GovernorCoreExecutionComponent::Event", }, - impls: [{ - name: 'GovernorCoreExecutionImpl', - value: 'GovernorCoreExecutionComponent::GovernorExecution', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "GovernorCoreExecutionImpl", + value: + "GovernorCoreExecutionComponent::GovernorExecution", + embed: false, + section: extensionInternalSection, + }, + ], }, GovernorTimelockExecutionComponent: { path: extensionPath, substorage: { - name: 'governor_timelock_execution', - type: 'GovernorTimelockExecutionComponent::Storage', + name: "governor_timelock_execution", + type: "GovernorTimelockExecutionComponent::Storage", }, event: { - name: 'GovernorTimelockExecutionEvent', - type: 'GovernorTimelockExecutionComponent::Event', + name: "GovernorTimelockExecutionEvent", + type: "GovernorTimelockExecutionComponent::Event", }, - impls: [{ - name: 'TimelockedImpl', - value: 'GovernorTimelockExecutionComponent::TimelockedImpl', - section: extensionExternalSection, - }, { - name: 'GovernorTimelockExecutionImpl', - value: 'GovernorTimelockExecutionComponent::GovernorExecution', - embed: false, - section: extensionInternalSection, - }], + impls: [ + { + name: "TimelockedImpl", + value: + "GovernorTimelockExecutionComponent::TimelockedImpl", + section: extensionExternalSection, + }, + { + name: "GovernorTimelockExecutionImpl", + value: + "GovernorTimelockExecutionComponent::GovernorExecution", + embed: false, + section: extensionInternalSection, + }, + ], }, }); function addBase(c: ContractBuilder, _: GovernorOptions) { - c.addUseClause('starknet', 'ContractAddress'); - c.addUseClause('openzeppelin::governance::governor', 'DefaultConfig'); - c.addConstructorArgument({ name: 'votes_token', type: 'ContractAddress' }); - c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalTrait', { alias: 'GovernorInternalTrait' }); + c.addUseClause("starknet", "ContractAddress"); + c.addUseClause("openzeppelin::governance::governor", "DefaultConfig"); + c.addConstructorArgument({ name: "votes_token", type: "ContractAddress" }); + c.addUseClause( + "openzeppelin::governance::governor::GovernorComponent", + "InternalTrait", + { alias: "GovernorInternalTrait" }, + ); c.addComponent(components.GovernorComponent, [], true); } function addSettings(c: ContractBuilder, allOpts: Required) { c.addConstant({ - name: 'VOTING_DELAY', - type: 'u64', + name: "VOTING_DELAY", + type: "u64", value: getVotingDelay(allOpts).toString(), comment: allOpts.delay, inlineComment: true, }); c.addConstant({ - name: 'VOTING_PERIOD', - type: 'u64', + name: "VOTING_PERIOD", + type: "u64", value: getVotingPeriod(allOpts).toString(), comment: allOpts.period, inlineComment: true, }); c.addConstant({ - name: 'PROPOSAL_THRESHOLD', - type: 'u256', + name: "PROPOSAL_THRESHOLD", + type: "u256", ...getProposalThreshold(allOpts), inlineComment: true, }); if (allOpts.settings) { - c.addUseClause(`${extensionPath}::GovernorSettingsComponent`, 'InternalTrait', { alias: 'GovernorSettingsInternalTrait' }); - c.addComponent(components.GovernorSettingsComponent, [ - { lit: 'VOTING_DELAY' }, - { lit: 'VOTING_PERIOD' }, - { lit: 'PROPOSAL_THRESHOLD' }, - ], true); + c.addUseClause( + `${extensionPath}::GovernorSettingsComponent`, + "InternalTrait", + { alias: "GovernorSettingsInternalTrait" }, + ); + c.addComponent( + components.GovernorSettingsComponent, + [ + { lit: "VOTING_DELAY" }, + { lit: "VOTING_PERIOD" }, + { lit: "PROPOSAL_THRESHOLD" }, + ], + true, + ); } else { addSettingsLocalImpl(c, allOpts); } @@ -291,10 +333,10 @@ function addSettings(c: ContractBuilder, allOpts: Required) { function getVotingDelay(opts: Required): number { try { - if (opts.clockMode === 'timestamp') { - return durationToTimestamp(opts.delay); + if (opts.clockMode === "timestamp") { + return durationToTimestamp(opts.delay); } else { - throw new Error('Invalid clock mode'); + throw new Error("Invalid clock mode"); } } catch (e) { if (e instanceof Error) { @@ -309,10 +351,10 @@ function getVotingDelay(opts: Required): number { function getVotingPeriod(opts: Required): number { try { - if (opts.clockMode === 'timestamp') { - return durationToTimestamp(opts.period); + if (opts.clockMode === "timestamp") { + return durationToTimestamp(opts.period); } else { - throw new Error('Invalid clock mode'); + throw new Error("Invalid clock mode"); } } catch (e) { if (e instanceof Error) { @@ -328,92 +370,114 @@ function getVotingPeriod(opts: Required): number { function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: 'Not a valid number', + decimals: "Not a valid number", }); } } -function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): {value: string, comment?: string} { +function getProposalThreshold({ + proposalThreshold, + decimals, + votes, +}: Required): { value: string; comment?: string } { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: 'Not a valid number', + proposalThreshold: "Not a valid number", }); } - if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { + if ( + /^0+$/.test(proposalThreshold) || + decimals === 0 || + votes === "erc721votes" + ) { return { value: proposalThreshold }; } else { - let value = `${BigInt(proposalThreshold)*BigInt(10)**BigInt(decimals)}`; - value = toUint(value, 'proposalThreshold', 'u256').toString(); - return { value: `${value}`, comment: `${proposalThreshold} * pow!(10, ${decimals})` }; + let value = `${BigInt(proposalThreshold) * BigInt(10) ** BigInt(decimals)}`; + value = toUint(value, "proposalThreshold", "u256").toString(); + return { + value: `${value}`, + comment: `${proposalThreshold} * pow!(10, ${decimals})`, + }; } } -function addSettingsLocalImpl(c: ContractBuilder, _: Required) { +function addSettingsLocalImpl( + c: ContractBuilder, + _: Required, +) { const settingsTrait = { - name: 'GovernorSettings', - of: 'GovernorComponent::GovernorSettingsTrait', + name: "GovernorSettings", + of: "GovernorComponent::GovernorSettingsTrait", tags: [], - section: 'Locally implemented extensions', + section: "Locally implemented extensions", priority: 2, }; c.addImplementedTrait(settingsTrait); c.addFunction(settingsTrait, { - name: 'voting_delay', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u64', - code: ['VOTING_DELAY'], + name: "voting_delay", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u64", + code: ["VOTING_DELAY"], }); c.addFunction(settingsTrait, { - name: 'voting_period', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u64', - code: ['VOTING_PERIOD'], + name: "voting_period", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u64", + code: ["VOTING_PERIOD"], }); c.addFunction(settingsTrait, { - name: 'proposal_threshold', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }], - returns: 'u256', - code: ['PROPOSAL_THRESHOLD'], + name: "proposal_threshold", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + ], + returns: "u256", + code: ["PROPOSAL_THRESHOLD"], }); } -function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { - if (allOpts.quorumMode === 'percent') { +function addQuorumAndVotes( + c: ContractBuilder, + allOpts: Required, +) { + if (allOpts.quorumMode === "percent") { if (allOpts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: 'Invalid percentage', + quorumPercent: "Invalid percentage", }); } addVotesQuorumFractionComponent(c, allOpts.quorumPercent); - } - else if (allOpts.quorumMode === 'absolute') { + } else if (allOpts.quorumMode === "absolute") { if (!numberPattern.test(allOpts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: 'Not a valid number', + quorumAbsolute: "Not a valid number", }); } let quorum: string; - let comment = ''; - if (allOpts.decimals === 0 || allOpts.votes === 'erc721votes') { + let comment = ""; + if (allOpts.decimals === 0 || allOpts.votes === "erc721votes") { quorum = `${allOpts.quorumAbsolute}`; } else { - quorum = `${BigInt(allOpts.quorumAbsolute)*BigInt(10)**BigInt(allOpts.decimals)}`; - quorum = toUint(quorum, 'quorumAbsolute', 'u256').toString(); + quorum = `${BigInt(allOpts.quorumAbsolute) * BigInt(10) ** BigInt(allOpts.decimals)}`; + quorum = toUint(quorum, "quorumAbsolute", "u256").toString(); comment = `${allOpts.quorumAbsolute} * pow!(10, ${allOpts.decimals})`; } @@ -422,56 +486,75 @@ function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { - c.addUseClause(`${extensionPath}::GovernorVotesComponent`, 'InternalTrait', { alias: 'GovernorVotesInternalTrait' }); - c.addComponent(components.GovernorVotesComponent, [ - { lit: 'votes_token' }, - ], true); + c.addUseClause(`${extensionPath}::GovernorVotesComponent`, "InternalTrait", { + alias: "GovernorVotesInternalTrait", + }); + c.addComponent( + components.GovernorVotesComponent, + [{ lit: "votes_token" }], + true, + ); } -function addQuorumLocalImpl(c: ContractBuilder, quorum: string, comment: string) { +function addQuorumLocalImpl( + c: ContractBuilder, + quorum: string, + comment: string, +) { c.addConstant({ - name: 'QUORUM', - type: 'u256', + name: "QUORUM", + type: "u256", value: quorum, comment, inlineComment: true, }); const quorumTrait = { - name: 'GovernorQuorum', - of: 'GovernorComponent::GovernorQuorumTrait', + name: "GovernorQuorum", + of: "GovernorComponent::GovernorQuorumTrait", tags: [], - section: 'Locally implemented extensions', + section: "Locally implemented extensions", priority: 1, }; c.addImplementedTrait(quorumTrait); c.addFunction(quorumTrait, { - name: 'quorum', - args: [{ - name: 'self', - type: '@GovernorComponent::ComponentState' - }, { - name: 'timepoint', - type: 'u64', - }], - returns: 'u256', - code: ['QUORUM'], + name: "quorum", + args: [ + { + name: "self", + type: "@GovernorComponent::ComponentState", + }, + { + name: "timepoint", + type: "u64", + }, + ], + returns: "u256", + code: ["QUORUM"], }); } @@ -479,15 +562,27 @@ function addCounting(c: ContractBuilder, _: Required) { c.addComponent(components.GovernorCountingSimpleComponent, [], false); } -function addExecution(c: ContractBuilder, { timelock }: Required) { +function addExecution( + c: ContractBuilder, + { timelock }: Required, +) { if (timelock === false) { c.addComponent(components.GovernorCoreExecutionComponent, [], false); } else { - c.addConstructorArgument({ name: 'timelock_controller', type: 'ContractAddress' }); - c.addUseClause(`${extensionPath}::GovernorTimelockExecutionComponent`, 'InternalTrait', { alias: 'GovernorTimelockExecutionInternalTrait' }); - c.addComponent(components.GovernorTimelockExecutionComponent, [ - { lit: 'timelock_controller' }, - ], true); + c.addConstructorArgument({ + name: "timelock_controller", + type: "ContractAddress", + }); + c.addUseClause( + `${extensionPath}::GovernorTimelockExecutionComponent`, + "InternalTrait", + { alias: "GovernorTimelockExecutionInternalTrait" }, + ); + c.addComponent( + components.GovernorTimelockExecutionComponent, + [{ lit: "timelock_controller" }], + true, + ); } } diff --git a/packages/core/cairo/src/index.ts b/packages/core/cairo/src/index.ts index 5f102fabb..461ec1823 100644 --- a/packages/core/cairo/src/index.ts +++ b/packages/core/cairo/src/index.ts @@ -1,28 +1,40 @@ -export type { GenericOptions, KindedOptions } from './build-generic'; -export { buildGeneric } from './build-generic'; - -export type { Contract } from './contract'; -export { ContractBuilder } from './contract'; - -export { printContract } from './print'; - -export type { Access } from './set-access-control'; -export type { Account } from './account'; -export type { Upgradeable } from './set-upgradeable'; -export type { Info } from './set-info'; -export type { RoyaltyInfoOptions } from './set-royalty-info'; - -export { premintPattern } from './erc20'; - -export { defaults as infoDefaults } from './set-info'; -export { defaults as royaltyInfoDefaults } from './set-royalty-info'; - -export type { OptionsErrorMessages } from './error'; -export { OptionsError } from './error'; - -export type { Kind } from './kind'; -export { sanitizeKind } from './kind'; - -export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; - -export { erc20, erc721, erc1155, account, governor, vesting, custom } from './api'; +export type { GenericOptions, KindedOptions } from "./build-generic"; +export { buildGeneric } from "./build-generic"; + +export type { Contract } from "./contract"; +export { ContractBuilder } from "./contract"; + +export { printContract } from "./print"; + +export type { Access } from "./set-access-control"; +export type { Account } from "./account"; +export type { Upgradeable } from "./set-upgradeable"; +export type { Info } from "./set-info"; +export type { RoyaltyInfoOptions } from "./set-royalty-info"; + +export { premintPattern } from "./erc20"; + +export { defaults as infoDefaults } from "./set-info"; +export { defaults as royaltyInfoDefaults } from "./set-royalty-info"; + +export type { OptionsErrorMessages } from "./error"; +export { OptionsError } from "./error"; + +export type { Kind } from "./kind"; +export { sanitizeKind } from "./kind"; + +export { + contractsVersion, + contractsVersionTag, + compatibleContractsSemver, +} from "./utils/version"; + +export { + erc20, + erc721, + erc1155, + account, + governor, + vesting, + custom, +} from "./api"; diff --git a/packages/core/cairo/src/kind.ts b/packages/core/cairo/src/kind.ts index d61180216..c205c7715 100644 --- a/packages/core/cairo/src/kind.ts +++ b/packages/core/cairo/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from './build-generic'; +import type { GenericOptions } from "./build-generic"; -export type Kind = GenericOptions['kind']; +export type Kind = GenericOptions["kind"]; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === 'string') { - const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); + if (typeof kind === "string") { + const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return 'ERC20'; + return "ERC20"; } function isKind(value: Kind | T): value is Kind { switch (value) { - case 'ERC20': - case 'ERC721': - case 'ERC1155': - case 'Account': - case 'Governor': - case 'Vesting': - case 'Custom': + case "ERC20": + case "ERC721": + case "ERC1155": + case "Account": + case "Governor": + case "Vesting": + case "Custom": return true; default: { @@ -30,4 +30,3 @@ function isKind(value: Kind | T): value is Kind { } } } - diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index 71f5dec38..2c55c403c 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -40,11 +40,11 @@ export function printContract(contract: Contract): string { printStorage(contract), printEvents(contract), printConstructor(contract), - printImplementedTraits(contract) + printImplementedTraits(contract), ), `}`, - ] - ) + ], + ), ); } @@ -55,8 +55,8 @@ function withSemicolons(lines: string[]): string[] { function printSuperVariables(contract: Contract): string[] { return withSemicolons( contract.superVariables.map( - (v) => `const ${v.name}: ${v.type} = ${v.value}` - ) + (v) => `const ${v.name}: ${v.type} = ${v.value}`, + ), ); } @@ -67,7 +67,7 @@ function printUseClauses(contract: Contract): Lines[] { const grouped = useClauses.reduce( ( result: { [containerPath: string]: UseClause[] }, - useClause: UseClause + useClause: UseClause, ) => { if (useClause.groupable) { (result[useClause.containerPath] = @@ -78,24 +78,24 @@ function printUseClauses(contract: Contract): Lines[] { } return result; }, - {} + {}, ); const lines = Object.entries(grouped).flatMap(([groupName, group]) => - getLinesFromUseClausesGroup(group, groupName) + getLinesFromUseClausesGroup(group, groupName), ); return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); } function getLinesFromUseClausesGroup( group: UseClause[], - groupName: string + groupName: string, ): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { lines.push( - `use ${useClause.containerPath}::${nameWithAlias(useClause)};` + `use ${useClause.containerPath}::${nameWithAlias(useClause)};`, ); } } else { @@ -168,15 +168,15 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};` + `const ${constant.name}: ${constant.type} = ${constant.value};`, ); } else if (commented) { lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}` + `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`, ); } else { lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};` + `const ${constant.name}: ${constant.type} = ${constant.value};`, ); } } @@ -187,7 +187,7 @@ function printComponentDeclarations(contract: Contract): Lines[] { const lines = []; for (const component of contract.components) { lines.push( - `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});` + `component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});`, ); } return lines; @@ -206,7 +206,7 @@ function printImpls(contract: Contract): Lines[] { (result[section] = result[section] || []).push(current); return result; }, - {} + {}, ); const sections = Object.entries(grouped) @@ -241,7 +241,7 @@ function printStorage(contract: Contract): (string | string[])[] { for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); storageLines.push( - `${component.substorage.name}: ${component.substorage.type},` + `${component.substorage.name}: ${component.substorage.type},`, ); } lines.push(storageLines); @@ -282,20 +282,20 @@ function printImplementedTraits(contract: Contract): Lines[] { const grouped = sortedTraits.reduce( ( result: { [section: string]: ImplementedTrait[] }, - current: ImplementedTrait + current: ImplementedTrait, ) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); return result; }, - {} + {}, ); const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) .map(([section, impls]) => - printImplementedTraitsSection(section, impls as ImplementedTrait[]) + printImplementedTraitsSection(section, impls as ImplementedTrait[]), ); return spaceBetween(...sections); @@ -303,7 +303,7 @@ function printImplementedTraits(contract: Contract): Lines[] { function printImplementedTraitsSection( section: string, - impls: ImplementedTrait[] + impls: ImplementedTrait[], ): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; @@ -327,7 +327,7 @@ function printImplementedTrait(trait: ImplementedTrait): Lines[] { implLines.push(`impl ${trait.name} of ${trait.of} {`); const superVars = withSemicolons( - trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`) + trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`), ); implLines.push(superVars); @@ -363,7 +363,7 @@ function printFunction(fn: ContractFunction): Lines[] { function printConstructor(contract: Contract): Lines[] { const hasInitializers = contract.components.some( - (p) => p.initializer !== undefined + (p) => p.initializer !== undefined, ); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { @@ -376,7 +376,7 @@ function printConstructor(contract: Contract): Lines[] { const body = spaceBetween( withSemicolons(parents), - withSemicolons(contract.constructorCode) + withSemicolons(contract.constructorCode), ); const constructor = printFunction2( @@ -385,7 +385,7 @@ function printConstructor(contract: Contract): Lines[] { tag, undefined, undefined, - body + body, ); return constructor; } else { @@ -441,7 +441,7 @@ function printFunction2( tag: string | undefined, returns: string | undefined, returnLine: string | undefined, - code: Lines[] + code: Lines[], ): Lines[] { const fn = []; diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index 53a626d05..ee5f24897 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -17,7 +17,7 @@ export async function updateScarbProject() { const contractNames = await writeGeneratedSources( generatedSourcesPath, "all", - true + true, ); // Generate lib.cairo file @@ -41,16 +41,16 @@ async function updateScarbToml() { .replace(/edition = "\w+"/, `edition = "${edition}"`) .replace( /cairo-version = "\d+\.\d+\.\d+"/, - `cairo-version = "${cairoVersion}"` + `cairo-version = "${cairoVersion}"`, ) .replace( /scarb-version = "\d+\.\d+\.\d+"/, - `scarb-version = "${scarbVersion}"` + `scarb-version = "${scarbVersion}"`, ) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) .replace( /openzeppelin = "\d+\.\d+\.\d+"/, - `openzeppelin = "${contractsVersion}"` + `openzeppelin = "${contractsVersion}"`, ); await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); diff --git a/packages/core/cairo/src/set-access-control.ts b/packages/core/cairo/src/set-access-control.ts index 7bd3c2218..fe9d0c65a 100644 --- a/packages/core/cairo/src/set-access-control.ts +++ b/packages/core/cairo/src/set-access-control.ts @@ -1,50 +1,64 @@ -import type { BaseFunction, BaseImplementedTrait, ContractBuilder } from './contract'; -import { defineComponents } from './utils/define-components'; -import { addSRC5Component } from './common-components'; +import type { + BaseFunction, + BaseImplementedTrait, + ContractBuilder, +} from "./contract"; +import { defineComponents } from "./utils/define-components"; +import { addSRC5Component } from "./common-components"; -export const accessOptions = [false, 'ownable', 'roles'] as const; -export const DEFAULT_ACCESS_CONTROL = 'ownable'; +export const accessOptions = [false, "ownable", "roles"] as const; +export const DEFAULT_ACCESS_CONTROL = "ownable"; -export type Access = typeof accessOptions[number]; +export type Access = (typeof accessOptions)[number]; /** * Sets access control for the contract by adding inheritance. */ - export function setAccessControl(c: ContractBuilder, access: Access): void { +export function setAccessControl(c: ContractBuilder, access: Access): void { switch (access) { - case 'ownable': { - c.addComponent(components.OwnableComponent, [{ lit: 'owner' }], true); + case "ownable": { + c.addComponent(components.OwnableComponent, [{ lit: "owner" }], true); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'owner', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: "owner", type: "ContractAddress" }); break; } - case 'roles': { + case "roles": { if (c.addComponent(components.AccessControlComponent, [], true)) { - if (c.interfaceFlags.has('ISRC5')) { + if (c.interfaceFlags.has("ISRC5")) { c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlImpl', - value: 'AccessControlComponent::AccessControlImpl', + name: "AccessControlImpl", + value: "AccessControlComponent::AccessControlImpl", }); c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlCamelImpl', - value: 'AccessControlComponent::AccessControlCamelImpl', + name: "AccessControlCamelImpl", + value: + "AccessControlComponent::AccessControlCamelImpl", }); } else { c.addImplToComponent(components.AccessControlComponent, { - name: 'AccessControlMixinImpl', - value: 'AccessControlComponent::AccessControlMixinImpl', + name: "AccessControlMixinImpl", + value: + "AccessControlComponent::AccessControlMixinImpl", }); - c.addInterfaceFlag('ISRC5'); + c.addInterfaceFlag("ISRC5"); } addSRC5Component(c); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'default_admin', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ + name: "default_admin", + type: "ContractAddress", + }); - c.addUseClause('openzeppelin::access::accesscontrol', 'DEFAULT_ADMIN_ROLE'); - c.addConstructorCode('self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)'); + c.addUseClause( + "openzeppelin::access::accesscontrol", + "DEFAULT_ADMIN_ROLE", + ); + c.addConstructorCode( + "self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)", + ); } break; } @@ -55,12 +69,12 @@ export type Access = typeof accessOptions[number]; * Enables access control for the contract and restricts the given function with access control. */ export function requireAccessControl( - c: ContractBuilder, - trait: BaseImplementedTrait, - fn: BaseFunction, - access: Access, - roleIdPrefix: string, - roleOwner: string | undefined + c: ContractBuilder, + trait: BaseImplementedTrait, + fn: BaseFunction, + access: Access, + roleIdPrefix: string, + roleOwner: string | undefined, ): void { if (access === false) { access = DEFAULT_ACCESS_CONTROL; @@ -68,62 +82,77 @@ export function requireAccessControl( setAccessControl(c, access); switch (access) { - case 'ownable': { - c.addFunctionCodeBefore(trait, fn, 'self.ownable.assert_only_owner()'); + case "ownable": { + c.addFunctionCodeBefore(trait, fn, "self.ownable.assert_only_owner()"); break; } - case 'roles': { - const roleId = roleIdPrefix + '_ROLE'; - const addedSuper = c.addSuperVariable({ name: roleId, type: 'felt252', value: `selector!("${roleId}")` }) + case "roles": { + const roleId = roleIdPrefix + "_ROLE"; + const addedSuper = c.addSuperVariable({ + name: roleId, + type: "felt252", + value: `selector!("${roleId}")`, + }); if (roleOwner !== undefined) { - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: roleOwner, type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ name: roleOwner, type: "ContractAddress" }); if (addedSuper) { - c.addConstructorCode(`self.accesscontrol._grant_role(${roleId}, ${roleOwner})`); + c.addConstructorCode( + `self.accesscontrol._grant_role(${roleId}, ${roleOwner})`, + ); } } - c.addFunctionCodeBefore(trait, fn, `self.accesscontrol.assert_only_role(${roleId})`); + c.addFunctionCodeBefore( + trait, + fn, + `self.accesscontrol.assert_only_role(${roleId})`, + ); break; } } } -const components = defineComponents( { +const components = defineComponents({ OwnableComponent: { - path: 'openzeppelin::access::ownable', + path: "openzeppelin::access::ownable", substorage: { - name: 'ownable', - type: 'OwnableComponent::Storage', + name: "ownable", + type: "OwnableComponent::Storage", }, event: { - name: 'OwnableEvent', - type: 'OwnableComponent::Event', + name: "OwnableEvent", + type: "OwnableComponent::Event", }, - impls: [{ - name: 'OwnableMixinImpl', - value: 'OwnableComponent::OwnableMixinImpl', - }, { - name: 'OwnableInternalImpl', - embed: false, - value: 'OwnableComponent::InternalImpl', - }], + impls: [ + { + name: "OwnableMixinImpl", + value: "OwnableComponent::OwnableMixinImpl", + }, + { + name: "OwnableInternalImpl", + embed: false, + value: "OwnableComponent::InternalImpl", + }, + ], }, AccessControlComponent: { - path: 'openzeppelin::access::accesscontrol', + path: "openzeppelin::access::accesscontrol", substorage: { - name: 'accesscontrol', - type: 'AccessControlComponent::Storage', + name: "accesscontrol", + type: "AccessControlComponent::Storage", }, event: { - name: 'AccessControlEvent', - type: 'AccessControlComponent::Event', + name: "AccessControlEvent", + type: "AccessControlComponent::Event", }, - impls: [{ - name: 'AccessControlInternalImpl', - embed: false, - value: 'AccessControlComponent::InternalImpl', - }], + impls: [ + { + name: "AccessControlInternalImpl", + embed: false, + value: "AccessControlComponent::InternalImpl", + }, + ], }, }); diff --git a/packages/core/cairo/src/set-info.ts b/packages/core/cairo/src/set-info.ts index 421a15029..30a110db5 100644 --- a/packages/core/cairo/src/set-info.ts +++ b/packages/core/cairo/src/set-info.ts @@ -1,12 +1,12 @@ import type { ContractBuilder } from "./contract"; -export const infoOptions = [{}, { license: 'WTFPL' }] as const; +export const infoOptions = [{}, { license: "WTFPL" }] as const; -export const defaults: Info = { license: 'MIT' }; +export const defaults: Info = { license: "MIT" }; export type Info = { license?: string; -} +}; export function setInfo(c: ContractBuilder, info: Info): void { const { license } = info; diff --git a/packages/core/cairo/src/set-royalty-info.ts b/packages/core/cairo/src/set-royalty-info.ts index 5464be105..fb27f0dfe 100644 --- a/packages/core/cairo/src/set-royalty-info.ts +++ b/packages/core/cairo/src/set-royalty-info.ts @@ -1,38 +1,46 @@ -import type { BaseImplementedTrait, ContractBuilder } from './contract'; -import { defineComponents } from './utils/define-components'; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; +import { defineComponents } from "./utils/define-components"; import { OptionsError } from "./error"; -import { toUint } from './utils/convert-strings'; -import { Access, setAccessControl, DEFAULT_ACCESS_CONTROL } from './set-access-control'; +import { toUint } from "./utils/convert-strings"; +import { + Access, + setAccessControl, + DEFAULT_ACCESS_CONTROL, +} from "./set-access-control"; const DEFAULT_FEE_DENOMINATOR = BigInt(10_000); export const defaults: RoyaltyInfoOptions = { enabled: false, - defaultRoyaltyFraction: '0', - feeDenominator: DEFAULT_FEE_DENOMINATOR.toString() + defaultRoyaltyFraction: "0", + feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }; export const royaltyInfoOptions = { disabled: defaults, enabledDefault: { enabled: true, - defaultRoyaltyFraction: '500', + defaultRoyaltyFraction: "500", feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }, enabledCustom: { enabled: true, - defaultRoyaltyFraction: '15125', - feeDenominator: '100000', - } -} + defaultRoyaltyFraction: "15125", + feeDenominator: "100000", + }, +}; export type RoyaltyInfoOptions = { - enabled: boolean, - defaultRoyaltyFraction: string, - feeDenominator: string, + enabled: boolean; + defaultRoyaltyFraction: string; + feeDenominator: string; }; -export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, access: Access): void { +export function setRoyaltyInfo( + c: ContractBuilder, + options: RoyaltyInfoOptions, + access: Access, +): void { if (!options.enabled) { return; } @@ -41,61 +49,77 @@ export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, } setAccessControl(c, access); - const { defaultRoyaltyFraction, feeDenominator } = getRoyaltyParameters(options); + const { defaultRoyaltyFraction, feeDenominator } = + getRoyaltyParameters(options); const initParams = [ - { lit: 'default_royalty_receiver' }, - defaultRoyaltyFraction + { lit: "default_royalty_receiver" }, + defaultRoyaltyFraction, ]; c.addComponent(components.ERC2981Component, initParams, true); - c.addUseClause('starknet', 'ContractAddress'); - c.addConstructorArgument({ name: 'default_royalty_receiver', type: 'ContractAddress'}); + c.addUseClause("starknet", "ContractAddress"); + c.addConstructorArgument({ + name: "default_royalty_receiver", + type: "ContractAddress", + }); switch (access) { - case 'ownable': + case "ownable": c.addImplToComponent(components.ERC2981Component, { - name: 'ERC2981AdminOwnableImpl', + name: "ERC2981AdminOwnableImpl", value: `ERC2981Component::ERC2981AdminOwnableImpl`, }); break; - case 'roles': + case "roles": c.addImplToComponent(components.ERC2981Component, { - name: 'ERC2981AdminAccessControlImpl', + name: "ERC2981AdminAccessControlImpl", value: `ERC2981Component::ERC2981AdminAccessControlImpl`, }); - c.addConstructorArgument({ name: 'royalty_admin', type: 'ContractAddress'}); - c.addConstructorCode('self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)'); + c.addConstructorArgument({ + name: "royalty_admin", + type: "ContractAddress", + }); + c.addConstructorCode( + "self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)", + ); break; } if (feeDenominator === DEFAULT_FEE_DENOMINATOR) { - c.addUseClause('openzeppelin::token::common::erc2981', 'DefaultConfig'); + c.addUseClause("openzeppelin::token::common::erc2981", "DefaultConfig"); } else { const trait: BaseImplementedTrait = { - name: 'ERC2981ImmutableConfig', - of: 'ERC2981Component::ImmutableConfig', + name: "ERC2981ImmutableConfig", + of: "ERC2981Component::ImmutableConfig", tags: [], }; c.addImplementedTrait(trait); c.addSuperVariableToTrait(trait, { - name: 'FEE_DENOMINATOR', - type: 'u128', - value: feeDenominator.toString() + name: "FEE_DENOMINATOR", + type: "u128", + value: feeDenominator.toString(), }); } } -function getRoyaltyParameters(opts: Required): { defaultRoyaltyFraction: bigint, feeDenominator: bigint } { - const feeDenominator = toUint(opts.feeDenominator, 'feeDenominator', 'u128'); +function getRoyaltyParameters(opts: Required): { + defaultRoyaltyFraction: bigint; + feeDenominator: bigint; +} { + const feeDenominator = toUint(opts.feeDenominator, "feeDenominator", "u128"); if (feeDenominator === BigInt(0)) { throw new OptionsError({ - feeDenominator: 'Must be greater than 0' + feeDenominator: "Must be greater than 0", }); } - const defaultRoyaltyFraction = toUint(opts.defaultRoyaltyFraction, 'defaultRoyaltyFraction', 'u128'); + const defaultRoyaltyFraction = toUint( + opts.defaultRoyaltyFraction, + "defaultRoyaltyFraction", + "u128", + ); if (defaultRoyaltyFraction > feeDenominator) { throw new OptionsError({ - defaultRoyaltyFraction: 'Cannot be greater than fee denominator' + defaultRoyaltyFraction: "Cannot be greater than fee denominator", }); } return { defaultRoyaltyFraction, feeDenominator }; @@ -103,29 +127,29 @@ function getRoyaltyParameters(opts: Required): { defaultRoya const components = defineComponents({ ERC2981Component: { - path: 'openzeppelin::token::common::erc2981', + path: "openzeppelin::token::common::erc2981", substorage: { - name: 'erc2981', - type: 'ERC2981Component::Storage', + name: "erc2981", + type: "ERC2981Component::Storage", }, event: { - name: 'ERC2981Event', - type: 'ERC2981Component::Event', + name: "ERC2981Event", + type: "ERC2981Component::Event", }, impls: [ { - name: 'ERC2981Impl', - value: 'ERC2981Component::ERC2981Impl', + name: "ERC2981Impl", + value: "ERC2981Component::ERC2981Impl", }, { - name: 'ERC2981InfoImpl', - value: 'ERC2981Component::ERC2981InfoImpl', + name: "ERC2981InfoImpl", + value: "ERC2981Component::ERC2981InfoImpl", }, { - name: 'ERC2981InternalImpl', - value: 'ERC2981Component::InternalImpl', - embed: false - } + name: "ERC2981InternalImpl", + value: "ERC2981Component::InternalImpl", + embed: false, + }, ], }, }); diff --git a/packages/core/cairo/src/set-upgradeable.ts b/packages/core/cairo/src/set-upgradeable.ts index 65a944128..fdb955e54 100644 --- a/packages/core/cairo/src/set-upgradeable.ts +++ b/packages/core/cairo/src/set-upgradeable.ts @@ -1,15 +1,18 @@ -import { getSelfArg } from './common-options'; -import type { BaseImplementedTrait, ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineComponents } from './utils/define-components'; -import { defineFunctions } from './utils/define-functions'; -import type { Account } from './account'; +import { getSelfArg } from "./common-options"; +import type { BaseImplementedTrait, ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineComponents } from "./utils/define-components"; +import { defineFunctions } from "./utils/define-functions"; +import type { Account } from "./account"; export const upgradeableOptions = [false, true] as const; -export type Upgradeable = typeof upgradeableOptions[number]; +export type Upgradeable = (typeof upgradeableOptions)[number]; -function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseImplementedTrait | undefined { +function setUpgradeableBase( + c: ContractBuilder, + upgradeable: Upgradeable, +): BaseImplementedTrait | undefined { if (upgradeable === false) { return undefined; } @@ -18,78 +21,106 @@ function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseI c.addComponent(components.UpgradeableComponent, [], false); - c.addUseClause('openzeppelin::upgrades::interface', 'IUpgradeable'); - c.addUseClause('starknet', 'ClassHash'); + c.addUseClause("openzeppelin::upgrades::interface", "IUpgradeable"); + c.addUseClause("starknet", "ClassHash"); const t: BaseImplementedTrait = { - name: 'UpgradeableImpl', - of: 'IUpgradeable', - section: 'Upgradeable', - tags: [ - 'abi(embed_v0)' - ], + name: "UpgradeableImpl", + of: "IUpgradeable", + section: "Upgradeable", + tags: ["abi(embed_v0)"], }; c.addImplementedTrait(t); return t; } -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access): void { +export function setUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + access: Access, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - requireAccessControl(c, trait, functions.upgrade, access, 'UPGRADER', 'upgrader'); + requireAccessControl( + c, + trait, + functions.upgrade, + access, + "UPGRADER", + "upgrader", + ); } } -export function setUpgradeableGovernor(c: ContractBuilder, upgradeable: Upgradeable): void { +export function setUpgradeableGovernor( + c: ContractBuilder, + upgradeable: Upgradeable, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalExtendedImpl'); - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.governor.assert_only_governance()'); + c.addUseClause( + "openzeppelin::governance::governor::GovernorComponent", + "InternalExtendedImpl", + ); + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.governor.assert_only_governance()", + ); } } -export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account): void { +export function setAccountUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + type: Account, +): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { switch (type) { - case 'stark': - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.account.assert_only_self()'); + case "stark": + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.account.assert_only_self()", + ); break; - case 'eth': - c.addFunctionCodeBefore(trait, functions.upgrade, 'self.eth_account.assert_only_self()'); + case "eth": + c.addFunctionCodeBefore( + trait, + functions.upgrade, + "self.eth_account.assert_only_self()", + ); break; } } } -const components = defineComponents( { +const components = defineComponents({ UpgradeableComponent: { - path: 'openzeppelin::upgrades', + path: "openzeppelin::upgrades", substorage: { - name: 'upgradeable', - type: 'UpgradeableComponent::Storage', + name: "upgradeable", + type: "UpgradeableComponent::Storage", }, event: { - name: 'UpgradeableEvent', - type: 'UpgradeableComponent::Event', + name: "UpgradeableEvent", + type: "UpgradeableComponent::Event", }, - impls: [{ - name: 'UpgradeableInternalImpl', - embed: false, - value: 'UpgradeableComponent::InternalImpl', - }], + impls: [ + { + name: "UpgradeableInternalImpl", + embed: false, + value: "UpgradeableComponent::InternalImpl", + }, + ], }, }); const functions = defineFunctions({ upgrade: { - args: [ - getSelfArg(), - { name: 'new_class_hash', type: 'ClassHash' }, - ], - code: [ - 'self.upgradeable.upgrade(new_class_hash)' - ] + args: [getSelfArg(), { name: "new_class_hash", type: "ClassHash" }], + code: ["self.upgradeable.upgrade(new_class_hash)"], }, }); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index e34e31247..e5974c16a 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -35,7 +35,7 @@ test.serial("custom result generated", async (t) => { async function testGenerate( t: ExecutionContext, - kind: keyof KindedOptions + kind: keyof KindedOptions, ) { const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); @@ -81,13 +81,13 @@ test("is access control required", async (t) => { t.regex( contract.source, regexOwnable, - JSON.stringify(contract.options) + JSON.stringify(contract.options), ); } else { t.notRegex( contract.source, regexOwnable, - JSON.stringify(contract.options) + JSON.stringify(contract.options), ); } } diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index 2bab75c9d..f4f9eca87 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -36,7 +36,7 @@ test("identifier - empty string", (t) => { const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); t.is( error.messages.name, - "Identifier is empty or does not have valid characters" + "Identifier is empty or does not have valid characters", ); }); @@ -46,7 +46,7 @@ test("identifier - no valid chars", (t) => { }); t.is( error.messages.name, - "Identifier is empty or does not have valid characters" + "Identifier is empty or does not have valid characters", ); }); @@ -77,11 +77,11 @@ test("toByteArray - escape backslash", (t) => { test("more than 31 characters", (t) => { t.is( toByteArray("A234567890123456789012345678901"), - "A234567890123456789012345678901" + "A234567890123456789012345678901", ); t.is( toByteArray("A2345678901234567890123456789012"), - "A2345678901234567890123456789012" + "A2345678901234567890123456789012", ); }); @@ -108,12 +108,12 @@ test("toFelt252 - escape backslash", (t) => { test("toFelt252 - max 31 characters", (t) => { t.is( toFelt252("A234567890123456789012345678901", "foo"), - "A234567890123456789012345678901" + "A234567890123456789012345678901", ); const error = t.throws( () => toFelt252("A2345678901234567890123456789012", "foo"), - { instanceOf: OptionsError } + { instanceOf: OptionsError }, ); t.is(error.messages.foo, "String is longer than 31 characters"); }); diff --git a/packages/core/cairo/src/utils/convert-strings.ts b/packages/core/cairo/src/utils/convert-strings.ts index d1681ec09..da8d0fe57 100644 --- a/packages/core/cairo/src/utils/convert-strings.ts +++ b/packages/core/cairo/src/utils/convert-strings.ts @@ -5,14 +5,15 @@ import { OptionsError } from "../error"; */ export function toIdentifier(str: string, capitalize = false): string { const result = str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/^[^a-zA-Z_]+/, '') - .replace(/^(.)/, c => capitalize ? c.toUpperCase() : c) + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/^[^a-zA-Z_]+/, "") + .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w]+(.?)/g, (_, c) => c.toUpperCase()); if (result.length === 0) { throw new OptionsError({ - name: 'Identifier is empty or does not have valid characters', + name: "Identifier is empty or does not have valid characters", }); } else { return result; @@ -24,9 +25,10 @@ export function toIdentifier(str: string, capitalize = false): string { */ export function toByteArray(str: string): string { return str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters - .replace(/(\\|")/g, (_, c) => '\\' + c); // escape backslash or double quotes + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters + .replace(/(\\|")/g, (_, c) => "\\" + c); // escape backslash or double quotes } /** @@ -34,13 +36,14 @@ export function toByteArray(str: string): string { */ export function toFelt252(str: string, field: string): string { const result = str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters - .replace(/(\\|')/g, (_, c) => '\\' + c); // escape backslash or single quote + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters + .replace(/(\\|')/g, (_, c) => "\\" + c); // escape backslash or single quote if (result.length > 31) { throw new OptionsError({ - [field]: 'String is longer than 31 characters', + [field]: "String is longer than 31 characters", }); } else { return result; @@ -49,22 +52,24 @@ export function toFelt252(str: string, field: string): string { function maxValueOfUint(bits: number): bigint { if (bits <= 0) { - throw new Error(`Number of bits must be positive (actual '${bits}').`) + throw new Error(`Number of bits must be positive (actual '${bits}').`); } if (bits % 8 !== 0) { - throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`) + throw new Error( + `The number of bits must be a multiple of 8 (actual '${bits}').`, + ); } const bytes = bits / 8; - return BigInt('0x' + 'ff'.repeat(bytes)) + return BigInt("0x" + "ff".repeat(bytes)); } const UINT_MAX_VALUES = { - 'u8': maxValueOfUint(8), - 'u16': maxValueOfUint(16), - 'u32': maxValueOfUint(32), - 'u64': maxValueOfUint(64), - 'u128': maxValueOfUint(128), - 'u256': maxValueOfUint(256) + u8: maxValueOfUint(8), + u16: maxValueOfUint(16), + u32: maxValueOfUint(32), + u64: maxValueOfUint(64), + u128: maxValueOfUint(128), + u256: maxValueOfUint(256), } as const; export type UintType = keyof typeof UINT_MAX_VALUES; @@ -72,18 +77,22 @@ export type UintType = keyof typeof UINT_MAX_VALUES; /** * Checks that a string/number value is a valid `uint` value and converts it to bigint */ -export function toUint(value: number | string, field: string, type: UintType): bigint { - const valueAsStr = value.toString(); +export function toUint( + value: number | string, + field: string, + type: UintType, +): bigint { + const valueAsStr = value.toString(); const isValidNumber = /^\d+$/.test(valueAsStr); if (!isValidNumber) { throw new OptionsError({ - [field]: 'Not a valid number' + [field]: "Not a valid number", }); } const numValue = BigInt(valueAsStr); if (numValue > UINT_MAX_VALUES[type]) { throw new OptionsError({ - [field]: `Value is greater than ${type} max value` + [field]: `Value is greater than ${type} max value`, }); } return numValue; @@ -92,6 +101,10 @@ export function toUint(value: number | string, field: string, type: UintType): b /** * Checks that a string/number value is a valid `uint` value */ -export function validateUint(value: number | string, field: string, type: UintType): void { +export function validateUint( + value: number | string, + field: string, + type: UintType, +): void { const _ = toUint(value, field, type); } diff --git a/packages/core/cairo/src/utils/define-components.ts b/packages/core/cairo/src/utils/define-components.ts index 62ba3ca6e..f929db64f 100644 --- a/packages/core/cairo/src/utils/define-components.ts +++ b/packages/core/cairo/src/utils/define-components.ts @@ -1,6 +1,6 @@ -import type { Component } from '../contract'; +import type { Component } from "../contract"; -type ImplicitNameComponent = Omit; +type ImplicitNameComponent = Omit; export function defineComponents( fns: Record, diff --git a/packages/core/cairo/src/utils/define-functions.ts b/packages/core/cairo/src/utils/define-functions.ts index c1f664e7e..c05316bce 100644 --- a/packages/core/cairo/src/utils/define-functions.ts +++ b/packages/core/cairo/src/utils/define-functions.ts @@ -1,6 +1,6 @@ -import type { BaseFunction } from '../contract'; +import type { BaseFunction } from "../contract"; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; export function defineFunctions( fns: Record, diff --git a/packages/core/cairo/src/utils/duration.ts b/packages/core/cairo/src/utils/duration.ts index ffc2fe5b5..224de5dfa 100644 --- a/packages/core/cairo/src/utils/duration.ts +++ b/packages/core/cairo/src/utils/duration.ts @@ -1,6 +1,16 @@ -const durationUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; -type DurationUnit = typeof durationUnits[number]; -export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); +const durationUnits = [ + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", +] as const; +type DurationUnit = (typeof durationUnits)[number]; +export const durationPattern = new RegExp( + `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, +); const second = 1; const minute = 60 * second; @@ -15,7 +25,7 @@ export function durationToTimestamp(duration: string): number { const match = duration.trim().match(durationPattern); if (!match || match.length < 2) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = parseFloat(match[1]!); diff --git a/packages/core/cairo/src/utils/find-cover.ts b/packages/core/cairo/src/utils/find-cover.ts index 939ed9240..0cc7f0bb6 100644 --- a/packages/core/cairo/src/utils/find-cover.ts +++ b/packages/core/cairo/src/utils/find-cover.ts @@ -1,11 +1,14 @@ -import { sortedBy } from './sorted-by'; +import { sortedBy } from "./sorted-by"; // Greedy approximation of minimum set cover. -export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { +export function findCover( + sets: T[], + getElements: (set: T) => unknown[], +): T[] { const sortedSets = sortedBy( - sets.map(set => ({ set, elems: getElements(set) })), - s => -s.elems.length, + sets.map((set) => ({ set, elems: getElements(set) })), + (s) => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/cairo/src/utils/format-lines.ts b/packages/core/cairo/src/utils/format-lines.ts index 5e18a0c4a..66a39941e 100644 --- a/packages/core/cairo/src/utils/format-lines.ts +++ b/packages/core/cairo/src/utils/format-lines.ts @@ -1,9 +1,9 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol('whitespace'); +const whitespace = Symbol("whitespace"); export function formatLines(...lines: Lines[]): string { - return [...indentEach(0, lines)].join('\n') + '\n'; + return [...indentEach(0, lines)].join("\n") + "\n"; } function* indentEach( @@ -12,18 +12,18 @@ function* indentEach( ): Generator { for (const line of lines) { if (line === whitespace) { - yield ''; + yield ""; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line); } else { - yield ' '.repeat(indent) + line; + yield " ".repeat(indent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter(l => l.length > 0) - .flatMap(l => [whitespace, ...l]) + .filter((l) => l.length > 0) + .flatMap((l) => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/cairo/src/utils/version.test.ts b/packages/core/cairo/src/utils/version.test.ts index 5b580626c..a612a88e8 100644 --- a/packages/core/cairo/src/utils/version.test.ts +++ b/packages/core/cairo/src/utils/version.test.ts @@ -1,11 +1,13 @@ -import test from 'ava'; +import test from "ava"; -import semver from 'semver'; +import semver from "semver"; -import { contractsVersion, compatibleContractsSemver } from './version'; +import { contractsVersion, compatibleContractsSemver } from "./version"; -test('latest target contracts satisfies compatible range', t => { - t.true(semver.satisfies(contractsVersion, compatibleContractsSemver), +test("latest target contracts satisfies compatible range", (t) => { + t.true( + semver.satisfies(contractsVersion, compatibleContractsSemver), `Latest target contracts version ${contractsVersion} does not satisfy compatible range ${compatibleContractsSemver}. -Check whether the compatible range is up to date.`); +Check whether the compatible range is up to date.`, + ); }); diff --git a/packages/core/cairo/src/utils/version.ts b/packages/core/cairo/src/utils/version.ts index ccba00acc..89eaca1a3 100644 --- a/packages/core/cairo/src/utils/version.ts +++ b/packages/core/cairo/src/utils/version.ts @@ -1,17 +1,17 @@ /** * The actual latest version to use in links. */ -export const contractsVersion = '0.20.0'; +export const contractsVersion = "0.20.0"; export const contractsVersionTag = `v${contractsVersion}`; /** * Cairo compiler versions. */ -export const edition = '2024_07'; -export const cairoVersion = '2.9.1'; -export const scarbVersion = '2.9.1'; +export const edition = "2024_07"; +export const cairoVersion = "2.9.1"; +export const scarbVersion = "2.9.1"; /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = '^0.20.0'; +export const compatibleContractsSemver = "^0.20.0"; diff --git a/packages/core/cairo/src/vesting.test.ts b/packages/core/cairo/src/vesting.test.ts index 3f9ead374..cf0fa48fc 100644 --- a/packages/core/cairo/src/vesting.test.ts +++ b/packages/core/cairo/src/vesting.test.ts @@ -1,41 +1,46 @@ -import test from 'ava'; -import { OptionsError, vesting } from '.'; -import { buildVesting, VestingOptions } from './vesting'; -import { printContract } from './print'; +import test from "ava"; +import { OptionsError, vesting } from "."; +import { buildVesting, VestingOptions } from "./vesting"; +import { printContract } from "./print"; const defaults: VestingOptions = { - name: 'MyVesting', - startDate: '', - duration: '0 day', - cliffDuration: '0 day', - schedule: 'linear' + name: "MyVesting", + startDate: "", + duration: "0 day", + cliffDuration: "0 day", + schedule: "linear", }; -const CUSTOM_NAME = 'CustomVesting'; -const CUSTOM_DATE = '2024-12-31T23:59'; -const CUSTOM_DURATION = '36 months'; -const CUSTOM_CLIFF = '90 days'; +const CUSTOM_NAME = "CustomVesting"; +const CUSTOM_DATE = "2024-12-31T23:59"; +const CUSTOM_DURATION = "36 months"; +const CUSTOM_CLIFF = "90 days"; // // Test helpers // function testVesting(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildVesting({ ...defaults, - ...opts + ...opts, }); t.snapshot(printContract(c)); }); } function testAPIEquivalence(title: string, opts?: VestingOptions) { - test(title, t => { - t.is(vesting.print(opts), printContract(buildVesting({ - ...defaults, - ...opts - }))); + test(title, (t) => { + t.is( + vesting.print(opts), + printContract( + buildVesting({ + ...defaults, + ...opts, + }), + ), + ); }); } @@ -43,77 +48,82 @@ function testAPIEquivalence(title: string, opts?: VestingOptions) { // Snapshot tests // -testVesting('custom name', { +testVesting("custom name", { name: CUSTOM_NAME, }); -testVesting('custom start date', { - startDate: CUSTOM_DATE +testVesting("custom start date", { + startDate: CUSTOM_DATE, }); -testVesting('custom duration', { - duration: CUSTOM_DURATION +testVesting("custom duration", { + duration: CUSTOM_DURATION, }); -testVesting('custom cliff', { +testVesting("custom cliff", { duration: CUSTOM_DURATION, - cliffDuration: CUSTOM_CLIFF + cliffDuration: CUSTOM_CLIFF, }); -testVesting('custom schedule', { - schedule: 'custom' +testVesting("custom schedule", { + schedule: "custom", }); -testVesting('all custom settings', { +testVesting("all custom settings", { startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: 'custom' + schedule: "custom", }); // // API tests // -testAPIEquivalence('API custom name', { +testAPIEquivalence("API custom name", { ...defaults, - name: CUSTOM_NAME + name: CUSTOM_NAME, }); -testAPIEquivalence('API custom start date', { +testAPIEquivalence("API custom start date", { ...defaults, - startDate: CUSTOM_DATE + startDate: CUSTOM_DATE, }); -testAPIEquivalence('API custom duration', { +testAPIEquivalence("API custom duration", { ...defaults, - duration: CUSTOM_DURATION + duration: CUSTOM_DURATION, }); -testAPIEquivalence('API custom cliff', { +testAPIEquivalence("API custom cliff", { ...defaults, duration: CUSTOM_DURATION, - cliffDuration: CUSTOM_CLIFF + cliffDuration: CUSTOM_CLIFF, }); -testAPIEquivalence('API custom schedule', { +testAPIEquivalence("API custom schedule", { ...defaults, - schedule: 'custom' + schedule: "custom", }); -testAPIEquivalence('API all custom settings', { +testAPIEquivalence("API all custom settings", { ...defaults, startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: 'custom' + schedule: "custom", }); -test('cliff too high', async t => { - const error = t.throws(() => buildVesting({ - ...defaults, - duration: '20 days', - cliffDuration: '21 days' - })); - t.is((error as OptionsError).messages.cliffDuration, 'Cliff duration must be less than or equal to the total duration'); +test("cliff too high", async (t) => { + const error = t.throws(() => + buildVesting({ + ...defaults, + duration: "20 days", + cliffDuration: "21 days", + }), + ); + t.is( + (error as OptionsError).messages.cliffDuration, + "Cliff duration must be less than or equal to the total duration", + ); }); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 372fde3b2..398a509a9 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -144,7 +144,7 @@ function addSchedule(c: ContractBuilder, opts: VestingOptions) { } function getVestingStart( - opts: VestingOptions + opts: VestingOptions, ): { timestampInSec: bigint; formattedDate: string } | undefined { if (opts.startDate === "" || opts.startDate === "NaN") { return undefined; @@ -154,7 +154,7 @@ function getVestingStart( const timestampInSec = toUint( Math.floor(timestampInMillis / 1000), "startDate", - "u64" + "u64", ); const formattedDate = startDate.toLocaleString("en-GB", { day: "2-digit", diff --git a/packages/core/solidity/ava.config.js b/packages/core/solidity/ava.config.js index 77a9117a9..a39075959 100644 --- a/packages/core/solidity/ava.config.js +++ b/packages/core/solidity/ava.config.js @@ -1,13 +1,9 @@ module.exports = { - extensions: ['ts'], - require: ['ts-node/register'], + extensions: ["ts"], + require: ["ts-node/register"], watchmode: { - ignoreChanges: [ - 'contracts', - 'artifacts', - 'cache', - ], + ignoreChanges: ["contracts", "artifacts", "cache"], }, - timeout: '10m', + timeout: "10m", workerThreads: false, }; diff --git a/packages/core/solidity/get-imports.d.ts b/packages/core/solidity/get-imports.d.ts index 4c0a44175..d89032e9e 100644 --- a/packages/core/solidity/get-imports.d.ts +++ b/packages/core/solidity/get-imports.d.ts @@ -1 +1 @@ -export * from './src/get-imports'; +export * from "./src/get-imports"; diff --git a/packages/core/solidity/get-imports.js b/packages/core/solidity/get-imports.js index c93672cc4..52dd2841a 100644 --- a/packages/core/solidity/get-imports.js +++ b/packages/core/solidity/get-imports.js @@ -1 +1 @@ -module.exports = require('./dist/get-imports'); \ No newline at end of file +module.exports = require("./dist/get-imports"); diff --git a/packages/core/solidity/hardhat.config.js b/packages/core/solidity/hardhat.config.js index 680ecea41..01edf1775 100644 --- a/packages/core/solidity/hardhat.config.js +++ b/packages/core/solidity/hardhat.config.js @@ -1,21 +1,24 @@ -const { task } = require('hardhat/config'); -const { HardhatError } = require('hardhat/internal/core/errors'); -const { ERRORS } = require('hardhat/internal/core/errors-list'); +const { task } = require("hardhat/config"); +const { HardhatError } = require("hardhat/internal/core/errors"); +const { ERRORS } = require("hardhat/internal/core/errors-list"); const { TASK_COMPILE_SOLIDITY_CHECK_ERRORS, TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, -} = require('hardhat/builtin-tasks/task-names'); -const SOLIDITY_VERSION = require('./src/solidity-version.json'); +} = require("hardhat/builtin-tasks/task-names"); +const SOLIDITY_VERSION = require("./src/solidity-version.json"); // Unused parameter warnings are caused by OpenZeppelin Upgradeable Contracts. -const WARN_UNUSED_PARAMETER = '5667'; -const WARN_CODE_SIZE = '5574'; +const WARN_UNUSED_PARAMETER = "5667"; +const WARN_CODE_SIZE = "5574"; const IGNORED_WARNINGS = [WARN_UNUSED_PARAMETER, WARN_CODE_SIZE]; // Overriding this task so that warnings are considered errors. task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { - const errors = output.errors && output.errors.filter(e => !IGNORED_WARNINGS.includes(e.errorCode)) || []; + const errors = + (output.errors && + output.errors.filter((e) => !IGNORED_WARNINGS.includes(e.errorCode))) || + []; await run(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, { output: { ...output, errors }, @@ -27,16 +30,20 @@ task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { } }); -task(TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, async ({ compilationJobs }, _, runSuper) => { - const CHUNK_SIZE = 100; - const chunks = []; - for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { - chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); - } - const mergedChunks = await Promise.all(chunks.map(cj => runSuper({ compilationJobs: cj }))); - return mergedChunks.flat(); -}); - +task( + TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, + async ({ compilationJobs }, _, runSuper) => { + const CHUNK_SIZE = 100; + const chunks = []; + for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { + chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); + } + const mergedChunks = await Promise.all( + chunks.map((cj) => runSuper({ compilationJobs: cj })), + ); + return mergedChunks.flat(); + }, +); /** * @type import('hardhat/config').HardhatUserConfig diff --git a/packages/core/solidity/print-versioned.js b/packages/core/solidity/print-versioned.js index 2bc0b8aa3..c3861b0fa 100644 --- a/packages/core/solidity/print-versioned.js +++ b/packages/core/solidity/print-versioned.js @@ -1 +1 @@ -module.exports = require('./dist/print-versioned'); +module.exports = require("./dist/print-versioned"); diff --git a/packages/core/solidity/print-versioned.ts b/packages/core/solidity/print-versioned.ts index 95629a774..ee52117b8 100644 --- a/packages/core/solidity/print-versioned.ts +++ b/packages/core/solidity/print-versioned.ts @@ -1 +1 @@ -export * from './src/print-versioned'; +export * from "./src/print-versioned"; diff --git a/packages/core/solidity/src/add-pausable.ts b/packages/core/solidity/src/add-pausable.ts index 7877905c7..e6fedab08 100644 --- a/packages/core/solidity/src/add-pausable.ts +++ b/packages/core/solidity/src/add-pausable.ts @@ -1,36 +1,40 @@ -import type { ContractBuilder, BaseFunction } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; +import type { ContractBuilder, BaseFunction } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; -export function addPausable(c: ContractBuilder, access: Access, pausableFns: BaseFunction[]) { +export function addPausable( + c: ContractBuilder, + access: Access, + pausableFns: BaseFunction[], +) { c.addParent({ - name: 'Pausable', - path: '@openzeppelin/contracts/utils/Pausable.sol', + name: "Pausable", + path: "@openzeppelin/contracts/utils/Pausable.sol", }); for (const fn of pausableFns) { - c.addModifier('whenNotPaused', fn); + c.addModifier("whenNotPaused", fn); } addPauseFunctions(c, access); } export function addPauseFunctions(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.pause, access, 'PAUSER', 'pauser'); - c.addFunctionCode('_pause();', functions.pause); + requireAccessControl(c, functions.pause, access, "PAUSER", "pauser"); + c.addFunctionCode("_pause();", functions.pause); - requireAccessControl(c, functions.unpause, access, 'PAUSER', 'pauser'); - c.addFunctionCode('_unpause();', functions.unpause); + requireAccessControl(c, functions.unpause, access, "PAUSER", "pauser"); + c.addFunctionCode("_unpause();", functions.unpause); } const functions = defineFunctions({ pause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, unpause: { - kind: 'public' as const, + kind: "public" as const, args: [], }, }); diff --git a/packages/core/solidity/src/api.ts b/packages/core/solidity/src/api.ts index 5a9a86190..e7c8f1941 100644 --- a/packages/core/solidity/src/api.ts +++ b/packages/core/solidity/src/api.ts @@ -1,27 +1,57 @@ -import type { CommonOptions } from './common-options'; -import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; -import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; -import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; -import { printStablecoin, defaults as stablecoinDefaults, isAccessControlRequired as stablecoinIsAccessControlRequired, StablecoinOptions } from './stablecoin'; -import { printGovernor, defaults as governorDefaults, isAccessControlRequired as governorIsAccessControlRequired, GovernorOptions } from './governor'; -import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; +import type { CommonOptions } from "./common-options"; +import { + printERC20, + defaults as erc20defaults, + isAccessControlRequired as erc20IsAccessControlRequired, + ERC20Options, +} from "./erc20"; +import { + printERC721, + defaults as erc721defaults, + isAccessControlRequired as erc721IsAccessControlRequired, + ERC721Options, +} from "./erc721"; +import { + printERC1155, + defaults as erc1155defaults, + isAccessControlRequired as erc1155IsAccessControlRequired, + ERC1155Options, +} from "./erc1155"; +import { + printStablecoin, + defaults as stablecoinDefaults, + isAccessControlRequired as stablecoinIsAccessControlRequired, + StablecoinOptions, +} from "./stablecoin"; +import { + printGovernor, + defaults as governorDefaults, + isAccessControlRequired as governorIsAccessControlRequired, + GovernorOptions, +} from "./governor"; +import { + printCustom, + defaults as customDefaults, + isAccessControlRequired as customIsAccessControlRequired, + CustomOptions, +} from "./custom"; export interface WizardContractAPI { /** * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. */ - print: (opts?: Options) => string, - + print: (opts?: Options) => string; + /** * The default options that are used for `print`. */ defaults: Required; /** - * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the - * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. + * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the + * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. */ - isAccessControlRequired: (opts: Partial) => boolean, + isAccessControlRequired: (opts: Partial) => boolean; } export type ERC20 = WizardContractAPI; @@ -35,35 +65,35 @@ export type Custom = WizardContractAPI; export const erc20: ERC20 = { print: printERC20, defaults: erc20defaults, - isAccessControlRequired: erc20IsAccessControlRequired -} + isAccessControlRequired: erc20IsAccessControlRequired, +}; export const erc721: ERC721 = { print: printERC721, defaults: erc721defaults, - isAccessControlRequired: erc721IsAccessControlRequired -} + isAccessControlRequired: erc721IsAccessControlRequired, +}; export const erc1155: ERC1155 = { print: printERC1155, defaults: erc1155defaults, - isAccessControlRequired: erc1155IsAccessControlRequired -} + isAccessControlRequired: erc1155IsAccessControlRequired, +}; export const stablecoin: Stablecoin = { print: printStablecoin, defaults: stablecoinDefaults, - isAccessControlRequired: stablecoinIsAccessControlRequired -} + isAccessControlRequired: stablecoinIsAccessControlRequired, +}; export const realWorldAsset: RealWorldAsset = { print: printStablecoin, defaults: stablecoinDefaults, - isAccessControlRequired: stablecoinIsAccessControlRequired -} + isAccessControlRequired: stablecoinIsAccessControlRequired, +}; export const governor: Governor = { print: printGovernor, defaults: governorDefaults, - isAccessControlRequired: governorIsAccessControlRequired -} + isAccessControlRequired: governorIsAccessControlRequired, +}; export const custom: Custom = { print: printCustom, defaults: customDefaults, - isAccessControlRequired: customIsAccessControlRequired -} \ No newline at end of file + isAccessControlRequired: customIsAccessControlRequired, +}; diff --git a/packages/core/solidity/src/common-functions.ts b/packages/core/solidity/src/common-functions.ts index 7d76c5f02..7647afdad 100644 --- a/packages/core/solidity/src/common-functions.ts +++ b/packages/core/solidity/src/common-functions.ts @@ -1,11 +1,9 @@ -import type { BaseFunction } from './contract'; +import type { BaseFunction } from "./contract"; export const supportsInterface: BaseFunction = { - name: 'supportsInterface', - kind: 'public', - args: [ - { name: 'interfaceId', type: 'bytes4' }, - ], - returns: ['bool'], - mutability: 'view', + name: "supportsInterface", + kind: "public", + args: [{ name: "interfaceId", type: "bytes4" }], + returns: ["bool"], + mutability: "view", }; diff --git a/packages/core/solidity/src/common-options.ts b/packages/core/solidity/src/common-options.ts index eb0cec1ba..9e3e59e9c 100644 --- a/packages/core/solidity/src/common-options.ts +++ b/packages/core/solidity/src/common-options.ts @@ -15,7 +15,9 @@ export interface CommonOptions { info?: Info; } -export function withCommonDefaults(opts: CommonOptions): Required { +export function withCommonDefaults( + opts: CommonOptions, +): Required { return { access: opts.access ?? false, upgradeable: opts.upgradeable ?? false, diff --git a/packages/core/solidity/src/contract.test.ts b/packages/core/solidity/src/contract.test.ts index b391a2445..1c74412f0 100644 --- a/packages/core/solidity/src/contract.test.ts +++ b/packages/core/solidity/src/contract.test.ts @@ -1,170 +1,169 @@ -import test from 'ava'; +import test from "ava"; -import { ContractBuilder } from './contract'; -import { printContract } from './print'; -import { TAG_SECURITY_CONTACT } from './set-info'; +import { ContractBuilder } from "./contract"; +import { printContract } from "./print"; +import { TAG_SECURITY_CONTACT } from "./set-info"; const toContractReference = (name: string) => { return { name: name, - } -} + }; +}; const toParentContract = (name: string, path: string) => { return { name: name, path: path, - } -} + }; +}; -test('contract basics', t => { - const Foo = new ContractBuilder('Foo'); +test("contract basics", (t) => { + const Foo = new ContractBuilder("Foo"); t.snapshot(printContract(Foo)); }); -test('contract with a parent', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with a parent", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar); t.snapshot(printContract(Foo)); }); -test('contract with two parents', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); - const Quux = toParentContract('Quux', './Quux.sol'); +test("contract with two parents", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); + const Quux = toParentContract("Quux", "./Quux.sol"); Foo.addParent(Bar); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test('contract with a parent with parameters', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with a parent with parameters", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar, ["param1", "param2"]); t.snapshot(printContract(Foo)); }); -test('contract with two parents only one with parameters', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); - const Quux = toParentContract('Quux', './Quux.sol'); +test("contract with two parents only one with parameters", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); + const Quux = toParentContract("Quux", "./Quux.sol"); Foo.addParent(Bar, ["param1", "param2"]); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test('contract with one override', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with one override", (t) => { + const Foo = new ContractBuilder("Foo"); const _beforeTokenTransfer = { - name: '_beforeTokenTransfer', - kind: 'internal' as const, + name: "_beforeTokenTransfer", + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }; - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test('contract with two overrides', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('ERC20Snapshot'), _beforeTokenTransfer); +test("contract with two overrides", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20Snapshot"), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test('contract with two different overrides', t => { - const Foo = new ContractBuilder('Foo'); +test("contract with two different overrides", (t) => { + const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('OtherParent'), _beforeTokenTransfer); - Foo.addOverride(toContractReference('ERC20'), _otherFunction); - Foo.addOverride(toContractReference('OtherParent'), _otherFunction); + Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("OtherParent"), _beforeTokenTransfer); + Foo.addOverride(toContractReference("ERC20"), _otherFunction); + Foo.addOverride(toContractReference("OtherParent"), _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with a modifier', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addModifier('whenNotPaused', _otherFunction); +test("contract with a modifier", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addModifier("whenNotPaused", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with a modifier and override', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addModifier('whenNotPaused', _otherFunction); +test("contract with a modifier and override", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addModifier("whenNotPaused", _otherFunction); - Foo.addOverride(toContractReference('ERC20'), _otherFunction); - Foo.addOverride(toContractReference('OtherParent'), _otherFunction); + Foo.addOverride(toContractReference("ERC20"), _otherFunction); + Foo.addOverride(toContractReference("OtherParent"), _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with constructor code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); +test("contract with constructor code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); t.snapshot(printContract(Foo)); }); -test('contract with constructor code and a parent', t => { - const Foo = new ContractBuilder('Foo'); - const Bar = toParentContract('Bar', './Bar.sol'); +test("contract with constructor code and a parent", (t) => { + const Foo = new ContractBuilder("Foo"); + const Bar = toParentContract("Bar", "./Bar.sol"); Foo.addParent(Bar, ["param1", "param2"]); - Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); + Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); t.snapshot(printContract(Foo)); }); -test('contract with function code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); +test("contract with function code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with overridden function with code', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference('Bar'), _otherFunction); - Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); +test("contract with overridden function with code", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addOverride(toContractReference("Bar"), _otherFunction); + Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); t.snapshot(printContract(Foo)); }); -test('contract with one variable', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addVariable('uint value = 42;'); +test("contract with one variable", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addVariable("uint value = 42;"); t.snapshot(printContract(Foo)); }); -test('contract with two variables', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addVariable('uint value = 42;'); +test("contract with two variables", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addVariable("uint value = 42;"); Foo.addVariable('string name = "john";'); t.snapshot(printContract(Foo)); }); -test('name with special characters', t => { - const Foo = new ContractBuilder('foo bar baz'); +test("name with special characters", (t) => { + const Foo = new ContractBuilder("foo bar baz"); t.snapshot(printContract(Foo)); }); -test('contract with info', t => { - const Foo = new ContractBuilder('Foo'); - Foo.addNatspecTag(TAG_SECURITY_CONTACT, 'security@example.com'); +test("contract with info", (t) => { + const Foo = new ContractBuilder("Foo"); + Foo.addNatspecTag(TAG_SECURITY_CONTACT, "security@example.com"); t.snapshot(printContract(Foo)); }); - const _beforeTokenTransfer = { - name: '_beforeTokenTransfer', - kind: 'internal' as const, + name: "_beforeTokenTransfer", + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "amount", type: "uint256" }, ], }; const _otherFunction = { - name: '_otherFunction', - kind: 'internal' as const, + name: "_otherFunction", + kind: "internal" as const, args: [], }; diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index 7ac998d20..560bf4cae 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -64,7 +64,7 @@ const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; function maxMutability( a: FunctionMutability, - b: FunctionMutability + b: FunctionMutability, ): FunctionMutability { return mutabilityRank[ Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) @@ -148,7 +148,7 @@ export class ContractBuilder implements Contract { addOverride( parent: ReferencedContract, baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); fn.override.add(parent); @@ -205,7 +205,7 @@ export class ContractBuilder implements Contract { addFunctionCode( code: string, baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); if (fn.final) { @@ -220,7 +220,7 @@ export class ContractBuilder implements Contract { setFunctionBody( code: string[], baseFn: BaseFunction, - mutability?: FunctionMutability + mutability?: FunctionMutability, ) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { diff --git a/packages/core/solidity/src/custom.test.ts b/packages/core/solidity/src/custom.test.ts index 2cfb60ce4..3299f1cbe 100644 --- a/packages/core/solidity/src/custom.test.ts +++ b/packages/core/solidity/src/custom.test.ts @@ -1,13 +1,13 @@ -import test from 'ava'; -import { custom } from '.'; +import test from "ava"; +import { custom } from "."; -import { buildCustom, CustomOptions } from './custom'; -import { printContract } from './print'; +import { buildCustom, CustomOptions } from "./custom"; +import { printContract } from "./print"; function testCustom(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildCustom({ - name: 'MyContract', + name: "MyContract", ...opts, }); t.snapshot(printContract(c)); @@ -17,75 +17,80 @@ function testCustom(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, t => { - t.is(custom.print(opts), printContract(buildCustom({ - name: 'MyContract', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: CustomOptions) { + test(title, (t) => { + t.is( + custom.print(opts), + printContract( + buildCustom({ + name: "MyContract", + ...opts, + }), + ), + ); }); } -testCustom('custom', {}); +testCustom("custom", {}); -testCustom('pausable', { +testCustom("pausable", { pausable: true, }); -testCustom('upgradeable transparent', { - upgradeable: 'transparent', +testCustom("upgradeable transparent", { + upgradeable: "transparent", }); -testCustom('upgradeable uups', { - upgradeable: 'uups', +testCustom("upgradeable uups", { + upgradeable: "uups", }); -testCustom('access control disabled', { +testCustom("access control disabled", { access: false, }); -testCustom('access control ownable', { - access: 'ownable', +testCustom("access control ownable", { + access: "ownable", }); -testCustom('access control roles', { - access: 'roles', +testCustom("access control roles", { + access: "roles", }); -testCustom('access control managed', { - access: 'managed', +testCustom("access control managed", { + access: "managed", }); -testCustom('upgradeable uups with access control disabled', { +testCustom("upgradeable uups with access control disabled", { // API should override access to true since it is required for UUPS access: false, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('custom API default'); +testAPIEquivalence("custom API default"); -testAPIEquivalence('custom API basic', { name: 'CustomContract' }); +testAPIEquivalence("custom API basic", { name: "CustomContract" }); -testAPIEquivalence('custom API full upgradeable', { - name: 'CustomContract', - access: 'roles', +testAPIEquivalence("custom API full upgradeable", { + name: "CustomContract", + access: "roles", pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('custom API full upgradeable with managed', { - name: 'CustomContract', - access: 'managed', +testAPIEquivalence("custom API full upgradeable with managed", { + name: "CustomContract", + access: "managed", pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('custom API assert defaults', async t => { +test("custom API assert defaults", async (t) => { t.is(custom.print(custom.defaults), custom.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(custom.isAccessControlRequired({ pausable: true }), true); - t.is(custom.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(custom.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(custom.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(custom.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/custom.ts b/packages/core/solidity/src/custom.ts index b82ca2c67..b167015ed 100644 --- a/packages/core/solidity/src/custom.ts +++ b/packages/core/solidity/src/custom.ts @@ -1,10 +1,14 @@ -import { Contract, ContractBuilder } from './contract'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { setAccessControl } from './set-access-control'; -import { addPausable } from './add-pausable'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { setAccessControl } from "./set-access-control"; +import { addPausable } from "./add-pausable"; +import { printContract } from "./print"; export interface CustomOptions extends CommonOptions { name: string; @@ -12,7 +16,7 @@ export interface CustomOptions extends CommonOptions { } export const defaults: Required = { - name: 'MyContract', + name: "MyContract", pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, @@ -32,7 +36,7 @@ export function printCustom(opts: CustomOptions = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.pausable || opts.upgradeable === 'uups'; + return opts.pausable || opts.upgradeable === "uups"; } export function buildCustom(opts: CustomOptions): Contract { @@ -52,4 +56,3 @@ export function buildCustom(opts: CustomOptions): Contract { return c; } - diff --git a/packages/core/solidity/src/erc1155.test.ts b/packages/core/solidity/src/erc1155.test.ts index d34232159..91e20ceae 100644 --- a/packages/core/solidity/src/erc1155.test.ts +++ b/packages/core/solidity/src/erc1155.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc1155 } from '.'; +import test from "ava"; +import { erc1155 } from "."; -import { buildERC1155, ERC1155Options } from './erc1155'; -import { printContract } from './print'; +import { buildERC1155, ERC1155Options } from "./erc1155"; +import { printContract } from "./print"; function testERC1155(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC1155({ - name: 'MyToken', - uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + name: "MyToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", ...opts, }); t.snapshot(printContract(c)); @@ -18,103 +18,123 @@ function testERC1155(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, t => { - t.is(erc1155.print(opts), printContract(buildERC1155({ - name: 'MyToken', - uri: '', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC1155Options) { + test(title, (t) => { + t.is( + erc1155.print(opts), + printContract( + buildERC1155({ + name: "MyToken", + uri: "", + ...opts, + }), + ), + ); }); } -testERC1155('basic', {}); +testERC1155("basic", {}); -testERC1155('basic + roles', { - access: 'roles', +testERC1155("basic + roles", { + access: "roles", }); -testERC1155('basic + managed', { - access: 'managed', +testERC1155("basic + managed", { + access: "managed", }); -testERC1155('no updatable uri', { +testERC1155("no updatable uri", { updatableUri: false, }); -testERC1155('burnable', { +testERC1155("burnable", { burnable: true, }); -testERC1155('pausable', { +testERC1155("pausable", { pausable: true, }); -testERC1155('mintable', { +testERC1155("mintable", { mintable: true, }); -testERC1155('mintable + roles', { +testERC1155("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC1155('mintable + managed', { +testERC1155("mintable + managed", { mintable: true, - access: 'managed', + access: "managed", }); -testERC1155('supply tracking', { +testERC1155("supply tracking", { supply: true, }); -testERC1155('full upgradeable transparent', { +testERC1155("full upgradeable transparent", { mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC1155('full upgradeable uups', { +testERC1155("full upgradeable uups", { mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC1155('full upgradeable transparent with managed', { +testERC1155("full upgradeable transparent with managed", { mintable: true, - access: 'managed', + access: "managed", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomToken', uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/' }); +testAPIEquivalence("API basic", { + name: "CustomToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +}); -testAPIEquivalence('API full upgradeable', { - name: 'CustomToken', - uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', +testAPIEquivalence("API full upgradeable", { + name: "CustomToken", + uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", mintable: true, - access: 'roles', + access: "roles", burnable: true, pausable: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test('API isAccessControlRequired', async t => { - t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: 'uups' }), true); +test("API isAccessControlRequired", async (t) => { + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), + true, + ); + t.is( + erc1155.isAccessControlRequired({ + updatableUri: false, + upgradeable: "uups", + }), + true, + ); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); - t.is(erc1155.isAccessControlRequired({ updatableUri: false}), false); + t.is(erc1155.isAccessControlRequired({ updatableUri: false }), false); t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/erc1155.ts b/packages/core/solidity/src/erc1155.ts index 8631e4220..1ec08a189 100644 --- a/packages/core/solidity/src/erc1155.ts +++ b/packages/core/solidity/src/erc1155.ts @@ -1,12 +1,20 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { supportsInterface } from './common-functions'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { supportsInterface } from "./common-functions"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; export interface ERC1155Options extends CommonOptions { name: string; @@ -19,8 +27,8 @@ export interface ERC1155Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - uri: '', + name: "MyToken", + uri: "", burnable: false, pausable: false, mintable: false, @@ -28,7 +36,7 @@ export const defaults: Required = { updatableUri: true, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, - info: commonDefaults.info + info: commonDefaults.info, } as const; function withDefaults(opts: ERC1155Options): Required { @@ -47,8 +55,15 @@ export function printERC1155(opts: ERC1155Options = defaults): string { return printContract(buildERC1155(opts)); } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.updatableUri !== false || opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial, +): boolean { + return ( + opts.mintable || + opts.pausable || + opts.updatableUri !== false || + opts.upgradeable === "uups" + ); } export function buildERC1155(opts: ERC1155Options): Contract { @@ -89,8 +104,8 @@ export function buildERC1155(opts: ERC1155Options): Contract { function addBase(c: ContractBuilder, uri: string) { const ERC1155 = { - name: 'ERC1155', - path: '@openzeppelin/contracts/token/ERC1155/ERC1155.sol', + name: "ERC1155", + path: "@openzeppelin/contracts/token/ERC1155/ERC1155.sol", }; c.addParent(ERC1155, [uri]); @@ -100,8 +115,8 @@ function addBase(c: ContractBuilder, uri: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC1155Pausable = { - name: 'ERC1155Pausable', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol', + name: "ERC1155Pausable", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol", }; c.addParent(ERC1155Pausable); c.addOverride(ERC1155Pausable, functions._update); @@ -111,27 +126,27 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC1155Burnable', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol', + name: "ERC1155Burnable", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol", }); } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); - requireAccessControl(c, functions.mintBatch, access, 'MINTER', 'minter'); - c.addFunctionCode('_mint(account, id, amount, data);', functions.mint); - c.addFunctionCode('_mintBatch(to, ids, amounts, data);', functions.mintBatch); + requireAccessControl(c, functions.mint, access, "MINTER", "minter"); + requireAccessControl(c, functions.mintBatch, access, "MINTER", "minter"); + c.addFunctionCode("_mint(account, id, amount, data);", functions.mint); + c.addFunctionCode("_mintBatch(to, ids, amounts, data);", functions.mintBatch); } function addSetUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.setURI, access, 'URI_SETTER', undefined); - c.addFunctionCode('_setURI(newuri);', functions.setURI); + requireAccessControl(c, functions.setURI, access, "URI_SETTER", undefined); + c.addFunctionCode("_setURI(newuri);", functions.setURI); } function addSupply(c: ContractBuilder) { const ERC1155Supply = { - name: 'ERC1155Supply', - path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol', + name: "ERC1155Supply", + path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol", }; c.addParent(ERC1155Supply); c.addOverride(ERC1155Supply, functions._update); @@ -139,39 +154,37 @@ function addSupply(c: ContractBuilder) { const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'from', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'ids', type: 'uint256[] memory' }, - { name: 'values', type: 'uint256[] memory' }, + { name: "from", type: "address" }, + { name: "to", type: "address" }, + { name: "ids", type: "uint256[] memory" }, + { name: "values", type: "uint256[] memory" }, ], }, setURI: { - kind: 'public' as const, - args: [ - { name: 'newuri', type: 'string memory' }, - ], + kind: "public" as const, + args: [{ name: "newuri", type: "string memory" }], }, mint: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'account', type: 'address' }, - { name: 'id', type: 'uint256' }, - { name: 'amount', type: 'uint256' }, - { name: 'data', type: 'bytes memory' }, + { name: "account", type: "address" }, + { name: "id", type: "uint256" }, + { name: "amount", type: "uint256" }, + { name: "data", type: "bytes memory" }, ], }, mintBatch: { - kind: 'public' as const, + kind: "public" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'ids', type: 'uint256[] memory' }, - { name: 'amounts', type: 'uint256[] memory' }, - { name: 'data', type: 'bytes memory' }, + { name: "to", type: "address" }, + { name: "ids", type: "uint256[] memory" }, + { name: "amounts", type: "uint256[] memory" }, + { name: "data", type: "bytes memory" }, ], }, }); diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 7cd107d26..4a1cbb586 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc20 } from '.'; +import test from "ava"; +import { erc20 } from "."; -import { buildERC20, ERC20Options } from './erc20'; -import { printContract } from './print'; +import { buildERC20, ERC20Options } from "./erc20"; +import { printContract } from "./print"; function testERC20(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC20({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -18,141 +18,146 @@ function testERC20(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, t => { - t.is(erc20.print(opts), printContract(buildERC20({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC20Options) { + test(title, (t) => { + t.is( + erc20.print(opts), + printContract( + buildERC20({ + name: "MyToken", + symbol: "MTK", + ...opts, + }), + ), + ); }); } -testERC20('basic erc20', {}); +testERC20("basic erc20", {}); -testERC20('erc20 burnable', { +testERC20("erc20 burnable", { burnable: true, }); -testERC20('erc20 pausable', { +testERC20("erc20 pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 pausable with roles', { +testERC20("erc20 pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 pausable with managed', { +testERC20("erc20 pausable with managed", { pausable: true, - access: 'managed', + access: "managed", }); -testERC20('erc20 burnable pausable', { +testERC20("erc20 burnable pausable", { burnable: true, pausable: true, }); -testERC20('erc20 preminted', { - premint: '1000', +testERC20("erc20 preminted", { + premint: "1000", }); -testERC20('erc20 premint of 0', { - premint: '0', +testERC20("erc20 premint of 0", { + premint: "0", }); -testERC20('erc20 mintable', { +testERC20("erc20 mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testERC20('erc20 mintable with roles', { +testERC20("erc20 mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC20('erc20 permit', { +testERC20("erc20 permit", { permit: true, }); -testERC20('erc20 votes', { +testERC20("erc20 votes", { votes: true, }); -testERC20('erc20 votes + blocknumber', { - votes: 'blocknumber', +testERC20("erc20 votes + blocknumber", { + votes: "blocknumber", }); -testERC20('erc20 votes + timestamp', { - votes: 'timestamp', +testERC20("erc20 votes + timestamp", { + votes: "timestamp", }); -testERC20('erc20 flashmint', { +testERC20("erc20 flashmint", { flashmint: true, }); -testERC20('erc20 full upgradeable transparent', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable transparent", { + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC20('erc20 full upgradeable uups', { - premint: '2000', - access: 'roles', +testERC20("erc20 full upgradeable uups", { + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC20('erc20 full upgradeable uups managed', { - premint: '2000', - access: 'managed', +testERC20("erc20 full upgradeable uups managed", { + premint: "2000", + access: "managed", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testAPIEquivalence('erc20 API default'); +testAPIEquivalence("erc20 API default"); -testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('erc20 API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', - premint: '2000', - access: 'roles', +testAPIEquivalence("erc20 API full upgradeable", { + name: "CustomToken", + symbol: "CTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('erc20 API assert defaults', async t => { +test("erc20 API assert defaults", async (t) => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test('erc20 API isAccessControlRequired', async t => { +test("erc20 API isAccessControlRequired", async (t) => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(erc20.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(erc20.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index ce0b9e49c..8a2643e09 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -199,7 +199,7 @@ function addVotes(c: ContractBuilder, clockMode: ClockMode) { { name: "Nonces", }, - functions.nonces + functions.nonces, ); setClockMode(c, ERC20Votes, clockMode); diff --git a/packages/core/solidity/src/erc721.test.ts b/packages/core/solidity/src/erc721.test.ts index 4b39a2472..96b608678 100644 --- a/packages/core/solidity/src/erc721.test.ts +++ b/packages/core/solidity/src/erc721.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { erc721 } from '.'; +import test from "ava"; +import { erc721 } from "."; -import { buildERC721, ERC721Options } from './erc721'; -import { printContract } from './print'; +import { buildERC721, ERC721Options } from "./erc721"; +import { printContract } from "./print"; function testERC721(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: "MyToken", + symbol: "MTK", ...opts, }); t.snapshot(printContract(c)); @@ -18,135 +18,141 @@ function testERC721(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, t => { - t.is(erc721.print(opts), printContract(buildERC721({ - name: 'MyToken', - symbol: 'MTK', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: ERC721Options) { + test(title, (t) => { + t.is( + erc721.print(opts), + printContract( + buildERC721({ + name: "MyToken", + symbol: "MTK", + ...opts, + }), + ), + ); }); } -testERC721('basic', {}); +testERC721("basic", {}); -testERC721('base uri', { - baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', +testERC721("base uri", { + baseUri: + "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", }); -testERC721('enumerable', { +testERC721("enumerable", { enumerable: true, }); -testERC721('uri storage', { +testERC721("uri storage", { uriStorage: true, }); -testERC721('mintable + uri storage', { +testERC721("mintable + uri storage", { mintable: true, uriStorage: true, }); -testERC721('mintable + uri storage + incremental', { +testERC721("mintable + uri storage + incremental", { mintable: true, uriStorage: true, incremental: true, }); -testERC721('burnable', { +testERC721("burnable", { burnable: true, }); -testERC721('burnable + uri storage', { +testERC721("burnable + uri storage", { uriStorage: true, burnable: true, }); -testERC721('pausable', { +testERC721("pausable", { pausable: true, }); -testERC721('mintable', { +testERC721("mintable", { mintable: true, }); -testERC721('mintable + roles', { +testERC721("mintable + roles", { mintable: true, - access: 'roles', + access: "roles", }); -testERC721('mintable + managed', { +testERC721("mintable + managed", { mintable: true, - access: 'managed', + access: "managed", }); -testERC721('mintable + incremental', { +testERC721("mintable + incremental", { mintable: true, incremental: true, }); -testERC721('votes', { +testERC721("votes", { votes: true, }); -testERC721('votes + blocknumber', { - votes: 'blocknumber', +testERC721("votes + blocknumber", { + votes: "blocknumber", }); -testERC721('votes + timestamp', { - votes: 'timestamp', +testERC721("votes + timestamp", { + votes: "timestamp", }); -testERC721('full upgradeable transparent', { +testERC721("full upgradeable transparent", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'transparent', + upgradeable: "transparent", }); -testERC721('full upgradeable uups', { +testERC721("full upgradeable uups", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', + upgradeable: "uups", }); -testERC721('full upgradeable uups + managed', { +testERC721("full upgradeable uups + managed", { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', - access: 'managed', + upgradeable: "uups", + access: "managed", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence("API basic", { name: "CustomToken", symbol: "CTK" }); -testAPIEquivalence('API full upgradeable', { - name: 'CustomToken', - symbol: 'CTK', +testAPIEquivalence("API full upgradeable", { + name: "CustomToken", + symbol: "CTK", mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: 'uups', + upgradeable: "uups", }); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test('API isAccessControlRequired', async t => { +test("API isAccessControlRequired", async (t) => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: 'uups' }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: 'transparent' }), false); -}); \ No newline at end of file + t.is(erc721.isAccessControlRequired({ upgradeable: "uups" }), true); + t.is(erc721.isAccessControlRequired({ upgradeable: "transparent" }), false); +}); diff --git a/packages/core/solidity/src/erc721.ts b/packages/core/solidity/src/erc721.ts index aa9a80c31..123bc91f4 100644 --- a/packages/core/solidity/src/erc721.ts +++ b/packages/core/solidity/src/erc721.ts @@ -1,13 +1,21 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { addPauseFunctions } from './add-pausable'; -import { supportsInterface } from './common-functions'; -import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, defaults as commonDefaults } from './common-options'; -import { setUpgradeable } from './set-upgradeable'; -import { setInfo } from './set-info'; -import { printContract } from './print'; -import { ClockMode, clockModeDefault, setClockMode } from './set-clock-mode'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { addPauseFunctions } from "./add-pausable"; +import { supportsInterface } from "./common-functions"; +import { defineFunctions } from "./utils/define-functions"; +import { + CommonOptions, + withCommonDefaults, + defaults as commonDefaults, +} from "./common-options"; +import { setUpgradeable } from "./set-upgradeable"; +import { setInfo } from "./set-info"; +import { printContract } from "./print"; +import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; export interface ERC721Options extends CommonOptions { name: string; @@ -27,9 +35,9 @@ export interface ERC721Options extends CommonOptions { } export const defaults: Required = { - name: 'MyToken', - symbol: 'MTK', - baseUri: '', + name: "MyToken", + symbol: "MTK", + baseUri: "", enumerable: false, uriStorage: false, burnable: false, @@ -62,7 +70,7 @@ export function printERC721(opts: ERC721Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; + return opts.mintable || opts.pausable || opts.upgradeable === "uups"; } export function buildERC721(opts: ERC721Options): Contract { @@ -112,8 +120,8 @@ export function buildERC721(opts: ERC721Options): Contract { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC721Pausable = { - name: 'ERC721Pausable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol', + name: "ERC721Pausable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol", }; c.addParent(ERC721Pausable); c.addOverride(ERC721Pausable, functions._update); @@ -122,8 +130,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { } const ERC721 = { - name: 'ERC721', - path: '@openzeppelin/contracts/token/ERC721/ERC721.sol', + name: "ERC721", + path: "@openzeppelin/contracts/token/ERC721/ERC721.sol", }; function addBase(c: ContractBuilder, name: string, symbol: string) { @@ -142,8 +150,8 @@ function addBaseURI(c: ContractBuilder, baseUri: string) { function addEnumerable(c: ContractBuilder) { const ERC721Enumerable = { - name: 'ERC721Enumerable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol', + name: "ERC721Enumerable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol", }; c.addParent(ERC721Enumerable); @@ -154,8 +162,8 @@ function addEnumerable(c: ContractBuilder) { function addURIStorage(c: ContractBuilder) { const ERC721URIStorage = { - name: 'ERC721URIStorage', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol', + name: "ERC721URIStorage", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol", }; c.addParent(ERC721URIStorage); @@ -165,38 +173,43 @@ function addURIStorage(c: ContractBuilder) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: 'ERC721Burnable', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol', + name: "ERC721Burnable", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol", }); } -function addMintable(c: ContractBuilder, access: Access, incremental = false, uriStorage = false) { +function addMintable( + c: ContractBuilder, + access: Access, + incremental = false, + uriStorage = false, +) { const fn = getMintFunction(incremental, uriStorage); - requireAccessControl(c, fn, access, 'MINTER', 'minter'); + requireAccessControl(c, fn, access, "MINTER", "minter"); if (incremental) { - c.addVariable('uint256 private _nextTokenId;'); - c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn); - c.addFunctionCode('_safeMint(to, tokenId);', fn); + c.addVariable("uint256 private _nextTokenId;"); + c.addFunctionCode("uint256 tokenId = _nextTokenId++;", fn); + c.addFunctionCode("_safeMint(to, tokenId);", fn); } else { - c.addFunctionCode('_safeMint(to, tokenId);', fn); + c.addFunctionCode("_safeMint(to, tokenId);", fn); } if (uriStorage) { - c.addFunctionCode('_setTokenURI(tokenId, uri);', fn); + c.addFunctionCode("_setTokenURI(tokenId, uri);", fn); } } function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const EIP712 = { - name: 'EIP712', - path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol', + name: "EIP712", + path: "@openzeppelin/contracts/utils/cryptography/EIP712.sol", }; c.addParent(EIP712, [name, "1"]); const ERC721Votes = { - name: 'ERC721Votes', - path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol', + name: "ERC721Votes", + path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol", }; c.addParent(ERC721Votes); @@ -208,55 +221,51 @@ function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const functions = defineFunctions({ _update: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'to', type: 'address' }, - { name: 'tokenId', type: 'uint256' }, - { name: 'auth', type: 'address' }, + { name: "to", type: "address" }, + { name: "tokenId", type: "uint256" }, + { name: "auth", type: "address" }, ], - returns: ['address'], + returns: ["address"], }, tokenURI: { - kind: 'public' as const, - args: [ - { name: 'tokenId', type: 'uint256' }, - ], - returns: ['string memory'], - mutability: 'view' as const, + kind: "public" as const, + args: [{ name: "tokenId", type: "uint256" }], + returns: ["string memory"], + mutability: "view" as const, }, _baseURI: { - kind: 'internal' as const, + kind: "internal" as const, args: [], - returns: ['string memory'], - mutability: 'pure' as const, + returns: ["string memory"], + mutability: "pure" as const, }, _increaseBalance: { - kind: 'internal' as const, + kind: "internal" as const, args: [ - { name: 'account', type: 'address' }, - { name: 'value', type: 'uint128' }, + { name: "account", type: "address" }, + { name: "value", type: "uint128" }, ], }, }); function getMintFunction(incremental: boolean, uriStorage: boolean) { const fn = { - name: 'safeMint', - kind: 'public' as const, - args: [ - { name: 'to', type: 'address' }, - ], + name: "safeMint", + kind: "public" as const, + args: [{ name: "to", type: "address" }], }; if (!incremental) { - fn.args.push({ name: 'tokenId', type: 'uint256' }); + fn.args.push({ name: "tokenId", type: "uint256" }); } if (uriStorage) { - fn.args.push({ name: 'uri', type: 'string memory' }); + fn.args.push({ name: "uri", type: "string memory" }); } return fn; diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index e37ac9562..c4b282064 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -5,7 +5,7 @@ type Alternatives = { }; export function* generateAlternatives( - blueprint: B + blueprint: B, ): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, @@ -16,7 +16,7 @@ export function* generateAlternatives( for (; !done(); advance()) { yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]) + entries.map((e) => [e.key, e.values[e.current % e.limit]]), ) as Alternatives; } diff --git a/packages/core/solidity/src/generate/custom.ts b/packages/core/solidity/src/generate/custom.ts index 40207666a..884032068 100644 --- a/packages/core/solidity/src/generate/custom.ts +++ b/packages/core/solidity/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from '../custom'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { CustomOptions } from "../custom"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyContract'], + name: ["MyContract"], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/solidity/src/generate/erc1155.ts b/packages/core/solidity/src/generate/erc1155.ts index fdb9c09fe..d340602a9 100644 --- a/packages/core/solidity/src/generate/erc1155.ts +++ b/packages/core/solidity/src/generate/erc1155.ts @@ -1,14 +1,14 @@ -import type { ERC1155Options } from '../erc1155'; -import { accessOptions } from '../set-access-control'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC1155Options } from "../erc1155"; +import { accessOptions } from "../set-access-control"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - uri: ['https://example.com/'], + name: ["MyToken"], + uri: ["https://example.com/"], burnable: booleans, pausable: booleans, mintable: booleans, diff --git a/packages/core/solidity/src/generate/erc20.ts b/packages/core/solidity/src/generate/erc20.ts index 929da1af5..5c3d6eb48 100644 --- a/packages/core/solidity/src/generate/erc20.ts +++ b/packages/core/solidity/src/generate/erc20.ts @@ -1,22 +1,22 @@ -import type { ERC20Options } from '../erc20'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC20Options } from "../erc20"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], + name: ["MyToken"], + symbol: ["MTK"], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - votes: [ ...booleans, ...clockModeOptions ] as const, + votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ['1'], + premint: ["1"], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/solidity/src/generate/erc721.ts b/packages/core/solidity/src/generate/erc721.ts index 4bf97e744..06d1ca530 100644 --- a/packages/core/solidity/src/generate/erc721.ts +++ b/packages/core/solidity/src/generate/erc721.ts @@ -1,16 +1,16 @@ -import type { ERC721Options } from '../erc721'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { ERC721Options } from "../erc721"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyToken'], - symbol: ['MTK'], - baseUri: ['https://example.com/'], + name: ["MyToken"], + symbol: ["MTK"], + baseUri: ["https://example.com/"], enumerable: booleans, uriStorage: booleans, burnable: booleans, @@ -20,7 +20,7 @@ const blueprint = { access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, - votes: [ ...booleans, ...clockModeOptions ] as const, + votes: [...booleans, ...clockModeOptions] as const, }; export function* generateERC721Options(): Generator> { diff --git a/packages/core/solidity/src/generate/governor.ts b/packages/core/solidity/src/generate/governor.ts index a5a93f26a..66679caae 100644 --- a/packages/core/solidity/src/generate/governor.ts +++ b/packages/core/solidity/src/generate/governor.ts @@ -1,22 +1,27 @@ -import { defaults, GovernorOptions, timelockOptions, votesOptions } from '../governor'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import { + defaults, + GovernorOptions, + timelockOptions, + votesOptions, +} from "../governor"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyGovernor'], - delay: ['1 week'], - period: ['1 week'], + name: ["MyGovernor"], + delay: ["1 week"], + period: ["1 week"], blockTime: [defaults.blockTime], - proposalThreshold: ['0', '1000'], + proposalThreshold: ["0", "1000"], decimals: [18], - quorumMode: ['percent', 'absolute'] as const, + quorumMode: ["percent", "absolute"] as const, quorumPercent: [4], - quorumAbsolute: ['1000'], + quorumAbsolute: ["1000"], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, @@ -27,6 +32,8 @@ const blueprint = { info: infoOptions, }; -export function* generateGovernorOptions(): Generator> { +export function* generateGovernorOptions(): Generator< + Required +> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/solidity/src/generate/sources.ts b/packages/core/solidity/src/generate/sources.ts index a6745f6eb..3ee3ad00c 100644 --- a/packages/core/solidity/src/generate/sources.ts +++ b/packages/core/solidity/src/generate/sources.ts @@ -1,63 +1,63 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import crypto from 'crypto'; - -import { generateERC20Options } from './erc20'; -import { generateERC721Options } from './erc721'; -import { generateERC1155Options } from './erc1155'; -import { generateStablecoinOptions } from './stablecoin'; -import { generateGovernorOptions } from './governor'; -import { generateCustomOptions } from './custom'; -import { buildGeneric, GenericOptions, KindedOptions } from '../build-generic'; -import { printContract } from '../print'; -import { OptionsError } from '../error'; -import { findCover } from '../utils/find-cover'; -import type { Contract } from '../contract'; - -type Subset = 'all' | 'minimal-cover'; +import { promises as fs } from "fs"; +import path from "path"; +import crypto from "crypto"; + +import { generateERC20Options } from "./erc20"; +import { generateERC721Options } from "./erc721"; +import { generateERC1155Options } from "./erc1155"; +import { generateStablecoinOptions } from "./stablecoin"; +import { generateGovernorOptions } from "./governor"; +import { generateCustomOptions } from "./custom"; +import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; +import { printContract } from "../print"; +import { OptionsError } from "../error"; +import { findCover } from "../utils/find-cover"; +import type { Contract } from "../contract"; + +type Subset = "all" | "minimal-cover"; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === 'ERC20') { + if (!kind || kind === "ERC20") { for (const kindOpts of generateERC20Options()) { - yield { kind: 'ERC20', ...kindOpts }; + yield { kind: "ERC20", ...kindOpts }; } } - if (!kind || kind === 'ERC721') { + if (!kind || kind === "ERC721") { for (const kindOpts of generateERC721Options()) { - yield { kind: 'ERC721', ...kindOpts }; + yield { kind: "ERC721", ...kindOpts }; } } - if (!kind || kind === 'ERC1155') { + if (!kind || kind === "ERC1155") { for (const kindOpts of generateERC1155Options()) { - yield { kind: 'ERC1155', ...kindOpts }; + yield { kind: "ERC1155", ...kindOpts }; } } - if (!kind || kind === 'Stablecoin') { + if (!kind || kind === "Stablecoin") { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: 'Stablecoin', ...kindOpts }; + yield { kind: "Stablecoin", ...kindOpts }; } } - if (!kind || kind === 'RealWorldAsset') { + if (!kind || kind === "RealWorldAsset") { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: 'RealWorldAsset', ...kindOpts }; + yield { kind: "RealWorldAsset", ...kindOpts }; } } - if (!kind || kind === 'Governor') { + if (!kind || kind === "Governor") { for (const kindOpts of generateGovernorOptions()) { - yield { kind: 'Governor', ...kindOpts }; + yield { kind: "Governor", ...kindOpts }; } } - if (!kind || kind === 'Custom') { + if (!kind || kind === "Custom") { for (const kindOpts of generateCustomOptions()) { - yield { kind: 'Custom', ...kindOpts }; + yield { kind: "Custom", ...kindOpts }; } } } @@ -72,15 +72,18 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { +function generateContractSubset( + subset: Subset, + kind?: Kind, +): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { const id = crypto - .createHash('sha1') + .createHash("sha1") .update(JSON.stringify(options)) .digest() - .toString('hex'); + .toString("hex"); try { const contract = buildGeneric(options); @@ -94,28 +97,42 @@ function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[ } } - if (subset === 'all') { + if (subset === "all") { return contracts; } else { - const getParents = (c: GeneratedContract) => c.contract.parents.map(p => p.contract.path); + const getParents = (c: GeneratedContract) => + c.contract.parents.map((p) => p.contract.path); return [ - ...findCover(contracts.filter(c => c.options.upgradeable), getParents), - ...findCover(contracts.filter(c => !c.options.upgradeable), getParents), + ...findCover( + contracts.filter((c) => c.options.upgradeable), + getParents, + ), + ...findCover( + contracts.filter((c) => !c.options.upgradeable), + getParents, + ), ]; } } -export function* generateSources(subset: Subset, kind?: Kind): Generator { +export function* generateSources( + subset: Subset, + kind?: Kind, +): Generator { for (const c of generateContractSubset(subset, kind)) { const source = printContract(c.contract); yield { ...c, source }; } } -export async function writeGeneratedSources(dir: string, subset: Subset, kind?: Kind): Promise { +export async function writeGeneratedSources( + dir: string, + subset: Subset, + kind?: Kind, +): Promise { await fs.mkdir(dir, { recursive: true }); for (const { id, source } of generateSources(subset, kind)) { - await fs.writeFile(path.format({ dir, name: id, ext: '.sol' }), source); + await fs.writeFile(path.format({ dir, name: id, ext: ".sol" }), source); } } diff --git a/packages/core/solidity/src/generate/stablecoin.ts b/packages/core/solidity/src/generate/stablecoin.ts index 358052aa9..e20841856 100644 --- a/packages/core/solidity/src/generate/stablecoin.ts +++ b/packages/core/solidity/src/generate/stablecoin.ts @@ -1,30 +1,32 @@ -import type { StablecoinOptions } from '../stablecoin'; -import { accessOptions } from '../set-access-control'; -import { clockModeOptions } from '../set-clock-mode'; -import { infoOptions } from '../set-info'; -import { upgradeableOptions } from '../set-upgradeable'; -import { generateAlternatives } from './alternatives'; +import type { StablecoinOptions } from "../stablecoin"; +import { accessOptions } from "../set-access-control"; +import { clockModeOptions } from "../set-clock-mode"; +import { infoOptions } from "../set-info"; +import { upgradeableOptions } from "../set-upgradeable"; +import { generateAlternatives } from "./alternatives"; const booleans = [true, false]; const blueprint = { - name: ['MyStablecoin'], - symbol: ['MST'], + name: ["MyStablecoin"], + symbol: ["MST"], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - limitations: [false, 'allowlist', 'blocklist'] as const, - votes: [ ...booleans, ...clockModeOptions ] as const, + limitations: [false, "allowlist", "blocklist"] as const, + votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ['1'], + premint: ["1"], custodian: booleans, access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateStablecoinOptions(): Generator> { +export function* generateStablecoinOptions(): Generator< + Required +> { for (const opts of generateAlternatives(blueprint)) { yield { ...opts, upgradeable: false }; } diff --git a/packages/core/solidity/src/get-imports.test.ts b/packages/core/solidity/src/get-imports.test.ts index 5678fdc57..089ce3678 100644 --- a/packages/core/solidity/src/get-imports.test.ts +++ b/packages/core/solidity/src/get-imports.test.ts @@ -1,86 +1,97 @@ -import test from 'ava'; +import test from "ava"; -import { getImports } from './get-imports'; -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { generateSources } from './generate/sources'; -import { buildGeneric } from './build-generic'; +import { getImports } from "./get-imports"; +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { generateSources } from "./generate/sources"; +import { buildGeneric } from "./build-generic"; -test('erc20 basic', t => { - const c = buildERC20({ name: 'MyToken', symbol: 'MTK', permit: false }); +test("erc20 basic", (t) => { + const c = buildERC20({ name: "MyToken", symbol: "MTK", permit: false }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/token/ERC20/ERC20.sol', - '@openzeppelin/contracts/token/ERC20/IERC20.sol', - '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol', - '@openzeppelin/contracts/utils/Context.sol', + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/token/ERC20/ERC20.sol", + "@openzeppelin/contracts/token/ERC20/IERC20.sol", + "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol", + "@openzeppelin/contracts/utils/Context.sol", ]); }); -test('erc721 auto increment', t => { - const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true }); +test("erc721 auto increment", (t) => { + const c = buildERC721({ + name: "MyToken", + symbol: "MTK", + mintable: true, + incremental: true, + }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts/access/Ownable.sol', - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/token/ERC721/ERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', - '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', - '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', - '@openzeppelin/contracts/utils/Context.sol', - '@openzeppelin/contracts/utils/Panic.sol', - '@openzeppelin/contracts/utils/Strings.sol', - '@openzeppelin/contracts/utils/introspection/ERC165.sol', - '@openzeppelin/contracts/utils/introspection/IERC165.sol', - '@openzeppelin/contracts/utils/math/Math.sol', - '@openzeppelin/contracts/utils/math/SafeCast.sol', - '@openzeppelin/contracts/utils/math/SignedMath.sol', + "@openzeppelin/contracts/access/Ownable.sol", + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/token/ERC721/ERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", + "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", + "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", + "@openzeppelin/contracts/utils/Context.sol", + "@openzeppelin/contracts/utils/Panic.sol", + "@openzeppelin/contracts/utils/Strings.sol", + "@openzeppelin/contracts/utils/introspection/ERC165.sol", + "@openzeppelin/contracts/utils/introspection/IERC165.sol", + "@openzeppelin/contracts/utils/math/Math.sol", + "@openzeppelin/contracts/utils/math/SafeCast.sol", + "@openzeppelin/contracts/utils/math/SignedMath.sol", ]); }); -test('erc721 auto increment uups', t => { - const c = buildERC721({ name: 'MyToken', symbol: 'MTK', mintable: true, incremental: true, upgradeable: 'uups' }); +test("erc721 auto increment uups", (t) => { + const c = buildERC721({ + name: "MyToken", + symbol: "MTK", + mintable: true, + incremental: true, + upgradeable: "uups", + }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', - '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol', - '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol', - '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol', - '@openzeppelin/contracts/interfaces/IERC1967.sol', - '@openzeppelin/contracts/interfaces/draft-IERC1822.sol', - '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', - '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol', - '@openzeppelin/contracts/proxy/beacon/IBeacon.sol', - '@openzeppelin/contracts/token/ERC721/IERC721.sol', - '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', - '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', - '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', - '@openzeppelin/contracts/utils/Address.sol', - '@openzeppelin/contracts/utils/Errors.sol', - '@openzeppelin/contracts/utils/Panic.sol', - '@openzeppelin/contracts/utils/StorageSlot.sol', - '@openzeppelin/contracts/utils/Strings.sol', - '@openzeppelin/contracts/utils/introspection/IERC165.sol', - '@openzeppelin/contracts/utils/math/Math.sol', - '@openzeppelin/contracts/utils/math/SafeCast.sol', - '@openzeppelin/contracts/utils/math/SignedMath.sol', + "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", + "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol", + "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol", + "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol", + "@openzeppelin/contracts/interfaces/IERC1967.sol", + "@openzeppelin/contracts/interfaces/draft-IERC1822.sol", + "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", + "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol", + "@openzeppelin/contracts/proxy/beacon/IBeacon.sol", + "@openzeppelin/contracts/token/ERC721/IERC721.sol", + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", + "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", + "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", + "@openzeppelin/contracts/utils/Address.sol", + "@openzeppelin/contracts/utils/Errors.sol", + "@openzeppelin/contracts/utils/Panic.sol", + "@openzeppelin/contracts/utils/StorageSlot.sol", + "@openzeppelin/contracts/utils/Strings.sol", + "@openzeppelin/contracts/utils/introspection/IERC165.sol", + "@openzeppelin/contracts/utils/math/Math.sol", + "@openzeppelin/contracts/utils/math/SafeCast.sol", + "@openzeppelin/contracts/utils/math/SignedMath.sol", ]); }); -test('can get imports for all combinations', t => { - for (const { options } of generateSources('all')) { +test("can get imports for all combinations", (t) => { + for (const { options } of generateSources("all")) { const c = buildGeneric(options); getImports(c); } t.pass(); -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/get-imports.ts b/packages/core/solidity/src/get-imports.ts index 9c51ab86b..4a5ca111b 100644 --- a/packages/core/solidity/src/get-imports.ts +++ b/packages/core/solidity/src/get-imports.ts @@ -1,8 +1,8 @@ -import type { Contract } from './contract'; -import { reachable } from './utils/transitive-closure'; +import type { Contract } from "./contract"; +import { reachable } from "./utils/transitive-closure"; -import contracts from '../openzeppelin-contracts'; -import { withHelpers } from './options'; +import contracts from "../openzeppelin-contracts"; +import { withHelpers } from "./options"; export interface SolcInputSources { [source: string]: { @@ -11,10 +11,10 @@ export interface SolcInputSources { } /** -* Gets the source code for all imports of a contract, including all transitive dependencies, -* in a format compatible with the Solidity compiler input's `sources` field. -* -* Does not include the contract itself (use `printContract` for that if needed). + * Gets the source code for all imports of a contract, including all transitive dependencies, + * in a format compatible with the Solidity compiler input's `sources` field. + * + * Does not include the contract itself (use `printContract` for that if needed). * * @param c The contract to get imports for. * @returns A record of import paths to `content` that contains the source code for each contract. @@ -24,10 +24,10 @@ export function getImports(c: Contract): SolcInputSources { const result: SolcInputSources = {}; - const fileName = c.name + '.sol'; + const fileName = c.name + ".sol"; const dependencies = { - [fileName]: c.imports.map(i => transformImport(i).path), + [fileName]: c.imports.map((i) => transformImport(i).path), ...contracts.dependencies, }; @@ -42,4 +42,4 @@ export function getImports(c: Contract): SolcInputSources { } return result; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/governor.test.ts b/packages/core/solidity/src/governor.test.ts index ee34b0a4b..b22c706d0 100644 --- a/packages/core/solidity/src/governor.test.ts +++ b/packages/core/solidity/src/governor.test.ts @@ -1,15 +1,15 @@ -import test from 'ava'; -import { governor } from '.'; +import test from "ava"; +import { governor } from "."; -import { buildGovernor, GovernorOptions } from './governor'; -import { printContract } from './print'; +import { buildGovernor, GovernorOptions } from "./governor"; +import { printContract } from "./print"; function testGovernor(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildGovernor({ - name: 'MyGovernor', - delay: '1 week', - period: '1 week', + name: "MyGovernor", + delay: "1 week", + period: "1 week", settings: false, ...opts, }); @@ -20,133 +20,147 @@ function testGovernor(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, t => { - t.is(governor.print(opts), printContract(buildGovernor({ - name: 'MyGovernor', - delay: '1 day', - period: '1 week', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: GovernorOptions) { + test(title, (t) => { + t.is( + governor.print(opts), + printContract( + buildGovernor({ + name: "MyGovernor", + delay: "1 day", + period: "1 week", + ...opts, + }), + ), + ); }); } -testGovernor('governor with proposal threshold', { - proposalThreshold: '1', +testGovernor("governor with proposal threshold", { + proposalThreshold: "1", }); -testGovernor('governor with custom block time', { +testGovernor("governor with custom block time", { blockTime: 6, }); -testGovernor('governor with custom decimals', { +testGovernor("governor with custom decimals", { decimals: 6, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '1', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "1", }); -testGovernor('governor with 0 decimals', { +testGovernor("governor with 0 decimals", { decimals: 0, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '1', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "1", }); -testGovernor('governor with settings', { +testGovernor("governor with settings", { settings: true, - proposalThreshold: '1', + proposalThreshold: "1", }); -testGovernor('governor with storage', { +testGovernor("governor with storage", { storage: true, }); -testGovernor('governor with erc20votes', { - votes: 'erc20votes', +testGovernor("governor with erc20votes", { + votes: "erc20votes", }); -testGovernor('governor with erc721votes', { - votes: 'erc721votes', +testGovernor("governor with erc721votes", { + votes: "erc721votes", }); -testGovernor('governor with erc721votes omit decimals', { - votes: 'erc721votes', +testGovernor("governor with erc721votes omit decimals", { + votes: "erc721votes", decimals: 6, - proposalThreshold: '1', - quorumMode: 'absolute', - quorumAbsolute: '5', + proposalThreshold: "1", + quorumMode: "absolute", + quorumAbsolute: "5", }); -testGovernor('governor with erc721votes settings omit decimals', { - votes: 'erc721votes', +testGovernor("governor with erc721votes settings omit decimals", { + votes: "erc721votes", decimals: 6, - proposalThreshold: '10', + proposalThreshold: "10", settings: true, }); -testGovernor('governor with percent quorum', { - quorumMode: 'percent', +testGovernor("governor with percent quorum", { + quorumMode: "percent", quorumPercent: 6, }); -testGovernor('governor with fractional percent quorum', { - quorumMode: 'percent', +testGovernor("governor with fractional percent quorum", { + quorumMode: "percent", quorumPercent: 0.5, }); -testGovernor('governor with openzeppelin timelock', { - timelock: 'openzeppelin', +testGovernor("governor with openzeppelin timelock", { + timelock: "openzeppelin", }); -testGovernor('governor with compound timelock', { - timelock: 'compound', +testGovernor("governor with compound timelock", { + timelock: "compound", }); -testGovernor('governor with blocknumber, updatable settings', { - clockMode: 'blocknumber', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with blocknumber, updatable settings", { + clockMode: "blocknumber", + delay: "1 day", + period: "2 weeks", settings: true, }); -testGovernor('governor with blocknumber, non-updatable settings', { - clockMode: 'blocknumber', - delay: '1 block', - period: '2 weeks', +testGovernor("governor with blocknumber, non-updatable settings", { + clockMode: "blocknumber", + delay: "1 block", + period: "2 weeks", settings: false, }); -testGovernor('governor with timestamp clock mode, updatable settings', { - clockMode: 'timestamp', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with timestamp clock mode, updatable settings", { + clockMode: "timestamp", + delay: "1 day", + period: "2 weeks", settings: true, }); -testGovernor('governor with timestamp clock mode, non-updatable settings', { - clockMode: 'timestamp', - delay: '1 day', - period: '2 weeks', +testGovernor("governor with timestamp clock mode, non-updatable settings", { + clockMode: "timestamp", + delay: "1 day", + period: "2 weeks", settings: false, }); -testGovernor('governor with erc20votes, upgradable', { - votes: 'erc20votes', - upgradeable: 'uups', +testGovernor("governor with erc20votes, upgradable", { + votes: "erc20votes", + upgradeable: "uups", }); -testAPIEquivalence('API default'); +testAPIEquivalence("API default"); -testAPIEquivalence('API basic', { name: 'CustomGovernor', delay: '2 weeks', period: '2 week' }); +testAPIEquivalence("API basic", { + name: "CustomGovernor", + delay: "2 weeks", + period: "2 week", +}); -testAPIEquivalence('API basic upgradeable', { name: 'CustomGovernor', delay: '2 weeks', period: '2 week', upgradeable: 'uups' }); +testAPIEquivalence("API basic upgradeable", { + name: "CustomGovernor", + delay: "2 weeks", + period: "2 week", + upgradeable: "uups", +}); -test('API assert defaults', async t => { +test("API assert defaults", async (t) => { t.is(governor.print(governor.defaults), governor.print()); }); -test('API isAccessControlRequired', async t => { - t.is(governor.isAccessControlRequired({ upgradeable: 'uups' }), true); +test("API isAccessControlRequired", async (t) => { + t.is(governor.isAccessControlRequired({ upgradeable: "uups" }), true); t.is(governor.isAccessControlRequired({}), false); -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 5f61a21e2..4e32d38b3 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -64,7 +64,7 @@ export interface GovernorOptions extends CommonOptions { } export function isAccessControlRequired( - opts: Partial + opts: Partial, ): boolean { return opts.upgradeable === "uups"; } @@ -151,7 +151,7 @@ function addSettings(c: ContractBuilder, allOpts: Required) { } function getVotingDelay( - opts: Required + opts: Required, ): { lit: string } | { note: string; value: number } { try { if (opts.clockMode === "timestamp") { @@ -176,7 +176,7 @@ function getVotingDelay( } function getVotingPeriod( - opts: Required + opts: Required, ): { lit: string } | { note: string; value: number } { try { if (opts.clockMode === "timestamp") { @@ -232,7 +232,7 @@ function getProposalThreshold({ function setVotingParameters( c: ContractBuilder, - opts: Required + opts: Required, ) { const delayBlocks = getVotingDelay(opts); if ("lit" in delayBlocks) { @@ -240,7 +240,7 @@ function setVotingParameters( } else { c.setFunctionBody( [`return ${delayBlocks.value}; // ${delayBlocks.note}`], - functions.votingDelay + functions.votingDelay, ); } @@ -250,14 +250,14 @@ function setVotingParameters( } else { c.setFunctionBody( [`return ${periodBlocks.value}; // ${periodBlocks.note}`], - functions.votingPeriod + functions.votingPeriod, ); } } function setProposalThreshold( c: ContractBuilder, - opts: Required + opts: Required, ) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -295,7 +295,7 @@ function addVotes(c: ContractBuilder) { name: "GovernorVotes", path: `@openzeppelin/contracts/governance/extensions/GovernorVotes.sol`, }, - [{ lit: tokenArg }] + [{ lit: tokenArg }], ); } @@ -322,7 +322,7 @@ function addQuorum(c: ContractBuilder, opts: Required) { c.setFunctionBody( [`return ${quorumFractionDenominator};`], functions.quorumDenominator, - "pure" + "pure", ); } @@ -386,7 +386,7 @@ function getQuorumFractionComponents(quorumPercent: number): { quorumPercentSegments[1] !== undefined ) { quorumFractionNumerator = parseInt( - quorumPercentSegments[0].concat(quorumPercentSegments[1]) + quorumPercentSegments[0].concat(quorumPercentSegments[1]), ); const decimals = quorumPercentSegments[1].length; quorumFractionDenominator = "100"; @@ -399,7 +399,7 @@ function getQuorumFractionComponents(quorumPercent: number): { function addTimelock( c: ContractBuilder, - { timelock }: Required + { timelock }: Required, ) { if (timelock === false) { return; diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index b6e0ed24c..2cfc6b1ee 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -1,24 +1,32 @@ -export type { GenericOptions, KindedOptions } from './build-generic'; -export { buildGeneric } from './build-generic'; +export type { GenericOptions, KindedOptions } from "./build-generic"; +export { buildGeneric } from "./build-generic"; -export type { Contract } from './contract'; -export { ContractBuilder } from './contract'; +export type { Contract } from "./contract"; +export { ContractBuilder } from "./contract"; -export { printContract } from './print'; +export { printContract } from "./print"; -export type { Access } from './set-access-control'; -export type { Upgradeable } from './set-upgradeable'; -export type { Info } from './set-info'; +export type { Access } from "./set-access-control"; +export type { Upgradeable } from "./set-upgradeable"; +export type { Info } from "./set-info"; -export { premintPattern } from './erc20'; -export { defaults as infoDefaults } from './set-info'; +export { premintPattern } from "./erc20"; +export { defaults as infoDefaults } from "./set-info"; -export type { OptionsErrorMessages } from './error'; -export { OptionsError } from './error'; +export type { OptionsErrorMessages } from "./error"; +export { OptionsError } from "./error"; -export type { Kind } from './kind'; -export { sanitizeKind } from './kind'; +export type { Kind } from "./kind"; +export { sanitizeKind } from "./kind"; -export { erc20, erc721, erc1155, stablecoin, realWorldAsset, governor, custom } from './api'; +export { + erc20, + erc721, + erc1155, + stablecoin, + realWorldAsset, + governor, + custom, +} from "./api"; -export { compatibleContractsSemver } from './utils/version'; \ No newline at end of file +export { compatibleContractsSemver } from "./utils/version"; diff --git a/packages/core/solidity/src/infer-transpiled.test.ts b/packages/core/solidity/src/infer-transpiled.test.ts index c6ead4149..262d80660 100644 --- a/packages/core/solidity/src/infer-transpiled.test.ts +++ b/packages/core/solidity/src/infer-transpiled.test.ts @@ -1,12 +1,12 @@ -import test from 'ava'; -import { inferTranspiled } from './infer-transpiled'; +import test from "ava"; +import { inferTranspiled } from "./infer-transpiled"; -test('infer transpiled', t => { - t.true(inferTranspiled({ name: 'Foo' })); - t.true(inferTranspiled({ name: 'Foo', transpiled: true })); - t.false(inferTranspiled({ name: 'Foo', transpiled: false })); +test("infer transpiled", (t) => { + t.true(inferTranspiled({ name: "Foo" })); + t.true(inferTranspiled({ name: "Foo", transpiled: true })); + t.false(inferTranspiled({ name: "Foo", transpiled: false })); - t.false(inferTranspiled({ name: 'IFoo' })); - t.true(inferTranspiled({ name: 'IFoo', transpiled: true })); - t.false(inferTranspiled({ name: 'IFoo', transpiled: false })); -}); \ No newline at end of file + t.false(inferTranspiled({ name: "IFoo" })); + t.true(inferTranspiled({ name: "IFoo", transpiled: true })); + t.false(inferTranspiled({ name: "IFoo", transpiled: false })); +}); diff --git a/packages/core/solidity/src/infer-transpiled.ts b/packages/core/solidity/src/infer-transpiled.ts index 52eeba679..9089099e1 100644 --- a/packages/core/solidity/src/infer-transpiled.ts +++ b/packages/core/solidity/src/infer-transpiled.ts @@ -2,4 +2,4 @@ import type { ReferencedContract } from "./contract"; export function inferTranspiled(c: ReferencedContract): boolean { return c.transpiled ?? !/^I[A-Z]/.test(c.name); -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/kind.ts b/packages/core/solidity/src/kind.ts index de78812fc..844bfddde 100644 --- a/packages/core/solidity/src/kind.ts +++ b/packages/core/solidity/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from './build-generic'; +import type { GenericOptions } from "./build-generic"; -export type Kind = GenericOptions['kind']; +export type Kind = GenericOptions["kind"]; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === 'string') { - const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); + if (typeof kind === "string") { + const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return 'ERC20'; + return "ERC20"; } function isKind(value: Kind | T): value is Kind { switch (value) { - case 'ERC20': - case 'ERC1155': - case 'ERC721': - case 'Stablecoin': - case 'RealWorldAsset': - case 'Governor': - case 'Custom': + case "ERC20": + case "ERC1155": + case "ERC721": + case "Stablecoin": + case "RealWorldAsset": + case "Governor": + case "Custom": return true; default: { @@ -30,4 +30,3 @@ function isKind(value: Kind | T): value is Kind { } } } - diff --git a/packages/core/solidity/src/options.ts b/packages/core/solidity/src/options.ts index b86fe6c4f..7ae76a616 100644 --- a/packages/core/solidity/src/options.ts +++ b/packages/core/solidity/src/options.ts @@ -1,15 +1,15 @@ -import path from 'path'; +import path from "path"; -import type { Contract, ReferencedContract, ImportContract } from './contract'; -import { inferTranspiled } from './infer-transpiled'; +import type { Contract, ReferencedContract, ImportContract } from "./contract"; +import { inferTranspiled } from "./infer-transpiled"; const upgradeableName = (n: string) => { - if (n === 'Initializable') { + if (n === "Initializable") { return n; } else { - return n.replace(/(Upgradeable)?(?=\.|$)/, 'Upgradeable'); + return n.replace(/(Upgradeable)?(?=\.|$)/, "Upgradeable"); } -} +}; const upgradeableImport = (p: ImportContract): ImportContract => { const { dir, ext, name } = path.parse(p.path); @@ -19,10 +19,13 @@ const upgradeableImport = (p: ImportContract): ImportContract => { name: upgradeableName(p.name), // Contract name path: path.posix.format({ ext, - dir: dir.replace(/^@openzeppelin\/contracts/, '@openzeppelin/contracts-upgradeable'), + dir: dir.replace( + /^@openzeppelin\/contracts/, + "@openzeppelin/contracts-upgradeable", + ), name: upgradeableName(name), // Solidity file name }), - } + }; }; export interface Options { @@ -36,12 +39,16 @@ export interface Helpers extends Required { export function withHelpers(contract: Contract, opts: Options = {}): Helpers { const contractUpgradeable = contract.upgradeable; - const transformName = (n: ReferencedContract) => contractUpgradeable && inferTranspiled(n) ? upgradeableName(n.name) : n.name; + const transformName = (n: ReferencedContract) => + contractUpgradeable && inferTranspiled(n) + ? upgradeableName(n.name) + : n.name; return { upgradeable: contractUpgradeable, transformName, - transformImport: p1 => { - const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; + transformImport: (p1) => { + const p2 = + contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; return opts.transformImport?.(p2) ?? p2; }, }; diff --git a/packages/core/solidity/src/print-versioned.ts b/packages/core/solidity/src/print-versioned.ts index 471b42c09..4f2a6a59d 100644 --- a/packages/core/solidity/src/print-versioned.ts +++ b/packages/core/solidity/src/print-versioned.ts @@ -1,16 +1,17 @@ -import contracts from '../openzeppelin-contracts'; +import contracts from "../openzeppelin-contracts"; import type { Contract } from "./contract"; import { printContract } from "./print"; export function printContractVersioned(contract: Contract): string { return printContract(contract, { - transformImport: p => { + transformImport: (p) => { return { ...p, - path: p.path.replace(/^@openzeppelin\/contracts(-upgradeable)?/, `$&@${contracts.version}`), - } - } + path: p.path.replace( + /^@openzeppelin\/contracts(-upgradeable)?/, + `$&@${contracts.version}`, + ), + }; + }, }); } - - diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index 3db246060..c910b9888 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -19,7 +19,7 @@ export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); const fns = mapValues(sortedFunctions(contract), (fns) => - fns.map((fn) => printFunction(fn, helpers)) + fns.map((fn) => printFunction(fn, helpers)), ); const hasOverrides = fns.override.some((l) => l.length > 0); @@ -50,18 +50,18 @@ export function printContract(contract: Contract, opts?: Options): string { hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], - ...fns.override + ...fns.override, ), `}`, - ] - ) + ], + ), ); } function printInheritance( contract: Contract, - { transformName }: Helpers + { transformName }: Helpers, ): [] | [string] { if (contract.parents.length > 0) { return [ @@ -82,14 +82,14 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { (helpers.upgradeable && parentsWithInitializers.length > 0) ) { const parents = parentsWithInitializers.flatMap((p) => - printParentConstructor(p, helpers) + printParentConstructor(p, helpers), ); const modifiers = helpers.upgradeable ? ["public initializer"] : parents; const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); const body = helpers.upgradeable ? spaceBetween( parents.map((p) => p + ";"), - contract.constructorCode + contract.constructorCode, ) : contract.constructorCode; const head = helpers.upgradeable ? "function initialize" : "constructor"; @@ -144,7 +144,7 @@ function sortedFunctions(contract: Contract): SortedFunctions { function printParentConstructor( { contract, params }: Parent, - helpers: Helpers + helpers: Helpers, ): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; @@ -196,7 +196,7 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { modifiers.push(`override`); } else if (fn.override.size > 1) { modifiers.push( - `override(${[...fn.override].map(transformName).join(", ")})` + `override(${[...fn.override].map(transformName).join(", ")})`, ); } @@ -219,7 +219,7 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { "function " + fn.name, fn.args.map((a) => printArgument(a, helpers)), modifiers, - code + code, ); } else { return []; @@ -233,7 +233,7 @@ function printFunction2( kindedName: string, args: string[], modifiers: string[], - code: Lines[] + code: Lines[], ): Lines[] { const fn: Lines[] = [...comments]; @@ -245,7 +245,7 @@ function printFunction2( if (headingLength <= 72) { fn.push( - [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" ") + [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" "), ); } else { fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); @@ -260,7 +260,7 @@ function printFunction2( function printArgument( arg: FunctionArgument, - { transformName }: Helpers + { transformName }: Helpers, ): string { let type: string; if (typeof arg.type === "string") { @@ -292,7 +292,7 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { imports.map((p) => { const importContract = helpers.transformImport(p); lines.push( - `import {${importContract.name}} from "${importContract.path}";` + `import {${importContract.name}} from "${importContract.path}";`, ); }); diff --git a/packages/core/solidity/src/scripts/prepare.ts b/packages/core/solidity/src/scripts/prepare.ts index a499b2005..50974bc2d 100644 --- a/packages/core/solidity/src/scripts/prepare.ts +++ b/packages/core/solidity/src/scripts/prepare.ts @@ -1,41 +1,51 @@ -import { promises as fs } from 'fs'; -import path from 'path'; -import hre from 'hardhat'; -import type { BuildInfo } from 'hardhat/types'; -import { findAll } from 'solidity-ast/utils'; -import { rimraf } from 'rimraf'; +import { promises as fs } from "fs"; +import path from "path"; +import hre from "hardhat"; +import type { BuildInfo } from "hardhat/types"; +import { findAll } from "solidity-ast/utils"; +import { rimraf } from "rimraf"; import { version } from "@openzeppelin/contracts/package.json"; -import type { OpenZeppelinContracts } from '../../openzeppelin-contracts'; -import { writeGeneratedSources } from '../generate/sources'; -import { mapValues } from '../utils/map-values'; -import { transitiveClosure } from '../utils/transitive-closure'; +import type { OpenZeppelinContracts } from "../../openzeppelin-contracts"; +import { writeGeneratedSources } from "../generate/sources"; +import { mapValues } from "../utils/map-values"; +import { transitiveClosure } from "../utils/transitive-closure"; async function main() { - const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated'); + const generatedSourcesPath = path.join(hre.config.paths.sources, "generated"); await rimraf(generatedSourcesPath); - await writeGeneratedSources(generatedSourcesPath, 'minimal-cover'); - await hre.run('compile'); + await writeGeneratedSources(generatedSourcesPath, "minimal-cover"); + await hre.run("compile"); const dependencies: Record> = {}; const sources: Record = {}; for (const buildInfoPath of await hre.artifacts.getBuildInfoPaths()) { const buildInfo: BuildInfo = JSON.parse( - await fs.readFile(buildInfoPath, 'utf8'), + await fs.readFile(buildInfoPath, "utf8"), ); - for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { + for (const [sourceFile, { ast }] of Object.entries( + buildInfo.output.sources, + )) { + if ( + sourceFile.startsWith("@openzeppelin/contracts") || + sourceFile.startsWith("@openzeppelin/community-contracts") + ) { const sourceDependencies = (dependencies[sourceFile] ??= new Set()); - for (const imp of findAll('ImportDirective', ast)) { + for (const imp of findAll("ImportDirective", ast)) { sourceDependencies.add(imp.absolutePath); } } } - for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) { - if (sourceFile.startsWith('@openzeppelin/contracts') || sourceFile.startsWith('@openzeppelin/community-contracts')) { + for (const [sourceFile, { content }] of Object.entries( + buildInfo.input.sources, + )) { + if ( + sourceFile.startsWith("@openzeppelin/contracts") || + sourceFile.startsWith("@openzeppelin/community-contracts") + ) { sources[sourceFile] = content; } } @@ -44,13 +54,18 @@ async function main() { const contracts: OpenZeppelinContracts = { version, sources, - dependencies: mapValues(transitiveClosure(dependencies), d => Array.from(d)), + dependencies: mapValues(transitiveClosure(dependencies), (d) => + Array.from(d), + ), }; - await fs.writeFile('openzeppelin-contracts.json', JSON.stringify(contracts, null, 2)); + await fs.writeFile( + "openzeppelin-contracts.json", + JSON.stringify(contracts, null, 2), + ); } -main().catch(e => { +main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/packages/core/solidity/src/set-access-control.ts b/packages/core/solidity/src/set-access-control.ts index 7394c31c1..0147594f2 100644 --- a/packages/core/solidity/src/set-access-control.ts +++ b/packages/core/solidity/src/set-access-control.ts @@ -1,40 +1,40 @@ -import type { ContractBuilder, BaseFunction } from './contract'; -import { supportsInterface } from './common-functions'; +import type { ContractBuilder, BaseFunction } from "./contract"; +import { supportsInterface } from "./common-functions"; -export const accessOptions = [false, 'ownable', 'roles', 'managed'] as const; +export const accessOptions = [false, "ownable", "roles", "managed"] as const; -export type Access = typeof accessOptions[number]; +export type Access = (typeof accessOptions)[number]; /** * Sets access control for the contract by adding inheritance. */ export function setAccessControl(c: ContractBuilder, access: Access) { switch (access) { - case 'ownable': { - if (c.addParent(parents.Ownable, [ {lit: 'initialOwner'} ])) { + case "ownable": { + if (c.addParent(parents.Ownable, [{ lit: "initialOwner" }])) { c.addConstructorArgument({ - type: 'address', - name: 'initialOwner' + type: "address", + name: "initialOwner", }); } break; } - case 'roles': { + case "roles": { if (c.addParent(parents.AccessControl)) { c.addConstructorArgument({ - type: 'address', - name: 'defaultAdmin' + type: "address", + name: "defaultAdmin", }); - c.addConstructorCode('_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);'); + c.addConstructorCode("_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);"); } c.addOverride(parents.AccessControl, supportsInterface); break; } - case 'managed': { - if (c.addParent(parents.AccessManaged, [ {lit: 'initialAuthority'} ])) { + case "managed": { + if (c.addParent(parents.AccessManaged, [{ lit: "initialAuthority" }])) { c.addConstructorArgument({ - type: 'address', - name: 'initialAuthority' + type: "address", + name: "initialAuthority", }); } break; @@ -45,30 +45,38 @@ export function setAccessControl(c: ContractBuilder, access: Access) { /** * Enables access control for the contract and restricts the given function with access control. */ -export function requireAccessControl(c: ContractBuilder, fn: BaseFunction, access: Access, roleIdPrefix: string, roleOwner: string | undefined) { +export function requireAccessControl( + c: ContractBuilder, + fn: BaseFunction, + access: Access, + roleIdPrefix: string, + roleOwner: string | undefined, +) { if (access === false) { - access = 'ownable'; + access = "ownable"; } - + setAccessControl(c, access); switch (access) { - case 'ownable': { - c.addModifier('onlyOwner', fn); + case "ownable": { + c.addModifier("onlyOwner", fn); break; } - case 'roles': { - const roleId = roleIdPrefix + '_ROLE'; - const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); + case "roles": { + const roleId = roleIdPrefix + "_ROLE"; + const addedConstant = c.addVariable( + `bytes32 public constant ${roleId} = keccak256("${roleId}");`, + ); if (roleOwner && addedConstant) { - c.addConstructorArgument({type: 'address', name: roleOwner}); + c.addConstructorArgument({ type: "address", name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } c.addModifier(`onlyRole(${roleId})`, fn); break; } - case 'managed': { - c.addModifier('restricted', fn); + case "managed": { + c.addModifier("restricted", fn); break; } } @@ -76,15 +84,15 @@ export function requireAccessControl(c: ContractBuilder, fn: BaseFunction, acces const parents = { Ownable: { - name: 'Ownable', - path: '@openzeppelin/contracts/access/Ownable.sol', + name: "Ownable", + path: "@openzeppelin/contracts/access/Ownable.sol", }, AccessControl: { - name: 'AccessControl', - path: '@openzeppelin/contracts/access/AccessControl.sol', + name: "AccessControl", + path: "@openzeppelin/contracts/access/AccessControl.sol", }, AccessManaged: { - name: 'AccessManaged', - path: '@openzeppelin/contracts/access/manager/AccessManaged.sol', + name: "AccessManaged", + path: "@openzeppelin/contracts/access/manager/AccessManaged.sol", }, }; diff --git a/packages/core/solidity/src/set-clock-mode.ts b/packages/core/solidity/src/set-clock-mode.ts index 65387cdd6..3569b1dfe 100644 --- a/packages/core/solidity/src/set-clock-mode.ts +++ b/packages/core/solidity/src/set-clock-mode.ts @@ -1,33 +1,40 @@ import type { ContractBuilder, ReferencedContract } from "./contract"; import { defineFunctions } from "./utils/define-functions"; -export const clockModeOptions = ['blocknumber', 'timestamp'] as const; -export const clockModeDefault = 'blocknumber' as const; -export type ClockMode = typeof clockModeOptions[number]; +export const clockModeOptions = ["blocknumber", "timestamp"] as const; +export const clockModeDefault = "blocknumber" as const; +export type ClockMode = (typeof clockModeOptions)[number]; const functions = defineFunctions({ clock: { - kind: 'public' as const, + kind: "public" as const, args: [], - returns: ['uint48'], - mutability: 'view' as const, + returns: ["uint48"], + mutability: "view" as const, }, CLOCK_MODE: { - kind: 'public' as const, + kind: "public" as const, args: [], - returns: ['string memory'], - mutability: 'pure' as const, - } + returns: ["string memory"], + mutability: "pure" as const, + }, }); -export function setClockMode(c: ContractBuilder, parent: ReferencedContract, votes: ClockMode) { - if (votes === 'timestamp') { +export function setClockMode( + c: ContractBuilder, + parent: ReferencedContract, + votes: ClockMode, +) { + if (votes === "timestamp") { c.addOverride(parent, functions.clock); - c.setFunctionBody(['return uint48(block.timestamp);'], functions.clock); + c.setFunctionBody(["return uint48(block.timestamp);"], functions.clock); - c.setFunctionComments(['// solhint-disable-next-line func-name-mixedcase'], functions.CLOCK_MODE); + c.setFunctionComments( + ["// solhint-disable-next-line func-name-mixedcase"], + functions.CLOCK_MODE, + ); c.addOverride(parent, functions.CLOCK_MODE); c.setFunctionBody(['return "mode=timestamp";'], functions.CLOCK_MODE); } -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/set-info.ts b/packages/core/solidity/src/set-info.ts index bf5b2300d..9821cd29a 100644 --- a/packages/core/solidity/src/set-info.ts +++ b/packages/core/solidity/src/set-info.ts @@ -2,18 +2,21 @@ import type { ContractBuilder } from "./contract"; export const TAG_SECURITY_CONTACT = `@custom:security-contact`; -export const infoOptions = [{}, { securityContact: 'security@example.com', license: 'WTFPL' }] as const; +export const infoOptions = [ + {}, + { securityContact: "security@example.com", license: "WTFPL" }, +] as const; -export const defaults: Info = { license: 'MIT' }; +export const defaults: Info = { license: "MIT" }; export type Info = { securityContact?: string; license?: string; -} +}; export function setInfo(c: ContractBuilder, info: Info) { const { securityContact, license } = info; - + if (securityContact) { c.addNatspecTag(TAG_SECURITY_CONTACT, securityContact); } diff --git a/packages/core/solidity/src/set-upgradeable.ts b/packages/core/solidity/src/set-upgradeable.ts index 04fcccc03..71073d743 100644 --- a/packages/core/solidity/src/set-upgradeable.ts +++ b/packages/core/solidity/src/set-upgradeable.ts @@ -1,12 +1,16 @@ -import type { ContractBuilder } from './contract'; -import { Access, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; +import type { ContractBuilder } from "./contract"; +import { Access, requireAccessControl } from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; -export const upgradeableOptions = [false, 'transparent', 'uups'] as const; +export const upgradeableOptions = [false, "transparent", "uups"] as const; -export type Upgradeable = typeof upgradeableOptions[number]; +export type Upgradeable = (typeof upgradeableOptions)[number]; -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { +export function setUpgradeable( + c: ContractBuilder, + upgradeable: Upgradeable, + access: Access, +) { if (upgradeable === false) { return; } @@ -14,18 +18,25 @@ export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, acc c.upgradeable = true; c.addParent({ - name: 'Initializable', - path: '@openzeppelin/contracts/proxy/utils/Initializable.sol', + name: "Initializable", + path: "@openzeppelin/contracts/proxy/utils/Initializable.sol", }); switch (upgradeable) { - case 'transparent': break; + case "transparent": + break; - case 'uups': { - requireAccessControl(c, functions._authorizeUpgrade, access, 'UPGRADER', 'upgrader'); + case "uups": { + requireAccessControl( + c, + functions._authorizeUpgrade, + access, + "UPGRADER", + "upgrader", + ); const UUPSUpgradeable = { - name: 'UUPSUpgradeable', - path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol', + name: "UUPSUpgradeable", + path: "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol", }; c.addParent(UUPSUpgradeable); c.addOverride(UUPSUpgradeable, functions._authorizeUpgrade); @@ -35,16 +46,14 @@ export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, acc default: { const _: never = upgradeable; - throw new Error('Unknown value for `upgradeable`'); + throw new Error("Unknown value for `upgradeable`"); } } } const functions = defineFunctions({ _authorizeUpgrade: { - args: [ - { name: 'newImplementation', type: 'address' }, - ], - kind: 'internal', + args: [{ name: "newImplementation", type: "address" }], + kind: "internal", }, }); diff --git a/packages/core/solidity/src/stablecoin.test.ts b/packages/core/solidity/src/stablecoin.test.ts index a3980f869..35d205e0c 100644 --- a/packages/core/solidity/src/stablecoin.test.ts +++ b/packages/core/solidity/src/stablecoin.test.ts @@ -1,14 +1,14 @@ -import test from 'ava'; -import { stablecoin } from '.'; +import test from "ava"; +import { stablecoin } from "."; -import { buildStablecoin, StablecoinOptions } from './stablecoin'; -import { printContract } from './print'; +import { buildStablecoin, StablecoinOptions } from "./stablecoin"; +import { printContract } from "./print"; function testStablecoin(title: string, opts: Partial) { - test(title, t => { + test(title, (t) => { const c = buildStablecoin({ - name: 'MyStablecoin', - symbol: 'MST', + name: "MyStablecoin", + symbol: "MST", ...opts, }); t.snapshot(printContract(c)); @@ -18,118 +18,126 @@ function testStablecoin(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: StablecoinOptions) { - test(title, t => { - t.is(stablecoin.print(opts), printContract(buildStablecoin({ - name: 'MyStablecoin', - symbol: 'MST', - ...opts, - }))); +function testAPIEquivalence(title: string, opts?: StablecoinOptions) { + test(title, (t) => { + t.is( + stablecoin.print(opts), + printContract( + buildStablecoin({ + name: "MyStablecoin", + symbol: "MST", + ...opts, + }), + ), + ); }); } -testStablecoin('basic stablecoin', {}); +testStablecoin("basic stablecoin", {}); -testStablecoin('stablecoin burnable', { +testStablecoin("stablecoin burnable", { burnable: true, }); -testStablecoin('stablecoin pausable', { +testStablecoin("stablecoin pausable", { pausable: true, - access: 'ownable', + access: "ownable", }); -testStablecoin('stablecoin pausable with roles', { +testStablecoin("stablecoin pausable with roles", { pausable: true, - access: 'roles', + access: "roles", }); -testStablecoin('stablecoin pausable with managed', { +testStablecoin("stablecoin pausable with managed", { pausable: true, - access: 'managed', + access: "managed", }); -testStablecoin('stablecoin burnable pausable', { +testStablecoin("stablecoin burnable pausable", { burnable: true, pausable: true, }); -testStablecoin('stablecoin preminted', { - premint: '1000', +testStablecoin("stablecoin preminted", { + premint: "1000", }); -testStablecoin('stablecoin premint of 0', { - premint: '0', +testStablecoin("stablecoin premint of 0", { + premint: "0", }); -testStablecoin('stablecoin mintable', { +testStablecoin("stablecoin mintable", { mintable: true, - access: 'ownable', + access: "ownable", }); -testStablecoin('stablecoin mintable with roles', { +testStablecoin("stablecoin mintable with roles", { mintable: true, - access: 'roles', + access: "roles", }); -testStablecoin('stablecoin permit', { +testStablecoin("stablecoin permit", { permit: true, }); -testStablecoin('stablecoin custodian', { +testStablecoin("stablecoin custodian", { custodian: true, }); -testStablecoin('stablecoin allowlist', { - limitations: 'allowlist', +testStablecoin("stablecoin allowlist", { + limitations: "allowlist", }); -testStablecoin('stablecoin blocklist', { - limitations: 'blocklist', +testStablecoin("stablecoin blocklist", { + limitations: "blocklist", }); -testStablecoin('stablecoin votes', { +testStablecoin("stablecoin votes", { votes: true, }); -testStablecoin('stablecoin votes + blocknumber', { - votes: 'blocknumber', +testStablecoin("stablecoin votes + blocknumber", { + votes: "blocknumber", }); -testStablecoin('stablecoin votes + timestamp', { - votes: 'timestamp', +testStablecoin("stablecoin votes + timestamp", { + votes: "timestamp", }); -testStablecoin('stablecoin flashmint', { +testStablecoin("stablecoin flashmint", { flashmint: true, }); -testAPIEquivalence('stablecoin API default'); +testAPIEquivalence("stablecoin API default"); -testAPIEquivalence('stablecoin API basic', { name: 'CustomStablecoin', symbol: 'CST' }); +testAPIEquivalence("stablecoin API basic", { + name: "CustomStablecoin", + symbol: "CST", +}); -testAPIEquivalence('stablecoin API full', { - name: 'CustomStablecoin', - symbol: 'CST', - premint: '2000', - access: 'roles', +testAPIEquivalence("stablecoin API full", { + name: "CustomStablecoin", + symbol: "CST", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - limitations: 'allowlist', - custodian: true + limitations: "allowlist", + custodian: true, }); -test('stablecoin API assert defaults', async t => { +test("stablecoin API assert defaults", async (t) => { t.is(stablecoin.print(stablecoin.defaults), stablecoin.print()); }); -test('stablecoin API isAccessControlRequired', async t => { +test("stablecoin API isAccessControlRequired", async (t) => { t.is(stablecoin.isAccessControlRequired({ mintable: true }), true); t.is(stablecoin.isAccessControlRequired({ pausable: true }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: 'allowlist' }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: 'blocklist' }), true); -}); \ No newline at end of file + t.is(stablecoin.isAccessControlRequired({ limitations: "allowlist" }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: "blocklist" }), true); +}); diff --git a/packages/core/solidity/src/stablecoin.ts b/packages/core/solidity/src/stablecoin.ts index 66ba36c0d..af0dec852 100644 --- a/packages/core/solidity/src/stablecoin.ts +++ b/packages/core/solidity/src/stablecoin.ts @@ -1,8 +1,18 @@ -import { Contract, ContractBuilder } from './contract'; -import { Access, setAccessControl, requireAccessControl } from './set-access-control'; -import { defineFunctions } from './utils/define-functions'; -import { printContract } from './print'; -import { buildERC20, ERC20Options, defaults as erc20defaults, withDefaults as withERC20Defaults, functions as erc20functions } from './erc20'; +import { Contract, ContractBuilder } from "./contract"; +import { + Access, + setAccessControl, + requireAccessControl, +} from "./set-access-control"; +import { defineFunctions } from "./utils/define-functions"; +import { printContract } from "./print"; +import { + buildERC20, + ERC20Options, + defaults as erc20defaults, + withDefaults as withERC20Defaults, + functions as erc20functions, +} from "./erc20"; export interface StablecoinOptions extends ERC20Options { limitations?: false | "allowlist" | "blocklist"; @@ -11,8 +21,8 @@ export interface StablecoinOptions extends ERC20Options { export const defaults: Required = { ...erc20defaults, - name: 'MyStablecoin', - symbol: 'MST', + name: "MyStablecoin", + symbol: "MST", limitations: false, custodian: false, } as const; @@ -31,8 +41,16 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string { return printContract(buildStablecoin(opts)); } -export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups'; +export function isAccessControlRequired( + opts: Partial, +): boolean { + return ( + opts.mintable || + opts.limitations !== false || + opts.custodian || + opts.pausable || + opts.upgradeable === "uups" + ); } export function buildStablecoin(opts: StablecoinOptions): Contract { @@ -54,32 +72,39 @@ export function buildStablecoin(opts: StablecoinOptions): Contract { return c; } -function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') { - const type = mode === 'allowlist'; +function addLimitations( + c: ContractBuilder, + access: Access, + mode: boolean | "allowlist" | "blocklist", +) { + const type = mode === "allowlist"; const ERC20Limitation = { - name: type ? 'ERC20Allowlist' : 'ERC20Blocklist', - path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`, + name: type ? "ERC20Allowlist" : "ERC20Blocklist", + path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? "ERC20Allowlist" : "ERC20Blocklist"}.sol`, }; c.addParent(ERC20Limitation); c.addOverride(ERC20Limitation, functions._update); c.addOverride(ERC20Limitation, functions._approve); - const [addFn, removeFn] = type + const [addFn, removeFn] = type ? [functions.allowUser, functions.disallowUser] : [functions.blockUser, functions.unblockUser]; - requireAccessControl(c, addFn, access, 'LIMITER', 'limiter'); - c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn); + requireAccessControl(c, addFn, access, "LIMITER", "limiter"); + c.addFunctionCode(`_${type ? "allowUser" : "blockUser"}(user);`, addFn); - requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter'); - c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn); + requireAccessControl(c, removeFn, access, "LIMITER", "limiter"); + c.addFunctionCode( + `_${type ? "disallowUser" : "unblockUser"}(user);`, + removeFn, + ); } function addCustodian(c: ContractBuilder, access: Access) { const ERC20Custodian = { - name: 'ERC20Custodian', - path: '@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol', + name: "ERC20Custodian", + path: "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol", }; c.addParent(ERC20Custodian); @@ -87,36 +112,41 @@ function addCustodian(c: ContractBuilder, access: Access) { c.addOverride(ERC20Custodian, functions._isCustodian); if (access === false) { - access = 'ownable'; + access = "ownable"; } - + setAccessControl(c, access); switch (access) { - case 'ownable': { + case "ownable": { c.setFunctionBody([`return user == owner();`], functions._isCustodian); break; } - case 'roles': { - const roleOwner = 'custodian'; - const roleId = 'CUSTODIAN_ROLE'; - const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); + case "roles": { + const roleOwner = "custodian"; + const roleId = "CUSTODIAN_ROLE"; + const addedConstant = c.addVariable( + `bytes32 public constant ${roleId} = keccak256("${roleId}");`, + ); if (roleOwner && addedConstant) { - c.addConstructorArgument({type: 'address', name: roleOwner}); + c.addConstructorArgument({ type: "address", name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } - c.setFunctionBody([`return hasRole(CUSTODIAN_ROLE, user);`], functions._isCustodian); + c.setFunctionBody( + [`return hasRole(CUSTODIAN_ROLE, user);`], + functions._isCustodian, + ); break; } - case 'managed': { + case "managed": { c.addImportOnly({ - name: 'AuthorityUtils', + name: "AuthorityUtils", path: `@openzeppelin/contracts/access/manager/AuthorityUtils.sol`, }); const logic = [ `(bool immediate,) = AuthorityUtils.canCallWithDelay(authority(), user, address(this), bytes4(_msgData()[0:4]));`, - `return immediate;` - ] + `return immediate;`, + ]; c.setFunctionBody(logic, functions._isCustodian); break; } @@ -127,41 +157,30 @@ const functions = { ...erc20functions, ...defineFunctions({ _isCustodian: { - kind: 'internal' as const, - args: [ - { name: 'user', type: 'address' }, - ], - returns: ['bool'], - mutability: 'view' as const + kind: "internal" as const, + args: [{ name: "user", type: "address" }], + returns: ["bool"], + mutability: "view" as const, }, allowUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, disallowUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, blockUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, unblockUser: { - kind: 'public' as const, - args: [ - { name: 'user', type: 'address' } - ], + kind: "public" as const, + args: [{ name: "user", type: "address" }], }, - }) + }), }; - diff --git a/packages/core/solidity/src/test.ts b/packages/core/solidity/src/test.ts index 9ff1738b2..e0e7211cb 100644 --- a/packages/core/solidity/src/test.ts +++ b/packages/core/solidity/src/test.ts @@ -1,46 +1,49 @@ -import { promises as fs } from 'fs'; -import _test, { TestFn, ExecutionContext } from 'ava'; -import hre from 'hardhat'; -import path from 'path'; +import { promises as fs } from "fs"; +import _test, { TestFn, ExecutionContext } from "ava"; +import hre from "hardhat"; +import path from "path"; -import { generateSources, writeGeneratedSources } from './generate/sources'; -import type { GenericOptions, KindedOptions } from './build-generic'; -import { custom, erc1155, stablecoin, erc20, erc721, governor } from './api'; +import { generateSources, writeGeneratedSources } from "./generate/sources"; +import type { GenericOptions, KindedOptions } from "./build-generic"; +import { custom, erc1155, stablecoin, erc20, erc721, governor } from "./api"; interface Context { - generatedSourcesPath: string + generatedSourcesPath: string; } const test = _test as TestFn; -test.serial('erc20 result compiles', async t => { - await testCompile(t, 'ERC20'); +test.serial("erc20 result compiles", async (t) => { + await testCompile(t, "ERC20"); }); -test.serial('erc721 result compiles', async t => { - await testCompile(t, 'ERC721'); +test.serial("erc721 result compiles", async (t) => { + await testCompile(t, "ERC721"); }); -test.serial('erc1155 result compiles', async t => { - await testCompile(t, 'ERC1155'); +test.serial("erc1155 result compiles", async (t) => { + await testCompile(t, "ERC1155"); }); -test.serial('stablecoin result compiles', async t => { - await testCompile(t, 'Stablecoin'); +test.serial("stablecoin result compiles", async (t) => { + await testCompile(t, "Stablecoin"); }); -test.serial('governor result compiles', async t => { - await testCompile(t, 'Governor'); +test.serial("governor result compiles", async (t) => { + await testCompile(t, "Governor"); }); -test.serial('custom result compiles', async t => { - await testCompile(t, 'Custom'); +test.serial("custom result compiles", async (t) => { + await testCompile(t, "Custom"); }); -async function testCompile(t: ExecutionContext, kind: keyof KindedOptions) { +async function testCompile( + t: ExecutionContext, + kind: keyof KindedOptions, +) { const generatedSourcesPath = path.join(hre.config.paths.sources, `generated`); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, 'all', kind); + await writeGeneratedSources(generatedSourcesPath, "all", kind); // We only want to check that contracts compile and we don't care about any // of the outputs. Setting empty outputSelection causes compilation to go a @@ -49,41 +52,49 @@ async function testCompile(t: ExecutionContext, kind: keyof KindedOptio settings.outputSelection = {}; } - await hre.run('compile'); + await hre.run("compile"); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { - switch(opts.kind) { - case 'ERC20': + switch (opts.kind) { + case "ERC20": return erc20.isAccessControlRequired(opts); - case 'ERC721': + case "ERC721": return erc721.isAccessControlRequired(opts); - case 'ERC1155': + case "ERC1155": return erc1155.isAccessControlRequired(opts); - case 'Stablecoin': + case "Stablecoin": return stablecoin.isAccessControlRequired(opts); - case 'RealWorldAsset': + case "RealWorldAsset": return stablecoin.isAccessControlRequired(opts); - case 'Governor': + case "Governor": return governor.isAccessControlRequired(opts); - case 'Custom': + case "Custom": return custom.isAccessControlRequired(opts); default: throw new Error("No such kind"); } } -test('is access control required', async t => { - for (const contract of generateSources('all')) { +test("is access control required", async (t) => { + for (const contract of generateSources("all")) { const regexOwnable = /import.*Ownable(Upgradeable)?.sol.*/gm; if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.regex( + contract.source, + regexOwnable, + JSON.stringify(contract.options), + ); } else { - t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); + t.notRegex( + contract.source, + regexOwnable, + JSON.stringify(contract.options), + ); } } } -}); \ No newline at end of file +}); diff --git a/packages/core/solidity/src/utils/define-functions.ts b/packages/core/solidity/src/utils/define-functions.ts index c1f664e7e..c05316bce 100644 --- a/packages/core/solidity/src/utils/define-functions.ts +++ b/packages/core/solidity/src/utils/define-functions.ts @@ -1,6 +1,6 @@ -import type { BaseFunction } from '../contract'; +import type { BaseFunction } from "../contract"; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; export function defineFunctions( fns: Record, diff --git a/packages/core/solidity/src/utils/duration.ts b/packages/core/solidity/src/utils/duration.ts index a0a4549f6..32c273fff 100644 --- a/packages/core/solidity/src/utils/duration.ts +++ b/packages/core/solidity/src/utils/duration.ts @@ -1,6 +1,17 @@ -const durationUnits = ['block', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; -type DurationUnit = typeof durationUnits[number]; -export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); +const durationUnits = [ + "block", + "second", + "minute", + "hour", + "day", + "week", + "month", + "year", +] as const; +type DurationUnit = (typeof durationUnits)[number]; +export const durationPattern = new RegExp( + `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, +); const second = 1; const minute = 60 * second; @@ -15,15 +26,15 @@ export function durationToBlocks(duration: string, blockTime: number): number { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = parseFloat(match[1]!); const unit = match[2]! as DurationUnit; - if (unit === 'block') { + if (unit === "block") { if (!Number.isInteger(value)) { - throw new Error('Invalid number of blocks'); + throw new Error("Invalid number of blocks"); } return value; @@ -37,16 +48,16 @@ export function durationToTimestamp(duration: string): string { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error('Bad duration format'); + throw new Error("Bad duration format"); } const value = match[1]!; const unit = match[2]! as DurationUnit; // unit must be a Solidity supported time unit - if (unit === 'block' || unit === 'month' || unit === 'year') { - throw new Error('Invalid unit for timestamp'); + if (unit === "block" || unit === "month" || unit === "year") { + throw new Error("Invalid unit for timestamp"); } return `${value} ${unit}s`; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/utils/find-cover.ts b/packages/core/solidity/src/utils/find-cover.ts index 939ed9240..0cc7f0bb6 100644 --- a/packages/core/solidity/src/utils/find-cover.ts +++ b/packages/core/solidity/src/utils/find-cover.ts @@ -1,11 +1,14 @@ -import { sortedBy } from './sorted-by'; +import { sortedBy } from "./sorted-by"; // Greedy approximation of minimum set cover. -export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { +export function findCover( + sets: T[], + getElements: (set: T) => unknown[], +): T[] { const sortedSets = sortedBy( - sets.map(set => ({ set, elems: getElements(set) })), - s => -s.elems.length, + sets.map((set) => ({ set, elems: getElements(set) })), + (s) => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/solidity/src/utils/format-lines.ts b/packages/core/solidity/src/utils/format-lines.ts index 1860507f8..a0ab46e87 100644 --- a/packages/core/solidity/src/utils/format-lines.ts +++ b/packages/core/solidity/src/utils/format-lines.ts @@ -1,13 +1,16 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol('whitespace'); +const whitespace = Symbol("whitespace"); export function formatLines(...lines: Lines[]): string { return formatLinesWithSpaces(4, ...lines); } -export function formatLinesWithSpaces(spacesPerIndent: number, ...lines: Lines[]): string { - return [...indentEach(0, lines, spacesPerIndent)].join('\n') + '\n'; +export function formatLinesWithSpaces( + spacesPerIndent: number, + ...lines: Lines[] +): string { + return [...indentEach(0, lines, spacesPerIndent)].join("\n") + "\n"; } function* indentEach( @@ -17,18 +20,18 @@ function* indentEach( ): Generator { for (const line of lines) { if (line === whitespace) { - yield ''; + yield ""; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line, spacesPerIndent); } else { - yield ' '.repeat(indent * spacesPerIndent) + line; + yield " ".repeat(indent * spacesPerIndent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter(l => l.length > 0) - .flatMap(l => [whitespace, ...l]) + .filter((l) => l.length > 0) + .flatMap((l) => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index 1d3a359b2..b177c69a4 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function mapValues( obj: Record, - fn: (val: V) => W + fn: (val: V) => W, ): Record { const res = {} as Record; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/to-identifier.test.ts b/packages/core/solidity/src/utils/to-identifier.test.ts index 6af3b3a11..870f062ff 100644 --- a/packages/core/solidity/src/utils/to-identifier.test.ts +++ b/packages/core/solidity/src/utils/to-identifier.test.ts @@ -1,20 +1,20 @@ -import test from 'ava'; +import test from "ava"; -import { toIdentifier } from './to-identifier'; +import { toIdentifier } from "./to-identifier"; -test('unmodified', t => { - t.is(toIdentifier('abc'), 'abc'); +test("unmodified", (t) => { + t.is(toIdentifier("abc"), "abc"); }); -test('remove leading specials', t => { - t.is(toIdentifier('--abc'), 'abc'); +test("remove leading specials", (t) => { + t.is(toIdentifier("--abc"), "abc"); }); -test('remove specials and upcase next char', t => { - t.is(toIdentifier('abc-def'), 'abcDef'); - t.is(toIdentifier('abc--def'), 'abcDef'); +test("remove specials and upcase next char", (t) => { + t.is(toIdentifier("abc-def"), "abcDef"); + t.is(toIdentifier("abc--def"), "abcDef"); }); -test('capitalize', t => { - t.is(toIdentifier('abc', true), 'Abc'); +test("capitalize", (t) => { + t.is(toIdentifier("abc", true), "Abc"); }); diff --git a/packages/core/solidity/src/utils/to-identifier.ts b/packages/core/solidity/src/utils/to-identifier.ts index 2e4a2f9f4..eb49e670f 100644 --- a/packages/core/solidity/src/utils/to-identifier.ts +++ b/packages/core/solidity/src/utils/to-identifier.ts @@ -1,7 +1,8 @@ export function toIdentifier(str: string, capitalize = false): string { return str - .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove accents - .replace(/^[^a-zA-Z$_]+/, '') - .replace(/^(.)/, c => capitalize ? c.toUpperCase() : c) + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") // remove accents + .replace(/^[^a-zA-Z$_]+/, "") + .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w$]+(.?)/g, (_, c) => c.toUpperCase()); } diff --git a/packages/core/solidity/src/utils/transitive-closure.ts b/packages/core/solidity/src/utils/transitive-closure.ts index f2e04ba2d..1b56e79fc 100644 --- a/packages/core/solidity/src/utils/transitive-closure.ts +++ b/packages/core/solidity/src/utils/transitive-closure.ts @@ -1,6 +1,8 @@ type T = string; -export function transitiveClosure(obj: Record>): Record> { +export function transitiveClosure( + obj: Record>, +): Record> { const closure = {} as Record>; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/version.test.ts b/packages/core/solidity/src/utils/version.test.ts index ceac997fb..bc114df8c 100644 --- a/packages/core/solidity/src/utils/version.test.ts +++ b/packages/core/solidity/src/utils/version.test.ts @@ -1,12 +1,14 @@ -import test from 'ava'; +import test from "ava"; -import semver from 'semver'; +import semver from "semver"; -import { compatibleContractsSemver } from './version'; -import contracts from '../../openzeppelin-contracts'; +import { compatibleContractsSemver } from "./version"; +import contracts from "../../openzeppelin-contracts"; -test('installed contracts satisfies compatible range', t => { - t.true(semver.satisfies(contracts.version, compatibleContractsSemver), +test("installed contracts satisfies compatible range", (t) => { + t.true( + semver.satisfies(contracts.version, compatibleContractsSemver), `Installed contracts version ${contracts.version} does not satisfy compatible range ${compatibleContractsSemver}. -Check whether the compatible range is up to date.`); +Check whether the compatible range is up to date.`, + ); }); diff --git a/packages/core/solidity/src/utils/version.ts b/packages/core/solidity/src/utils/version.ts index 3daddc218..31bf323b7 100644 --- a/packages/core/solidity/src/utils/version.ts +++ b/packages/core/solidity/src/utils/version.ts @@ -1,4 +1,4 @@ /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = '^5.0.0'; +export const compatibleContractsSemver = "^5.0.0"; diff --git a/packages/core/solidity/src/zip-foundry.test.ts b/packages/core/solidity/src/zip-foundry.test.ts index eef1c8791..ae1a640fc 100644 --- a/packages/core/solidity/src/zip-foundry.test.ts +++ b/packages/core/solidity/src/zip-foundry.test.ts @@ -1,21 +1,21 @@ -import _test, { TestFn, ExecutionContext } from 'ava'; - -import { zipFoundry } from './zip-foundry'; - -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { buildERC1155 } from './erc1155'; -import { buildCustom } from './custom'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import util from 'util'; +import _test, { TestFn, ExecutionContext } from "ava"; + +import { zipFoundry } from "./zip-foundry"; + +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { buildERC1155 } from "./erc1155"; +import { buildCustom } from "./custom"; +import { promises as fs } from "fs"; +import path from "path"; +import os from "os"; +import util from "util"; import child from "child_process"; -import type { Contract } from './contract'; -import { rimraf } from 'rimraf'; -import type { JSZipObject } from 'jszip'; -import type JSZip from 'jszip'; -import type { GenericOptions } from './build-generic'; +import type { Contract } from "./contract"; +import { rimraf } from "rimraf"; +import type { JSZipObject } from "jszip"; +import type JSZip from "jszip"; +import type { GenericOptions } from "./build-generic"; interface Context { tempFolder: string; @@ -23,21 +23,23 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async t => { - t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); +test.beforeEach(async (t) => { + t.context.tempFolder = await fs.mkdtemp( + path.join(os.tmpdir(), "openzeppelin-wizard-"), + ); }); -test.afterEach.always(async t => { +test.afterEach.always(async (t) => { await rimraf(t.context.tempFolder); }); -test.serial('erc20 full', async t => { +test.serial("erc20 full", async (t) => { const opts: GenericOptions = { - kind: 'ERC20', - name: 'My Token', - symbol: 'MTK', - premint: '2000', - access: 'roles', + kind: "ERC20", + name: "My Token", + symbol: "MTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, @@ -49,43 +51,74 @@ test.serial('erc20 full', async t => { await runTest(c, t, opts); }); -test.serial('erc20 uups, roles', async t => { - const opts: GenericOptions = { kind: 'ERC20', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'roles' }; +test.serial("erc20 uups, roles", async (t) => { + const opts: GenericOptions = { + kind: "ERC20", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + access: "roles", + }; const c = buildERC20(opts); await runTest(c, t, opts); }); -test.serial('erc721 uups, ownable', async t => { - const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'ownable' }; +test.serial("erc721 uups, ownable", async (t) => { + const opts: GenericOptions = { + kind: "ERC721", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + access: "ownable", + }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial('erc1155 basic', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}' }; +test.serial("erc1155 basic", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('erc1155 transparent, ownable', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}', upgradeable: 'transparent', access: 'ownable' }; +test.serial("erc1155 transparent, ownable", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + upgradeable: "transparent", + access: "ownable", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('custom basic', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; +test.serial("custom basic", async (t) => { + const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial('custom transparent, managed', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract', upgradeable: 'transparent', access: 'managed' }; +test.serial("custom transparent, managed", async (t) => { + const opts: GenericOptions = { + kind: "Custom", + name: "My Contract", + upgradeable: "transparent", + access: "managed", + }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { +async function runTest( + c: Contract, + t: ExecutionContext, + opts: GenericOptions, +) { const zip = await zipFoundry(c, opts); assertLayout(zip, c, t); @@ -94,20 +127,26 @@ async function runTest(c: Contract, t: ExecutionContext, opts: GenericO } function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { - const sorted = Object.values(zip.files).map(f => f.name).sort(); + const sorted = Object.values(zip.files) + .map((f) => f.name) + .sort(); t.deepEqual(sorted, [ - 'README.md', - 'script/', + "README.md", + "script/", `script/${c.name}.s.sol`, - 'setup.sh', - 'src/', + "setup.sh", + "src/", `src/${c.name}.sol`, - 'test/', + "test/", `test/${c.name}.t.sol`, ]); } -async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { +async function extractAndRunPackage( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -117,16 +156,22 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); + await fs.writeFile( + path.join(tempFolder, item.name), + await asString(item), + ); } } - const setGitUser = 'git init && git config user.email "test@test.test" && git config user.name "Test"'; - const setup = 'bash setup.sh'; - const test = 'forge test' + (c.upgradeable ? ' --force' : ''); - const script = `forge script script/${c.name}.s.sol` + (c.upgradeable ? ' --force' : ''); + const setGitUser = + 'git init && git config user.email "test@test.test" && git config user.name "Test"'; + const setup = "bash setup.sh"; + const test = "forge test" + (c.upgradeable ? " --force" : ""); + const script = + `forge script script/${c.name}.s.sol` + (c.upgradeable ? " --force" : ""); - const exec = (cmd: string) => util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: '' } }); + const exec = (cmd: string) => + util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: "" } }); const command = `cd "${tempFolder}" && ${setGitUser} && ${setup} && ${test} && ${script}`; const result = await exec(command); @@ -145,8 +190,13 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext t.regex(rerunResult.stdout, /Foundry project already initialized\./); } -async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { - const normalizeVersion = (text: string) => text.replace(/\bv\d+\.\d+\.\d+\b/g, 'vX.Y.Z'); +async function assertContents( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { + const normalizeVersion = (text: string) => + text.replace(/\bv\d+\.\d+\.\d+\b/g, "vX.Y.Z"); const contentComparison = [ normalizeVersion(await getItemString(zip, `setup.sh`)), @@ -168,5 +218,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async('arraybuffer')).toString(); + return Buffer.from(await item.async("arraybuffer")).toString(); } diff --git a/packages/core/solidity/src/zip-foundry.ts b/packages/core/solidity/src/zip-foundry.ts index 96b88122e..4a0e5c8af 100644 --- a/packages/core/solidity/src/zip-foundry.ts +++ b/packages/core/solidity/src/zip-foundry.ts @@ -2,33 +2,33 @@ import JSZip from "jszip"; import type { GenericOptions } from "./build-generic"; import type { Contract } from "./contract"; import { printContract } from "./print"; -import SOLIDITY_VERSION from './solidity-version.json'; -import contracts from '../openzeppelin-contracts'; -import { formatLinesWithSpaces, Lines, spaceBetween } from "./utils/format-lines"; +import SOLIDITY_VERSION from "./solidity-version.json"; +import contracts from "../openzeppelin-contracts"; +import { + formatLinesWithSpaces, + Lines, + spaceBetween, +} from "./utils/format-lines"; function getHeader(c: Contract) { return [ `// SPDX-License-Identifier: ${c.license}`, - `pragma solidity ^${SOLIDITY_VERSION};` + `pragma solidity ^${SOLIDITY_VERSION};`, ]; } const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getHeader(c), - getImports(c), - getTestCase(c), - ), + ...spaceBetween(getHeader(c), getImports(c), getTestCase(c)), ); function getImports(c: Contract) { - const result = [ - 'import {Test} from "forge-std/Test.sol";', - ]; + const result = ['import {Test} from "forge-std/Test.sol";']; if (c.upgradeable) { - result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + result.push( + 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', + ); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -39,32 +39,30 @@ const test = (c: Contract, opts?: GenericOptions) => { return [ `contract ${c.name}Test is Test {`, spaceBetween( + [`${c.name} public instance;`], [ - `${c.name} public instance;`, - ], - [ - 'function setUp() public {', + "function setUp() public {", getAddressVariables(c, args), getDeploymentCode(c, args), - '}', + "}", ], getContractSpecificTestFunction(), ), - '}', + "}", ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === 'transparent') { + if (opts?.upgradeable === "transparent") { return [ `address proxy = Upgrades.deployTransparentProxy(`, [ `"${c.name}.sol",`, `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `instance = ${c.name}(proxy);`, ]; } else { @@ -72,23 +70,25 @@ const test = (c: Contract, opts?: GenericOptions) => { `address proxy = Upgrades.deployUUPSProxy(`, [ `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `instance = ${c.name}(proxy);`, ]; } } else { - return [ - `instance = new ${c.name}(${args.join(', ')});`, - ]; + return [`instance = new ${c.name}(${args.join(", ")});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; let i = 1; // private key index starts from 1 since it must be non-zero - if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + if ( + c.upgradeable && + opts?.upgradeable === "transparent" && + !args.includes("initialOwner") + ) { vars.push(`address initialOwner = vm.addr(${i++});`); } for (const arg of args) { @@ -100,37 +100,31 @@ const test = (c: Contract, opts?: GenericOptions) => { function getContractSpecificTestFunction(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case 'ERC20': - case 'ERC721': + case "ERC20": + case "ERC721": return [ - 'function testName() public view {', - [ - `assertEq(instance.name(), "${opts.name}");` - ], - '}', + "function testName() public view {", + [`assertEq(instance.name(), "${opts.name}");`], + "}", ]; - case 'ERC1155': + case "ERC1155": return [ - 'function testUri() public view {', - [ - `assertEq(instance.uri(0), "${opts.uri}");` - ], - '}', + "function testUri() public view {", + [`assertEq(instance.uri(0), "${opts.uri}");`], + "}", ]; - case 'Governor': - case 'Custom': + case "Governor": + case "Custom": return [ - 'function testSomething() public {', - [ - '// Add your test here', - ], - '}', - ] + "function testSomething() public {", + ["// Add your test here"], + "}", + ]; default: - throw new Error('Unknown ERC'); + throw new Error("Unknown ERC"); } } return []; @@ -140,7 +134,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === 'address') { + if (constructorArg.type === "address") { args.push(constructorArg.name); } } @@ -150,11 +144,7 @@ function getAddressArgs(c: Contract): string[] { const script = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween( - getHeader(c), - getImports(c), - getScript(c), - ), + ...spaceBetween(getHeader(c), getImports(c), getScript(c)), ); function getImports(c: Contract) { @@ -163,7 +153,9 @@ const script = (c: Contract, opts?: GenericOptions) => { 'import {console} from "forge-std/console.sol";', ]; if (c.upgradeable) { - result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + result.push( + 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', + ); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -172,39 +164,39 @@ const script = (c: Contract, opts?: GenericOptions) => { function getScript(c: Contract) { const args = getAddressArgs(c); const deploymentLines = [ - 'vm.startBroadcast();', + "vm.startBroadcast();", ...getAddressVariables(c, args), ...getDeploymentCode(c, args), - `console.log("${c.upgradeable ? 'Proxy' : 'Contract'} deployed to %s", address(instance));`, - 'vm.stopBroadcast();', + `console.log("${c.upgradeable ? "Proxy" : "Contract"} deployed to %s", address(instance));`, + "vm.stopBroadcast();", ]; return [ `contract ${c.name}Script is Script {`, spaceBetween( + ["function setUp() public {}"], [ - 'function setUp() public {}', - ], - [ - 'function run() public {', - args.length > 0 ? addTodoAndCommentOut(deploymentLines) : deploymentLines, - '}', + "function run() public {", + args.length > 0 + ? addTodoAndCommentOut(deploymentLines) + : deploymentLines, + "}", ], ), - '}', + "}", ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === 'transparent') { + if (opts?.upgradeable === "transparent") { return [ `address proxy = Upgrades.deployTransparentProxy(`, [ `"${c.name}.sol",`, `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `${c.name} instance = ${c.name}(proxy);`, ]; } else { @@ -212,23 +204,25 @@ const script = (c: Contract, opts?: GenericOptions) => { `address proxy = Upgrades.deployUUPSProxy(`, [ `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, ], - ');', + ");", `${c.name} instance = ${c.name}(proxy);`, ]; } } else { - return [ - `${c.name} instance = new ${c.name}(${args.join(', ')});`, - ]; + return [`${c.name} instance = new ${c.name}(${args.join(", ")});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; - if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { - vars.push('address initialOwner = ;'); + if ( + c.upgradeable && + opts?.upgradeable === "transparent" && + !args.includes("initialOwner") + ) { + vars.push("address initialOwner = ;"); } for (const arg of args) { vars.push(`address ${arg} = ;`); @@ -238,10 +232,10 @@ const script = (c: Contract, opts?: GenericOptions) => { function addTodoAndCommentOut(lines: Lines[]) { return [ - '// TODO: Set addresses for the variables below, then uncomment the following section:', - '/*', + "// TODO: Set addresses for the variables below, then uncomment the following section:", + "/*", ...lines, - '*/', + "*/", ]; } }; @@ -274,14 +268,18 @@ then # Initialize sample Foundry project forge init --force --no-commit --quiet -${c.upgradeable ? `\ +${ + c.upgradeable + ? `\ # Install OpenZeppelin Contracts and Upgrades forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v${contracts.version} --no-commit --quiet forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet\ -` : `\ +` + : `\ # Install OpenZeppelin Contracts forge install OpenZeppelin/openzeppelin-contracts@v${contracts.version} --no-commit --quiet\ -`} +` +} # Remove unneeded Foundry template files rm src/Counter.sol @@ -297,7 +295,9 @@ ${c.upgradeable ? `\ then echo "" >> remappings.txt fi -${c.upgradeable ? `\ +${ + c.upgradeable + ? `\ echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt @@ -307,9 +307,11 @@ ${c.upgradeable ? `\ echo "ast = true" >> foundry.toml echo "build_info = true" >> foundry.toml echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml\ -` : `\ +` + : `\ echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt\ -`} +` +} # Perform initial git commit git add . @@ -339,7 +341,7 @@ bash setup.sh ## Testing the contract \`\`\` -forge test${c.upgradeable ? ' --force' : ''} +forge test${c.upgradeable ? " --force" : ""} \`\`\` ## Deploying the contract @@ -347,7 +349,7 @@ forge test${c.upgradeable ? ' --force' : ''} You can simulate a deployment by running the script: \`\`\` -forge script script/${c.name}.s.sol${c.upgradeable ? ' --force' : ''} +forge script script/${c.name}.s.sol${c.upgradeable ? " --force" : ""} \`\`\` See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information. @@ -359,8 +361,8 @@ export async function zipFoundry(c: Contract, opts?: GenericOptions) { zip.file(`src/${c.name}.sol`, printContract(c)); zip.file(`test/${c.name}.t.sol`, test(c, opts)); zip.file(`script/${c.name}.s.sol`, script(c, opts)); - zip.file('setup.sh', setupSh(c)); - zip.file('README.md', readme(c)); + zip.file("setup.sh", setupSh(c)); + zip.file("README.md", readme(c)); return zip; -} \ No newline at end of file +} diff --git a/packages/core/solidity/src/zip-hardhat.test.ts b/packages/core/solidity/src/zip-hardhat.test.ts index 7ad696284..30c1380f8 100644 --- a/packages/core/solidity/src/zip-hardhat.test.ts +++ b/packages/core/solidity/src/zip-hardhat.test.ts @@ -1,21 +1,21 @@ -import _test, { TestFn, ExecutionContext } from 'ava'; - -import { zipHardhat } from './zip-hardhat'; - -import { buildERC20 } from './erc20'; -import { buildERC721 } from './erc721'; -import { buildERC1155 } from './erc1155'; -import { buildCustom } from './custom'; -import { promises as fs } from 'fs'; -import path from 'path'; -import os from 'os'; -import util from 'util'; +import _test, { TestFn, ExecutionContext } from "ava"; + +import { zipHardhat } from "./zip-hardhat"; + +import { buildERC20 } from "./erc20"; +import { buildERC721 } from "./erc721"; +import { buildERC1155 } from "./erc1155"; +import { buildCustom } from "./custom"; +import { promises as fs } from "fs"; +import path from "path"; +import os from "os"; +import util from "util"; import child from "child_process"; -import type { Contract } from './contract'; -import { rimraf } from 'rimraf'; -import type { JSZipObject } from 'jszip'; -import type JSZip from 'jszip'; -import type { GenericOptions } from './build-generic'; +import type { Contract } from "./contract"; +import { rimraf } from "rimraf"; +import type { JSZipObject } from "jszip"; +import type JSZip from "jszip"; +import type { GenericOptions } from "./build-generic"; interface Context { tempFolder: string; @@ -23,21 +23,23 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async t => { - t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); +test.beforeEach(async (t) => { + t.context.tempFolder = await fs.mkdtemp( + path.join(os.tmpdir(), "openzeppelin-wizard-"), + ); }); -test.afterEach.always(async t => { +test.afterEach.always(async (t) => { await rimraf(t.context.tempFolder); }); -test.serial('erc20 full', async t => { +test.serial("erc20 full", async (t) => { const opts: GenericOptions = { - kind: 'ERC20', - name: 'My Token', - symbol: 'MTK', - premint: '2000', - access: 'roles', + kind: "ERC20", + name: "My Token", + symbol: "MTK", + premint: "2000", + access: "roles", burnable: true, mintable: true, pausable: true, @@ -49,31 +51,48 @@ test.serial('erc20 full', async t => { await runTest(c, t, opts); }); -test.serial('erc721 upgradeable', async t => { - const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK', upgradeable: 'uups' }; +test.serial("erc721 upgradeable", async (t) => { + const opts: GenericOptions = { + kind: "ERC721", + name: "My Token", + symbol: "MTK", + upgradeable: "uups", + }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial('erc1155 basic', async t => { - const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}' }; +test.serial("erc1155 basic", async (t) => { + const opts: GenericOptions = { + kind: "ERC1155", + name: "My Token", + uri: "https://myuri/{id}", + }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial('custom basic', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; +test.serial("custom basic", async (t) => { + const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial('custom upgradeable', async t => { - const opts: GenericOptions = { kind: 'Custom', name: 'My Contract', upgradeable: 'transparent' }; +test.serial("custom upgradeable", async (t) => { + const opts: GenericOptions = { + kind: "Custom", + name: "My Contract", + upgradeable: "transparent", + }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { +async function runTest( + c: Contract, + t: ExecutionContext, + opts: GenericOptions, +) { const zip = await zipHardhat(c, opts); assertLayout(zip, c, t); @@ -82,24 +101,30 @@ async function runTest(c: Contract, t: ExecutionContext, opts: GenericO } function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { - const sorted = Object.values(zip.files).map(f => f.name).sort(); + const sorted = Object.values(zip.files) + .map((f) => f.name) + .sort(); t.deepEqual(sorted, [ - '.gitignore', - 'README.md', - 'contracts/', + ".gitignore", + "README.md", + "contracts/", `contracts/${c.name}.sol`, - 'hardhat.config.ts', - 'package-lock.json', - 'package.json', - 'scripts/', - 'scripts/deploy.ts', - 'test/', - 'test/test.ts', - 'tsconfig.json', + "hardhat.config.ts", + "package-lock.json", + "package.json", + "scripts/", + "scripts/deploy.ts", + "test/", + "test/test.ts", + "tsconfig.json", ]); } -async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { +async function extractAndRunPackage( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -109,14 +134,17 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); + await fs.writeFile( + path.join(tempFolder, item.name), + await asString(item), + ); } } let command = `cd "${tempFolder}" && npm install && npm test`; if (c.constructorArgs === undefined) { // only test deploying the contract if there are no constructor args needed - command += ' && npx hardhat run scripts/deploy.ts'; + command += " && npx hardhat run scripts/deploy.ts"; } const exec = util.promisify(child.exec); @@ -128,13 +156,17 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext } } -async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { +async function assertContents( + zip: JSZip, + c: Contract, + t: ExecutionContext, +) { const contentComparison = [ await getItemString(zip, `contracts/${c.name}.sol`), - await getItemString(zip, 'hardhat.config.ts'), - await getItemString(zip, 'package.json'), - await getItemString(zip, 'scripts/deploy.ts'), - await getItemString(zip, 'test/test.ts'), + await getItemString(zip, "hardhat.config.ts"), + await getItemString(zip, "package.json"), + await getItemString(zip, "scripts/deploy.ts"), + await getItemString(zip, "test/test.ts"), ]; t.snapshot(contentComparison); @@ -149,5 +181,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async('arraybuffer')).toString(); + return Buffer.from(await item.async("arraybuffer")).toString(); } diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index 8f09757d5..f9092b67f 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -58,7 +58,7 @@ artifacts const test = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, - ...spaceBetween(getImports(c), getTestCase(c)) + ...spaceBetween(getImports(c), getTestCase(c)), ); function getTestCase(c: Contract) { @@ -76,7 +76,7 @@ const test = (c: Contract, opts?: GenericOptions) => { `const instance = await ${getDeploymentCall(c, args)};`, "await instance.waitForDeployment();", ], - getExpects() + getExpects(), ), "});", ], @@ -116,7 +116,7 @@ const test = (c: Contract, opts?: GenericOptions) => { const vars = []; for (let i = 0; i < args.length; i++) { vars.push( - `const ${args[i]} = (await ethers.getSigners())[${i}].address;` + `const ${args[i]} = (await ethers.getSigners())[${i}].address;`, ); } return vars; diff --git a/packages/core/solidity/zip-env-foundry.js b/packages/core/solidity/zip-env-foundry.js index 836eb4d8e..5cfb903a7 100644 --- a/packages/core/solidity/zip-env-foundry.js +++ b/packages/core/solidity/zip-env-foundry.js @@ -1 +1 @@ -module.exports = require('./dist/zip-foundry'); +module.exports = require("./dist/zip-foundry"); diff --git a/packages/core/solidity/zip-env-foundry.ts b/packages/core/solidity/zip-env-foundry.ts index 17e647662..7b167f759 100644 --- a/packages/core/solidity/zip-env-foundry.ts +++ b/packages/core/solidity/zip-env-foundry.ts @@ -1 +1 @@ -export * from './src/zip-foundry'; \ No newline at end of file +export * from "./src/zip-foundry"; diff --git a/packages/core/solidity/zip-env-hardhat.js b/packages/core/solidity/zip-env-hardhat.js index 2d13c70df..5ce54ca53 100644 --- a/packages/core/solidity/zip-env-hardhat.js +++ b/packages/core/solidity/zip-env-hardhat.js @@ -1 +1 @@ -module.exports = require('./dist/zip-hardhat'); +module.exports = require("./dist/zip-hardhat"); diff --git a/packages/core/solidity/zip-env-hardhat.ts b/packages/core/solidity/zip-env-hardhat.ts index 353801961..3934d6d7f 100644 --- a/packages/core/solidity/zip-env-hardhat.ts +++ b/packages/core/solidity/zip-env-hardhat.ts @@ -1 +1 @@ -export * from './src/zip-hardhat'; \ No newline at end of file +export * from "./src/zip-hardhat"; diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index fd010e506..938e5370d 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -35,7 +35,7 @@ export default async (req: Request) => { const validatedMessages = data.messages.filter( (message: { role: string; content: string }) => { return message.content.length < 500; - } + }, ); const messages = [ diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js index 3dd96fd1e..0b17b641f 100644 --- a/packages/ui/postcss.config.js +++ b/packages/ui/postcss.config.js @@ -1,11 +1,7 @@ -const nesting = require('tailwindcss/nesting'); -const tailwindcss = require('tailwindcss'); -const autoprefixer = require('autoprefixer'); +const nesting = require("tailwindcss/nesting"); +const tailwindcss = require("tailwindcss"); +const autoprefixer = require("autoprefixer"); module.exports = { - plugins: [ - nesting, - tailwindcss, - autoprefixer, - ], + plugins: [nesting, tailwindcss, autoprefixer], }; diff --git a/packages/ui/rollup.config.mjs b/packages/ui/rollup.config.mjs index 28b9628c9..65f0d7635 100644 --- a/packages/ui/rollup.config.mjs +++ b/packages/ui/rollup.config.mjs @@ -1,27 +1,27 @@ -import svelte from 'rollup-plugin-svelte'; -import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; -import resolve from '@rollup/plugin-node-resolve'; -import replace from '@rollup/plugin-replace'; -import alias from '@rollup/plugin-alias'; -import livereload from 'rollup-plugin-livereload'; -import { terser } from 'rollup-plugin-terser'; -import typescript from '@rollup/plugin-typescript'; -import styles from 'rollup-plugin-styles'; -import proc from 'child_process'; -import events from 'events'; -import serve from './rollup.server.mjs'; +import svelte from "rollup-plugin-svelte"; +import commonjs from "@rollup/plugin-commonjs"; +import json from "@rollup/plugin-json"; +import resolve from "@rollup/plugin-node-resolve"; +import replace from "@rollup/plugin-replace"; +import alias from "@rollup/plugin-alias"; +import livereload from "rollup-plugin-livereload"; +import { terser } from "rollup-plugin-terser"; +import typescript from "@rollup/plugin-typescript"; +import styles from "rollup-plugin-styles"; +import proc from "child_process"; +import events from "events"; +import serve from "./rollup.server.mjs"; const production = !process.env.ROLLUP_WATCH; -process.env.NODE_ENV = production ? 'production' : 'development'; +process.env.NODE_ENV = production ? "production" : "development"; // Watch the `public` directory and refresh the // browser on changes when not in production const livereloader = !production && livereload({ - watch: 'public', + watch: "public", port: 35731, }); @@ -30,8 +30,11 @@ function onStartRun(cmd, ...args) { return { async buildStart() { if (ran) return; - const child = proc.spawn(cmd, args, { stdio: 'inherit', shell: process.platform == 'win32' }); - const [code, signal] = await events.once(child, 'exit'); + const child = proc.spawn(cmd, args, { + stdio: "inherit", + shell: process.platform == "win32", + }); + const [code, signal] = await events.once(child, "exit"); if (code || signal) { throw new Error(`Command \`${cmd}\` failed`); } @@ -43,37 +46,37 @@ function onStartRun(cmd, ...args) { /** @type import('rollup').RollupOptions */ export default [ { - input: 'src/standalone.js', + input: "src/standalone.js", output: { - dir: 'public/build', - assetFileNames: '[name][extname]', + dir: "public/build", + assetFileNames: "[name][extname]", sourcemap: true, }, onwarn(warning, warn) { - if (warning.code !== 'EMPTY_BUNDLE') { + if (warning.code !== "EMPTY_BUNDLE") { warn(warning); } }, plugins: [ styles({ - include: 'src/standalone.css', - mode: ['extract'], + include: "src/standalone.css", + mode: ["extract"], url: false, sourceMap: true, }), ], }, { - input: 'src/embed.ts', + input: "src/embed.ts", output: { sourcemap: true, - format: 'iife', - name: 'embed', - file: 'public/build/embed.js', + format: "iife", + name: "embed", + file: "public/build/embed.js", }, plugins: [ typescript({ - include: ['src/**/*.ts'], + include: ["src/**/*.ts"], sourceMap: true, inlineSources: true, }), @@ -87,44 +90,45 @@ export default [ }, { preserveEntrySignatures: false, - input: 'src/main.ts', + input: "src/main.ts", output: { sourcemap: true, - format: 'es', - dir: 'public/build', - chunkFileNames: '[name].js', - assetFileNames: '[name][extname]', + format: "es", + dir: "public/build", + chunkFileNames: "[name].js", + assetFileNames: "[name][extname]", }, plugins: [ // Generate openzeppelin-contracts.js data file - onStartRun(...'yarn --cwd ../core/solidity prepare'.split(' ')), + onStartRun(..."yarn --cwd ../core/solidity prepare".split(" ")), - svelte(await import('./svelte.config.js')), + svelte(await import("./svelte.config.js")), styles({ - mode: ['extract', 'bundle.css'], + mode: ["extract", "bundle.css"], sourceMap: true, }), alias({ entries: { - path: 'path-browserify', - 'highlight.js/lib/languages/python': '../../node_modules/highlight.js/lib/languages/python.js', + path: "path-browserify", + "highlight.js/lib/languages/python": + "../../node_modules/highlight.js/lib/languages/python.js", }, }), resolve({ browser: true, - dedupe: ['svelte'], - mainFields: ['ts:main', 'module', 'main'], + dedupe: ["svelte"], + mainFields: ["ts:main", "module", "main"], preferBuiltins: false, }), replace({ preventAssignment: true, - include: '../../**/node_modules/**/*', - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), + include: "../../**/node_modules/**/*", + "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), + "process.env.NODE_DEBUG": JSON.stringify(process.env.NODE_DEBUG), }), json(), @@ -132,7 +136,7 @@ export default [ commonjs(), typescript({ - include: ['src/**/*.ts', '../core/*/src/**/*.ts'], + include: ["src/**/*.ts", "../core/*/src/**/*.ts"], sourceMap: true, inlineSources: true, }), diff --git a/packages/ui/rollup.server.mjs b/packages/ui/rollup.server.mjs index 33e18e92c..e456b3d2e 100644 --- a/packages/ui/rollup.server.mjs +++ b/packages/ui/rollup.server.mjs @@ -1,4 +1,4 @@ -import proc from 'child_process'; +import proc from "child_process"; const state = (global.ROLLUP_SERVER = global.ROLLUP_SERVER || { server: undefined, @@ -12,17 +12,13 @@ export default function serve() { return { writeBundle() { if (state.server) return; - state.server = proc.spawn( - 'npm', - ['run', 'start', '--', '--dev'], - { - stdio: ['ignore', 'inherit', 'inherit'], - shell: true, - }, - ); + state.server = proc.spawn("npm", ["run", "start", "--", "--dev"], { + stdio: ["ignore", "inherit", "inherit"], + shell: true, + }); - process.on('SIGTERM', toExit); - process.on('exit', toExit); + process.on("SIGTERM", toExit); + process.on("exit", toExit); }, }; } diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index 0db04cc5d..6f3becaf5 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -24,7 +24,7 @@ export function injectHyperlinks(code: string) { if (suffix === "::{") { // Multiple components are imported, so remove components and link to the parent .cairo file replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported @@ -33,7 +33,7 @@ export function injectHyperlinks(code: string) { const mapping = componentMappings[componentName]; const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } diff --git a/packages/ui/src/common/initial-options.ts b/packages/ui/src/common/initial-options.ts index 9a4ec972d..fb217fa7d 100644 --- a/packages/ui/src/common/initial-options.ts +++ b/packages/ui/src/common/initial-options.ts @@ -1 +1,5 @@ -export interface InitialOptions { name?: string, symbol?: string, premint?: string }; \ No newline at end of file +export interface InitialOptions { + name?: string; + symbol?: string; + premint?: string; +} diff --git a/packages/ui/src/common/post-config.ts b/packages/ui/src/common/post-config.ts index cfc3f4955..365cd3c80 100644 --- a/packages/ui/src/common/post-config.ts +++ b/packages/ui/src/common/post-config.ts @@ -1,5 +1,5 @@ -import type { GenericOptions as SolidityOptions } from '@openzeppelin/wizard'; -import type { GenericOptions as CairoOptions } from '@openzeppelin/wizard-cairo'; +import type { GenericOptions as SolidityOptions } from "@openzeppelin/wizard"; +import type { GenericOptions as CairoOptions } from "@openzeppelin/wizard-cairo"; declare global { interface Window { @@ -7,12 +7,23 @@ declare global { } } -export type Action = 'copy' | 'remix' | 'download-file' | 'download-hardhat' | 'download-foundry' | 'defender'; -export type Language = 'solidity' | 'cairo' | 'stylus' | 'stellar'; +export type Action = + | "copy" + | "remix" + | "download-file" + | "download-hardhat" + | "download-foundry" + | "defender"; +export type Language = "solidity" | "cairo" | "stylus" | "stellar"; export async function postConfig( - opts: Required | Required, - action: Action, - language: Language) { - window.gtag?.('event', 'wizard_action', { ...opts, action, wizard_lang: language }); -} \ No newline at end of file + opts: Required | Required, + action: Action, + language: Language, +) { + window.gtag?.("event", "wizard_action", { + ...opts, + action, + wizard_lang: language, + }); +} diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index 6f9a3a3f0..c63bba812 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -33,7 +33,7 @@ export function postMessage(msg: Message) { export function postMessageToIframe(id: "defender-deploy", msg: Message) { const iframe: HTMLIFrameElement | null = document.getElementById( - id + id, ) as HTMLIFrameElement; if (iframe) { iframe.contentWindow?.postMessage(msg, "*"); diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index 6e8e7ce8e..52b9097ed 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -14,7 +14,7 @@ export function resizeToFit(node: HTMLInputElement) { "border-right-width", ].map((p) => style.getPropertyValue(p)); const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( - " + " + " + ", )})`; node.style.setProperty("width", result); }; diff --git a/packages/ui/src/embed.ts b/packages/ui/src/embed.ts index 2ac1e629d..0ea4c6d52 100644 --- a/packages/ui/src/embed.ts +++ b/packages/ui/src/embed.ts @@ -1,7 +1,7 @@ -import type { Message } from './common/post-message'; +import type { Message } from "./common/post-message"; -if (!document.currentScript || !('src' in document.currentScript)) { - throw new Error('Unknown script URL'); +if (!document.currentScript || !("src" in document.currentScript)) { + throw new Error("Unknown script URL"); } const currentScript = new URL(document.currentScript.src); @@ -9,59 +9,61 @@ const currentScript = new URL(document.currentScript.src); const iframes = new WeakMap(); let unsupportedVersion: boolean = false; -const unsupportedVersionFrameHeight = 'auto'; +const unsupportedVersionFrameHeight = "auto"; -window.addEventListener('message', function (e: MessageEvent) { +window.addEventListener("message", function (e: MessageEvent) { if (e.source) { - if (e.data.kind === 'oz-wizard-unsupported-version') { + if (e.data.kind === "oz-wizard-unsupported-version") { unsupportedVersion = true; const iframe = iframes.get(e.source); if (iframe) { iframe.style.height = unsupportedVersionFrameHeight; } - } else if (e.data.kind === 'oz-wizard-resize') { + } else if (e.data.kind === "oz-wizard-resize") { const iframe = iframes.get(e.source); if (iframe) { - iframe.style.height = unsupportedVersion ? unsupportedVersionFrameHeight : 'calc(100vh - 100px)'; + iframe.style.height = unsupportedVersion + ? unsupportedVersionFrameHeight + : "calc(100vh - 100px)"; } } } }); onDOMContentLoaded(function () { - const wizards = document.querySelectorAll('oz-wizard'); + const wizards = document.querySelectorAll("oz-wizard"); for (const w of wizards) { - w.style.display = 'block'; + w.style.display = "block"; - const src = new URL('embed', currentScript.origin); + const src = new URL("embed", currentScript.origin); - setSearchParam(w, src.searchParams, 'data-lang', 'lang'); - setSearchParam(w, src.searchParams, 'data-tab', 'tab'); - setSearchParam(w, src.searchParams, 'version', 'version'); + setSearchParam(w, src.searchParams, "data-lang", "lang"); + setSearchParam(w, src.searchParams, "data-tab", "tab"); + setSearchParam(w, src.searchParams, "version", "version"); - const sync = w.getAttribute('data-sync-url'); + const sync = w.getAttribute("data-sync-url"); - if (sync === 'fragment') { + if (sync === "fragment") { // Uses format: #tab&key=value&key=value... - const fragments = window.location.hash.replace('#', '').split('&'); + const fragments = window.location.hash.replace("#", "").split("&"); for (const fragment of fragments) { - const [key, value] = fragment.split('=', 2); + const [key, value] = fragment.split("=", 2); if (key && value) { src.searchParams.set(key, value); } else { - src.searchParams.set('tab', fragment); + src.searchParams.set("tab", fragment); } } } - const iframe = document.createElement('iframe'); + const iframe = document.createElement("iframe"); iframe.src = src.toString(); - iframe.style.display = 'block'; - iframe.style.border = '0'; - iframe.style.width = '100%'; - iframe.style.height = 'calc(100vh - 100px)'; - iframe.allow = 'clipboard-write'; + iframe.style.display = "block"; + iframe.style.border = "0"; + iframe.style.width = "100%"; + iframe.style.height = "calc(100vh - 100px)"; + iframe.allow = "clipboard-write"; w.appendChild(iframe); @@ -69,9 +71,9 @@ onDOMContentLoaded(function () { iframes.set(iframe.contentWindow, iframe); } - if (sync === 'fragment') { - window.addEventListener('message', (e: MessageEvent) => { - if (e.source && e.data.kind === 'oz-wizard-tab-change') { + if (sync === "fragment") { + window.addEventListener("message", (e: MessageEvent) => { + if (e.source && e.data.kind === "oz-wizard-tab-change") { if (iframe === iframes.get(e.source)) { window.location.hash = e.data.tab; } @@ -81,7 +83,12 @@ onDOMContentLoaded(function () { } }); -function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam: string, param: string) { +function setSearchParam( + w: HTMLElement, + searchParams: URLSearchParams, + dataParam: string, + param: string, +) { const value = w.getAttribute(dataParam) ?? w.getAttribute(param); if (value) { searchParams.set(param, value); @@ -89,8 +96,8 @@ function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam } function onDOMContentLoaded(callback: () => void) { - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', callback); + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", callback); } else { callback(); } diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 77efa3002..8f5b9305c 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -2,10 +2,18 @@ import { version as contractsVersion } from "@openzeppelin/contracts/package.jso export function injectHyperlinks(code: string) { // We are modifying HTML, so use HTML escaped chars. The pattern excludes paths that include /../ in the URL. - const contractsRegex = /"(@openzeppelin\/)(contracts-upgradeable\/|contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g - const communityContractsRegex = /"(@openzeppelin\/)(community-contracts\/contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g + const contractsRegex = + /"(@openzeppelin\/)(contracts-upgradeable\/|contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; + const communityContractsRegex = + /"(@openzeppelin\/)(community-contracts\/contracts\/)((?:(?!\.\.)[^/]+\/)*?[^/]*?)"/g; - return code. - replace(contractsRegex, `"$1$2$3"`). - replace(communityContractsRegex, `"$1$2$3"`); + return code + .replace( + contractsRegex, + `"$1$2$3"`, + ) + .replace( + communityContractsRegex, + `"$1$2$3"`, + ); } diff --git a/packages/ui/src/solidity/remix.ts b/packages/ui/src/solidity/remix.ts index a43087074..ee479a157 100644 --- a/packages/ui/src/solidity/remix.ts +++ b/packages/ui/src/solidity/remix.ts @@ -1,8 +1,8 @@ export function remixURL(code: string, upgradeable = false): URL { - const remix = new URL('https://remix.ethereum.org'); - remix.searchParams.set('code', btoa(code).replace(/=*$/, '')); + const remix = new URL("https://remix.ethereum.org"); + remix.searchParams.set("code", btoa(code).replace(/=*$/, "")); if (upgradeable) { - remix.searchParams.set('deployProxy', upgradeable.toString()); + remix.searchParams.set("deployProxy", upgradeable.toString()); } return remix; } diff --git a/packages/ui/src/solidity/wiz-functions.ts b/packages/ui/src/solidity/wiz-functions.ts index 2c8ee6d8a..be6877bd3 100644 --- a/packages/ui/src/solidity/wiz-functions.ts +++ b/packages/ui/src/solidity/wiz-functions.ts @@ -1,153 +1,292 @@ const commonOptions = { // 'false' gets converted to false - access: { type: 'string', enum: ['false', 'ownable', 'roles', 'managed'], description: 'The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.' }, + access: { + type: "string", + enum: ["false", "ownable", "roles", "managed"], + description: + "The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.", + }, // 'false' gets converted to false - upgradeable: { type: 'string', enum: ['false', 'transparent', 'uups'], description: 'Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.' }, + upgradeable: { + type: "string", + enum: ["false", "transparent", "uups"], + description: + "Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.", + }, info: { - type: 'object', - description: 'Metadata about the contract and author', + type: "object", + description: "Metadata about the contract and author", properties: { - securityContact: { type: 'string', description: 'Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.' }, - license: { type: 'string', description: 'The license used by the contract, default is "MIT"' } - } - } -} + securityContact: { + type: "string", + description: + "Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.", + }, + license: { + type: "string", + description: 'The license used by the contract, default is "MIT"', + }, + }, + }, +}; const repeatedOptions = { - name: { type: 'string', description: 'The name of the contract' }, - symbol: { type: 'string', description: 'The short symbol for the token' }, - burnable: { type: 'boolean', description: 'Whether token holders will be able to destroy their tokens' }, - pausable: { type: 'boolean', description: 'Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.' }, - mintable: { type: 'boolean', description: 'Whether privileged accounts will be able to create more supply or emit more tokens' }, -} + name: { type: "string", description: "The name of the contract" }, + symbol: { type: "string", description: "The short symbol for the token" }, + burnable: { + type: "boolean", + description: "Whether token holders will be able to destroy their tokens", + }, + pausable: { + type: "boolean", + description: + "Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.", + }, + mintable: { + type: "boolean", + description: + "Whether privileged accounts will be able to create more supply or emit more tokens", + }, +}; export const erc20Function = { - name: 'erc20', - description: 'Make a fungible token per the ERC-20 standard', + name: "erc20", + description: "Make a fungible token per the ERC-20 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, - premint: { type: 'number', description: 'The number of tokens to premint for the deployer.' }, + premint: { + type: "number", + description: "The number of tokens to premint for the deployer.", + }, mintable: repeatedOptions.mintable, - permit: { type: 'boolean', description: 'Whether without paying gas, token holders will be able to allow third parties to transfer from their account.' }, + permit: { + type: "boolean", + description: + "Whether without paying gas, token holders will be able to allow third parties to transfer from their account.", + }, // 'false' gets converted to false - votes: { type: 'string', enum: ['false', 'blocknumber', 'timestamp'], description: 'Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.'}, - flashmint: { type: 'boolean', description: 'Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they\'re returned in the same transaction.' }, - ...commonOptions + votes: { + type: "string", + enum: ["false", "blocknumber", "timestamp"], + description: + "Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + }, + flashmint: { + type: "boolean", + description: + "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", + }, + ...commonOptions, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const erc721Function = { - name: 'erc721', - description: 'Make a non-fungible token per the ERC-721 standard', + name: "erc721", + description: "Make a non-fungible token per the ERC-721 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, - baseUri: { type: 'string', description: 'A base uri for the token' }, - enumerable: { type: 'boolean', description: 'Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.' }, - uriStorage: { type: 'boolean', description: 'Allows updating token URIs for individual token IDs'}, + baseUri: { type: "string", description: "A base uri for the token" }, + enumerable: { + type: "boolean", + description: + "Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.", + }, + uriStorage: { + type: "boolean", + description: "Allows updating token URIs for individual token IDs", + }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, - incremental: { type: 'boolean', description: 'Whether new tokens will be automatically assigned an incremental id' }, + incremental: { + type: "boolean", + description: + "Whether new tokens will be automatically assigned an incremental id", + }, // 'false' gets converted to false - votes: { type: 'string', enum: ['false', 'blocknumber', 'timestamp'], description: 'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.'}, - ...commonOptions + votes: { + type: "string", + enum: ["false", "blocknumber", "timestamp"], + description: + "Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + }, + ...commonOptions, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const erc1155Function = { - name: 'erc1155', - description: 'Make a non-fungible token per the ERC-1155 standard', + name: "erc1155", + description: "Make a non-fungible token per the ERC-1155 standard", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, - uri: { type: 'string', description: 'The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.' }, + uri: { + type: "string", + description: + "The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.", + }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, - supply: { type: 'boolean', description: 'Whether to keep track of total supply of tokens' }, - updatableUri: { type: 'boolean', description: 'Whether privileged accounts will be able to set a new URI for all token types' }, - ...commonOptions + supply: { + type: "boolean", + description: "Whether to keep track of total supply of tokens", + }, + updatableUri: { + type: "boolean", + description: + "Whether privileged accounts will be able to set a new URI for all token types", + }, + ...commonOptions, }, - required: ['name', 'uri'], - } -} + required: ["name", "uri"], + }, +}; export const stablecoinFunction = { - name: 'stablecoin', - description: 'Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', + name: "stablecoin", + description: + "Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", parameters: { - type: 'object', + type: "object", properties: { ...erc20Function.parameters.properties, - custodian: { type: 'boolean', description: 'Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.' }, + custodian: { + type: "boolean", + description: + "Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.", + }, // 'false' gets converted to false - limitations: { type: 'string', enum: ['false', 'allowlist', 'blocklist'], description: 'Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.' }, - upgradeable: { type: 'string', enum: ['false'], description: 'Upgradeability is not yet available for features that use @openzeppelin/community-contracts' }, + limitations: { + type: "string", + enum: ["false", "allowlist", "blocklist"], + description: + "Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.", + }, + upgradeable: { + type: "string", + enum: ["false"], + description: + "Upgradeability is not yet available for features that use @openzeppelin/community-contracts", + }, }, - required: ['name', 'symbol'], - } -} + required: ["name", "symbol"], + }, +}; export const realWorldAssetFunction = { - name: 'realworldasset', - description: 'Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', + name: "realworldasset", + description: + "Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", parameters: stablecoinFunction.parameters, -} +}; export const governorFunction = { - name: 'governor', - description: 'Make a contract to implement governance, such as for a DAO', + name: "governor", + description: "Make a contract to implement governance, such as for a DAO", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, - delay: { type: 'string', description: 'The delay since proposal is created until voting starts, default is "1 day"' }, - period: { type: 'string', description: 'The length of period during which people can cast their vote, default is "1 week"' }, - blockTime: { type: 'number', description: 'The number of seconds assumed for a block, default is 12' }, + delay: { + type: "string", + description: + 'The delay since proposal is created until voting starts, default is "1 day"', + }, + period: { + type: "string", + description: + 'The length of period during which people can cast their vote, default is "1 week"', + }, + blockTime: { + type: "number", + description: "The number of seconds assumed for a block, default is 12", + }, // gets converted to a string to follow the API - proposalThreshold: { type: 'number', description: 'Minimum number of votes an account must have to create a proposal, default is 0.' }, - decimals: { type: 'number', description: 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)' }, - quorumMode: { type: 'string', enum: ['percent', 'absolute'], description: 'The type of quorum mode to use' }, - quorumPercent: { type: 'number', description: 'The percent required, in cases of quorumMode equals percent' }, + proposalThreshold: { + type: "number", + description: + "Minimum number of votes an account must have to create a proposal, default is 0.", + }, + decimals: { + type: "number", + description: + "The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)", + }, + quorumMode: { + type: "string", + enum: ["percent", "absolute"], + description: "The type of quorum mode to use", + }, + quorumPercent: { + type: "number", + description: + "The percent required, in cases of quorumMode equals percent", + }, // gets converted to a string to follow the API - quorumAbsolute: { type: 'number', description: 'The absolute quorum required, in cases of quorumMode equals absolute' }, - votes: { type: 'string', enum: ['erc20votes', 'erc721votes'], description: 'The type of voting to use' }, - clockMode: { type: 'string', enum: ['blocknumber', 'timestamp'], description: 'The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.' }, + quorumAbsolute: { + type: "number", + description: + "The absolute quorum required, in cases of quorumMode equals absolute", + }, + votes: { + type: "string", + enum: ["erc20votes", "erc721votes"], + description: "The type of voting to use", + }, + clockMode: { + type: "string", + enum: ["blocknumber", "timestamp"], + description: + "The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.", + }, // 'false' gets converted to false - timelock: { type: 'string', enum: ['false', 'openzeppelin', 'compound'], description: 'The type of timelock to use' }, - storage: { type: 'boolean', description: 'Enable storage of proposal details and enumerability of proposals' }, - settings: { type: 'boolean', description: 'Allow governance to update voting settings (delay, period, proposal threshold)' }, - ...commonOptions + timelock: { + type: "string", + enum: ["false", "openzeppelin", "compound"], + description: "The type of timelock to use", + }, + storage: { + type: "boolean", + description: + "Enable storage of proposal details and enumerability of proposals", + }, + settings: { + type: "boolean", + description: + "Allow governance to update voting settings (delay, period, proposal threshold)", + }, + ...commonOptions, }, - required: ['name', 'delay', 'period'], - } -} + required: ["name", "delay", "period"], + }, +}; export const customFunction = { - name: 'custom', - description: 'Make a custom smart contract', + name: "custom", + description: "Make a custom smart contract", parameters: { - type: 'object', + type: "object", properties: { name: repeatedOptions.name, pausable: repeatedOptions.pausable, - ...commonOptions + ...commonOptions, }, - required: ['name'], - } -} + required: ["name"], + }, +}; diff --git a/packages/ui/src/standalone.js b/packages/ui/src/standalone.js index fe423caf7..66b97fc1d 100644 --- a/packages/ui/src/standalone.js +++ b/packages/ui/src/standalone.js @@ -1,3 +1,3 @@ // Used as Rollup entry point to preprocess CSS for wizard.openzeppelin.com -import './standalone.css'; +import "./standalone.css"; diff --git a/packages/ui/svelte.config.js b/packages/ui/svelte.config.js index dc6f828f6..f043d9dd9 100644 --- a/packages/ui/svelte.config.js +++ b/packages/ui/svelte.config.js @@ -1,4 +1,4 @@ -const sveltePreprocess = require('svelte-preprocess'); +const sveltePreprocess = require("svelte-preprocess"); const production = !process.env.ROLLUP_WATCH; diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js index ddd5b15e0..cafb69e25 100644 --- a/packages/ui/tailwind.config.js +++ b/packages/ui/tailwind.config.js @@ -1,39 +1,38 @@ module.exports = { content: [ - './src/**/*.{html,svelte}', + "./src/**/*.{html,svelte}", // Using glob patterns results in infinite loop - './public/index.html', - './public/cairo.html', - './public/stylus.html', - './public/stellar.html', - './public/embed.html', + "./public/index.html", + "./public/cairo.html", + "./public/stylus.html", + "./public/stellar.html", + "./public/embed.html", ], theme: { extend: { keyframes: { - 'fade-in': { - '0%': { opacity: '0' }, - '100%': { opacity: '1' }, + "fade-in": { + "0%": { opacity: "0" }, + "100%": { opacity: "1" }, }, - 'fade-up': { - '0%': { opacity: '0', transform: 'translateY(1rem)' }, - '100%': { opacity: '1', transform: 'translateY(0)' }, + "fade-up": { + "0%": { opacity: "0", transform: "translateY(1rem)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, }, - 'fade-down': { - '0%': { opacity: '0', transform: 'translateY(-1rem)' }, - '100%': { opacity: '1', transform: 'translateY(0)' }, + "fade-down": { + "0%": { opacity: "0", transform: "translateY(-1rem)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, }, }, animation: { - 'fade-in': 'fade-in 0.3s ease-out', - 'fade-up': 'fade-up 0.2s ease-out', - 'fade-down': 'fade-down 0.5s ease-out', - 'spin-slow': 'spin 2s linear infinite', + "fade-in": "fade-in 0.3s ease-out", + "fade-up": "fade-up 0.2s ease-out", + "fade-down": "fade-down 0.5s ease-out", + "spin-slow": "spin 2s linear infinite", }, - - } + }, }, - plugins: [] + plugins: [], }; diff --git a/scripts/bump-changelog.js b/scripts/bump-changelog.js index 541b8e0c3..de6f39a3d 100644 --- a/scripts/bump-changelog.js +++ b/scripts/bump-changelog.js @@ -1,19 +1,22 @@ #!/usr/bin/env node -const { version } = require(process.cwd() + '/package.json'); -const [date] = new Date().toISOString().split('T'); +const { version } = require(process.cwd() + "/package.json"); +const [date] = new Date().toISOString().split("T"); -const fs = require('fs'); -const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); +const fs = require("fs"); +const changelog = fs.readFileSync("CHANGELOG.md", "utf8"); const unreleased = /^## Unreleased$/im; if (!unreleased.test(changelog)) { - console.error('Missing changelog entry'); + console.error("Missing changelog entry"); process.exit(1); } -fs.writeFileSync('CHANGELOG.md', changelog.replace(unreleased, `## ${version} (${date})`)); +fs.writeFileSync( + "CHANGELOG.md", + changelog.replace(unreleased, `## ${version} (${date})`), +); -const proc = require('child_process'); -proc.execSync('git add CHANGELOG.md', { stdio: 'inherit' }); \ No newline at end of file +const proc = require("child_process"); +proc.execSync("git add CHANGELOG.md", { stdio: "inherit" }); From cedaeaa8dfcbdf08a370056e8e6d61d52d16a6bf Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:11:28 -0500 Subject: [PATCH 07/16] resolve prettier conflict --- .gitignore | 1 + .prettierrc | 7 + eslint.config.mjs | 53 +-- packages/core/cairo/ava.config.js | 8 +- packages/core/cairo/src/account.test.ts | 111 ++--- packages/core/cairo/src/account.ts | 123 +++--- packages/core/cairo/src/add-pausable.ts | 53 +-- packages/core/cairo/src/api.ts | 51 +-- packages/core/cairo/src/build-generic.ts | 51 ++- packages/core/cairo/src/common-components.ts | 82 ++-- packages/core/cairo/src/common-options.ts | 26 +- packages/core/cairo/src/contract.test.ts | 108 +++-- packages/core/cairo/src/contract.ts | 52 +-- packages/core/cairo/src/custom.test.ts | 49 +-- packages/core/cairo/src/custom.ts | 23 +- packages/core/cairo/src/erc1155.test.ts | 97 ++--- packages/core/cairo/src/erc1155.ts | 212 ++++------ packages/core/cairo/src/erc20.test.ts | 164 ++++---- packages/core/cairo/src/erc20.ts | 178 ++++---- packages/core/cairo/src/erc721.test.ts | 114 +++-- packages/core/cairo/src/erc721.ts | 190 ++++----- packages/core/cairo/src/error.ts | 2 +- packages/core/cairo/src/external-trait.ts | 10 +- packages/core/cairo/src/generate/account.ts | 10 +- .../core/cairo/src/generate/alternatives.ts | 8 +- packages/core/cairo/src/generate/custom.ts | 12 +- packages/core/cairo/src/generate/erc1155.ts | 22 +- packages/core/cairo/src/generate/erc20.ts | 20 +- packages/core/cairo/src/generate/erc721.ts | 28 +- packages/core/cairo/src/generate/governor.ts | 33 +- packages/core/cairo/src/generate/sources.ts | 121 +++--- packages/core/cairo/src/generate/vesting.ts | 16 +- packages/core/cairo/src/governor.test.ts | 187 ++++----- packages/core/cairo/src/governor.ts | 393 ++++++++---------- packages/core/cairo/src/index.ts | 68 ++- packages/core/cairo/src/kind.ts | 24 +- packages/core/cairo/src/print.ts | 239 ++++------- .../cairo/src/scripts/update-scarb-project.ts | 46 +- packages/core/cairo/src/set-access-control.ts | 113 +++-- packages/core/cairo/src/set-info.ts | 6 +- packages/core/cairo/src/set-royalty-info.ts | 103 ++--- packages/core/cairo/src/set-upgradeable.ts | 101 ++--- packages/core/cairo/src/test.ts | 93 ++--- .../cairo/src/utils/convert-strings.test.ts | 120 +++--- .../core/cairo/src/utils/convert-strings.ts | 50 +-- .../core/cairo/src/utils/define-components.ts | 19 +- .../core/cairo/src/utils/define-functions.ts | 19 +- packages/core/cairo/src/utils/duration.ts | 16 +- packages/core/cairo/src/utils/find-cover.ts | 11 +- packages/core/cairo/src/utils/format-lines.ts | 17 +- packages/core/cairo/src/utils/version.test.ts | 8 +- packages/core/cairo/src/utils/version.ts | 10 +- packages/core/cairo/src/vesting.test.ts | 72 ++-- packages/core/cairo/src/vesting.ts | 143 +++---- packages/core/solidity/ava.config.js | 8 +- packages/core/solidity/get-imports.d.ts | 2 +- packages/core/solidity/get-imports.js | 2 +- packages/core/solidity/hardhat.config.js | 42 +- packages/core/solidity/print-versioned.js | 2 +- packages/core/solidity/print-versioned.ts | 2 +- packages/core/solidity/src/add-pausable.ts | 31 +- packages/core/solidity/src/api.ts | 26 +- packages/core/solidity/src/build-generic.ts | 50 ++- .../core/solidity/src/common-functions.ts | 12 +- packages/core/solidity/src/common-options.ts | 12 +- packages/core/solidity/src/contract.test.ts | 162 ++++---- packages/core/solidity/src/contract.ts | 66 +-- packages/core/solidity/src/custom.test.ts | 75 ++-- packages/core/solidity/src/custom.ts | 24 +- packages/core/solidity/src/erc1155.test.ts | 101 +++-- packages/core/solidity/src/erc1155.ts | 106 +++-- packages/core/solidity/src/erc20.test.ts | 117 +++--- packages/core/solidity/src/erc20.ts | 124 +++--- packages/core/solidity/src/erc721.test.ts | 98 ++--- packages/core/solidity/src/erc721.ts | 129 +++--- packages/core/solidity/src/error.ts | 2 +- .../solidity/src/generate/alternatives.ts | 8 +- packages/core/solidity/src/generate/custom.ts | 12 +- .../core/solidity/src/generate/erc1155.ts | 14 +- packages/core/solidity/src/generate/erc20.ts | 18 +- packages/core/solidity/src/generate/erc721.ts | 18 +- .../core/solidity/src/generate/governor.ts | 34 +- .../core/solidity/src/generate/sources.ts | 96 ++--- .../core/solidity/src/generate/stablecoin.ts | 24 +- .../core/solidity/src/get-imports.test.ts | 122 +++--- packages/core/solidity/src/get-imports.ts | 12 +- packages/core/solidity/src/governor.test.ts | 157 +++---- packages/core/solidity/src/governor.ts | 308 ++++++-------- packages/core/solidity/src/index.ts | 40 +- .../solidity/src/infer-transpiled.test.ts | 18 +- .../core/solidity/src/infer-transpiled.ts | 2 +- packages/core/solidity/src/kind.ts | 24 +- packages/core/solidity/src/options.ts | 24 +- packages/core/solidity/src/print-versioned.ts | 13 +- packages/core/solidity/src/print.ts | 142 +++---- packages/core/solidity/src/scripts/prepare.ts | 63 ++- .../core/solidity/src/set-access-control.ts | 62 ++- packages/core/solidity/src/set-clock-mode.ts | 35 +- packages/core/solidity/src/set-info.ts | 9 +- packages/core/solidity/src/set-upgradeable.ts | 41 +- packages/core/solidity/src/stablecoin.test.ts | 107 ++--- packages/core/solidity/src/stablecoin.ts | 111 ++--- packages/core/solidity/src/test.ts | 80 ++-- .../solidity/src/utils/define-functions.ts | 19 +- packages/core/solidity/src/utils/duration.ts | 27 +- .../core/solidity/src/utils/find-cover.ts | 11 +- .../core/solidity/src/utils/format-lines.ts | 23 +- .../core/solidity/src/utils/map-values.ts | 5 +- .../solidity/src/utils/to-identifier.test.ts | 22 +- .../core/solidity/src/utils/to-identifier.ts | 8 +- .../solidity/src/utils/transitive-closure.ts | 4 +- .../core/solidity/src/utils/version.test.ts | 10 +- packages/core/solidity/src/utils/version.ts | 2 +- .../core/solidity/src/zip-foundry.test.ts | 168 ++++---- packages/core/solidity/src/zip-foundry.ts | 176 +++----- .../core/solidity/src/zip-hardhat.test.ts | 146 +++---- packages/core/solidity/src/zip-hardhat.ts | 104 ++--- packages/core/solidity/zip-env-foundry.js | 2 +- packages/core/solidity/zip-env-foundry.ts | 2 +- packages/core/solidity/zip-env-hardhat.js | 2 +- packages/core/solidity/zip-env-hardhat.ts | 2 +- packages/ui/api/ai.ts | 34 +- packages/ui/postcss.config.js | 6 +- packages/ui/rollup.config.mjs | 91 ++-- packages/ui/rollup.server.mjs | 10 +- packages/ui/src/cairo/highlightjs.ts | 4 +- packages/ui/src/cairo/inject-hyperlinks.ts | 28 +- packages/ui/src/common/error-tooltip.ts | 8 +- packages/ui/src/common/post-config.ts | 16 +- packages/ui/src/common/post-message.ts | 28 +- packages/ui/src/common/resize-to-fit.ts | 27 +- packages/ui/src/embed.ts | 69 ++- packages/ui/src/main.ts | 54 ++- packages/ui/src/solidity/highlightjs.ts | 4 +- packages/ui/src/solidity/inject-hyperlinks.ts | 2 +- packages/ui/src/solidity/remix.ts | 6 +- packages/ui/src/solidity/wiz-functions.ts | 225 +++++----- packages/ui/src/standalone.js | 2 +- packages/ui/svelte.config.js | 2 +- packages/ui/tailwind.config.js | 38 +- scripts/bump-changelog.js | 19 +- 141 files changed, 3561 insertions(+), 4525 deletions(-) create mode 100644 .prettierrc diff --git a/.gitignore b/.gitignore index c61302beb..63a8b9661 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules .env .env.local +.vscode \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..b62b39794 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "all", + "printWidth": 120, + "bracketSpacing": true +} diff --git a/eslint.config.mjs b/eslint.config.mjs index e5715f264..c0969cd4d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,62 +1,49 @@ // @ts-check -import eslint from "@eslint/js"; -import prettierRecommended from "eslint-plugin-prettier/recommended"; -import unicornPlugin from "eslint-plugin-unicorn"; -import typescriptEslint from "typescript-eslint"; +import eslint from '@eslint/js'; +import prettierPluginRecommended from 'eslint-plugin-prettier/recommended'; +import unicornPlugin from 'eslint-plugin-unicorn'; +import typescriptEslint from 'typescript-eslint'; export default typescriptEslint.config( eslint.configs.recommended, typescriptEslint.configs.strict, - prettierRecommended, + prettierPluginRecommended, { plugins: { unicorn: unicornPlugin, }, rules: { - "@typescript-eslint/no-unused-vars": [ - "warn", - { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': 'error', + 'prettier/prettier': [ + 'error', + { singleQuote: true, arrowParens: 'avoid', trailingComma: 'all', printWidth: 120, bracketSpacing: true }, ], - "@typescript-eslint/no-non-null-assertion": "warn", }, }, { - ignores: [ - "node_modules/", - "*.sol", - "packages/*/node_modules/", - "packages/**/dist/", - "packages/**/build/", - ], + ignores: ['node_modules/', '*.sol', 'packages/*/node_modules/', 'packages/**/dist/', 'packages/**/build/'], }, { - files: ["**/*.config.js"], + files: ['**/*.config.js'], languageOptions: { - sourceType: "commonjs", + sourceType: 'commonjs', }, }, { - files: ["**/*.js"], + files: ['**/*.mjs', '**/*.js'], languageOptions: { - sourceType: "commonjs", - }, - rules: { - "@typescript-eslint/*": "off", - }, - }, - { - files: ["**/*.mjs", "**/*.js"], - languageOptions: { - sourceType: "commonjs", + sourceType: 'commonjs', globals: { - process: "readonly", - global: "readonly", - console: "readonly", + process: 'readonly', + global: 'readonly', + console: 'readonly', }, }, rules: { - "@typescript-eslint/no-require-imports": "off", + '@typescript-eslint/no-require-imports': 'off', }, }, ); diff --git a/packages/core/cairo/ava.config.js b/packages/core/cairo/ava.config.js index a39075959..e39146f7a 100644 --- a/packages/core/cairo/ava.config.js +++ b/packages/core/cairo/ava.config.js @@ -1,9 +1,9 @@ module.exports = { - extensions: ["ts"], - require: ["ts-node/register"], + extensions: ['ts'], + require: ['ts-node/register'], watchmode: { - ignoreChanges: ["contracts", "artifacts", "cache"], + ignoreChanges: ['contracts', 'artifacts', 'cache'], }, - timeout: "10m", + timeout: '10m', workerThreads: false, }; diff --git a/packages/core/cairo/src/account.test.ts b/packages/core/cairo/src/account.test.ts index 4b4097e5c..cad307c75 100644 --- a/packages/core/cairo/src/account.test.ts +++ b/packages/core/cairo/src/account.test.ts @@ -1,15 +1,16 @@ -import test from "ava"; +import test from 'ava'; -import { buildAccount, AccountOptions } from "./account"; -import { printContract } from "./print"; +import type { AccountOptions } from './account'; +import { buildAccount } from './account'; +import { printContract } from './print'; -import { account } from "."; +import { account } from '.'; function testAccount(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildAccount({ - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', ...opts, }); t.snapshot(printContract(c)); @@ -17,10 +18,10 @@ function testAccount(title: string, opts: Partial) { } function testEthAccount(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildAccount({ - name: "MyAccount", - type: "eth", + name: 'MyAccount', + type: 'eth', ...opts, }); t.snapshot(printContract(c)); @@ -31,13 +32,13 @@ function testEthAccount(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: AccountOptions) { - test(title, (t) => { + test(title, t => { t.is( account.print(opts), printContract( buildAccount({ - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -49,15 +50,15 @@ function testAPIEquivalence(title: string, opts?: AccountOptions) { }); } -testAccount("default full account, mixin + upgradeable", {}); +testAccount('default full account, mixin + upgradeable', {}); -testAccount("default full account, mixin + non-upgradeable", { +testAccount('default full account, mixin + non-upgradeable', { upgradeable: false, }); -testAccount("explicit full account, mixin + upgradeable", { - name: "MyAccount", - type: "stark", +testAccount('explicit full account, mixin + upgradeable', { + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -65,9 +66,9 @@ testAccount("explicit full account, mixin + upgradeable", { upgradeable: true, }); -testAccount("explicit full account, mixin + non-upgradeable", { - name: "MyAccount", - type: "stark", +testAccount('explicit full account, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -75,14 +76,14 @@ testAccount("explicit full account, mixin + non-upgradeable", { upgradeable: false, }); -testAccount("basic account, upgradeable", { +testAccount('basic account, upgradeable', { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testAccount("basic account, non-upgradeable", { +testAccount('basic account, non-upgradeable', { declare: false, deploy: false, pubkey: false, @@ -90,54 +91,54 @@ testAccount("basic account, non-upgradeable", { upgradeable: false, }); -testAccount("account outside execution", { +testAccount('account outside execution', { deploy: false, pubkey: false, declare: false, }); -testAccount("account declarer", { +testAccount('account declarer', { deploy: false, pubkey: false, outsideExecution: false, }); -testAccount("account deployable", { +testAccount('account deployable', { declare: false, pubkey: false, outsideExecution: false, }); -testAccount("account public key", { +testAccount('account public key', { declare: false, deploy: false, outsideExecution: false, }); -testAccount("account declarer, deployable", { +testAccount('account declarer, deployable', { pubkey: false, outsideExecution: false, }); -testAccount("account declarer, public key", { +testAccount('account declarer, public key', { deploy: false, outsideExecution: false, }); -testAccount("account deployable, public key", { +testAccount('account deployable, public key', { declare: false, outsideExecution: false, }); -testEthAccount("default full ethAccount, mixin + upgradeable", {}); +testEthAccount('default full ethAccount, mixin + upgradeable', {}); -testEthAccount("default full ethAccount, mixin + non-upgradeable", { +testEthAccount('default full ethAccount, mixin + non-upgradeable', { upgradeable: false, }); -testEthAccount("explicit full ethAccount, mixin + upgradeable", { - name: "MyAccount", - type: "eth", +testEthAccount('explicit full ethAccount, mixin + upgradeable', { + name: 'MyAccount', + type: 'eth', declare: true, deploy: true, pubkey: true, @@ -145,9 +146,9 @@ testEthAccount("explicit full ethAccount, mixin + upgradeable", { upgradeable: true, }); -testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { - name: "MyAccount", - type: "eth", +testEthAccount('explicit full ethAccount, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'eth', declare: true, deploy: true, pubkey: true, @@ -155,14 +156,14 @@ testEthAccount("explicit full ethAccount, mixin + non-upgradeable", { upgradeable: false, }); -testEthAccount("basic ethAccount, upgradeable", { +testEthAccount('basic ethAccount, upgradeable', { declare: false, deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount("basic ethAccount, non-upgradeable", { +testEthAccount('basic ethAccount, non-upgradeable', { declare: false, deploy: false, pubkey: false, @@ -170,50 +171,50 @@ testEthAccount("basic ethAccount, non-upgradeable", { upgradeable: false, }); -testEthAccount("ethAccount outside execution", { +testEthAccount('ethAccount outside execution', { deploy: false, pubkey: false, declare: false, }); -testEthAccount("ethAccount declarer", { +testEthAccount('ethAccount declarer', { deploy: false, pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount deployable", { +testEthAccount('ethAccount deployable', { declare: false, pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount public key", { +testEthAccount('ethAccount public key', { declare: false, deploy: false, outsideExecution: false, }); -testEthAccount("ethAccount declarer, deployable", { +testEthAccount('ethAccount declarer, deployable', { pubkey: false, outsideExecution: false, }); -testEthAccount("ethAccount declarer, public key", { +testEthAccount('ethAccount declarer, public key', { deploy: false, outsideExecution: false, }); -testEthAccount("ethAccount deployable, public key", { +testEthAccount('ethAccount deployable, public key', { declare: false, outsideExecution: false, }); -testAPIEquivalence("account API default"); +testAPIEquivalence('account API default'); -testAPIEquivalence("account API basic", { - name: "CustomAccount", - type: "stark", +testAPIEquivalence('account API basic', { + name: 'CustomAccount', + type: 'stark', declare: false, deploy: false, pubkey: false, @@ -221,9 +222,9 @@ testAPIEquivalence("account API basic", { upgradeable: false, }); -testAPIEquivalence("account API full upgradeable", { - name: "CustomAccount", - type: "stark", +testAPIEquivalence('account API full upgradeable', { + name: 'CustomAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -231,6 +232,6 @@ testAPIEquivalence("account API full upgradeable", { upgradeable: true, }); -test("account API assert defaults", async (t) => { +test('account API assert defaults', async t => { t.is(account.print(account.defaults), account.print()); }); diff --git a/packages/core/cairo/src/account.ts b/packages/core/cairo/src/account.ts index 5f1659de9..f07293ede 100644 --- a/packages/core/cairo/src/account.ts +++ b/packages/core/cairo/src/account.ts @@ -1,18 +1,20 @@ -import { Contract, ContractBuilder } from "./contract"; -import { CommonOptions, withCommonDefaults } from "./common-options"; -import { defaults as commonDefaults } from "./common-options"; -import { setAccountUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { printContract } from "./print"; -import { addSRC5Component } from "./common-components"; - -export const accountTypes = ["stark", "eth"] as const; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults } from './common-options'; +import { defaults as commonDefaults } from './common-options'; +import { setAccountUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; + +export const accountTypes = ['stark', 'eth'] as const; export type Account = (typeof accountTypes)[number]; export const defaults: Required = { - name: "MyAccount", - type: "stark", + name: 'MyAccount', + type: 'stark', declare: true, deploy: true, pubkey: true, @@ -52,22 +54,14 @@ export function buildAccount(opts: AccountOptions): Contract { const allOpts = withDefaults(opts); switch (allOpts.type) { - case "stark": - c.addConstructorArgument({ name: "public_key", type: "felt252" }); - c.addComponent( - components.AccountComponent, - [{ lit: "public_key" }], - true, - ); + case 'stark': + c.addConstructorArgument({ name: 'public_key', type: 'felt252' }); + c.addComponent(components.AccountComponent, [{ lit: 'public_key' }], true); break; - case "eth": - c.addUseClause("openzeppelin::account::interface", "EthPublicKey"); - c.addConstructorArgument({ name: "public_key", type: "EthPublicKey" }); - c.addComponent( - components.EthAccountComponent, - [{ lit: "public_key" }], - true, - ); + case 'eth': + c.addUseClause('openzeppelin::account::interface', 'EthPublicKey'); + c.addConstructorArgument({ name: 'public_key', type: 'EthPublicKey' }); + c.addComponent(components.EthAccountComponent, [{ lit: 'public_key' }], true); break; } @@ -103,11 +97,11 @@ function addSRC6(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "SRC6Impl", + name: 'SRC6Impl', value: `${baseComponent}::SRC6Impl`, }); c.addImplToComponent(componentType, { - name: "SRC6CamelOnlyImpl", + name: 'SRC6CamelOnlyImpl', value: `${baseComponent}::SRC6CamelOnlyImpl`, }); @@ -118,7 +112,7 @@ function addDeclarer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "DeclarerImpl", + name: 'DeclarerImpl', value: `${baseComponent}::DeclarerImpl`, }); } @@ -127,7 +121,7 @@ function addDeployer(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "DeployableImpl", + name: 'DeployableImpl', value: `${baseComponent}::DeployableImpl`, }); } @@ -136,23 +130,22 @@ function addPublicKey(c: ContractBuilder, accountType: Account) { const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { - name: "PublicKeyImpl", + name: 'PublicKeyImpl', value: `${baseComponent}::PublicKeyImpl`, }); c.addImplToComponent(componentType, { - name: "PublicKeyCamelImpl", + name: 'PublicKeyCamelImpl', value: `${baseComponent}::PublicKeyCamelImpl`, }); } function addOutsideExecution(c: ContractBuilder) { - c.addUseClause("openzeppelin::account::extensions", "SRC9Component"); + c.addUseClause('openzeppelin::account::extensions', 'SRC9Component'); c.addComponent(components.SRC9Component, [], true); } function addAccountMixin(c: ContractBuilder, accountType: Account) { - const accountMixinImpl = - accountType === "stark" ? "AccountMixinImpl" : "EthAccountMixinImpl"; + const accountMixinImpl = accountType === 'stark' ? 'AccountMixinImpl' : 'EthAccountMixinImpl'; const [baseComponent, componentType] = getBaseCompAndCompType(accountType); c.addImplToComponent(componentType, { @@ -160,76 +153,74 @@ function addAccountMixin(c: ContractBuilder, accountType: Account) { value: `${baseComponent}::${accountMixinImpl}`, }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } -function getBaseCompAndCompType( - accountType: Account, -): [string, typeof componentType] { +function getBaseCompAndCompType(accountType: Account): [string, typeof componentType] { const [baseComponent, componentType] = - accountType === "stark" - ? ["AccountComponent", components.AccountComponent] - : ["EthAccountComponent", components.EthAccountComponent]; + accountType === 'stark' + ? ['AccountComponent', components.AccountComponent] + : ['EthAccountComponent', components.EthAccountComponent]; return [baseComponent, componentType]; } const components = defineComponents({ AccountComponent: { - path: "openzeppelin::account", + path: 'openzeppelin::account', substorage: { - name: "account", - type: "AccountComponent::Storage", + name: 'account', + type: 'AccountComponent::Storage', }, event: { - name: "AccountEvent", - type: "AccountComponent::Event", + name: 'AccountEvent', + type: 'AccountComponent::Event', }, impls: [ { - name: "AccountInternalImpl", + name: 'AccountInternalImpl', embed: false, - value: "AccountComponent::InternalImpl", + value: 'AccountComponent::InternalImpl', }, ], }, EthAccountComponent: { - path: "openzeppelin::account::eth_account", + path: 'openzeppelin::account::eth_account', substorage: { - name: "eth_account", - type: "EthAccountComponent::Storage", + name: 'eth_account', + type: 'EthAccountComponent::Storage', }, event: { - name: "EthAccountEvent", - type: "EthAccountComponent::Event", + name: 'EthAccountEvent', + type: 'EthAccountComponent::Event', }, impls: [ { - name: "EthAccountInternalImpl", + name: 'EthAccountInternalImpl', embed: false, - value: "EthAccountComponent::InternalImpl", + value: 'EthAccountComponent::InternalImpl', }, ], }, SRC9Component: { - path: "openzeppelin::account::extensions", + path: 'openzeppelin::account::extensions', substorage: { - name: "src9", - type: "SRC9Component::Storage", + name: 'src9', + type: 'SRC9Component::Storage', }, event: { - name: "SRC9Event", - type: "SRC9Component::Event", + name: 'SRC9Event', + type: 'SRC9Component::Event', }, impls: [ { - name: "OutsideExecutionV2Impl", - value: "SRC9Component::OutsideExecutionV2Impl", + name: 'OutsideExecutionV2Impl', + value: 'SRC9Component::OutsideExecutionV2Impl', }, { - name: "OutsideExecutionInternalImpl", + name: 'OutsideExecutionInternalImpl', embed: false, - value: "SRC9Component::InternalImpl", + value: 'SRC9Component::InternalImpl', }, ], }, diff --git a/packages/core/cairo/src/add-pausable.ts b/packages/core/cairo/src/add-pausable.ts index 4b33dd23b..6d4f1c3c4 100644 --- a/packages/core/cairo/src/add-pausable.ts +++ b/packages/core/cairo/src/add-pausable.ts @@ -1,53 +1,40 @@ -import { getSelfArg } from "./common-options"; -import type { ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; -import { defineComponents } from "./utils/define-components"; -import { externalTrait } from "./external-trait"; +import { getSelfArg } from './common-options'; +import type { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; +import { defineComponents } from './utils/define-components'; +import { externalTrait } from './external-trait'; export function addPausable(c: ContractBuilder, access: Access) { c.addComponent(components.PausableComponent, [], false); c.addFunction(externalTrait, functions.pause); c.addFunction(externalTrait, functions.unpause); - requireAccessControl( - c, - externalTrait, - functions.pause, - access, - "PAUSER", - "pauser", - ); - requireAccessControl( - c, - externalTrait, - functions.unpause, - access, - "PAUSER", - "pauser", - ); + requireAccessControl(c, externalTrait, functions.pause, access, 'PAUSER', 'pauser'); + requireAccessControl(c, externalTrait, functions.unpause, access, 'PAUSER', 'pauser'); } const components = defineComponents({ PausableComponent: { - path: "openzeppelin::security::pausable", + path: 'openzeppelin::security::pausable', substorage: { - name: "pausable", - type: "PausableComponent::Storage", + name: 'pausable', + type: 'PausableComponent::Storage', }, event: { - name: "PausableEvent", - type: "PausableComponent::Event", + name: 'PausableEvent', + type: 'PausableComponent::Event', }, impls: [ { - name: "PausableImpl", - value: "PausableComponent::PausableImpl", + name: 'PausableImpl', + value: 'PausableComponent::PausableImpl', }, { - name: "PausableInternalImpl", + name: 'PausableInternalImpl', embed: false, - value: "PausableComponent::InternalImpl", + value: 'PausableComponent::InternalImpl', }, ], }, @@ -56,10 +43,10 @@ const components = defineComponents({ const functions = defineFunctions({ pause: { args: [getSelfArg()], - code: ["self.pausable.pause()"], + code: ['self.pausable.pause()'], }, unpause: { args: [getSelfArg()], - code: ["self.pausable.unpause()"], + code: ['self.pausable.unpause()'], }, }); diff --git a/packages/core/cairo/src/api.ts b/packages/core/cairo/src/api.ts index 67bca280b..dd8daf73e 100644 --- a/packages/core/cairo/src/api.ts +++ b/packages/core/cairo/src/api.ts @@ -1,43 +1,34 @@ -import type { CommonOptions, CommonContractOptions } from "./common-options"; +import type { CommonOptions, CommonContractOptions } from './common-options'; +import type { ERC20Options } from './erc20'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, - ERC20Options, -} from "./erc20"; +} from './erc20'; +import type { ERC721Options } from './erc721'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, - ERC721Options, -} from "./erc721"; +} from './erc721'; +import type { ERC1155Options } from './erc1155'; import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, - ERC1155Options, -} from "./erc1155"; -import { - printAccount, - defaults as accountDefaults, - AccountOptions, -} from "./account"; -import { - printGovernor, - defaults as governorDefaults, - GovernorOptions, -} from "./governor"; +} from './erc1155'; +import type { AccountOptions } from './account'; +import { printAccount, defaults as accountDefaults } from './account'; +import type { GovernorOptions } from './governor'; +import { printGovernor, defaults as governorDefaults } from './governor'; +import type { CustomOptions } from './custom'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, - CustomOptions, -} from "./custom"; -import { - printVesting, - defaults as vestingDefaults, - VestingOptions, -} from "./vesting"; +} from './custom'; +import type { VestingOptions } from './vesting'; +import { printVesting, defaults as vestingDefaults } from './vesting'; export interface WizardAccountAPI { /** @@ -71,17 +62,13 @@ export interface AccessControlAPI { isAccessControlRequired: (opts: Partial) => boolean; } -export type ERC20 = WizardContractAPI & - AccessControlAPI; -export type ERC721 = WizardContractAPI & - AccessControlAPI; -export type ERC1155 = WizardContractAPI & - AccessControlAPI; +export type ERC20 = WizardContractAPI & AccessControlAPI; +export type ERC721 = WizardContractAPI & AccessControlAPI; +export type ERC1155 = WizardContractAPI & AccessControlAPI; export type Account = WizardAccountAPI; export type Governor = WizardContractAPI; export type Vesting = WizardContractAPI; -export type Custom = WizardContractAPI & - AccessControlAPI; +export type Custom = WizardContractAPI & AccessControlAPI; export const erc20: ERC20 = { print: printERC20, diff --git a/packages/core/cairo/src/build-generic.ts b/packages/core/cairo/src/build-generic.ts index abfab3d16..a956ed94f 100644 --- a/packages/core/cairo/src/build-generic.ts +++ b/packages/core/cairo/src/build-generic.ts @@ -1,49 +1,56 @@ -import { ERC20Options, buildERC20 } from "./erc20"; -import { ERC721Options, buildERC721 } from "./erc721"; -import { ERC1155Options, buildERC1155 } from "./erc1155"; -import { CustomOptions, buildCustom } from "./custom"; -import { AccountOptions, buildAccount } from "./account"; -import { GovernorOptions, buildGovernor } from "./governor"; -import { VestingOptions, buildVesting } from "./vesting"; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import type { AccountOptions } from './account'; +import { buildAccount } from './account'; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import type { VestingOptions } from './vesting'; +import { buildVesting } from './vesting'; export interface KindedOptions { - ERC20: { kind: "ERC20" } & ERC20Options; - ERC721: { kind: "ERC721" } & ERC721Options; - ERC1155: { kind: "ERC1155" } & ERC1155Options; - Account: { kind: "Account" } & AccountOptions; - Governor: { kind: "Governor" } & GovernorOptions; - Vesting: { kind: "Vesting" } & VestingOptions; - Custom: { kind: "Custom" } & CustomOptions; + ERC20: { kind: 'ERC20' } & ERC20Options; + ERC721: { kind: 'ERC721' } & ERC721Options; + ERC1155: { kind: 'ERC1155' } & ERC1155Options; + Account: { kind: 'Account' } & AccountOptions; + Governor: { kind: 'Governor' } & GovernorOptions; + Vesting: { kind: 'Vesting' } & VestingOptions; + Custom: { kind: 'Custom' } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return buildERC20(opts); - case "ERC721": + case 'ERC721': return buildERC721(opts); - case "ERC1155": + case 'ERC1155': return buildERC1155(opts); - case "Account": + case 'Account': return buildAccount(opts); - case "Governor": + case 'Governor': return buildGovernor(opts); - case "Vesting": + case 'Vesting': return buildVesting(opts); - case "Custom": + case 'Custom': return buildCustom(opts); default: { const _: never = opts; - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } } diff --git a/packages/core/cairo/src/common-components.ts b/packages/core/cairo/src/common-components.ts index 90615a056..bdfcef0e8 100644 --- a/packages/core/cairo/src/common-components.ts +++ b/packages/core/cairo/src/common-components.ts @@ -1,56 +1,56 @@ -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { defineComponents } from "./utils/define-components"; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; -export const tokenTypes = ["ERC20", "ERC721"] as const; +export const tokenTypes = ['ERC20', 'ERC721'] as const; export type Token = (typeof tokenTypes)[number]; const components = defineComponents({ SRC5Component: { - path: "openzeppelin::introspection::src5", + path: 'openzeppelin::introspection::src5', substorage: { - name: "src5", - type: "SRC5Component::Storage", + name: 'src5', + type: 'SRC5Component::Storage', }, event: { - name: "SRC5Event", - type: "SRC5Component::Event", + name: 'SRC5Event', + type: 'SRC5Component::Event', }, impls: [], }, VotesComponent: { - path: "openzeppelin::governance::votes", + path: 'openzeppelin::governance::votes', substorage: { - name: "votes", - type: "VotesComponent::Storage", + name: 'votes', + type: 'VotesComponent::Storage', }, event: { - name: "VotesEvent", - type: "VotesComponent::Event", + name: 'VotesEvent', + type: 'VotesComponent::Event', }, impls: [ { - name: "VotesInternalImpl", + name: 'VotesInternalImpl', embed: false, - value: "VotesComponent::InternalImpl", + value: 'VotesComponent::InternalImpl', }, ], }, NoncesComponent: { - path: "openzeppelin::utils::cryptography::nonces", + path: 'openzeppelin::utils::cryptography::nonces', substorage: { - name: "nonces", - type: "NoncesComponent::Storage", + name: 'nonces', + type: 'NoncesComponent::Storage', }, event: { - name: "NoncesEvent", - type: "NoncesComponent::Event", + name: 'NoncesEvent', + type: 'NoncesComponent::Event', }, impls: [ { - name: "NoncesImpl", - value: "NoncesComponent::NoncesImpl", + name: 'NoncesImpl', + value: 'NoncesComponent::NoncesImpl', }, ], }, @@ -59,42 +59,32 @@ const components = defineComponents({ export function addSRC5Component(c: ContractBuilder, section?: string) { c.addComponent(components.SRC5Component, [], false); - if (!c.interfaceFlags.has("ISRC5")) { + if (!c.interfaceFlags.has('ISRC5')) { c.addImplToComponent(components.SRC5Component, { - name: "SRC5Impl", - value: "SRC5Component::SRC5Impl", + name: 'SRC5Impl', + value: 'SRC5Component::SRC5Impl', section, }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); } } -export function addVotesComponent( - c: ContractBuilder, - name: string, - version: string, - section?: string, -) { +export function addVotesComponent(c: ContractBuilder, name: string, version: string, section?: string) { addSNIP12Metadata(c, name, version, section); c.addComponent(components.NoncesComponent, [], false); c.addComponent(components.VotesComponent, [], false); c.addImplToComponent(components.VotesComponent, { - name: "VotesImpl", + name: 'VotesImpl', value: `VotesComponent::VotesImpl`, }); } -export function addSNIP12Metadata( - c: ContractBuilder, - name: string, - version: string, - section?: string, -) { - c.addUseClause("openzeppelin::utils::cryptography::snip12", "SNIP12Metadata"); +export function addSNIP12Metadata(c: ContractBuilder, name: string, version: string, section?: string) { + c.addUseClause('openzeppelin::utils::cryptography::snip12', 'SNIP12Metadata'); const SNIP12Metadata: BaseImplementedTrait = { - name: "SNIP12MetadataImpl", - of: "SNIP12Metadata", + name: 'SNIP12MetadataImpl', + of: 'SNIP12Metadata', tags: [], priority: 0, section, @@ -102,16 +92,16 @@ export function addSNIP12Metadata( c.addImplementedTrait(SNIP12Metadata); c.addFunction(SNIP12Metadata, { - name: "name", + name: 'name', args: [], - returns: "felt252", + returns: 'felt252', code: [`'${name}'`], }); c.addFunction(SNIP12Metadata, { - name: "version", + name: 'version', args: [], - returns: "felt252", + returns: 'felt252', code: [`'${version}'`], }); } diff --git a/packages/core/cairo/src/common-options.ts b/packages/core/cairo/src/common-options.ts index de54d727c..5cdee43e0 100644 --- a/packages/core/cairo/src/common-options.ts +++ b/packages/core/cairo/src/common-options.ts @@ -1,8 +1,8 @@ -import type { Argument } from "./contract"; -import type { Access } from "./set-access-control"; -import type { Info } from "./set-info"; -import { defaults as infoDefaults } from "./set-info"; -import type { Upgradeable } from "./set-upgradeable"; +import type { Argument } from './contract'; +import type { Access } from './set-access-control'; +import type { Info } from './set-info'; +import { defaults as infoDefaults } from './set-info'; +import type { Upgradeable } from './set-upgradeable'; export const defaults: Required = { upgradeable: true, @@ -23,28 +23,24 @@ export interface CommonContractOptions extends CommonOptions { access?: Access; } -export function withCommonDefaults( - opts: CommonOptions, -): Required { +export function withCommonDefaults(opts: CommonOptions): Required { return { upgradeable: opts.upgradeable ?? defaults.upgradeable, info: opts.info ?? defaults.info, }; } -export function withCommonContractDefaults( - opts: CommonContractOptions, -): Required { +export function withCommonContractDefaults(opts: CommonContractOptions): Required { return { ...withCommonDefaults(opts), access: opts.access ?? contractDefaults.access, }; } -export function getSelfArg(scope: "external" | "view" = "external"): Argument { - if (scope === "view") { - return { name: "self", type: "@ContractState" }; +export function getSelfArg(scope: 'external' | 'view' = 'external'): Argument { + if (scope === 'view') { + return { name: 'self', type: '@ContractState' }; } else { - return { name: "ref self", type: "ContractState" }; + return { name: 'ref self', type: 'ContractState' }; } } diff --git a/packages/core/cairo/src/contract.test.ts b/packages/core/cairo/src/contract.test.ts index c6351dcc2..a7675a25f 100644 --- a/packages/core/cairo/src/contract.test.ts +++ b/packages/core/cairo/src/contract.test.ts @@ -1,112 +1,108 @@ -import test from "ava"; +import test from 'ava'; -import { - ContractBuilder, - BaseFunction, - BaseImplementedTrait, - Component, -} from "./contract"; -import { printContract } from "./print"; +import type { BaseFunction, BaseImplementedTrait, Component } from './contract'; +import { ContractBuilder } from './contract'; +import { printContract } from './print'; const FOO_COMPONENT: Component = { - name: "FooComponent", - path: "some::path", + name: 'FooComponent', + path: 'some::path', substorage: { - name: "foo", - type: "FooComponent::Storage", + name: 'foo', + type: 'FooComponent::Storage', }, event: { - name: "FooEvent", - type: "FooComponent::Event", + name: 'FooEvent', + type: 'FooComponent::Event', }, impls: [ { - name: "FooImpl", - value: "FooComponent::FooImpl", + name: 'FooImpl', + value: 'FooComponent::FooImpl', }, { - name: "FooInternalImpl", + name: 'FooInternalImpl', embed: false, - value: "FooComponent::InternalImpl", + value: 'FooComponent::InternalImpl', }, ], }; -test("contract basics", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract basics', t => { + const Foo = new ContractBuilder('Foo'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("someFunction()"); +test('contract with constructor code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('someFunction()'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code with semicolon", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("someFunction();"); +test('contract with constructor code with semicolon', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('someFunction();'); t.snapshot(printContract(Foo)); }); -test("contract with function code before", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with function code before', t => { + const Foo = new ContractBuilder('Foo'); const trait: BaseImplementedTrait = { - name: "External", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'External', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: "someFunction", + name: 'someFunction', args: [], - code: ["someFunction()"], + code: ['someFunction()'], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, "before()"); + Foo.addFunctionCodeBefore(trait, fn, 'before()'); t.snapshot(printContract(Foo)); }); -test("contract with function code before with semicolons", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with function code before with semicolons', t => { + const Foo = new ContractBuilder('Foo'); const trait: BaseImplementedTrait = { - name: "External", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'External', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; Foo.addImplementedTrait(trait); const fn: BaseFunction = { - name: "someFunction", + name: 'someFunction', args: [], - code: ["someFunction();"], + code: ['someFunction();'], }; Foo.addFunction(trait, fn); - Foo.addFunctionCodeBefore(trait, fn, "before();"); + Foo.addFunctionCodeBefore(trait, fn, 'before();'); t.snapshot(printContract(Foo)); }); -test("contract with initializer params", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with initializer params', t => { + const Foo = new ContractBuilder('Foo'); - Foo.addComponent(FOO_COMPONENT, ["param1"], true); + Foo.addComponent(FOO_COMPONENT, ['param1'], true); t.snapshot(printContract(Foo)); }); -test("contract with standalone import", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with standalone import', t => { + const Foo = new ContractBuilder('Foo'); Foo.addComponent(FOO_COMPONENT); - Foo.addUseClause("some::library", "SomeLibrary"); + Foo.addUseClause('some::library', 'SomeLibrary'); t.snapshot(printContract(Foo)); }); -test("contract with sorted use clauses", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with sorted use clauses', t => { + const Foo = new ContractBuilder('Foo'); Foo.addComponent(FOO_COMPONENT); - Foo.addUseClause("some::library", "SomeLibrary"); - Foo.addUseClause("another::library", "AnotherLibrary"); - Foo.addUseClause("another::library", "Foo", { alias: "Custom2" }); - Foo.addUseClause("another::library", "Foo", { alias: "Custom1" }); + Foo.addUseClause('some::library', 'SomeLibrary'); + Foo.addUseClause('another::library', 'AnotherLibrary'); + Foo.addUseClause('another::library', 'Foo', { alias: 'Custom2' }); + Foo.addUseClause('another::library', 'Foo', { alias: 'Custom1' }); t.snapshot(printContract(Foo)); }); diff --git a/packages/core/cairo/src/contract.ts b/packages/core/cairo/src/contract.ts index b876ee2ee..05ef6778b 100644 --- a/packages/core/cairo/src/contract.ts +++ b/packages/core/cairo/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from "./utils/convert-strings"; +import { toIdentifier } from './utils/convert-strings'; export interface Contract { license: string; @@ -14,12 +14,7 @@ export interface Contract { superVariables: Variable[]; } -export type Value = - | string - | number - | bigint - | { lit: string } - | { note: string; value: Value }; +export type Value = string | number | bigint | { lit: string } | { note: string; value: Value }; export interface UseClause { containerPath: string; @@ -105,7 +100,7 @@ export interface Argument { export class ContractBuilder implements Contract { readonly name: string; readonly account: boolean; - license = "MIT"; + license = 'MIT'; upgradeable = false; readonly constructorArgs: Argument[] = []; @@ -150,14 +145,10 @@ export class ContractBuilder implements Contract { return this.interfaceFlagsSet; } - addUseClause( - containerPath: string, - name: string, - options?: { groupable?: boolean; alias?: string }, - ): void { + addUseClause(containerPath: string, name: string, options?: { groupable?: boolean; alias?: string }): void { // groupable defaults to true const groupable = options?.groupable ?? true; - const alias = options?.alias ?? ""; + const alias = options?.alias ?? ''; const uniqueName = alias.length > 0 ? alias : name; const present = this.useClausesMap.has(uniqueName); if (!present) { @@ -170,11 +161,7 @@ export class ContractBuilder implements Contract { } } - addComponent( - component: Component, - params: Value[] = [], - initializable: boolean = true, - ): boolean { + addComponent(component: Component, params: Value[] = [], initializable: boolean = true): boolean { this.addUseClause(component.path, component.name); const key = component.name; const present = this.componentsMap.has(key); @@ -197,7 +184,7 @@ export class ContractBuilder implements Contract { throw new Error(`Component ${component.name} has not been added yet`); } - if (!c.impls.some((i) => i.name === impl.name)) { + if (!c.impls.some(i => i.name === impl.name)) { c.impls.push(impl); } } @@ -216,7 +203,7 @@ export class ContractBuilder implements Contract { return false; } else { this.superVariablesMap.set(variable.name, variable); - this.addUseClause("super", variable.name); + this.addUseClause('super', variable.name); return true; } } @@ -241,10 +228,7 @@ export class ContractBuilder implements Contract { } } - addSuperVariableToTrait( - baseTrait: BaseImplementedTrait, - newVar: Variable, - ): boolean { + addSuperVariableToTrait(baseTrait: BaseImplementedTrait, newVar: Variable): boolean { const trait = this.addImplementedTrait(baseTrait); for (const existingVar of trait.superVariables) { if (existingVar.name === newVar.name) { @@ -265,10 +249,7 @@ export class ContractBuilder implements Contract { return true; } - addFunction( - baseTrait: BaseImplementedTrait, - fn: BaseFunction, - ): ContractFunction { + addFunction(baseTrait: BaseImplementedTrait, fn: BaseFunction): ContractFunction { const t = this.addImplementedTrait(baseTrait); const signature = this.getFunctionSignature(fn); @@ -276,10 +257,7 @@ export class ContractBuilder implements Contract { // Look for the existing function with the same signature and return it if found for (let i = 0; i < t.functions.length; i++) { const existingFn = t.functions[i]; - if ( - existingFn !== undefined && - this.getFunctionSignature(existingFn) === signature - ) { + if (existingFn !== undefined && this.getFunctionSignature(existingFn) === signature) { return existingFn; } } @@ -295,14 +273,10 @@ export class ContractBuilder implements Contract { } private getFunctionSignature(fn: BaseFunction): string { - return [fn.name, "(", ...fn.args.map((a) => a.name), ")"].join(""); + return [fn.name, '(', ...fn.args.map(a => a.name), ')'].join(''); } - addFunctionCodeBefore( - baseTrait: BaseImplementedTrait, - fn: BaseFunction, - codeBefore: string, - ): void { + addFunctionCodeBefore(baseTrait: BaseImplementedTrait, fn: BaseFunction, codeBefore: string): void { this.addImplementedTrait(baseTrait); const existingFn = this.addFunction(baseTrait, fn); existingFn.codeBefore = [...(existingFn.codeBefore ?? []), codeBefore]; diff --git a/packages/core/cairo/src/custom.test.ts b/packages/core/cairo/src/custom.test.ts index 85b3fb9be..9b3059094 100644 --- a/packages/core/cairo/src/custom.test.ts +++ b/packages/core/cairo/src/custom.test.ts @@ -1,13 +1,14 @@ -import test from "ava"; -import { custom } from "."; +import test from 'ava'; +import { custom } from '.'; -import { buildCustom, CustomOptions } from "./custom"; -import { printContract } from "./print"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import { printContract } from './print'; function testCustom(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }); t.snapshot(printContract(c)); @@ -18,12 +19,12 @@ function testCustom(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, (t) => { + test(title, t => { t.is( custom.print(opts), printContract( buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }), ), @@ -31,54 +32,54 @@ function testAPIEquivalence(title: string, opts?: CustomOptions) { }); } -testCustom("custom non-upgradeable", { +testCustom('custom non-upgradeable', { upgradeable: false, }); -testCustom("custom defaults", {}); +testCustom('custom defaults', {}); -testCustom("pausable", { +testCustom('pausable', { pausable: true, }); -testCustom("upgradeable", { +testCustom('upgradeable', { upgradeable: true, }); -testCustom("access control disabled", { +testCustom('access control disabled', { upgradeable: false, access: false, }); -testCustom("access control ownable", { - access: "ownable", +testCustom('access control ownable', { + access: 'ownable', }); -testCustom("access control roles", { - access: "roles", +testCustom('access control roles', { + access: 'roles', }); -testCustom("pausable with access control disabled", { +testCustom('pausable with access control disabled', { // API should override access to true since it is required for pausable access: false, pausable: true, upgradeable: false, }); -testAPIEquivalence("custom API default"); +testAPIEquivalence('custom API default'); -testAPIEquivalence("custom API full upgradeable", { - name: "CustomContract", - access: "roles", +testAPIEquivalence('custom API full upgradeable', { + name: 'CustomContract', + access: 'roles', pausable: true, upgradeable: true, }); -test("custom API assert defaults", async (t) => { +test('custom API assert defaults', async t => { t.is(custom.print(custom.defaults), custom.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(custom.isAccessControlRequired({ pausable: true }), true); t.is(custom.isAccessControlRequired({ upgradeable: true }), true); }); diff --git a/packages/core/cairo/src/custom.ts b/packages/core/cairo/src/custom.ts index a97ea2c7a..3fe6bc86a 100644 --- a/packages/core/cairo/src/custom.ts +++ b/packages/core/cairo/src/custom.ts @@ -1,17 +1,16 @@ -import { Contract, ContractBuilder } from "./contract"; -import { setAccessControl } from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { - CommonContractOptions, - withCommonContractDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; export const defaults: Required = { - name: "MyContract", + name: 'MyContract', pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, diff --git a/packages/core/cairo/src/erc1155.test.ts b/packages/core/cairo/src/erc1155.test.ts index 55f9b240c..066e86c4b 100644 --- a/packages/core/cairo/src/erc1155.test.ts +++ b/packages/core/cairo/src/erc1155.test.ts @@ -1,14 +1,14 @@ -import test from "ava"; -import { erc1155 } from "."; +import test from 'ava'; +import { erc1155 } from '.'; -import { buildERC1155, ERC1155Options } from "./erc1155"; -import { printContract } from "./print"; -import { royaltyInfoOptions } from "./set-royalty-info"; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; -const NAME = "MyToken"; -const CUSTOM_NAME = "CustomToken"; -const BASE_URI = - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; const allFeaturesON: Partial = { mintable: true, @@ -19,7 +19,7 @@ const allFeaturesON: Partial = { } as const; function testERC1155(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC1155({ name: NAME, baseUri: BASE_URI, @@ -33,13 +33,13 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, (t) => { + test(title, t => { t.is( erc1155.print(opts), printContract( buildERC1155({ name: NAME, - baseUri: "", + baseUri: '', ...opts, }), ), @@ -47,102 +47,93 @@ function testAPIEquivalence(title: string, opts?: ERC1155Options) { }); } -testERC1155("basic non-upgradeable", { +testERC1155('basic non-upgradeable', { upgradeable: false, }); -testERC1155("basic", {}); +testERC1155('basic', {}); -testERC1155("basic + roles", { - access: "roles", +testERC1155('basic + roles', { + access: 'roles', }); -testERC1155("no updatable uri", { +testERC1155('no updatable uri', { updatableUri: false, }); -testERC1155("burnable", { +testERC1155('burnable', { burnable: true, }); -testERC1155("pausable", { +testERC1155('pausable', { pausable: true, }); -testERC1155("mintable", { +testERC1155('mintable', { mintable: true, }); -testERC1155("mintable + roles", { +testERC1155('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC1155("royalty info disabled", { +testERC1155('royalty info disabled', { royaltyInfo: royaltyInfoOptions.disabled, }); -testERC1155("royalty info enabled default + ownable", { +testERC1155('royalty info enabled default + ownable', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "ownable", + access: 'ownable', }); -testERC1155("royalty info enabled default + roles", { +testERC1155('royalty info enabled default + roles', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "roles", + access: 'roles', }); -testERC1155("royalty info enabled custom + ownable", { +testERC1155('royalty info enabled custom + ownable', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "ownable", + access: 'ownable', }); -testERC1155("royalty info enabled custom + roles", { +testERC1155('royalty info enabled custom + roles', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "roles", + access: 'roles', }); -testERC1155("full non-upgradeable", { +testERC1155('full non-upgradeable', { ...allFeaturesON, - access: "roles", + access: 'roles', upgradeable: false, }); -testERC1155("full upgradeable", { +testERC1155('full upgradeable', { ...allFeaturesON, - access: "roles", + access: 'roles', upgradeable: true, }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: CUSTOM_NAME, baseUri: BASE_URI }); +testAPIEquivalence('API basic', { name: CUSTOM_NAME, baseUri: BASE_URI }); -testAPIEquivalence("API full upgradeable", { +testAPIEquivalence('API full upgradeable', { ...allFeaturesON, name: CUSTOM_NAME, baseUri: BASE_URI, - access: "roles", + access: 'roles', upgradeable: true, }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), - true, - ); +test('API isAccessControlRequired', async t => { + t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), true); t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); t.is( erc1155.isAccessControlRequired({ diff --git a/packages/core/cairo/src/erc1155.ts b/packages/core/cairo/src/erc1155.ts index 59baee0c8..78788a7f6 100644 --- a/packages/core/cairo/src/erc1155.ts +++ b/packages/core/cairo/src/erc1155.ts @@ -1,33 +1,25 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { addSRC5Component } from "./common-components"; -import { externalTrait } from "./external-trait"; -import { toByteArray } from "./utils/convert-strings"; -import { - RoyaltyInfoOptions, - setRoyaltyInfo, - defaults as royaltyInfoDefaults, -} from "./set-royalty-info"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; +import { externalTrait } from './external-trait'; +import { toByteArray } from './utils/convert-strings'; +import type { RoyaltyInfoOptions } from './set-royalty-info'; +import { setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; export const defaults: Required = { - name: "MyToken", - baseUri: "", + name: 'MyToken', + baseUri: '', burnable: false, pausable: false, mintable: false, @@ -64,9 +56,7 @@ function withDefaults(opts: ERC1155Options): Required { }; } -export function isAccessControlRequired( - opts: Partial, -): boolean { +export function isAccessControlRequired(opts: Partial): boolean { return ( opts.mintable === true || opts.pausable === true || @@ -114,42 +104,39 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable; if (usesCustomHooks) { const hooksTrait = { - name: "ERC1155HooksImpl", - of: "ERC1155Component::ERC1155HooksTrait", + name: 'ERC1155HooksImpl', + of: 'ERC1155Component::ERC1155HooksTrait', tags: [], priority: 1, }; c.addImplementedTrait(hooksTrait); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addFunction(hooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", + name: 'ref self', type: `ERC1155Component::ComponentState`, }, - { name: "from", type: "ContractAddress" }, - { name: "to", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, - ], - code: [ - "let contract_state = self.get_contract()", - "contract_state.pausable.assert_not_paused()", + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, ], + code: ['let contract_state = self.get_contract()', 'contract_state.pausable.assert_not_paused()'], }); } else { - c.addUseClause("openzeppelin::token::erc1155", "ERC1155HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc1155', 'ERC1155HooksEmptyImpl'); } } function addERC1155Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC1155Component, { - name: "ERC1155MixinImpl", - value: "ERC1155Component::ERC1155MixinImpl", + name: 'ERC1155MixinImpl', + value: 'ERC1155Component::ERC1155MixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } @@ -158,45 +145,24 @@ function addBase(c: ContractBuilder, baseUri: string) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("starknet", "ContractAddress"); - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); c.addFunction(externalTrait, functions.batch_burn); c.addFunction(externalTrait, functions.batchBurn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.mint, - access, - "MINTER", - "minter", - ); - requireAccessControl( - c, - externalTrait, - functions.batch_mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); + requireAccessControl(c, externalTrait, functions.batch_mint, access, 'MINTER', 'minter'); // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. c.addFunction(externalTrait, functions.batchMint); } function addSetBaseUri(c: ContractBuilder, access: Access) { - requireAccessControl( - c, - externalTrait, - functions.set_base_uri, - access, - "URI_SETTER", - "uri_setter", - ); + requireAccessControl(c, externalTrait, functions.set_base_uri, access, 'URI_SETTER', 'uri_setter'); // Camel case version of set_base_uri. Access control is already set on set_base_uri. c.addFunction(externalTrait, functions.setBaseUri); @@ -204,20 +170,20 @@ function addSetBaseUri(c: ContractBuilder, access: Access) { const components = defineComponents({ ERC1155Component: { - path: "openzeppelin::token::erc1155", + path: 'openzeppelin::token::erc1155', substorage: { - name: "erc1155", - type: "ERC1155Component::Storage", + name: 'erc1155', + type: 'ERC1155Component::Storage', }, event: { - name: "ERC1155Event", - type: "ERC1155Component::Event", + name: 'ERC1155Event', + type: 'ERC1155Component::Event', }, impls: [ { - name: "ERC1155InternalImpl", + name: 'ERC1155InternalImpl', embed: false, - value: "ERC1155Component::InternalImpl", + value: 'ERC1155Component::InternalImpl', }, ], }, @@ -227,82 +193,78 @@ const functions = defineFunctions({ burn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "value", type: "u256" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, ], code: [ - "let caller = get_caller_address();", - "if account != caller {", - " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", - "}", - "self.erc1155.burn(account, token_id, value);", + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.burn(account, token_id, value);', ], }, batch_burn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, ], code: [ - "let caller = get_caller_address();", - "if account != caller {", - " assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)", - "}", - "self.erc1155.batch_burn(account, token_ids, values);", + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.batch_burn(account, token_ids, values);', ], }, batchBurn: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "tokenIds", type: "Span" }, - { name: "values", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, ], - code: ["self.batch_burn(account, tokenIds, values);"], + code: ['self.batch_burn(account, tokenIds, values);'], }, mint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "value", type: "u256" }, - { name: "data", type: "Span" }, - ], - code: [ - "self.erc1155.mint_with_acceptance_check(account, token_id, value, data);", + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, + { name: 'data', type: 'Span' }, ], + code: ['self.erc1155.mint_with_acceptance_check(account, token_id, value, data);'], }, batch_mint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "token_ids", type: "Span" }, - { name: "values", type: "Span" }, - { name: "data", type: "Span" }, - ], - code: [ - "self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);", + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, ], + code: ['self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);'], }, batchMint: { args: [ getSelfArg(), - { name: "account", type: "ContractAddress" }, - { name: "tokenIds", type: "Span" }, - { name: "values", type: "Span" }, - { name: "data", type: "Span" }, + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, ], - code: ["self.batch_mint(account, tokenIds, values, data);"], + code: ['self.batch_mint(account, tokenIds, values, data);'], }, set_base_uri: { - args: [getSelfArg(), { name: "base_uri", type: "ByteArray" }], - code: ["self.erc1155._set_base_uri(base_uri);"], + args: [getSelfArg(), { name: 'base_uri', type: 'ByteArray' }], + code: ['self.erc1155._set_base_uri(base_uri);'], }, setBaseUri: { - args: [getSelfArg(), { name: "baseUri", type: "ByteArray" }], - code: ["self.set_base_uri(baseUri);"], + args: [getSelfArg(), { name: 'baseUri', type: 'ByteArray' }], + code: ['self.set_base_uri(baseUri);'], }, }); diff --git a/packages/core/cairo/src/erc20.test.ts b/packages/core/cairo/src/erc20.test.ts index 798659d7b..45fb5bf87 100644 --- a/packages/core/cairo/src/erc20.test.ts +++ b/packages/core/cairo/src/erc20.test.ts @@ -1,15 +1,17 @@ -import test from "ava"; +import test from 'ava'; -import { buildERC20, ERC20Options, getInitialSupply } from "./erc20"; -import { printContract } from "./print"; +import type { ERC20Options } from './erc20'; +import { buildERC20, getInitialSupply } from './erc20'; +import { printContract } from './print'; -import { erc20, OptionsError } from "."; +import type { OptionsError } from '.'; +import { erc20 } from '.'; function testERC20(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -20,13 +22,13 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, (t) => { + test(title, t => { t.is( erc20.print(opts), printContract( buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -34,171 +36,165 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { }); } -testERC20("basic erc20, non-upgradeable", { +testERC20('basic erc20, non-upgradeable', { upgradeable: false, }); -testERC20("basic erc20", {}); +testERC20('basic erc20', {}); -testERC20("erc20 burnable", { +testERC20('erc20 burnable', { burnable: true, }); -testERC20("erc20 pausable", { +testERC20('erc20 pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 pausable with roles", { +testERC20('erc20 pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 burnable pausable", { +testERC20('erc20 burnable pausable', { burnable: true, pausable: true, }); -testERC20("erc20 preminted", { - premint: "1000", +testERC20('erc20 preminted', { + premint: '1000', }); -testERC20("erc20 premint of 0", { - premint: "0", +testERC20('erc20 premint of 0', { + premint: '0', }); -testERC20("erc20 mintable", { +testERC20('erc20 mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 mintable with roles", { +testERC20('erc20 mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 votes", { +testERC20('erc20 votes', { votes: true, - appName: "MY_DAPP_NAME", + appName: 'MY_DAPP_NAME', }); -testERC20("erc20 votes, version", { +testERC20('erc20 votes, version', { votes: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -test("erc20 votes, no name", async (t) => { +test('erc20 votes, no name', async t => { const error = t.throws(() => buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', votes: true, }), ); - t.is( - (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); }); -test("erc20 votes, empty version", async (t) => { +test('erc20 votes, empty version', async t => { const error = t.throws(() => buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', votes: true, - appName: "MY_DAPP_NAME", - appVersion: "", // avoids default value of v1 + appName: 'MY_DAPP_NAME', + appVersion: '', // avoids default value of v1 }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -testERC20("erc20 votes, non-upgradeable", { +testERC20('erc20 votes, non-upgradeable', { votes: true, - appName: "MY_DAPP_NAME", + appName: 'MY_DAPP_NAME', upgradeable: false, }); -testERC20("erc20 full, non-upgradeable", { - premint: "2000", - access: "ownable", +testERC20('erc20 full, non-upgradeable', { + premint: '2000', + access: 'ownable', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: false, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testERC20("erc20 full upgradeable", { - premint: "2000", - access: "ownable", +testERC20('erc20 full upgradeable', { + premint: '2000', + access: 'ownable', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testERC20("erc20 full upgradeable with roles", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable with roles', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -testAPIEquivalence("erc20 API default"); +testAPIEquivalence('erc20 API default'); -testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("erc20 API full upgradeable", { - name: "CustomToken", - symbol: "CTK", - premint: "2000", - access: "roles", +testAPIEquivalence('erc20 API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, votes: true, pausable: true, upgradeable: true, - appName: "MY_DAPP_NAME", - appVersion: "MY_DAPP_VERSION", + appName: 'MY_DAPP_NAME', + appVersion: 'MY_DAPP_VERSION', }); -test("erc20 API assert defaults", async (t) => { +test('erc20 API assert defaults', async t => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test("erc20 API isAccessControlRequired", async (t) => { +test('erc20 API isAccessControlRequired', async t => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); t.is(erc20.isAccessControlRequired({ upgradeable: true }), true); }); -test("erc20 getInitialSupply", async (t) => { - t.is(getInitialSupply("1000", 18), "1000000000000000000000"); - t.is(getInitialSupply("1000.1", 18), "1000100000000000000000"); - t.is(getInitialSupply(".1", 18), "100000000000000000"); - t.is(getInitialSupply(".01", 2), "1"); +test('erc20 getInitialSupply', async t => { + t.is(getInitialSupply('1000', 18), '1000000000000000000000'); + t.is(getInitialSupply('1000.1', 18), '1000100000000000000000'); + t.is(getInitialSupply('.1', 18), '100000000000000000'); + t.is(getInitialSupply('.01', 2), '1'); - let error = t.throws(() => getInitialSupply(".01", 1)); + let error = t.throws(() => getInitialSupply('.01', 1)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, "Too many decimals"); + t.is((error as OptionsError).messages.premint, 'Too many decimals'); - error = t.throws(() => getInitialSupply("1.1.1", 18)); + error = t.throws(() => getInitialSupply('1.1.1', 18)); t.not(error, undefined); - t.is((error as OptionsError).messages.premint, "Not a valid number"); + t.is((error as OptionsError).messages.premint, 'Not a valid number'); }); diff --git a/packages/core/cairo/src/erc20.ts b/packages/core/cairo/src/erc20.ts index 3334cd78b..e780f48ad 100644 --- a/packages/core/cairo/src/erc20.ts +++ b/packages/core/cairo/src/erc20.ts @@ -1,36 +1,31 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { OptionsError } from "./error"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { externalTrait } from "./external-trait"; -import { toByteArray, toFelt252, toUint } from "./utils/convert-strings"; -import { addVotesComponent } from "./common-components"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { OptionsError } from './error'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { externalTrait } from './external-trait'; +import { toByteArray, toFelt252, toUint } from './utils/convert-strings'; +import { addVotesComponent } from './common-components'; export const defaults: Required = { - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', burnable: false, pausable: false, - premint: "0", + premint: '0', mintable: false, votes: false, - appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: "v1", + appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: 'v1', access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info, @@ -67,11 +62,7 @@ function withDefaults(opts: ERC20Options): Required { } export function isAccessControlRequired(opts: Partial): boolean { - return ( - opts.mintable === true || - opts.pausable === true || - opts.upgradeable === true - ); + return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; } export function buildERC20(opts: ERC20Options): Contract { @@ -111,8 +102,8 @@ function addHooks(c: ContractBuilder, allOpts: Required) { const usesCustomHooks = allOpts.pausable || allOpts.votes; if (usesCustomHooks) { const hooksTrait = { - name: "ERC20HooksImpl", - of: "ERC20Component::ERC20HooksTrait", + name: 'ERC20HooksImpl', + of: 'ERC20Component::ERC20HooksTrait', tags: [], priority: 1, }; @@ -120,73 +111,73 @@ function addHooks(c: ContractBuilder, allOpts: Required) { if (allOpts.pausable) { const beforeUpdateFn = c.addFunction(hooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", - type: "ERC20Component::ComponentState", + name: 'ref self', + type: 'ERC20Component::ComponentState', }, - { name: "from", type: "ContractAddress" }, - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, + { name: 'from', type: 'ContractAddress' }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'amount', type: 'u256' }, ], code: [], }); beforeUpdateFn.code.push( - "let contract_state = self.get_contract();", - "contract_state.pausable.assert_not_paused();", + 'let contract_state = self.get_contract();', + 'contract_state.pausable.assert_not_paused();', ); } if (allOpts.votes) { if (!allOpts.appName) { throw new OptionsError({ - appName: "Application Name is required when Votes are enabled", + appName: 'Application Name is required when Votes are enabled', }); } if (!allOpts.appVersion) { throw new OptionsError({ - appVersion: "Application Version is required when Votes are enabled", + appVersion: 'Application Version is required when Votes are enabled', }); } addVotesComponent( c, - toFelt252(allOpts.appName, "appName"), - toFelt252(allOpts.appVersion, "appVersion"), - "SNIP12 Metadata", + toFelt252(allOpts.appName, 'appName'), + toFelt252(allOpts.appVersion, 'appVersion'), + 'SNIP12 Metadata', ); const afterUpdateFn = c.addFunction(hooksTrait, { - name: "after_update", + name: 'after_update', args: [ { - name: "ref self", - type: "ERC20Component::ComponentState", + name: 'ref self', + type: 'ERC20Component::ComponentState', }, - { name: "from", type: "ContractAddress" }, - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, + { name: 'from', type: 'ContractAddress' }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'amount', type: 'u256' }, ], code: [], }); afterUpdateFn.code.push( - "let mut contract_state = self.get_contract_mut();", - "contract_state.votes.transfer_voting_units(from, recipient, amount);", + 'let mut contract_state = self.get_contract_mut();', + 'contract_state.votes.transfer_voting_units(from, recipient, amount);', ); } } else { - c.addUseClause("openzeppelin::token::erc20", "ERC20HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc20', 'ERC20HooksEmptyImpl'); } } function addERC20Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC20Component, { - name: "ERC20MixinImpl", - value: "ERC20Component::ERC20MixinImpl", + name: 'ERC20MixinImpl', + value: 'ERC20Component::ERC20MixinImpl', }); } @@ -195,28 +186,24 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); } export const premintPattern = /^(\d*\.?\d*)$/; function addPremint(c: ContractBuilder, amount: string) { - if (amount !== undefined && amount !== "0") { + if (amount !== undefined && amount !== '0') { if (!premintPattern.test(amount)) { throw new OptionsError({ - premint: "Not a valid number", + premint: 'Not a valid number', }); } - const premintAbsolute = toUint( - getInitialSupply(amount, 18), - "premint", - "u256", - ); + const premintAbsolute = toUint(getInitialSupply(amount, 18), 'premint', 'u256'); - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: "recipient", type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: 'recipient', type: 'ContractAddress' }); c.addConstructorCode(`self.erc20.mint(recipient, ${premintAbsolute})`); } } @@ -231,65 +218,58 @@ function addPremint(c: ContractBuilder, amount: string) { */ export function getInitialSupply(premint: string, decimals: number): string { let result; - const premintSegments = premint.split("."); + const premintSegments = premint.split('.'); if (premintSegments.length > 2) { throw new OptionsError({ - premint: "Not a valid number", + premint: 'Not a valid number', }); } else { - const firstSegment = premintSegments[0] ?? ""; - let lastSegment = premintSegments[1] ?? ""; + const firstSegment = premintSegments[0] ?? ''; + let lastSegment = premintSegments[1] ?? ''; if (decimals > lastSegment.length) { try { - lastSegment += "0".repeat(decimals - lastSegment.length); + lastSegment += '0'.repeat(decimals - lastSegment.length); } catch { // .repeat gives an error if decimals number is too large throw new OptionsError({ - premint: "Decimals number too large", + premint: 'Decimals number too large', }); } } else if (decimals < lastSegment.length) { throw new OptionsError({ - premint: "Too many decimals", + premint: 'Too many decimals', }); } // concat segments without leading zeros - result = firstSegment.concat(lastSegment).replace(/^0+/, ""); + result = firstSegment.concat(lastSegment).replace(/^0+/, ''); } if (result.length === 0) { - result = "0"; + result = '0'; } return result; } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); } const components = defineComponents({ ERC20Component: { - path: "openzeppelin::token::erc20", + path: 'openzeppelin::token::erc20', substorage: { - name: "erc20", - type: "ERC20Component::Storage", + name: 'erc20', + type: 'ERC20Component::Storage', }, event: { - name: "ERC20Event", - type: "ERC20Component::Event", + name: 'ERC20Event', + type: 'ERC20Component::Event', }, impls: [ { - name: "ERC20InternalImpl", + name: 'ERC20InternalImpl', embed: false, - value: "ERC20Component::InternalImpl", + value: 'ERC20Component::InternalImpl', }, ], }, @@ -297,15 +277,11 @@ const components = defineComponents({ const functions = defineFunctions({ burn: { - args: [getSelfArg(), { name: "value", type: "u256" }], - code: ["self.erc20.burn(get_caller_address(), value);"], + args: [getSelfArg(), { name: 'value', type: 'u256' }], + code: ['self.erc20.burn(get_caller_address(), value);'], }, mint: { - args: [ - getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "amount", type: "u256" }, - ], - code: ["self.erc20.mint(recipient, amount);"], + args: [getSelfArg(), { name: 'recipient', type: 'ContractAddress' }, { name: 'amount', type: 'u256' }], + code: ['self.erc20.mint(recipient, amount);'], }, }); diff --git a/packages/core/cairo/src/erc721.test.ts b/packages/core/cairo/src/erc721.test.ts index 7f260e482..b5f4583e9 100644 --- a/packages/core/cairo/src/erc721.test.ts +++ b/packages/core/cairo/src/erc721.test.ts @@ -1,19 +1,20 @@ -import test from "ava"; +import test from 'ava'; -import { buildERC721, ERC721Options } from "./erc721"; -import { printContract } from "./print"; -import { royaltyInfoOptions } from "./set-royalty-info"; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; -import { erc721, OptionsError } from "."; +import type { OptionsError } from '.'; +import { erc721 } from '.'; -const NAME = "MyToken"; -const CUSTOM_NAME = "CustomToken"; -const SYMBOL = "MTK"; -const CUSTOM_SYMBOL = "CTK"; -const APP_NAME = "MY_DAPP_NAME"; -const APP_VERSION = "MY_DAPP_VERSION"; -const BASE_URI = - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/"; +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const SYMBOL = 'MTK'; +const CUSTOM_SYMBOL = 'CTK'; +const APP_NAME = 'MY_DAPP_NAME'; +const APP_VERSION = 'MY_DAPP_VERSION'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; const allFeaturesON: Partial = { mintable: true, @@ -28,7 +29,7 @@ const allFeaturesON: Partial = { } as const; function testERC721(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC721({ name: NAME, symbol: SYMBOL, @@ -42,7 +43,7 @@ function testERC721(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, (t) => { + test(title, t => { t.is( erc721.print(opts), printContract( @@ -56,83 +57,83 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { }); } -testERC721("basic non-upgradeable", { +testERC721('basic non-upgradeable', { upgradeable: false, }); -testERC721("basic", {}); +testERC721('basic', {}); -testERC721("base uri", { +testERC721('base uri', { baseUri: BASE_URI, }); -testERC721("burnable", { +testERC721('burnable', { burnable: true, }); -testERC721("pausable", { +testERC721('pausable', { pausable: true, }); -testERC721("mintable", { +testERC721('mintable', { mintable: true, }); -testERC721("enumerable", { +testERC721('enumerable', { enumerable: true, }); -testERC721("pausable + enumerable", { +testERC721('pausable + enumerable', { pausable: true, enumerable: true, }); -testERC721("mintable + roles", { +testERC721('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC721("royalty info disabled", { +testERC721('royalty info disabled', { royaltyInfo: royaltyInfoOptions.disabled, }); -testERC721("royalty info enabled default + ownable", { +testERC721('royalty info enabled default + ownable', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "ownable", + access: 'ownable', }); -testERC721("royalty info enabled default + roles", { +testERC721('royalty info enabled default + roles', { royaltyInfo: royaltyInfoOptions.enabledDefault, - access: "roles", + access: 'roles', }); -testERC721("royalty info enabled custom + ownable", { +testERC721('royalty info enabled custom + ownable', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "ownable", + access: 'ownable', }); -testERC721("royalty info enabled custom + roles", { +testERC721('royalty info enabled custom + roles', { royaltyInfo: royaltyInfoOptions.enabledCustom, - access: "roles", + access: 'roles', }); -testERC721("full non-upgradeable", { +testERC721('full non-upgradeable', { ...allFeaturesON, upgradeable: false, }); -testERC721("erc721 votes", { +testERC721('erc721 votes', { votes: true, appName: APP_NAME, }); -testERC721("erc721 votes, version", { +testERC721('erc721 votes, version', { votes: true, appName: APP_NAME, appVersion: APP_VERSION, }); -test("erc721 votes, no name", async (t) => { +test('erc721 votes, no name', async t => { const error = t.throws(() => buildERC721({ name: NAME, @@ -140,67 +141,58 @@ test("erc721 votes, no name", async (t) => { votes: true, }), ); - t.is( - (error as OptionsError).messages.appName, - "Application Name is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); }); -test("erc721 votes, no version", async (t) => { +test('erc721 votes, no version', async t => { const error = t.throws(() => buildERC721({ name: NAME, symbol: SYMBOL, votes: true, appName: APP_NAME, - appVersion: "", + appVersion: '', }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -test("erc721 votes, empty version", async (t) => { +test('erc721 votes, empty version', async t => { const error = t.throws(() => buildERC721({ name: NAME, symbol: SYMBOL, votes: true, appName: APP_NAME, - appVersion: "", // avoids default value of v1 + appVersion: '', // avoids default value of v1 }), ); - t.is( - (error as OptionsError).messages.appVersion, - "Application Version is required when Votes are enabled", - ); + t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); }); -testERC721("erc721 votes, non-upgradeable", { +testERC721('erc721 votes, non-upgradeable', { votes: true, appName: APP_NAME, upgradeable: false, }); -testERC721("full upgradeable", allFeaturesON); +testERC721('full upgradeable', allFeaturesON); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); +testAPIEquivalence('API basic', { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); -testAPIEquivalence("API full upgradeable", { +testAPIEquivalence('API full upgradeable', { ...allFeaturesON, name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL, }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); t.is(erc721.isAccessControlRequired({ upgradeable: true }), true); diff --git a/packages/core/cairo/src/erc721.ts b/packages/core/cairo/src/erc721.ts index 12f7ecd34..6ddaf2efd 100644 --- a/packages/core/cairo/src/erc721.ts +++ b/packages/core/cairo/src/erc721.ts @@ -1,43 +1,35 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; -import { - Access, - requireAccessControl, - setAccessControl, -} from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonContractOptions, - withCommonContractDefaults, - getSelfArg, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { printContract } from "./print"; -import { addSRC5Component, addVotesComponent } from "./common-components"; -import { externalTrait } from "./external-trait"; -import { toByteArray, toFelt252 } from "./utils/convert-strings"; -import { OptionsError } from "./error"; -import { - RoyaltyInfoOptions, - setRoyaltyInfo, - defaults as royaltyInfoDefaults, -} from "./set-royalty-info"; +import type { BaseImplementedTrait, Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonContractOptions } from './common-options'; +import { withCommonContractDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { contractDefaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { addSRC5Component, addVotesComponent } from './common-components'; +import { externalTrait } from './external-trait'; +import { toByteArray, toFelt252 } from './utils/convert-strings'; +import { OptionsError } from './error'; +import type { RoyaltyInfoOptions } from './set-royalty-info'; +import { setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; export const defaults: Required = { - name: "MyToken", - symbol: "MTK", - baseUri: "", + name: 'MyToken', + symbol: 'MTK', + baseUri: '', burnable: false, pausable: false, mintable: false, enumerable: false, votes: false, royaltyInfo: royaltyInfoDefaults, - appName: "", // Defaults to empty string, but user must provide a non-empty value if votes are enabled - appVersion: "v1", + appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled + appVersion: 'v1', access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info, @@ -79,10 +71,7 @@ function withDefaults(opts: ERC721Options): Required { export function isAccessControlRequired(opts: Partial): boolean { return ( - opts.mintable === true || - opts.pausable === true || - opts.upgradeable === true || - opts.royaltyInfo?.enabled === true + opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.royaltyInfo?.enabled === true ); } @@ -91,12 +80,7 @@ export function buildERC721(opts: ERC721Options): Contract { const allOpts = withDefaults(opts); - addBase( - c, - toByteArray(allOpts.name), - toByteArray(allOpts.symbol), - toByteArray(allOpts.baseUri), - ); + addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.baseUri)); addERC721Mixin(c); if (allOpts.pausable) { @@ -130,83 +114,74 @@ function addHooks(c: ContractBuilder, opts: Required) { if (usesCustomHooks) { const ERC721HooksTrait: BaseImplementedTrait = { name: `ERC721HooksImpl`, - of: "ERC721Component::ERC721HooksTrait", + of: 'ERC721Component::ERC721HooksTrait', tags: [], priority: 0, }; c.addImplementedTrait(ERC721HooksTrait); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); const requiresMutState = opts.enumerable || opts.votes; const initStateLine = requiresMutState - ? "let mut contract_state = self.get_contract_mut()" - : "let contract_state = self.get_contract()"; + ? 'let mut contract_state = self.get_contract_mut()' + : 'let contract_state = self.get_contract()'; const beforeUpdateCode = [initStateLine]; if (opts.pausable) { - beforeUpdateCode.push("contract_state.pausable.assert_not_paused()"); + beforeUpdateCode.push('contract_state.pausable.assert_not_paused()'); } if (opts.enumerable) { - beforeUpdateCode.push( - "contract_state.erc721_enumerable.before_update(to, token_id)", - ); + beforeUpdateCode.push('contract_state.erc721_enumerable.before_update(to, token_id)'); } if (opts.votes) { if (!opts.appName) { throw new OptionsError({ - appName: "Application Name is required when Votes are enabled", + appName: 'Application Name is required when Votes are enabled', }); } if (!opts.appVersion) { throw new OptionsError({ - appVersion: "Application Version is required when Votes are enabled", + appVersion: 'Application Version is required when Votes are enabled', }); } addVotesComponent( c, - toFelt252(opts.appName, "appName"), - toFelt252(opts.appVersion, "appVersion"), - "SNIP12 Metadata", - ); - beforeUpdateCode.push("let previous_owner = self._owner_of(token_id);"); - beforeUpdateCode.push( - "contract_state.votes.transfer_voting_units(previous_owner, to, 1);", + toFelt252(opts.appName, 'appName'), + toFelt252(opts.appVersion, 'appVersion'), + 'SNIP12 Metadata', ); + beforeUpdateCode.push('let previous_owner = self._owner_of(token_id);'); + beforeUpdateCode.push('contract_state.votes.transfer_voting_units(previous_owner, to, 1);'); } c.addFunction(ERC721HooksTrait, { - name: "before_update", + name: 'before_update', args: [ { - name: "ref self", + name: 'ref self', type: `ERC721Component::ComponentState`, }, - { name: "to", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "auth", type: "ContractAddress" }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'auth', type: 'ContractAddress' }, ], code: beforeUpdateCode, }); } else { - c.addUseClause("openzeppelin::token::erc721", "ERC721HooksEmptyImpl"); + c.addUseClause('openzeppelin::token::erc721', 'ERC721HooksEmptyImpl'); } } function addERC721Mixin(c: ContractBuilder) { c.addImplToComponent(components.ERC721Component, { - name: "ERC721MixinImpl", - value: "ERC721Component::ERC721MixinImpl", + name: 'ERC721MixinImpl', + value: 'ERC721Component::ERC721MixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); addSRC5Component(c); } -function addBase( - c: ContractBuilder, - name: string, - symbol: string, - baseUri: string, -) { +function addBase(c: ContractBuilder, name: string, symbol: string, baseUri: string) { c.addComponent(components.ERC721Component, [name, symbol, baseUri], true); } @@ -215,22 +190,15 @@ function addEnumerable(c: ContractBuilder) { } function addBurnable(c: ContractBuilder) { - c.addUseClause("core::num::traits", "Zero"); - c.addUseClause("starknet", "get_caller_address"); + c.addUseClause('core::num::traits', 'Zero'); + c.addUseClause('starknet', 'get_caller_address'); c.addFunction(externalTrait, functions.burn); } function addMintable(c: ContractBuilder, access: Access) { - c.addUseClause("starknet", "ContractAddress"); - requireAccessControl( - c, - externalTrait, - functions.safe_mint, - access, - "MINTER", - "minter", - ); + c.addUseClause('starknet', 'ContractAddress'); + requireAccessControl(c, externalTrait, functions.safe_mint, access, 'MINTER', 'minter'); // Camel case version of safe_mint. Access control and pausable are already set on safe_mint. c.addFunction(externalTrait, functions.safeMint); @@ -238,42 +206,42 @@ function addMintable(c: ContractBuilder, access: Access) { const components = defineComponents({ ERC721Component: { - path: "openzeppelin::token::erc721", + path: 'openzeppelin::token::erc721', substorage: { - name: "erc721", - type: "ERC721Component::Storage", + name: 'erc721', + type: 'ERC721Component::Storage', }, event: { - name: "ERC721Event", - type: "ERC721Component::Event", + name: 'ERC721Event', + type: 'ERC721Component::Event', }, impls: [ { - name: "ERC721InternalImpl", + name: 'ERC721InternalImpl', embed: false, - value: "ERC721Component::InternalImpl", + value: 'ERC721Component::InternalImpl', }, ], }, ERC721EnumerableComponent: { - path: "openzeppelin::token::erc721::extensions", + path: 'openzeppelin::token::erc721::extensions', substorage: { - name: "erc721_enumerable", - type: "ERC721EnumerableComponent::Storage", + name: 'erc721_enumerable', + type: 'ERC721EnumerableComponent::Storage', }, event: { - name: "ERC721EnumerableEvent", - type: "ERC721EnumerableComponent::Event", + name: 'ERC721EnumerableEvent', + type: 'ERC721EnumerableComponent::Event', }, impls: [ { - name: "ERC721EnumerableImpl", - value: "ERC721EnumerableComponent::ERC721EnumerableImpl", + name: 'ERC721EnumerableImpl', + value: 'ERC721EnumerableComponent::ERC721EnumerableImpl', }, { - name: "ERC721EnumerableInternalImpl", + name: 'ERC721EnumerableInternalImpl', embed: false, - value: "ERC721EnumerableComponent::InternalImpl", + value: 'ERC721EnumerableComponent::InternalImpl', }, ], }, @@ -281,25 +249,25 @@ const components = defineComponents({ const functions = defineFunctions({ burn: { - args: [getSelfArg(), { name: "token_id", type: "u256" }], - code: ["self.erc721.update(Zero::zero(), token_id, get_caller_address());"], + args: [getSelfArg(), { name: 'token_id', type: 'u256' }], + code: ['self.erc721.update(Zero::zero(), token_id, get_caller_address());'], }, safe_mint: { args: [ getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "token_id", type: "u256" }, - { name: "data", type: "Span" }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'data', type: 'Span' }, ], - code: ["self.erc721.safe_mint(recipient, token_id, data);"], + code: ['self.erc721.safe_mint(recipient, token_id, data);'], }, safeMint: { args: [ getSelfArg(), - { name: "recipient", type: "ContractAddress" }, - { name: "tokenId", type: "u256" }, - { name: "data", type: "Span" }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'tokenId', type: 'u256' }, + { name: 'data', type: 'Span' }, ], - code: ["self.safe_mint(recipient, tokenId, data);"], + code: ['self.safe_mint(recipient, tokenId, data);'], }, }); diff --git a/packages/core/cairo/src/error.ts b/packages/core/cairo/src/error.ts index 8b3d2b8e3..a26433319 100644 --- a/packages/core/cairo/src/error.ts +++ b/packages/core/cairo/src/error.ts @@ -2,6 +2,6 @@ export type OptionsErrorMessages = { [prop in string]?: string }; export class OptionsError extends Error { constructor(readonly messages: OptionsErrorMessages) { - super("Invalid options"); + super('Invalid options'); } } diff --git a/packages/core/cairo/src/external-trait.ts b/packages/core/cairo/src/external-trait.ts index 04e671d6d..152ab3ed2 100644 --- a/packages/core/cairo/src/external-trait.ts +++ b/packages/core/cairo/src/external-trait.ts @@ -1,8 +1,8 @@ -import type { BaseImplementedTrait } from "./contract"; +import type { BaseImplementedTrait } from './contract'; export const externalTrait: BaseImplementedTrait = { - name: "ExternalImpl", - of: "ExternalTrait", - tags: ["generate_trait", "abi(per_item)"], - perItemTag: "external(v0)", + name: 'ExternalImpl', + of: 'ExternalTrait', + tags: ['generate_trait', 'abi(per_item)'], + perItemTag: 'external(v0)', }; diff --git a/packages/core/cairo/src/generate/account.ts b/packages/core/cairo/src/generate/account.ts index b61bf2056..175ba8df9 100644 --- a/packages/core/cairo/src/generate/account.ts +++ b/packages/core/cairo/src/generate/account.ts @@ -1,12 +1,12 @@ -import { accountTypes, type AccountOptions } from "../account"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import { accountTypes, type AccountOptions } from '../account'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyAccount"], + name: ['MyAccount'], type: accountTypes, declare: booleans, deploy: booleans, diff --git a/packages/core/cairo/src/generate/alternatives.ts b/packages/core/cairo/src/generate/alternatives.ts index c4b282064..b66e733cf 100644 --- a/packages/core/cairo/src/generate/alternatives.ts +++ b/packages/core/cairo/src/generate/alternatives.ts @@ -4,9 +4,7 @@ type Alternatives = { [k in keyof B]: B[k][number]; }; -export function* generateAlternatives( - blueprint: B, -): Generator> { +export function* generateAlternatives(blueprint: B): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, values, @@ -15,9 +13,7 @@ export function* generateAlternatives( })); for (; !done(); advance()) { - yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]), - ) as Alternatives; + yield Object.fromEntries(entries.map(e => [e.key, e.values[e.current % e.limit]])) as Alternatives; } function done() { diff --git a/packages/core/cairo/src/generate/custom.ts b/packages/core/cairo/src/generate/custom.ts index 884032068..40207666a 100644 --- a/packages/core/cairo/src/generate/custom.ts +++ b/packages/core/cairo/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from "../custom"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { CustomOptions } from '../custom'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyContract"], + name: ['MyContract'], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/cairo/src/generate/erc1155.ts b/packages/core/cairo/src/generate/erc1155.ts index 9fc09c9ca..07170fcbd 100644 --- a/packages/core/cairo/src/generate/erc1155.ts +++ b/packages/core/cairo/src/generate/erc1155.ts @@ -1,25 +1,21 @@ -import type { ERC1155Options } from "../erc1155"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { royaltyInfoOptions } from "../set-royalty-info"; -import { generateAlternatives } from "./alternatives"; +import type { ERC1155Options } from '../erc1155'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + baseUri: ['https://example.com/'], burnable: booleans, pausable: booleans, mintable: booleans, updatableUri: booleans, upgradeable: upgradeableOptions, - royaltyInfo: [ - royaltyInfoOptions.disabled, - royaltyInfoOptions.enabledDefault, - royaltyInfoOptions.enabledCustom, - ], + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], access: accessOptions, info: infoOptions, }; diff --git a/packages/core/cairo/src/generate/erc20.ts b/packages/core/cairo/src/generate/erc20.ts index 2f8235af1..51659af86 100644 --- a/packages/core/cairo/src/generate/erc20.ts +++ b/packages/core/cairo/src/generate/erc20.ts @@ -1,21 +1,21 @@ -import type { ERC20Options } from "../erc20"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC20Options } from '../erc20'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], + name: ['MyToken'], + symbol: ['MTK'], burnable: booleans, pausable: booleans, mintable: booleans, - premint: ["1"], + premint: ['1'], votes: booleans, - appName: ["MyApp"], - appVersion: ["v1"], + appName: ['MyApp'], + appVersion: ['v1'], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/erc721.ts b/packages/core/cairo/src/generate/erc721.ts index f3003120a..e78a94f15 100644 --- a/packages/core/cairo/src/generate/erc721.ts +++ b/packages/core/cairo/src/generate/erc721.ts @@ -1,28 +1,24 @@ -import type { ERC721Options } from "../erc721"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { royaltyInfoOptions } from "../set-royalty-info"; -import { generateAlternatives } from "./alternatives"; +import type { ERC721Options } from '../erc721'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + symbol: ['MTK'], + baseUri: ['https://example.com/'], burnable: booleans, enumerable: booleans, votes: booleans, - appName: ["MyApp"], - appVersion: ["v1"], + appName: ['MyApp'], + appVersion: ['v1'], pausable: booleans, mintable: booleans, - royaltyInfo: [ - royaltyInfoOptions.disabled, - royaltyInfoOptions.enabledDefault, - royaltyInfoOptions.enabledCustom, - ], + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/cairo/src/generate/governor.ts b/packages/core/cairo/src/generate/governor.ts index 6d55cb762..9cb8d6f26 100644 --- a/packages/core/cairo/src/generate/governor.ts +++ b/packages/core/cairo/src/generate/governor.ts @@ -1,37 +1,30 @@ -import { - clockModeOptions, - GovernorOptions, - quorumModeOptions, - timelockOptions, - votesOptions, -} from "../governor"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { GovernorOptions } from '../governor'; +import { clockModeOptions, quorumModeOptions, timelockOptions, votesOptions } from '../governor'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyGovernor"], - delay: ["1 day"], - period: ["1 week"], - proposalThreshold: ["1"], + name: ['MyGovernor'], + delay: ['1 day'], + period: ['1 week'], + proposalThreshold: ['1'], decimals: [18], quorumMode: quorumModeOptions, quorumPercent: [10], - quorumAbsolute: ["10"], + quorumAbsolute: ['10'], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, settings: booleans, - appName: ["Openzeppelin Governor"], - appVersion: ["v1"], + appName: ['Openzeppelin Governor'], + appVersion: ['v1'], upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateGovernorOptions(): Generator< - Required -> { +export function* generateGovernorOptions(): Generator> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/cairo/src/generate/sources.ts b/packages/core/cairo/src/generate/sources.ts index ab6ebd08d..44d777f26 100644 --- a/packages/core/cairo/src/generate/sources.ts +++ b/packages/core/cairo/src/generate/sources.ts @@ -1,64 +1,65 @@ -import { promises as fs } from "fs"; -import path from "path"; -import crypto from "crypto"; - -import { generateERC20Options } from "./erc20"; -import { generateERC721Options } from "./erc721"; -import { generateERC1155Options } from "./erc1155"; -import { generateAccountOptions } from "./account"; -import { generateCustomOptions } from "./custom"; -import { generateGovernorOptions } from "./governor"; -import { generateVestingOptions } from "./vesting"; -import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; -import { printContract } from "../print"; -import { OptionsError } from "../error"; -import { findCover } from "../utils/find-cover"; -import type { Contract } from "../contract"; - -type Subset = "all" | "minimal-cover"; +import { promises as fs } from 'fs'; +import path from 'path'; +import crypto from 'crypto'; + +import { generateERC20Options } from './erc20'; +import { generateERC721Options } from './erc721'; +import { generateERC1155Options } from './erc1155'; +import { generateAccountOptions } from './account'; +import { generateCustomOptions } from './custom'; +import { generateGovernorOptions } from './governor'; +import { generateVestingOptions } from './vesting'; +import type { GenericOptions, KindedOptions } from '../build-generic'; +import { buildGeneric } from '../build-generic'; +import { printContract } from '../print'; +import { OptionsError } from '../error'; +import { findCover } from '../utils/find-cover'; +import type { Contract } from '../contract'; + +type Subset = 'all' | 'minimal-cover'; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === "ERC20") { + if (!kind || kind === 'ERC20') { for (const kindOpts of generateERC20Options()) { - yield { kind: "ERC20", ...kindOpts }; + yield { kind: 'ERC20', ...kindOpts }; } } - if (!kind || kind === "ERC721") { + if (!kind || kind === 'ERC721') { for (const kindOpts of generateERC721Options()) { - yield { kind: "ERC721", ...kindOpts }; + yield { kind: 'ERC721', ...kindOpts }; } } - if (!kind || kind === "ERC1155") { + if (!kind || kind === 'ERC1155') { for (const kindOpts of generateERC1155Options()) { - yield { kind: "ERC1155", ...kindOpts }; + yield { kind: 'ERC1155', ...kindOpts }; } } - if (!kind || kind === "Account") { + if (!kind || kind === 'Account') { for (const kindOpts of generateAccountOptions()) { - yield { kind: "Account", ...kindOpts }; + yield { kind: 'Account', ...kindOpts }; } } - if (!kind || kind === "Custom") { + if (!kind || kind === 'Custom') { for (const kindOpts of generateCustomOptions()) { - yield { kind: "Custom", ...kindOpts }; + yield { kind: 'Custom', ...kindOpts }; } } - if (!kind || kind === "Governor") { + if (!kind || kind === 'Governor') { for (const kindOpts of generateGovernorOptions()) { - yield { kind: "Governor", ...kindOpts }; + yield { kind: 'Governor', ...kindOpts }; } } - if (!kind || kind === "Vesting") { + if (!kind || kind === 'Vesting') { for (const kindOpts of generateVestingOptions()) { - yield { kind: "Vesting", ...kindOpts }; + yield { kind: 'Vesting', ...kindOpts }; } } } @@ -73,18 +74,11 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset( - subset: Subset, - kind?: Kind, -): GeneratedContract[] { +function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { - const id = crypto - .createHash("sha1") - .update(JSON.stringify(options)) - .digest() - .toString("hex"); + const id = crypto.createHash('sha1').update(JSON.stringify(options)).digest().toString('hex'); try { const contract = buildGeneric(options); @@ -98,48 +92,37 @@ function generateContractSubset( } } - if (subset === "all") { + if (subset === 'all') { return contracts; } else { - const getParents = (c: GeneratedContract) => - c.contract.components.map((p) => p.path); + const getParents = (c: GeneratedContract) => c.contract.components.map(p => p.path); function filterByUpgradeableSetTo(isUpgradeable: boolean) { return (c: GeneratedContract) => { switch (c.options.kind) { - case "Vesting": + case 'Vesting': return isUpgradeable === false; - case "Account": - case "ERC20": - case "ERC721": - case "ERC1155": - case "Governor": - case "Custom": + case 'Account': + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Governor': + case 'Custom': return c.options.upgradeable === isUpgradeable; default: { const _: never = c.options; - throw new Error("Unknown kind"); + throw new Error('Unknown kind'); } } }; } return [ - ...findCover( - contracts.filter(filterByUpgradeableSetTo(true)), - getParents, - ), - ...findCover( - contracts.filter(filterByUpgradeableSetTo(false)), - getParents, - ), + ...findCover(contracts.filter(filterByUpgradeableSetTo(true)), getParents), + ...findCover(contracts.filter(filterByUpgradeableSetTo(false)), getParents), ]; } } -export function* generateSources( - subset: Subset, - uniqueName?: boolean, - kind?: Kind, -): Generator { +export function* generateSources(subset: Subset, uniqueName?: boolean, kind?: Kind): Generator { let counter = 1; for (const c of generateContractSubset(subset, kind)) { if (uniqueName) { @@ -159,13 +142,9 @@ export async function writeGeneratedSources( await fs.mkdir(dir, { recursive: true }); const contractNames = []; - for (const { id, contract, source } of generateSources( - subset, - uniqueName, - kind, - )) { + for (const { id, contract, source } of generateSources(subset, uniqueName, kind)) { const name = uniqueName ? contract.name : id; - await fs.writeFile(path.format({ dir, name, ext: ".cairo" }), source); + await fs.writeFile(path.format({ dir, name, ext: '.cairo' }), source); contractNames.push(name); } diff --git a/packages/core/cairo/src/generate/vesting.ts b/packages/core/cairo/src/generate/vesting.ts index 49a34f72e..9be2e455d 100644 --- a/packages/core/cairo/src/generate/vesting.ts +++ b/packages/core/cairo/src/generate/vesting.ts @@ -1,13 +1,13 @@ -import { infoOptions } from "../set-info"; -import type { VestingOptions } from "../vesting"; -import { generateAlternatives } from "./alternatives"; +import { infoOptions } from '../set-info'; +import type { VestingOptions } from '../vesting'; +import { generateAlternatives } from './alternatives'; const blueprint = { - name: ["MyVesting"], - startDate: ["2024-12-31T23:59"], - duration: ["90 days", "1 year"], - cliffDuration: ["0 seconds", "30 day"], - schedule: ["linear", "custom"] as const, + name: ['MyVesting'], + startDate: ['2024-12-31T23:59'], + duration: ['90 days', '1 year'], + cliffDuration: ['0 seconds', '30 day'], + schedule: ['linear', 'custom'] as const, info: infoOptions, }; diff --git a/packages/core/cairo/src/governor.test.ts b/packages/core/cairo/src/governor.test.ts index 561e4dc08..0f102c18f 100644 --- a/packages/core/cairo/src/governor.test.ts +++ b/packages/core/cairo/src/governor.test.ts @@ -1,17 +1,18 @@ -import test from "ava"; -import { governor } from "."; +import test from 'ava'; +import { governor } from '.'; -import { buildGovernor, GovernorOptions } from "./governor"; -import { printContract } from "./print"; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import { printContract } from './print'; -const NAME = "MyGovernor"; +const NAME = 'MyGovernor'; function testGovernor(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildGovernor({ name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', ...opts, }); t.snapshot(printContract(c)); @@ -22,14 +23,14 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, (t) => { + test(title, t => { t.is( governor.print(opts), printContract( buildGovernor({ name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', ...opts, }), ), @@ -37,150 +38,150 @@ function testAPIEquivalence(title: string, opts?: GovernorOptions) { }); } -testGovernor("basic + upgradeable", { +testGovernor('basic + upgradeable', { upgradeable: true, }); -testGovernor("basic non-upgradeable", { +testGovernor('basic non-upgradeable', { upgradeable: false, }); -testGovernor("erc20 votes + timelock", { - votes: "erc20votes", - timelock: "openzeppelin", +testGovernor('erc20 votes + timelock', { + votes: 'erc20votes', + timelock: 'openzeppelin', }); -testGovernor("erc721 votes + timelock", { - votes: "erc721votes", - timelock: "openzeppelin", +testGovernor('erc721 votes + timelock', { + votes: 'erc721votes', + timelock: 'openzeppelin', }); -testGovernor("custom name", { - name: "CustomGovernor", +testGovernor('custom name', { + name: 'CustomGovernor', }); -testGovernor("custom settings", { - delay: "2 hours", - period: "1 year", - proposalThreshold: "300", +testGovernor('custom settings', { + delay: '2 hours', + period: '1 year', + proposalThreshold: '300', settings: true, }); -testGovernor("quorum mode absolute", { - quorumMode: "absolute", - quorumAbsolute: "200", +testGovernor('quorum mode absolute', { + quorumMode: 'absolute', + quorumAbsolute: '200', }); -testGovernor("quorum mode percent", { - quorumMode: "percent", +testGovernor('quorum mode percent', { + quorumMode: 'percent', quorumPercent: 40, }); -testGovernor("custom snip12 metadata", { - appName: "Governor", - appVersion: "v3", +testGovernor('custom snip12 metadata', { + appName: 'Governor', + appVersion: 'v3', }); -testGovernor("all options", { +testGovernor('all options', { name: NAME, - delay: "4 day", - period: "4 week", - proposalThreshold: "500", + delay: '4 day', + period: '4 week', + proposalThreshold: '500', decimals: 10, - quorumMode: "absolute", + quorumMode: 'absolute', quorumPercent: 50, - quorumAbsolute: "200", - votes: "erc721votes", - clockMode: "timestamp", - timelock: "openzeppelin", + quorumAbsolute: '200', + votes: 'erc721votes', + clockMode: 'timestamp', + timelock: 'openzeppelin', settings: true, - appName: "MyApp2", - appVersion: "v5", + appName: 'MyApp2', + appVersion: 'v5', upgradeable: true, }); -testAPIEquivalence("API basic + upgradeable", { +testAPIEquivalence('API basic + upgradeable', { name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', upgradeable: true, }); -testAPIEquivalence("API basic non-upgradeable", { +testAPIEquivalence('API basic non-upgradeable', { name: NAME, - delay: "1 day", - period: "1 week", + delay: '1 day', + period: '1 week', upgradeable: false, }); -testAPIEquivalence("API erc20 votes + timelock", { +testAPIEquivalence('API erc20 votes + timelock', { name: NAME, - delay: "1 day", - period: "1 week", - votes: "erc20votes", - timelock: "openzeppelin", + delay: '1 day', + period: '1 week', + votes: 'erc20votes', + timelock: 'openzeppelin', }); -testAPIEquivalence("API erc721 votes + timelock", { +testAPIEquivalence('API erc721 votes + timelock', { name: NAME, - delay: "1 day", - period: "1 week", - votes: "erc721votes", - timelock: "openzeppelin", + delay: '1 day', + period: '1 week', + votes: 'erc721votes', + timelock: 'openzeppelin', }); -testAPIEquivalence("API custom name", { - name: "CustomGovernor", - delay: "1 day", - period: "1 week", +testAPIEquivalence('API custom name', { + name: 'CustomGovernor', + delay: '1 day', + period: '1 week', }); -testAPIEquivalence("API custom settings", { +testAPIEquivalence('API custom settings', { name: NAME, - delay: "2 hours", - period: "1 year", - proposalThreshold: "300", + delay: '2 hours', + period: '1 year', + proposalThreshold: '300', settings: true, }); -testAPIEquivalence("API quorum mode absolute", { +testAPIEquivalence('API quorum mode absolute', { name: NAME, - delay: "1 day", - period: "1 week", - quorumMode: "absolute", - quorumAbsolute: "200", + delay: '1 day', + period: '1 week', + quorumMode: 'absolute', + quorumAbsolute: '200', }); -testAPIEquivalence("API quorum mode percent", { +testAPIEquivalence('API quorum mode percent', { name: NAME, - delay: "1 day", - period: "1 week", - quorumMode: "percent", + delay: '1 day', + period: '1 week', + quorumMode: 'percent', quorumPercent: 40, }); -testAPIEquivalence("API custom snip12 metadata", { +testAPIEquivalence('API custom snip12 metadata', { name: NAME, - delay: "1 day", - period: "1 week", - appName: "Governor", - appVersion: "v3", + delay: '1 day', + period: '1 week', + appName: 'Governor', + appVersion: 'v3', }); -testAPIEquivalence("API all options", { +testAPIEquivalence('API all options', { name: NAME, - delay: "4 day", - period: "4 week", - proposalThreshold: "500", + delay: '4 day', + period: '4 week', + proposalThreshold: '500', decimals: 10, - quorumMode: "absolute", + quorumMode: 'absolute', quorumPercent: 50, - quorumAbsolute: "200", - votes: "erc721votes", - clockMode: "timestamp", - timelock: "openzeppelin", + quorumAbsolute: '200', + votes: 'erc721votes', + clockMode: 'timestamp', + timelock: 'openzeppelin', settings: true, - appName: "MyApp2", - appVersion: "v5", + appName: 'MyApp2', + appVersion: 'v5', upgradeable: true, }); diff --git a/packages/core/cairo/src/governor.ts b/packages/core/cairo/src/governor.ts index 50b6c65ef..f0c05f2e7 100644 --- a/packages/core/cairo/src/governor.ts +++ b/packages/core/cairo/src/governor.ts @@ -1,51 +1,49 @@ -import { - contractDefaults as commonDefaults, - withCommonDefaults, -} from "./common-options"; -import type { CommonOptions } from "./common-options"; -import { ContractBuilder, Contract } from "./contract"; -import { OptionsError } from "./error"; -import { printContract } from "./print"; -import { setInfo } from "./set-info"; -import { setUpgradeableGovernor } from "./set-upgradeable"; -import { defineComponents } from "./utils/define-components"; -import { durationToTimestamp } from "./utils/duration"; -import { addSNIP12Metadata, addSRC5Component } from "./common-components"; -import { toUint } from "./utils/convert-strings"; -export const clockModeOptions = ["timestamp"] as const; -export const clockModeDefault = "timestamp" as const; +import { contractDefaults as commonDefaults, withCommonDefaults } from './common-options'; +import type { CommonOptions } from './common-options'; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { OptionsError } from './error'; +import { printContract } from './print'; +import { setInfo } from './set-info'; +import { setUpgradeableGovernor } from './set-upgradeable'; +import { defineComponents } from './utils/define-components'; +import { durationToTimestamp } from './utils/duration'; +import { addSNIP12Metadata, addSRC5Component } from './common-components'; +import { toUint } from './utils/convert-strings'; +export const clockModeOptions = ['timestamp'] as const; +export const clockModeDefault = 'timestamp' as const; export type ClockMode = (typeof clockModeOptions)[number]; -const extensionPath = "openzeppelin::governance::governor::extensions"; -const extensionExternalSection = "Extensions (external)"; -const extensionInternalSection = "Extensions (internal)"; +const extensionPath = 'openzeppelin::governance::governor::extensions'; +const extensionExternalSection = 'Extensions (external)'; +const extensionInternalSection = 'Extensions (internal)'; export const defaults: Required = { - name: "MyGovernor", - delay: "1 day", - period: "1 week", - votes: "erc20votes", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', + votes: 'erc20votes', clockMode: clockModeDefault, - timelock: "openzeppelin", + timelock: 'openzeppelin', decimals: 18, - proposalThreshold: "0", - quorumMode: "percent", + proposalThreshold: '0', + quorumMode: 'percent', quorumPercent: 4, - quorumAbsolute: "", + quorumAbsolute: '', settings: true, upgradeable: commonDefaults.upgradeable, - appName: "OpenZeppelin Governor", - appVersion: "v1", + appName: 'OpenZeppelin Governor', + appVersion: 'v1', info: commonDefaults.info, } as const; -export const quorumModeOptions = ["percent", "absolute"] as const; +export const quorumModeOptions = ['percent', 'absolute'] as const; export type QuorumMode = (typeof quorumModeOptions)[number]; -export const votesOptions = ["erc20votes", "erc721votes"] as const; +export const votesOptions = ['erc20votes', 'erc721votes'] as const; export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, "openzeppelin"] as const; +export const timelockOptions = [false, 'openzeppelin'] as const; export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { @@ -96,8 +94,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { validateDecimals(allOpts.decimals); addBase(c, allOpts); - addSRC5Component(c, "SRC5"); - addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, "SNIP12 Metadata"); + addSRC5Component(c, 'SRC5'); + addSNIP12Metadata(c, allOpts.appName, allOpts.appVersion, 'SNIP12 Metadata'); addCounting(c, allOpts); addQuorumAndVotes(c, allOpts); addSettings(c, allOpts); @@ -110,43 +108,42 @@ export function buildGovernor(opts: GovernorOptions): Contract { const components = defineComponents({ GovernorComponent: { - path: "openzeppelin::governance::governor", + path: 'openzeppelin::governance::governor', substorage: { - name: "governor", - type: "GovernorComponent::Storage", + name: 'governor', + type: 'GovernorComponent::Storage', }, event: { - name: "GovernorEvent", - type: "GovernorComponent::Event", + name: 'GovernorEvent', + type: 'GovernorComponent::Event', }, impls: [ { - name: "GovernorImpl", - value: "GovernorComponent::GovernorImpl", - section: "Governor Core", + name: 'GovernorImpl', + value: 'GovernorComponent::GovernorImpl', + section: 'Governor Core', }, ], }, GovernorSettingsComponent: { path: extensionPath, substorage: { - name: "governor_settings", - type: "GovernorSettingsComponent::Storage", + name: 'governor_settings', + type: 'GovernorSettingsComponent::Storage', }, event: { - name: "GovernorSettingsEvent", - type: "GovernorSettingsComponent::Event", + name: 'GovernorSettingsEvent', + type: 'GovernorSettingsComponent::Event', }, impls: [ { - name: "GovernorSettingsAdminImpl", - value: - "GovernorSettingsComponent::GovernorSettingsAdminImpl", + name: 'GovernorSettingsAdminImpl', + value: 'GovernorSettingsComponent::GovernorSettingsAdminImpl', section: extensionExternalSection, }, { - name: "GovernorSettingsImpl", - value: "GovernorSettingsComponent::GovernorSettings", + name: 'GovernorSettingsImpl', + value: 'GovernorSettingsComponent::GovernorSettings', embed: false, section: extensionInternalSection, }, @@ -155,22 +152,22 @@ const components = defineComponents({ GovernorVotesComponent: { path: extensionPath, substorage: { - name: "governor_votes", - type: "GovernorVotesComponent::Storage", + name: 'governor_votes', + type: 'GovernorVotesComponent::Storage', }, event: { - name: "GovernorVotesEvent", - type: "GovernorVotesComponent::Event", + name: 'GovernorVotesEvent', + type: 'GovernorVotesComponent::Event', }, impls: [ { - name: "VotesTokenImpl", - value: "GovernorVotesComponent::VotesTokenImpl", + name: 'VotesTokenImpl', + value: 'GovernorVotesComponent::VotesTokenImpl', section: extensionExternalSection, }, { - name: "GovernorVotesImpl", - value: "GovernorVotesComponent::GovernorVotes", + name: 'GovernorVotesImpl', + value: 'GovernorVotesComponent::GovernorVotes', embed: false, section: extensionInternalSection, }, @@ -179,32 +176,29 @@ const components = defineComponents({ GovernorVotesQuorumFractionComponent: { path: extensionPath, substorage: { - name: "governor_votes", - type: "GovernorVotesQuorumFractionComponent::Storage", + name: 'governor_votes', + type: 'GovernorVotesQuorumFractionComponent::Storage', }, event: { - name: "GovernorVotesEvent", - type: "GovernorVotesQuorumFractionComponent::Event", + name: 'GovernorVotesEvent', + type: 'GovernorVotesQuorumFractionComponent::Event', }, impls: [ { - name: "GovernorQuorumImpl", - value: - "GovernorVotesQuorumFractionComponent::GovernorQuorum", + name: 'GovernorQuorumImpl', + value: 'GovernorVotesQuorumFractionComponent::GovernorQuorum', embed: false, section: extensionInternalSection, }, { - name: "GovernorVotesImpl", - value: - "GovernorVotesQuorumFractionComponent::GovernorVotes", + name: 'GovernorVotesImpl', + value: 'GovernorVotesQuorumFractionComponent::GovernorVotes', embed: false, section: extensionInternalSection, }, { - name: "QuorumFractionImpl", - value: - "GovernorVotesQuorumFractionComponent::QuorumFractionImpl", + name: 'QuorumFractionImpl', + value: 'GovernorVotesQuorumFractionComponent::QuorumFractionImpl', section: extensionExternalSection, }, ], @@ -212,18 +206,17 @@ const components = defineComponents({ GovernorCountingSimpleComponent: { path: extensionPath, substorage: { - name: "governor_counting", - type: "GovernorCountingSimpleComponent::Storage", + name: 'governor_counting', + type: 'GovernorCountingSimpleComponent::Storage', }, event: { - name: "GovernorCountingSimpleEvent", - type: "GovernorCountingSimpleComponent::Event", + name: 'GovernorCountingSimpleEvent', + type: 'GovernorCountingSimpleComponent::Event', }, impls: [ { - name: "GovernorCountingSimpleImpl", - value: - "GovernorCountingSimpleComponent::GovernorCounting", + name: 'GovernorCountingSimpleImpl', + value: 'GovernorCountingSimpleComponent::GovernorCounting', embed: false, section: extensionInternalSection, }, @@ -232,18 +225,17 @@ const components = defineComponents({ GovernorCoreExecutionComponent: { path: extensionPath, substorage: { - name: "governor_execution", - type: "GovernorCoreExecutionComponent::Storage", + name: 'governor_execution', + type: 'GovernorCoreExecutionComponent::Storage', }, event: { - name: "GovernorCoreExecutionEvent", - type: "GovernorCoreExecutionComponent::Event", + name: 'GovernorCoreExecutionEvent', + type: 'GovernorCoreExecutionComponent::Event', }, impls: [ { - name: "GovernorCoreExecutionImpl", - value: - "GovernorCoreExecutionComponent::GovernorExecution", + name: 'GovernorCoreExecutionImpl', + value: 'GovernorCoreExecutionComponent::GovernorExecution', embed: false, section: extensionInternalSection, }, @@ -252,24 +244,22 @@ const components = defineComponents({ GovernorTimelockExecutionComponent: { path: extensionPath, substorage: { - name: "governor_timelock_execution", - type: "GovernorTimelockExecutionComponent::Storage", + name: 'governor_timelock_execution', + type: 'GovernorTimelockExecutionComponent::Storage', }, event: { - name: "GovernorTimelockExecutionEvent", - type: "GovernorTimelockExecutionComponent::Event", + name: 'GovernorTimelockExecutionEvent', + type: 'GovernorTimelockExecutionComponent::Event', }, impls: [ { - name: "TimelockedImpl", - value: - "GovernorTimelockExecutionComponent::TimelockedImpl", + name: 'TimelockedImpl', + value: 'GovernorTimelockExecutionComponent::TimelockedImpl', section: extensionExternalSection, }, { - name: "GovernorTimelockExecutionImpl", - value: - "GovernorTimelockExecutionComponent::GovernorExecution", + name: 'GovernorTimelockExecutionImpl', + value: 'GovernorTimelockExecutionComponent::GovernorExecution', embed: false, section: extensionInternalSection, }, @@ -278,52 +268,44 @@ const components = defineComponents({ }); function addBase(c: ContractBuilder, _: GovernorOptions) { - c.addUseClause("starknet", "ContractAddress"); - c.addUseClause("openzeppelin::governance::governor", "DefaultConfig"); - c.addConstructorArgument({ name: "votes_token", type: "ContractAddress" }); - c.addUseClause( - "openzeppelin::governance::governor::GovernorComponent", - "InternalTrait", - { alias: "GovernorInternalTrait" }, - ); + c.addUseClause('starknet', 'ContractAddress'); + c.addUseClause('openzeppelin::governance::governor', 'DefaultConfig'); + c.addConstructorArgument({ name: 'votes_token', type: 'ContractAddress' }); + c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalTrait', { + alias: 'GovernorInternalTrait', + }); c.addComponent(components.GovernorComponent, [], true); } function addSettings(c: ContractBuilder, allOpts: Required) { c.addConstant({ - name: "VOTING_DELAY", - type: "u64", + name: 'VOTING_DELAY', + type: 'u64', value: getVotingDelay(allOpts).toString(), comment: allOpts.delay, inlineComment: true, }); c.addConstant({ - name: "VOTING_PERIOD", - type: "u64", + name: 'VOTING_PERIOD', + type: 'u64', value: getVotingPeriod(allOpts).toString(), comment: allOpts.period, inlineComment: true, }); c.addConstant({ - name: "PROPOSAL_THRESHOLD", - type: "u256", + name: 'PROPOSAL_THRESHOLD', + type: 'u256', ...getProposalThreshold(allOpts), inlineComment: true, }); if (allOpts.settings) { - c.addUseClause( - `${extensionPath}::GovernorSettingsComponent`, - "InternalTrait", - { alias: "GovernorSettingsInternalTrait" }, - ); + c.addUseClause(`${extensionPath}::GovernorSettingsComponent`, 'InternalTrait', { + alias: 'GovernorSettingsInternalTrait', + }); c.addComponent( components.GovernorSettingsComponent, - [ - { lit: "VOTING_DELAY" }, - { lit: "VOTING_PERIOD" }, - { lit: "PROPOSAL_THRESHOLD" }, - ], + [{ lit: 'VOTING_DELAY' }, { lit: 'VOTING_PERIOD' }, { lit: 'PROPOSAL_THRESHOLD' }], true, ); } else { @@ -333,10 +315,10 @@ function addSettings(c: ContractBuilder, allOpts: Required) { function getVotingDelay(opts: Required): number { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return durationToTimestamp(opts.delay); } else { - throw new Error("Invalid clock mode"); + throw new Error('Invalid clock mode'); } } catch (e) { if (e instanceof Error) { @@ -351,10 +333,10 @@ function getVotingDelay(opts: Required): number { function getVotingPeriod(opts: Required): number { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return durationToTimestamp(opts.period); } else { - throw new Error("Invalid clock mode"); + throw new Error('Invalid clock mode'); } } catch (e) { if (e instanceof Error) { @@ -370,31 +352,26 @@ function getVotingPeriod(opts: Required): number { function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: "Not a valid number", + decimals: 'Not a valid number', }); } } -function getProposalThreshold({ - proposalThreshold, - decimals, - votes, -}: Required): { value: string; comment?: string } { +function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): { + value: string; + comment?: string; +} { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: "Not a valid number", + proposalThreshold: 'Not a valid number', }); } - if ( - /^0+$/.test(proposalThreshold) || - decimals === 0 || - votes === "erc721votes" - ) { + if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { return { value: proposalThreshold }; } else { let value = `${BigInt(proposalThreshold) * BigInt(10) ** BigInt(decimals)}`; - value = toUint(value, "proposalThreshold", "u256").toString(); + value = toUint(value, 'proposalThreshold', 'u256').toString(); return { value: `${value}`, comment: `${proposalThreshold} * pow!(10, ${decimals})`, @@ -402,82 +379,76 @@ function getProposalThreshold({ } } -function addSettingsLocalImpl( - c: ContractBuilder, - _: Required, -) { +function addSettingsLocalImpl(c: ContractBuilder, _: Required) { const settingsTrait = { - name: "GovernorSettings", - of: "GovernorComponent::GovernorSettingsTrait", + name: 'GovernorSettings', + of: 'GovernorComponent::GovernorSettingsTrait', tags: [], - section: "Locally implemented extensions", + section: 'Locally implemented extensions', priority: 2, }; c.addImplementedTrait(settingsTrait); c.addFunction(settingsTrait, { - name: "voting_delay", + name: 'voting_delay', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u64", - code: ["VOTING_DELAY"], + returns: 'u64', + code: ['VOTING_DELAY'], }); c.addFunction(settingsTrait, { - name: "voting_period", + name: 'voting_period', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u64", - code: ["VOTING_PERIOD"], + returns: 'u64', + code: ['VOTING_PERIOD'], }); c.addFunction(settingsTrait, { - name: "proposal_threshold", + name: 'proposal_threshold', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, ], - returns: "u256", - code: ["PROPOSAL_THRESHOLD"], + returns: 'u256', + code: ['PROPOSAL_THRESHOLD'], }); } -function addQuorumAndVotes( - c: ContractBuilder, - allOpts: Required, -) { - if (allOpts.quorumMode === "percent") { +function addQuorumAndVotes(c: ContractBuilder, allOpts: Required) { + if (allOpts.quorumMode === 'percent') { if (allOpts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } addVotesQuorumFractionComponent(c, allOpts.quorumPercent); - } else if (allOpts.quorumMode === "absolute") { + } else if (allOpts.quorumMode === 'absolute') { if (!numberPattern.test(allOpts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: "Not a valid number", + quorumAbsolute: 'Not a valid number', }); } let quorum: string; - let comment = ""; - if (allOpts.decimals === 0 || allOpts.votes === "erc721votes") { + let comment = ''; + if (allOpts.decimals === 0 || allOpts.votes === 'erc721votes') { quorum = `${allOpts.quorumAbsolute}`; } else { quorum = `${BigInt(allOpts.quorumAbsolute) * BigInt(10) ** BigInt(allOpts.decimals)}`; - quorum = toUint(quorum, "quorumAbsolute", "u256").toString(); + quorum = toUint(quorum, 'quorumAbsolute', 'u256').toString(); comment = `${allOpts.quorumAbsolute} * pow!(10, ${allOpts.decimals})`; } @@ -486,75 +457,62 @@ function addQuorumAndVotes( } } -function addVotesQuorumFractionComponent( - c: ContractBuilder, - quorumNumerator: number, -) { +function addVotesQuorumFractionComponent(c: ContractBuilder, quorumNumerator: number) { c.addConstant({ - name: "QUORUM_NUMERATOR", - type: "u256", + name: 'QUORUM_NUMERATOR', + type: 'u256', value: (quorumNumerator * 10).toString(), comment: `${quorumNumerator}%`, inlineComment: true, }); - c.addUseClause( - `${extensionPath}::GovernorVotesQuorumFractionComponent`, - "InternalTrait", - { alias: "GovernorVotesQuorumFractionInternalTrait" }, - ); + c.addUseClause(`${extensionPath}::GovernorVotesQuorumFractionComponent`, 'InternalTrait', { + alias: 'GovernorVotesQuorumFractionInternalTrait', + }); c.addComponent( components.GovernorVotesQuorumFractionComponent, - [{ lit: "votes_token" }, { lit: "QUORUM_NUMERATOR" }], + [{ lit: 'votes_token' }, { lit: 'QUORUM_NUMERATOR' }], true, ); } function addVotesComponent(c: ContractBuilder, _: Required) { - c.addUseClause(`${extensionPath}::GovernorVotesComponent`, "InternalTrait", { - alias: "GovernorVotesInternalTrait", + c.addUseClause(`${extensionPath}::GovernorVotesComponent`, 'InternalTrait', { + alias: 'GovernorVotesInternalTrait', }); - c.addComponent( - components.GovernorVotesComponent, - [{ lit: "votes_token" }], - true, - ); + c.addComponent(components.GovernorVotesComponent, [{ lit: 'votes_token' }], true); } -function addQuorumLocalImpl( - c: ContractBuilder, - quorum: string, - comment: string, -) { +function addQuorumLocalImpl(c: ContractBuilder, quorum: string, comment: string) { c.addConstant({ - name: "QUORUM", - type: "u256", + name: 'QUORUM', + type: 'u256', value: quorum, comment, inlineComment: true, }); const quorumTrait = { - name: "GovernorQuorum", - of: "GovernorComponent::GovernorQuorumTrait", + name: 'GovernorQuorum', + of: 'GovernorComponent::GovernorQuorumTrait', tags: [], - section: "Locally implemented extensions", + section: 'Locally implemented extensions', priority: 1, }; c.addImplementedTrait(quorumTrait); c.addFunction(quorumTrait, { - name: "quorum", + name: 'quorum', args: [ { - name: "self", - type: "@GovernorComponent::ComponentState", + name: 'self', + type: '@GovernorComponent::ComponentState', }, { - name: "timepoint", - type: "u64", + name: 'timepoint', + type: 'u64', }, ], - returns: "u256", - code: ["QUORUM"], + returns: 'u256', + code: ['QUORUM'], }); } @@ -562,27 +520,18 @@ function addCounting(c: ContractBuilder, _: Required) { c.addComponent(components.GovernorCountingSimpleComponent, [], false); } -function addExecution( - c: ContractBuilder, - { timelock }: Required, -) { +function addExecution(c: ContractBuilder, { timelock }: Required) { if (timelock === false) { c.addComponent(components.GovernorCoreExecutionComponent, [], false); } else { c.addConstructorArgument({ - name: "timelock_controller", - type: "ContractAddress", + name: 'timelock_controller', + type: 'ContractAddress', }); - c.addUseClause( - `${extensionPath}::GovernorTimelockExecutionComponent`, - "InternalTrait", - { alias: "GovernorTimelockExecutionInternalTrait" }, - ); - c.addComponent( - components.GovernorTimelockExecutionComponent, - [{ lit: "timelock_controller" }], - true, - ); + c.addUseClause(`${extensionPath}::GovernorTimelockExecutionComponent`, 'InternalTrait', { + alias: 'GovernorTimelockExecutionInternalTrait', + }); + c.addComponent(components.GovernorTimelockExecutionComponent, [{ lit: 'timelock_controller' }], true); } } diff --git a/packages/core/cairo/src/index.ts b/packages/core/cairo/src/index.ts index 461ec1823..5f102fabb 100644 --- a/packages/core/cairo/src/index.ts +++ b/packages/core/cairo/src/index.ts @@ -1,40 +1,28 @@ -export type { GenericOptions, KindedOptions } from "./build-generic"; -export { buildGeneric } from "./build-generic"; - -export type { Contract } from "./contract"; -export { ContractBuilder } from "./contract"; - -export { printContract } from "./print"; - -export type { Access } from "./set-access-control"; -export type { Account } from "./account"; -export type { Upgradeable } from "./set-upgradeable"; -export type { Info } from "./set-info"; -export type { RoyaltyInfoOptions } from "./set-royalty-info"; - -export { premintPattern } from "./erc20"; - -export { defaults as infoDefaults } from "./set-info"; -export { defaults as royaltyInfoDefaults } from "./set-royalty-info"; - -export type { OptionsErrorMessages } from "./error"; -export { OptionsError } from "./error"; - -export type { Kind } from "./kind"; -export { sanitizeKind } from "./kind"; - -export { - contractsVersion, - contractsVersionTag, - compatibleContractsSemver, -} from "./utils/version"; - -export { - erc20, - erc721, - erc1155, - account, - governor, - vesting, - custom, -} from "./api"; +export type { GenericOptions, KindedOptions } from './build-generic'; +export { buildGeneric } from './build-generic'; + +export type { Contract } from './contract'; +export { ContractBuilder } from './contract'; + +export { printContract } from './print'; + +export type { Access } from './set-access-control'; +export type { Account } from './account'; +export type { Upgradeable } from './set-upgradeable'; +export type { Info } from './set-info'; +export type { RoyaltyInfoOptions } from './set-royalty-info'; + +export { premintPattern } from './erc20'; + +export { defaults as infoDefaults } from './set-info'; +export { defaults as royaltyInfoDefaults } from './set-royalty-info'; + +export type { OptionsErrorMessages } from './error'; +export { OptionsError } from './error'; + +export type { Kind } from './kind'; +export { sanitizeKind } from './kind'; + +export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; + +export { erc20, erc721, erc1155, account, governor, vesting, custom } from './api'; diff --git a/packages/core/cairo/src/kind.ts b/packages/core/cairo/src/kind.ts index c205c7715..272bb1a79 100644 --- a/packages/core/cairo/src/kind.ts +++ b/packages/core/cairo/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from "./build-generic"; +import type { GenericOptions } from './build-generic'; -export type Kind = GenericOptions["kind"]; +export type Kind = GenericOptions['kind']; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === "string") { - const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); + if (typeof kind === 'string') { + const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return "ERC20"; + return 'ERC20'; } function isKind(value: Kind | T): value is Kind { switch (value) { - case "ERC20": - case "ERC721": - case "ERC1155": - case "Account": - case "Governor": - case "Vesting": - case "Custom": + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Account': + case 'Governor': + case 'Vesting': + case 'Custom': return true; default: { diff --git a/packages/core/cairo/src/print.ts b/packages/core/cairo/src/print.ts index 2c55c403c..30eecae76 100644 --- a/packages/core/cairo/src/print.ts +++ b/packages/core/cairo/src/print.ts @@ -7,21 +7,20 @@ import type { ContractFunction, ImplementedTrait, UseClause, -} from "./contract"; +} from './contract'; -import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; -import { getSelfArg } from "./common-options"; -import { compatibleContractsSemver } from "./utils/version"; +import { formatLines, spaceBetween } from './utils/format-lines'; +import type { Lines } from './utils/format-lines'; +import { getSelfArg } from './common-options'; +import { compatibleContractsSemver } from './utils/version'; -const DEFAULT_SECTION = "1. with no section"; -const STANDALONE_IMPORTS_GROUP = "Standalone Imports"; +const DEFAULT_SECTION = '1. with no section'; +const STANDALONE_IMPORTS_GROUP = 'Standalone Imports'; const MAX_USE_CLAUSE_LINE_LENGTH = 90; -const TAB = "\t"; +const TAB = '\t'; export function printContract(contract: Contract): string { - const contractAttribute = contract.account - ? "#[starknet::contract(account)]" - : "#[starknet::contract]"; + const contractAttribute = contract.account ? '#[starknet::contract(account)]' : '#[starknet::contract]'; return formatLines( ...spaceBetween( [ @@ -49,62 +48,41 @@ export function printContract(contract: Contract): string { } function withSemicolons(lines: string[]): string[] { - return lines.map((line) => (line.endsWith(";") ? line : line + ";")); + return lines.map(line => (line.endsWith(';') ? line : line + ';')); } function printSuperVariables(contract: Contract): string[] { - return withSemicolons( - contract.superVariables.map( - (v) => `const ${v.name}: ${v.type} = ${v.value}`, - ), - ); + return withSemicolons(contract.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); } function printUseClauses(contract: Contract): Lines[] { const useClauses = sortUseClauses(contract); // group by containerPath - const grouped = useClauses.reduce( - ( - result: { [containerPath: string]: UseClause[] }, - useClause: UseClause, - ) => { - if (useClause.groupable) { - (result[useClause.containerPath] = - result[useClause.containerPath] || []).push(useClause); - } else { - (result[STANDALONE_IMPORTS_GROUP] = - result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); - } - return result; - }, - {}, - ); + const grouped = useClauses.reduce((result: { [containerPath: string]: UseClause[] }, useClause: UseClause) => { + if (useClause.groupable) { + (result[useClause.containerPath] = result[useClause.containerPath] || []).push(useClause); + } else { + (result[STANDALONE_IMPORTS_GROUP] = result[STANDALONE_IMPORTS_GROUP] || []).push(useClause); + } + return result; + }, {}); - const lines = Object.entries(grouped).flatMap(([groupName, group]) => - getLinesFromUseClausesGroup(group, groupName), - ); - return lines.flatMap((line) => splitLongUseClauseLine(line.toString())); + const lines = Object.entries(grouped).flatMap(([groupName, group]) => getLinesFromUseClausesGroup(group, groupName)); + return lines.flatMap(line => splitLongUseClauseLine(line.toString())); } -function getLinesFromUseClausesGroup( - group: UseClause[], - groupName: string, -): Lines[] { +function getLinesFromUseClausesGroup(group: UseClause[], groupName: string): Lines[] { const lines = []; if (groupName === STANDALONE_IMPORTS_GROUP) { for (const useClause of group) { - lines.push( - `use ${useClause.containerPath}::${nameWithAlias(useClause)};`, - ); + lines.push(`use ${useClause.containerPath}::${nameWithAlias(useClause)};`); } } else { if (group.length == 1) { lines.push(`use ${groupName}::${nameWithAlias(group[0]!)};`); } else if (group.length > 1) { - const names = group - .map((useClause) => nameWithAlias(useClause)) - .join(", "); + const names = group.map(useClause => nameWithAlias(useClause)).join(', '); lines.push(`use ${groupName}::{${names}};`); } } @@ -112,21 +90,19 @@ function getLinesFromUseClausesGroup( } function nameWithAlias(useClause: UseClause): string { - return useClause.alias - ? `${useClause.name} as ${useClause.alias}` - : useClause.name; + return useClause.alias ? `${useClause.name} as ${useClause.alias}` : useClause.name; } // TODO: remove this when we can use a formatting js library function splitLongUseClauseLine(line: string): Lines[] { const lines = []; - const containsBraces = line.indexOf("{") !== -1; + const containsBraces = line.indexOf('{') !== -1; if (containsBraces && line.length > MAX_USE_CLAUSE_LINE_LENGTH) { // split at the first brace - lines.push(line.slice(0, line.indexOf("{") + 1)); - lines.push(...splitLongLineInner(line.slice(line.indexOf("{") + 1, -2))); - lines.push("};"); + lines.push(line.slice(0, line.indexOf('{') + 1)); + lines.push(...splitLongLineInner(line.slice(line.indexOf('{') + 1, -2))); + lines.push('};'); } else { lines.push(line); } @@ -137,7 +113,7 @@ function splitLongLineInner(line: string): Lines[] { const lines = []; if (line.length > MAX_USE_CLAUSE_LINE_LENGTH) { const max_accessible_string = line.slice(0, MAX_USE_CLAUSE_LINE_LENGTH); - const lastCommaIndex = max_accessible_string.lastIndexOf(","); + const lastCommaIndex = max_accessible_string.lastIndexOf(','); if (lastCommaIndex !== -1) { lines.push(TAB + max_accessible_string.slice(0, lastCommaIndex + 1)); lines.push(...splitLongLineInner(line.slice(lastCommaIndex + 2))); @@ -167,17 +143,11 @@ function printConstants(contract: Contract): Lines[] { if (commented && !inlineComment) { lines.push(`// ${constant.comment}`); - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); } else if (commented) { - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value}; // ${constant.comment}`); } else { - lines.push( - `const ${constant.name}: ${constant.type} = ${constant.value};`, - ); + lines.push(`const ${constant.name}: ${constant.type} = ${constant.value};`); } } return lines; @@ -194,20 +164,17 @@ function printComponentDeclarations(contract: Contract): Lines[] { } function printImpls(contract: Contract): Lines[] { - const impls = contract.components.flatMap((c) => c.impls); + const impls = contract.components.flatMap(c => c.impls); // group by section - const grouped = impls.reduce( - (result: { [section: string]: Impl[] }, current: Impl) => { - // default section depends on embed - // embed defaults to true - const embed = current.embed ?? true; - const section = current.section ?? (embed ? "External" : "Internal"); - (result[section] = result[section] || []).push(current); - return result; - }, - {}, - ); + const grouped = impls.reduce((result: { [section: string]: Impl[] }, current: Impl) => { + // default section depends on embed + // embed defaults to true + const embed = current.embed ?? true; + const section = current.section ?? (embed ? 'External' : 'Internal'); + (result[section] = result[section] || []).push(current); + return result; + }, {}); const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) @@ -218,7 +185,7 @@ function printImpls(contract: Contract): Lines[] { function printSection(section: string, impls: Impl[]): Lines[] { const lines = []; lines.push(`// ${section}`); - impls.map((impl) => lines.push(...printImpl(impl))); + impls.map(impl => lines.push(...printImpl(impl))); return lines; } @@ -226,7 +193,7 @@ function printImpl(impl: Impl): Lines[] { const lines = []; // embed is optional, default to true if (impl.embed ?? true) { - lines.push("#[abi(embed_v0)]"); + lines.push('#[abi(embed_v0)]'); } lines.push(`impl ${impl.name} = ${impl.value};`); return lines; @@ -235,33 +202,31 @@ function printImpl(impl: Impl): Lines[] { function printStorage(contract: Contract): (string | string[])[] { const lines = []; // storage is required regardless of whether there are components - lines.push("#[storage]"); - lines.push("struct Storage {"); + lines.push('#[storage]'); + lines.push('struct Storage {'); const storageLines = []; for (const component of contract.components) { storageLines.push(`#[substorage(v0)]`); - storageLines.push( - `${component.substorage.name}: ${component.substorage.type},`, - ); + storageLines.push(`${component.substorage.name}: ${component.substorage.type},`); } lines.push(storageLines); - lines.push("}"); + lines.push('}'); return lines; } function printEvents(contract: Contract): (string | string[])[] { const lines = []; if (contract.components.length > 0) { - lines.push("#[event]"); - lines.push("#[derive(Drop, starknet::Event)]"); - lines.push("enum Event {"); + lines.push('#[event]'); + lines.push('#[derive(Drop, starknet::Event)]'); + lines.push('enum Event {'); const eventLines = []; for (const component of contract.components) { - eventLines.push("#[flat]"); + eventLines.push('#[flat]'); eventLines.push(`${component.event.name}: ${component.event.type},`); } lines.push(eventLines); - lines.push("}"); + lines.push('}'); } return lines; } @@ -280,10 +245,7 @@ function printImplementedTraits(contract: Contract): Lines[] { // group by section const grouped = sortedTraits.reduce( - ( - result: { [section: string]: ImplementedTrait[] }, - current: ImplementedTrait, - ) => { + (result: { [section: string]: ImplementedTrait[] }, current: ImplementedTrait) => { // default to no section const section = current.section ?? DEFAULT_SECTION; (result[section] = result[section] || []).push(current); @@ -294,27 +256,22 @@ function printImplementedTraits(contract: Contract): Lines[] { const sections = Object.entries(grouped) .sort((a, b) => a[0].localeCompare(b[0])) - .map(([section, impls]) => - printImplementedTraitsSection(section, impls as ImplementedTrait[]), - ); + .map(([section, impls]) => printImplementedTraitsSection(section, impls as ImplementedTrait[])); return spaceBetween(...sections); } -function printImplementedTraitsSection( - section: string, - impls: ImplementedTrait[], -): Lines[] { +function printImplementedTraitsSection(section: string, impls: ImplementedTrait[]): Lines[] { const lines = []; const isDefaultSection = section === DEFAULT_SECTION; if (!isDefaultSection) { - lines.push("//"); + lines.push('//'); lines.push(`// ${section}`); - lines.push("//"); + lines.push('//'); } impls.forEach((trait, index) => { if (index > 0 || !isDefaultSection) { - lines.push(""); + lines.push(''); } lines.push(...printImplementedTrait(trait)); }); @@ -323,36 +280,30 @@ function printImplementedTraitsSection( function printImplementedTrait(trait: ImplementedTrait): Lines[] { const implLines = []; - implLines.push(...trait.tags.map((t) => `#[${t}]`)); + implLines.push(...trait.tags.map(t => `#[${t}]`)); implLines.push(`impl ${trait.name} of ${trait.of} {`); - const superVars = withSemicolons( - trait.superVariables.map((v) => `const ${v.name}: ${v.type} = ${v.value}`), - ); + const superVars = withSemicolons(trait.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); implLines.push(superVars); - const fns = trait.functions.map((fn) => printFunction(fn)); + const fns = trait.functions.map(fn => printFunction(fn)); implLines.push(spaceBetween(...fns)); - implLines.push("}"); + implLines.push('}'); return implLines; } function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; - const args = fn.args.map((a) => printArgument(a)); + const args = fn.args.map(a => printArgument(a)); const codeLines = fn.codeBefore?.concat(fn.code) ?? fn.code; for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; - const shouldEndWithSemicolon = - i < codeLines.length - 1 || fn.returns === undefined; + const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; if (line !== undefined && line.length > 0) { - if ( - shouldEndWithSemicolon && - !["{", "}", ";"].includes(line.charAt(line.length - 1)) - ) { - codeLines[i] += ";"; - } else if (!shouldEndWithSemicolon && line.endsWith(";")) { + if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { + codeLines[i] += ';'; + } else if (!shouldEndWithSemicolon && line.endsWith(';')) { codeLines[i] = line.slice(0, line.length - 1); } } @@ -362,26 +313,19 @@ function printFunction(fn: ContractFunction): Lines[] { } function printConstructor(contract: Contract): Lines[] { - const hasInitializers = contract.components.some( - (p) => p.initializer !== undefined, - ); + const hasInitializers = contract.components.some(p => p.initializer !== undefined); const hasConstructorCode = contract.constructorCode.length > 0; if (hasInitializers || hasConstructorCode) { - const parents = contract.components - .filter(hasInitializer) - .flatMap((p) => printParentConstructor(p)); - const tag = "constructor"; - const head = "fn constructor"; + const parents = contract.components.filter(hasInitializer).flatMap(p => printParentConstructor(p)); + const tag = 'constructor'; + const head = 'fn constructor'; const args = [getSelfArg(), ...contract.constructorArgs]; - const body = spaceBetween( - withSemicolons(parents), - withSemicolons(contract.constructorCode), - ); + const body = spaceBetween(withSemicolons(parents), withSemicolons(contract.constructorCode)); const constructor = printFunction2( head, - args.map((a) => printArgument(a)), + args.map(a => printArgument(a)), tag, undefined, undefined, @@ -394,39 +338,34 @@ function printConstructor(contract: Contract): Lines[] { } function hasInitializer(parent: Component): boolean { - return ( - parent.initializer !== undefined && parent.substorage?.name !== undefined - ); + return parent.initializer !== undefined && parent.substorage?.name !== undefined; } -function printParentConstructor({ - substorage, - initializer, -}: Component): [] | [string] { +function printParentConstructor({ substorage, initializer }: Component): [] | [string] { if (initializer === undefined || substorage?.name === undefined) { return []; } const fn = `self.${substorage.name}.initializer`; - return [fn + "(" + initializer.params.map(printValue).join(", ") + ")"]; + return [fn + '(' + initializer.params.map(printValue).join(', ') + ')']; } export function printValue(value: Value): string { - if (typeof value === "object") { - if ("lit" in value) { + if (typeof value === 'object') { + if ('lit' in value) { return value.lit; - } else if ("note" in value) { + } else if ('note' in value) { // TODO: add /* ${value.note} */ after lsp is fixed return `${printValue(value.value)}`; } else { - throw Error("Unknown value type"); + throw Error('Unknown value type'); } - } else if (typeof value === "number") { + } else if (typeof value === 'number') { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { throw new Error(`Number not representable (${value})`); } - } else if (typeof value === "bigint") { + } else if (typeof value === 'bigint') { return `${value}`; } else { return `"${value}"`; @@ -452,20 +391,20 @@ function printFunction2( let accum = `${kindedName}(`; if (args.length > 0) { - const formattedArgs = args.join(", "); + const formattedArgs = args.join(', '); if (formattedArgs.length > 80) { fn.push(accum); - accum = ""; + accum = ''; // print each arg in a separate line - fn.push(args.map((arg) => `${arg},`)); + fn.push(args.map(arg => `${arg},`)); } else { accum += `${formattedArgs}`; } } - accum += ")"; + accum += ')'; if (returns === undefined) { - accum += " {"; + accum += ' {'; } else { accum += ` -> ${returns} {`; } @@ -475,7 +414,7 @@ function printFunction2( if (returnLine !== undefined) { fn.push([returnLine]); } - fn.push("}"); + fn.push('}'); return fn; } diff --git a/packages/core/cairo/src/scripts/update-scarb-project.ts b/packages/core/cairo/src/scripts/update-scarb-project.ts index ee5f24897..b6ee6a901 100644 --- a/packages/core/cairo/src/scripts/update-scarb-project.ts +++ b/packages/core/cairo/src/scripts/update-scarb-project.ts @@ -1,24 +1,15 @@ -import { promises as fs } from "fs"; -import path from "path"; +import { promises as fs } from 'fs'; +import path from 'path'; -import { writeGeneratedSources } from "../generate/sources"; -import { - contractsVersion, - edition, - cairoVersion, - scarbVersion, -} from "../utils/version"; +import { writeGeneratedSources } from '../generate/sources'; +import { contractsVersion, edition, cairoVersion, scarbVersion } from '../utils/version'; export async function updateScarbProject() { - const generatedSourcesPath = path.join("test_project", "src"); + const generatedSourcesPath = path.join('test_project', 'src'); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); // Generate the contracts source code - const contractNames = await writeGeneratedSources( - generatedSourcesPath, - "all", - true, - ); + const contractNames = await writeGeneratedSources(generatedSourcesPath, 'all', true); // Generate lib.cairo file writeLibCairo(contractNames); @@ -28,32 +19,23 @@ export async function updateScarbProject() { } async function writeLibCairo(contractNames: string[]) { - const libCairoPath = path.join("test_project/src", "lib.cairo"); - const libCairo = contractNames.map((name) => `pub mod ${name};\n`).join(""); + const libCairoPath = path.join('test_project/src', 'lib.cairo'); + const libCairo = contractNames.map(name => `pub mod ${name};\n`).join(''); await fs.writeFile(libCairoPath, libCairo); } async function updateScarbToml() { - const scarbTomlPath = path.join("test_project", "Scarb.toml"); + const scarbTomlPath = path.join('test_project', 'Scarb.toml'); - const currentContent = await fs.readFile(scarbTomlPath, "utf8"); + const currentContent = await fs.readFile(scarbTomlPath, 'utf8'); const updatedContent = currentContent .replace(/edition = "\w+"/, `edition = "${edition}"`) - .replace( - /cairo-version = "\d+\.\d+\.\d+"/, - `cairo-version = "${cairoVersion}"`, - ) - .replace( - /scarb-version = "\d+\.\d+\.\d+"/, - `scarb-version = "${scarbVersion}"`, - ) + .replace(/cairo-version = "\d+\.\d+\.\d+"/, `cairo-version = "${cairoVersion}"`) + .replace(/scarb-version = "\d+\.\d+\.\d+"/, `scarb-version = "${scarbVersion}"`) .replace(/starknet = "\d+\.\d+\.\d+"/, `starknet = "${cairoVersion}"`) - .replace( - /openzeppelin = "\d+\.\d+\.\d+"/, - `openzeppelin = "${contractsVersion}"`, - ); + .replace(/openzeppelin = "\d+\.\d+\.\d+"/, `openzeppelin = "${contractsVersion}"`); - await fs.writeFile(scarbTomlPath, updatedContent, "utf8"); + await fs.writeFile(scarbTomlPath, updatedContent, 'utf8'); } updateScarbProject(); diff --git a/packages/core/cairo/src/set-access-control.ts b/packages/core/cairo/src/set-access-control.ts index fe9d0c65a..d567c15c8 100644 --- a/packages/core/cairo/src/set-access-control.ts +++ b/packages/core/cairo/src/set-access-control.ts @@ -1,13 +1,9 @@ -import type { - BaseFunction, - BaseImplementedTrait, - ContractBuilder, -} from "./contract"; -import { defineComponents } from "./utils/define-components"; -import { addSRC5Component } from "./common-components"; +import type { BaseFunction, BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; +import { addSRC5Component } from './common-components'; -export const accessOptions = [false, "ownable", "roles"] as const; -export const DEFAULT_ACCESS_CONTROL = "ownable"; +export const accessOptions = [false, 'ownable', 'roles'] as const; +export const DEFAULT_ACCESS_CONTROL = 'ownable'; export type Access = (typeof accessOptions)[number]; @@ -16,49 +12,42 @@ export type Access = (typeof accessOptions)[number]; */ export function setAccessControl(c: ContractBuilder, access: Access): void { switch (access) { - case "ownable": { - c.addComponent(components.OwnableComponent, [{ lit: "owner" }], true); + case 'ownable': { + c.addComponent(components.OwnableComponent, [{ lit: 'owner' }], true); - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: "owner", type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: 'owner', type: 'ContractAddress' }); break; } - case "roles": { + case 'roles': { if (c.addComponent(components.AccessControlComponent, [], true)) { - if (c.interfaceFlags.has("ISRC5")) { + if (c.interfaceFlags.has('ISRC5')) { c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlImpl", - value: "AccessControlComponent::AccessControlImpl", + name: 'AccessControlImpl', + value: 'AccessControlComponent::AccessControlImpl', }); c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlCamelImpl", - value: - "AccessControlComponent::AccessControlCamelImpl", + name: 'AccessControlCamelImpl', + value: 'AccessControlComponent::AccessControlCamelImpl', }); } else { c.addImplToComponent(components.AccessControlComponent, { - name: "AccessControlMixinImpl", - value: - "AccessControlComponent::AccessControlMixinImpl", + name: 'AccessControlMixinImpl', + value: 'AccessControlComponent::AccessControlMixinImpl', }); - c.addInterfaceFlag("ISRC5"); + c.addInterfaceFlag('ISRC5'); } addSRC5Component(c); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addConstructorArgument({ - name: "default_admin", - type: "ContractAddress", + name: 'default_admin', + type: 'ContractAddress', }); - c.addUseClause( - "openzeppelin::access::accesscontrol", - "DEFAULT_ADMIN_ROLE", - ); - c.addConstructorCode( - "self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)", - ); + c.addUseClause('openzeppelin::access::accesscontrol', 'DEFAULT_ADMIN_ROLE'); + c.addConstructorCode('self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin)'); } break; } @@ -82,32 +71,26 @@ export function requireAccessControl( setAccessControl(c, access); switch (access) { - case "ownable": { - c.addFunctionCodeBefore(trait, fn, "self.ownable.assert_only_owner()"); + case 'ownable': { + c.addFunctionCodeBefore(trait, fn, 'self.ownable.assert_only_owner()'); break; } - case "roles": { - const roleId = roleIdPrefix + "_ROLE"; + case 'roles': { + const roleId = roleIdPrefix + '_ROLE'; const addedSuper = c.addSuperVariable({ name: roleId, - type: "felt252", + type: 'felt252', value: `selector!("${roleId}")`, }); if (roleOwner !== undefined) { - c.addUseClause("starknet", "ContractAddress"); - c.addConstructorArgument({ name: roleOwner, type: "ContractAddress" }); + c.addUseClause('starknet', 'ContractAddress'); + c.addConstructorArgument({ name: roleOwner, type: 'ContractAddress' }); if (addedSuper) { - c.addConstructorCode( - `self.accesscontrol._grant_role(${roleId}, ${roleOwner})`, - ); + c.addConstructorCode(`self.accesscontrol._grant_role(${roleId}, ${roleOwner})`); } } - c.addFunctionCodeBefore( - trait, - fn, - `self.accesscontrol.assert_only_role(${roleId})`, - ); + c.addFunctionCodeBefore(trait, fn, `self.accesscontrol.assert_only_role(${roleId})`); break; } @@ -116,42 +99,42 @@ export function requireAccessControl( const components = defineComponents({ OwnableComponent: { - path: "openzeppelin::access::ownable", + path: 'openzeppelin::access::ownable', substorage: { - name: "ownable", - type: "OwnableComponent::Storage", + name: 'ownable', + type: 'OwnableComponent::Storage', }, event: { - name: "OwnableEvent", - type: "OwnableComponent::Event", + name: 'OwnableEvent', + type: 'OwnableComponent::Event', }, impls: [ { - name: "OwnableMixinImpl", - value: "OwnableComponent::OwnableMixinImpl", + name: 'OwnableMixinImpl', + value: 'OwnableComponent::OwnableMixinImpl', }, { - name: "OwnableInternalImpl", + name: 'OwnableInternalImpl', embed: false, - value: "OwnableComponent::InternalImpl", + value: 'OwnableComponent::InternalImpl', }, ], }, AccessControlComponent: { - path: "openzeppelin::access::accesscontrol", + path: 'openzeppelin::access::accesscontrol', substorage: { - name: "accesscontrol", - type: "AccessControlComponent::Storage", + name: 'accesscontrol', + type: 'AccessControlComponent::Storage', }, event: { - name: "AccessControlEvent", - type: "AccessControlComponent::Event", + name: 'AccessControlEvent', + type: 'AccessControlComponent::Event', }, impls: [ { - name: "AccessControlInternalImpl", + name: 'AccessControlInternalImpl', embed: false, - value: "AccessControlComponent::InternalImpl", + value: 'AccessControlComponent::InternalImpl', }, ], }, diff --git a/packages/core/cairo/src/set-info.ts b/packages/core/cairo/src/set-info.ts index 30a110db5..232662ae8 100644 --- a/packages/core/cairo/src/set-info.ts +++ b/packages/core/cairo/src/set-info.ts @@ -1,8 +1,8 @@ -import type { ContractBuilder } from "./contract"; +import type { ContractBuilder } from './contract'; -export const infoOptions = [{}, { license: "WTFPL" }] as const; +export const infoOptions = [{}, { license: 'WTFPL' }] as const; -export const defaults: Info = { license: "MIT" }; +export const defaults: Info = { license: 'MIT' }; export type Info = { license?: string; diff --git a/packages/core/cairo/src/set-royalty-info.ts b/packages/core/cairo/src/set-royalty-info.ts index fb27f0dfe..cd3920db8 100644 --- a/packages/core/cairo/src/set-royalty-info.ts +++ b/packages/core/cairo/src/set-royalty-info.ts @@ -1,18 +1,15 @@ -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { defineComponents } from "./utils/define-components"; -import { OptionsError } from "./error"; -import { toUint } from "./utils/convert-strings"; -import { - Access, - setAccessControl, - DEFAULT_ACCESS_CONTROL, -} from "./set-access-control"; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; +import { OptionsError } from './error'; +import { toUint } from './utils/convert-strings'; +import type { Access } from './set-access-control'; +import { setAccessControl, DEFAULT_ACCESS_CONTROL } from './set-access-control'; const DEFAULT_FEE_DENOMINATOR = BigInt(10_000); export const defaults: RoyaltyInfoOptions = { enabled: false, - defaultRoyaltyFraction: "0", + defaultRoyaltyFraction: '0', feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }; @@ -20,13 +17,13 @@ export const royaltyInfoOptions = { disabled: defaults, enabledDefault: { enabled: true, - defaultRoyaltyFraction: "500", + defaultRoyaltyFraction: '500', feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), }, enabledCustom: { enabled: true, - defaultRoyaltyFraction: "15125", - feeDenominator: "100000", + defaultRoyaltyFraction: '15125', + feeDenominator: '100000', }, }; @@ -36,11 +33,7 @@ export type RoyaltyInfoOptions = { feeDenominator: string; }; -export function setRoyaltyInfo( - c: ContractBuilder, - options: RoyaltyInfoOptions, - access: Access, -): void { +export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, access: Access): void { if (!options.enabled) { return; } @@ -49,54 +42,48 @@ export function setRoyaltyInfo( } setAccessControl(c, access); - const { defaultRoyaltyFraction, feeDenominator } = - getRoyaltyParameters(options); - const initParams = [ - { lit: "default_royalty_receiver" }, - defaultRoyaltyFraction, - ]; + const { defaultRoyaltyFraction, feeDenominator } = getRoyaltyParameters(options); + const initParams = [{ lit: 'default_royalty_receiver' }, defaultRoyaltyFraction]; c.addComponent(components.ERC2981Component, initParams, true); - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); c.addConstructorArgument({ - name: "default_royalty_receiver", - type: "ContractAddress", + name: 'default_royalty_receiver', + type: 'ContractAddress', }); switch (access) { - case "ownable": + case 'ownable': c.addImplToComponent(components.ERC2981Component, { - name: "ERC2981AdminOwnableImpl", + name: 'ERC2981AdminOwnableImpl', value: `ERC2981Component::ERC2981AdminOwnableImpl`, }); break; - case "roles": + case 'roles': c.addImplToComponent(components.ERC2981Component, { - name: "ERC2981AdminAccessControlImpl", + name: 'ERC2981AdminAccessControlImpl', value: `ERC2981Component::ERC2981AdminAccessControlImpl`, }); c.addConstructorArgument({ - name: "royalty_admin", - type: "ContractAddress", + name: 'royalty_admin', + type: 'ContractAddress', }); - c.addConstructorCode( - "self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)", - ); + c.addConstructorCode('self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)'); break; } if (feeDenominator === DEFAULT_FEE_DENOMINATOR) { - c.addUseClause("openzeppelin::token::common::erc2981", "DefaultConfig"); + c.addUseClause('openzeppelin::token::common::erc2981', 'DefaultConfig'); } else { const trait: BaseImplementedTrait = { - name: "ERC2981ImmutableConfig", - of: "ERC2981Component::ImmutableConfig", + name: 'ERC2981ImmutableConfig', + of: 'ERC2981Component::ImmutableConfig', tags: [], }; c.addImplementedTrait(trait); c.addSuperVariableToTrait(trait, { - name: "FEE_DENOMINATOR", - type: "u128", + name: 'FEE_DENOMINATOR', + type: 'u128', value: feeDenominator.toString(), }); } @@ -106,20 +93,16 @@ function getRoyaltyParameters(opts: Required): { defaultRoyaltyFraction: bigint; feeDenominator: bigint; } { - const feeDenominator = toUint(opts.feeDenominator, "feeDenominator", "u128"); + const feeDenominator = toUint(opts.feeDenominator, 'feeDenominator', 'u128'); if (feeDenominator === BigInt(0)) { throw new OptionsError({ - feeDenominator: "Must be greater than 0", + feeDenominator: 'Must be greater than 0', }); } - const defaultRoyaltyFraction = toUint( - opts.defaultRoyaltyFraction, - "defaultRoyaltyFraction", - "u128", - ); + const defaultRoyaltyFraction = toUint(opts.defaultRoyaltyFraction, 'defaultRoyaltyFraction', 'u128'); if (defaultRoyaltyFraction > feeDenominator) { throw new OptionsError({ - defaultRoyaltyFraction: "Cannot be greater than fee denominator", + defaultRoyaltyFraction: 'Cannot be greater than fee denominator', }); } return { defaultRoyaltyFraction, feeDenominator }; @@ -127,27 +110,27 @@ function getRoyaltyParameters(opts: Required): { const components = defineComponents({ ERC2981Component: { - path: "openzeppelin::token::common::erc2981", + path: 'openzeppelin::token::common::erc2981', substorage: { - name: "erc2981", - type: "ERC2981Component::Storage", + name: 'erc2981', + type: 'ERC2981Component::Storage', }, event: { - name: "ERC2981Event", - type: "ERC2981Component::Event", + name: 'ERC2981Event', + type: 'ERC2981Component::Event', }, impls: [ { - name: "ERC2981Impl", - value: "ERC2981Component::ERC2981Impl", + name: 'ERC2981Impl', + value: 'ERC2981Component::ERC2981Impl', }, { - name: "ERC2981InfoImpl", - value: "ERC2981Component::ERC2981InfoImpl", + name: 'ERC2981InfoImpl', + value: 'ERC2981Component::ERC2981InfoImpl', }, { - name: "ERC2981InternalImpl", - value: "ERC2981Component::InternalImpl", + name: 'ERC2981InternalImpl', + value: 'ERC2981Component::InternalImpl', embed: false, }, ], diff --git a/packages/core/cairo/src/set-upgradeable.ts b/packages/core/cairo/src/set-upgradeable.ts index fdb955e54..8948c419a 100644 --- a/packages/core/cairo/src/set-upgradeable.ts +++ b/packages/core/cairo/src/set-upgradeable.ts @@ -1,18 +1,16 @@ -import { getSelfArg } from "./common-options"; -import type { BaseImplementedTrait, ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineComponents } from "./utils/define-components"; -import { defineFunctions } from "./utils/define-functions"; -import type { Account } from "./account"; +import { getSelfArg } from './common-options'; +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineComponents } from './utils/define-components'; +import { defineFunctions } from './utils/define-functions'; +import type { Account } from './account'; export const upgradeableOptions = [false, true] as const; export type Upgradeable = (typeof upgradeableOptions)[number]; -function setUpgradeableBase( - c: ContractBuilder, - upgradeable: Upgradeable, -): BaseImplementedTrait | undefined { +function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseImplementedTrait | undefined { if (upgradeable === false) { return undefined; } @@ -21,77 +19,44 @@ function setUpgradeableBase( c.addComponent(components.UpgradeableComponent, [], false); - c.addUseClause("openzeppelin::upgrades::interface", "IUpgradeable"); - c.addUseClause("starknet", "ClassHash"); + c.addUseClause('openzeppelin::upgrades::interface', 'IUpgradeable'); + c.addUseClause('starknet', 'ClassHash'); const t: BaseImplementedTrait = { - name: "UpgradeableImpl", - of: "IUpgradeable", - section: "Upgradeable", - tags: ["abi(embed_v0)"], + name: 'UpgradeableImpl', + of: 'IUpgradeable', + section: 'Upgradeable', + tags: ['abi(embed_v0)'], }; c.addImplementedTrait(t); return t; } -export function setUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - access: Access, -): void { +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - requireAccessControl( - c, - trait, - functions.upgrade, - access, - "UPGRADER", - "upgrader", - ); + requireAccessControl(c, trait, functions.upgrade, access, 'UPGRADER', 'upgrader'); } } -export function setUpgradeableGovernor( - c: ContractBuilder, - upgradeable: Upgradeable, -): void { +export function setUpgradeableGovernor(c: ContractBuilder, upgradeable: Upgradeable): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { - c.addUseClause( - "openzeppelin::governance::governor::GovernorComponent", - "InternalExtendedImpl", - ); - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.governor.assert_only_governance()", - ); + c.addUseClause('openzeppelin::governance::governor::GovernorComponent', 'InternalExtendedImpl'); + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.governor.assert_only_governance()'); } } -export function setAccountUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - type: Account, -): void { +export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { switch (type) { - case "stark": - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.account.assert_only_self()", - ); + case 'stark': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.account.assert_only_self()'); break; - case "eth": - c.addFunctionCodeBefore( - trait, - functions.upgrade, - "self.eth_account.assert_only_self()", - ); + case 'eth': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.eth_account.assert_only_self()'); break; } } @@ -99,20 +64,20 @@ export function setAccountUpgradeable( const components = defineComponents({ UpgradeableComponent: { - path: "openzeppelin::upgrades", + path: 'openzeppelin::upgrades', substorage: { - name: "upgradeable", - type: "UpgradeableComponent::Storage", + name: 'upgradeable', + type: 'UpgradeableComponent::Storage', }, event: { - name: "UpgradeableEvent", - type: "UpgradeableComponent::Event", + name: 'UpgradeableEvent', + type: 'UpgradeableComponent::Event', }, impls: [ { - name: "UpgradeableInternalImpl", + name: 'UpgradeableInternalImpl', embed: false, - value: "UpgradeableComponent::InternalImpl", + value: 'UpgradeableComponent::InternalImpl', }, ], }, @@ -120,7 +85,7 @@ const components = defineComponents({ const functions = defineFunctions({ upgrade: { - args: [getSelfArg(), { name: "new_class_hash", type: "ClassHash" }], - code: ["self.upgradeable.upgrade(new_class_hash)"], + args: [getSelfArg(), { name: 'new_class_hash', type: 'ClassHash' }], + code: ['self.upgradeable.upgrade(new_class_hash)'], }, }); diff --git a/packages/core/cairo/src/test.ts b/packages/core/cairo/src/test.ts index e5974c16a..28c6dbdc1 100644 --- a/packages/core/cairo/src/test.ts +++ b/packages/core/cairo/src/test.ts @@ -1,11 +1,12 @@ -import { promises as fs } from "fs"; -import os from "os"; -import _test, { TestFn, ExecutionContext } from "ava"; -import path from "path"; +import { promises as fs } from 'fs'; +import os from 'os'; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import path from 'path'; -import { generateSources, writeGeneratedSources } from "./generate/sources"; -import type { GenericOptions, KindedOptions } from "./build-generic"; -import { custom, erc20, erc721, erc1155 } from "./api"; +import { generateSources, writeGeneratedSources } from './generate/sources'; +import type { GenericOptions, KindedOptions } from './build-generic'; +import { custom, erc20, erc721, erc1155 } from './api'; interface Context { generatedSourcesPath: string; @@ -13,88 +14,76 @@ interface Context { const test = _test as TestFn; -test.serial("erc20 result generated", async (t) => { - await testGenerate(t, "ERC20"); +test.serial('erc20 result generated', async t => { + await testGenerate(t, 'ERC20'); }); -test.serial("erc721 result generated", async (t) => { - await testGenerate(t, "ERC721"); +test.serial('erc721 result generated', async t => { + await testGenerate(t, 'ERC721'); }); -test.serial("erc1155 result generated", async (t) => { - await testGenerate(t, "ERC1155"); +test.serial('erc1155 result generated', async t => { + await testGenerate(t, 'ERC1155'); }); -test.serial("account result generated", async (t) => { - await testGenerate(t, "Account"); +test.serial('account result generated', async t => { + await testGenerate(t, 'Account'); }); -test.serial("custom result generated", async (t) => { - await testGenerate(t, "Custom"); +test.serial('custom result generated', async t => { + await testGenerate(t, 'Custom'); }); -async function testGenerate( - t: ExecutionContext, - kind: keyof KindedOptions, -) { - const generatedSourcesPath = path.join(os.tmpdir(), "oz-wizard-cairo"); +async function testGenerate(t: ExecutionContext, kind: keyof KindedOptions) { + const generatedSourcesPath = path.join(os.tmpdir(), 'oz-wizard-cairo'); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, "all", true, kind); + await writeGeneratedSources(generatedSourcesPath, 'all', true, kind); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return erc20.isAccessControlRequired(opts); - case "ERC721": + case 'ERC721': return erc721.isAccessControlRequired(opts); - case "ERC1155": + case 'ERC1155': return erc1155.isAccessControlRequired(opts); - case "Account": - throw new Error("Not applicable for accounts"); - case "Custom": + case 'Account': + throw new Error('Not applicable for accounts'); + case 'Custom': return custom.isAccessControlRequired(opts); default: - throw new Error("No such kind"); + throw new Error('No such kind'); } } -test("is access control required", async (t) => { - for (const contract of generateSources("all")) { - const regexOwnable = - /(use openzeppelin::access::ownable::OwnableComponent)/gm; +test('is access control required', async t => { + for (const contract of generateSources('all')) { + const regexOwnable = /(use openzeppelin::access::ownable::OwnableComponent)/gm; switch (contract.options.kind) { - case "Account": - case "Governor": - case "Vesting": + case 'Account': + case 'Governor': + case 'Vesting': // These contracts have no access control option break; - case "ERC20": - case "ERC721": - case "ERC1155": - case "Custom": + case 'ERC20': + case 'ERC721': + case 'ERC1155': + case 'Custom': if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); } else { - t.notRegex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); } } break; default: { const _: never = contract.options; - throw new Error("Unknown kind"); + throw new Error('Unknown kind'); } } } diff --git a/packages/core/cairo/src/utils/convert-strings.test.ts b/packages/core/cairo/src/utils/convert-strings.test.ts index f4f9eca87..51bb4a7a9 100644 --- a/packages/core/cairo/src/utils/convert-strings.test.ts +++ b/packages/core/cairo/src/utils/convert-strings.test.ts @@ -1,119 +1,101 @@ -import test from "ava"; +import test from 'ava'; -import { toIdentifier, toByteArray, toFelt252 } from "./convert-strings"; -import { OptionsError } from "../error"; +import { toIdentifier, toByteArray, toFelt252 } from './convert-strings'; +import { OptionsError } from '../error'; -test("identifier - unmodified", (t) => { - t.is(toIdentifier("abc"), "abc"); +test('identifier - unmodified', t => { + t.is(toIdentifier('abc'), 'abc'); }); -test("identifier - remove leading specials", (t) => { - t.is(toIdentifier("--abc"), "abc"); +test('identifier - remove leading specials', t => { + t.is(toIdentifier('--abc'), 'abc'); }); -test("identifier - remove specials and upcase next char", (t) => { - t.is(toIdentifier("abc-def"), "abcDef"); - t.is(toIdentifier("abc--def"), "abcDef"); +test('identifier - remove specials and upcase next char', t => { + t.is(toIdentifier('abc-def'), 'abcDef'); + t.is(toIdentifier('abc--def'), 'abcDef'); }); -test("identifier - capitalize", (t) => { - t.is(toIdentifier("abc", true), "Abc"); +test('identifier - capitalize', t => { + t.is(toIdentifier('abc', true), 'Abc'); }); -test("identifier - remove accents", (t) => { - t.is(toIdentifier("ábc"), "abc"); +test('identifier - remove accents', t => { + t.is(toIdentifier('ábc'), 'abc'); }); -test("identifier - underscores", (t) => { - t.is(toIdentifier("_abc_"), "_abc_"); +test('identifier - underscores', t => { + t.is(toIdentifier('_abc_'), '_abc_'); }); -test("identifier - remove starting numbers", (t) => { - t.is(toIdentifier("123abc456"), "abc456"); +test('identifier - remove starting numbers', t => { + t.is(toIdentifier('123abc456'), 'abc456'); }); -test("identifier - empty string", (t) => { - const error = t.throws(() => toIdentifier(""), { instanceOf: OptionsError }); - t.is( - error.messages.name, - "Identifier is empty or does not have valid characters", - ); +test('identifier - empty string', t => { + const error = t.throws(() => toIdentifier(''), { instanceOf: OptionsError }); + t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); }); -test("identifier - no valid chars", (t) => { - const error = t.throws(() => toIdentifier("123"), { +test('identifier - no valid chars', t => { + const error = t.throws(() => toIdentifier('123'), { instanceOf: OptionsError, }); - t.is( - error.messages.name, - "Identifier is empty or does not have valid characters", - ); + t.is(error.messages.name, 'Identifier is empty or does not have valid characters'); }); -test("toByteArray - unmodified", (t) => { - t.is(toByteArray("abc"), "abc"); +test('toByteArray - unmodified', t => { + t.is(toByteArray('abc'), 'abc'); }); -test("toByteArray - remove accents", (t) => { - t.is(toByteArray("ábc"), "abc"); +test('toByteArray - remove accents', t => { + t.is(toByteArray('ábc'), 'abc'); }); -test("toByteArray - remove non-ascii-printable characters", (t) => { - t.is(toByteArray("abc😀"), "abc"); +test('toByteArray - remove non-ascii-printable characters', t => { + t.is(toByteArray('abc😀'), 'abc'); }); -test("toByteArray - escape double quote", (t) => { +test('toByteArray - escape double quote', t => { t.is(toByteArray('abc"def'), 'abc\\"def'); }); -test("toByteArray - does not escape single quote", (t) => { +test('toByteArray - does not escape single quote', t => { t.is(toByteArray("abc'def"), "abc'def"); }); -test("toByteArray - escape backslash", (t) => { - t.is(toByteArray("abc\\def"), "abc\\\\def"); +test('toByteArray - escape backslash', t => { + t.is(toByteArray('abc\\def'), 'abc\\\\def'); }); -test("more than 31 characters", (t) => { - t.is( - toByteArray("A234567890123456789012345678901"), - "A234567890123456789012345678901", - ); - t.is( - toByteArray("A2345678901234567890123456789012"), - "A2345678901234567890123456789012", - ); +test('more than 31 characters', t => { + t.is(toByteArray('A234567890123456789012345678901'), 'A234567890123456789012345678901'); + t.is(toByteArray('A2345678901234567890123456789012'), 'A2345678901234567890123456789012'); }); -test("toFelt252 - unmodified", (t) => { - t.is(toFelt252("abc", "foo"), "abc"); +test('toFelt252 - unmodified', t => { + t.is(toFelt252('abc', 'foo'), 'abc'); }); -test("toFelt252 - remove accents", (t) => { - t.is(toFelt252("ábc", "foo"), "abc"); +test('toFelt252 - remove accents', t => { + t.is(toFelt252('ábc', 'foo'), 'abc'); }); -test("toFelt252 - remove non-ascii-printable characters", (t) => { - t.is(toFelt252("abc😀", "foo"), "abc"); +test('toFelt252 - remove non-ascii-printable characters', t => { + t.is(toFelt252('abc😀', 'foo'), 'abc'); }); -test("toFelt252 - escape single quote", (t) => { - t.is(toFelt252("abc'def", "foo"), "abc\\'def"); +test('toFelt252 - escape single quote', t => { + t.is(toFelt252("abc'def", 'foo'), "abc\\'def"); }); -test("toFelt252 - escape backslash", (t) => { - t.is(toFelt252("abc\\def", "foo"), "abc\\\\def"); +test('toFelt252 - escape backslash', t => { + t.is(toFelt252('abc\\def', 'foo'), 'abc\\\\def'); }); -test("toFelt252 - max 31 characters", (t) => { - t.is( - toFelt252("A234567890123456789012345678901", "foo"), - "A234567890123456789012345678901", - ); +test('toFelt252 - max 31 characters', t => { + t.is(toFelt252('A234567890123456789012345678901', 'foo'), 'A234567890123456789012345678901'); - const error = t.throws( - () => toFelt252("A2345678901234567890123456789012", "foo"), - { instanceOf: OptionsError }, - ); - t.is(error.messages.foo, "String is longer than 31 characters"); + const error = t.throws(() => toFelt252('A2345678901234567890123456789012', 'foo'), { instanceOf: OptionsError }); + t.is(error.messages.foo, 'String is longer than 31 characters'); }); diff --git a/packages/core/cairo/src/utils/convert-strings.ts b/packages/core/cairo/src/utils/convert-strings.ts index da8d0fe57..4f00d33d9 100644 --- a/packages/core/cairo/src/utils/convert-strings.ts +++ b/packages/core/cairo/src/utils/convert-strings.ts @@ -1,19 +1,19 @@ -import { OptionsError } from "../error"; +import { OptionsError } from '../error'; /** * Converts to an identifier according to the rules in https://docs.cairo-lang.org/language_constructs/identifiers.html */ export function toIdentifier(str: string, capitalize = false): string { const result = str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/^[^a-zA-Z_]+/, "") - .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/^[^a-zA-Z_]+/, '') + .replace(/^(.)/, c => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w]+(.?)/g, (_, c) => c.toUpperCase()); if (result.length === 0) { throw new OptionsError({ - name: "Identifier is empty or does not have valid characters", + name: 'Identifier is empty or does not have valid characters', }); } else { return result; @@ -25,10 +25,10 @@ export function toIdentifier(str: string, capitalize = false): string { */ export function toByteArray(str: string): string { return str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters - .replace(/(\\|")/g, (_, c) => "\\" + c); // escape backslash or double quotes + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters + .replace(/(\\|")/g, (_, c) => '\\' + c); // escape backslash or double quotes } /** @@ -36,14 +36,14 @@ export function toByteArray(str: string): string { */ export function toFelt252(str: string, field: string): string { const result = str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/[^\x20-\x7E]+/g, "") // remove non-ascii-printable characters - .replace(/(\\|')/g, (_, c) => "\\" + c); // escape backslash or single quote + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/[^\x20-\x7E]+/g, '') // remove non-ascii-printable characters + .replace(/(\\|')/g, (_, c) => '\\' + c); // escape backslash or single quote if (result.length > 31) { throw new OptionsError({ - [field]: "String is longer than 31 characters", + [field]: 'String is longer than 31 characters', }); } else { return result; @@ -55,12 +55,10 @@ function maxValueOfUint(bits: number): bigint { throw new Error(`Number of bits must be positive (actual '${bits}').`); } if (bits % 8 !== 0) { - throw new Error( - `The number of bits must be a multiple of 8 (actual '${bits}').`, - ); + throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`); } const bytes = bits / 8; - return BigInt("0x" + "ff".repeat(bytes)); + return BigInt('0x' + 'ff'.repeat(bytes)); } const UINT_MAX_VALUES = { @@ -77,16 +75,12 @@ export type UintType = keyof typeof UINT_MAX_VALUES; /** * Checks that a string/number value is a valid `uint` value and converts it to bigint */ -export function toUint( - value: number | string, - field: string, - type: UintType, -): bigint { +export function toUint(value: number | string, field: string, type: UintType): bigint { const valueAsStr = value.toString(); const isValidNumber = /^\d+$/.test(valueAsStr); if (!isValidNumber) { throw new OptionsError({ - [field]: "Not a valid number", + [field]: 'Not a valid number', }); } const numValue = BigInt(valueAsStr); @@ -101,10 +95,6 @@ export function toUint( /** * Checks that a string/number value is a valid `uint` value */ -export function validateUint( - value: number | string, - field: string, - type: UintType, -): void { +export function validateUint(value: number | string, field: string, type: UintType): void { const _ = toUint(value, field, type); } diff --git a/packages/core/cairo/src/utils/define-components.ts b/packages/core/cairo/src/utils/define-components.ts index f929db64f..4fb13f4b2 100644 --- a/packages/core/cairo/src/utils/define-components.ts +++ b/packages/core/cairo/src/utils/define-components.ts @@ -1,18 +1,9 @@ -import type { Component } from "../contract"; +import type { Component } from '../contract'; -type ImplicitNameComponent = Omit; +type ImplicitNameComponent = Omit; -export function defineComponents( - fns: Record, -): Record; +export function defineComponents(fns: Record): Record; -export function defineComponents( - modules: Record, -): Record { - return Object.fromEntries( - Object.entries(modules).map(([name, module]) => [ - name, - Object.assign({ name }, module), - ]), - ); +export function defineComponents(modules: Record): Record { + return Object.fromEntries(Object.entries(modules).map(([name, module]) => [name, Object.assign({ name }, module)])); } diff --git a/packages/core/cairo/src/utils/define-functions.ts b/packages/core/cairo/src/utils/define-functions.ts index c05316bce..3c89e6c76 100644 --- a/packages/core/cairo/src/utils/define-functions.ts +++ b/packages/core/cairo/src/utils/define-functions.ts @@ -1,18 +1,9 @@ -import type { BaseFunction } from "../contract"; +import type { BaseFunction } from '../contract'; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; -export function defineFunctions( - fns: Record, -): Record; +export function defineFunctions(fns: Record): Record; -export function defineFunctions( - fns: Record, -): Record { - return Object.fromEntries( - Object.entries(fns).map(([name, fn]) => [ - name, - Object.assign({ name }, fn), - ]), - ); +export function defineFunctions(fns: Record): Record { + return Object.fromEntries(Object.entries(fns).map(([name, fn]) => [name, Object.assign({ name }, fn)])); } diff --git a/packages/core/cairo/src/utils/duration.ts b/packages/core/cairo/src/utils/duration.ts index 224de5dfa..cfbaf279e 100644 --- a/packages/core/cairo/src/utils/duration.ts +++ b/packages/core/cairo/src/utils/duration.ts @@ -1,16 +1,6 @@ -const durationUnits = [ - "second", - "minute", - "hour", - "day", - "week", - "month", - "year", -] as const; +const durationUnits = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; type DurationUnit = (typeof durationUnits)[number]; -export const durationPattern = new RegExp( - `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, -); +export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); const second = 1; const minute = 60 * second; @@ -25,7 +15,7 @@ export function durationToTimestamp(duration: string): number { const match = duration.trim().match(durationPattern); if (!match || match.length < 2) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = parseFloat(match[1]!); diff --git a/packages/core/cairo/src/utils/find-cover.ts b/packages/core/cairo/src/utils/find-cover.ts index 0cc7f0bb6..939ed9240 100644 --- a/packages/core/cairo/src/utils/find-cover.ts +++ b/packages/core/cairo/src/utils/find-cover.ts @@ -1,14 +1,11 @@ -import { sortedBy } from "./sorted-by"; +import { sortedBy } from './sorted-by'; // Greedy approximation of minimum set cover. -export function findCover( - sets: T[], - getElements: (set: T) => unknown[], -): T[] { +export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { const sortedSets = sortedBy( - sets.map((set) => ({ set, elems: getElements(set) })), - (s) => -s.elems.length, + sets.map(set => ({ set, elems: getElements(set) })), + s => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/cairo/src/utils/format-lines.ts b/packages/core/cairo/src/utils/format-lines.ts index 66a39941e..52af7ab52 100644 --- a/packages/core/cairo/src/utils/format-lines.ts +++ b/packages/core/cairo/src/utils/format-lines.ts @@ -1,29 +1,26 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol("whitespace"); +const whitespace = Symbol('whitespace'); export function formatLines(...lines: Lines[]): string { - return [...indentEach(0, lines)].join("\n") + "\n"; + return [...indentEach(0, lines)].join('\n') + '\n'; } -function* indentEach( - indent: number, - lines: Lines[], -): Generator { +function* indentEach(indent: number, lines: Lines[]): Generator { for (const line of lines) { if (line === whitespace) { - yield ""; + yield ''; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line); } else { - yield " ".repeat(indent) + line; + yield ' '.repeat(indent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter((l) => l.length > 0) - .flatMap((l) => [whitespace, ...l]) + .filter(l => l.length > 0) + .flatMap(l => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/cairo/src/utils/version.test.ts b/packages/core/cairo/src/utils/version.test.ts index a612a88e8..735d62527 100644 --- a/packages/core/cairo/src/utils/version.test.ts +++ b/packages/core/cairo/src/utils/version.test.ts @@ -1,10 +1,10 @@ -import test from "ava"; +import test from 'ava'; -import semver from "semver"; +import semver from 'semver'; -import { contractsVersion, compatibleContractsSemver } from "./version"; +import { contractsVersion, compatibleContractsSemver } from './version'; -test("latest target contracts satisfies compatible range", (t) => { +test('latest target contracts satisfies compatible range', t => { t.true( semver.satisfies(contractsVersion, compatibleContractsSemver), `Latest target contracts version ${contractsVersion} does not satisfy compatible range ${compatibleContractsSemver}. diff --git a/packages/core/cairo/src/utils/version.ts b/packages/core/cairo/src/utils/version.ts index 89eaca1a3..ccba00acc 100644 --- a/packages/core/cairo/src/utils/version.ts +++ b/packages/core/cairo/src/utils/version.ts @@ -1,17 +1,17 @@ /** * The actual latest version to use in links. */ -export const contractsVersion = "0.20.0"; +export const contractsVersion = '0.20.0'; export const contractsVersionTag = `v${contractsVersion}`; /** * Cairo compiler versions. */ -export const edition = "2024_07"; -export const cairoVersion = "2.9.1"; -export const scarbVersion = "2.9.1"; +export const edition = '2024_07'; +export const cairoVersion = '2.9.1'; +export const scarbVersion = '2.9.1'; /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = "^0.20.0"; +export const compatibleContractsSemver = '^0.20.0'; diff --git a/packages/core/cairo/src/vesting.test.ts b/packages/core/cairo/src/vesting.test.ts index cf0fa48fc..76139b28b 100644 --- a/packages/core/cairo/src/vesting.test.ts +++ b/packages/core/cairo/src/vesting.test.ts @@ -1,27 +1,29 @@ -import test from "ava"; -import { OptionsError, vesting } from "."; -import { buildVesting, VestingOptions } from "./vesting"; -import { printContract } from "./print"; +import test from 'ava'; +import type { OptionsError } from '.'; +import { vesting } from '.'; +import type { VestingOptions } from './vesting'; +import { buildVesting } from './vesting'; +import { printContract } from './print'; const defaults: VestingOptions = { - name: "MyVesting", - startDate: "", - duration: "0 day", - cliffDuration: "0 day", - schedule: "linear", + name: 'MyVesting', + startDate: '', + duration: '0 day', + cliffDuration: '0 day', + schedule: 'linear', }; -const CUSTOM_NAME = "CustomVesting"; -const CUSTOM_DATE = "2024-12-31T23:59"; -const CUSTOM_DURATION = "36 months"; -const CUSTOM_CLIFF = "90 days"; +const CUSTOM_NAME = 'CustomVesting'; +const CUSTOM_DATE = '2024-12-31T23:59'; +const CUSTOM_DURATION = '36 months'; +const CUSTOM_CLIFF = '90 days'; // // Test helpers // function testVesting(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildVesting({ ...defaults, ...opts, @@ -31,7 +33,7 @@ function testVesting(title: string, opts: Partial) { } function testAPIEquivalence(title: string, opts?: VestingOptions) { - test(title, (t) => { + test(title, t => { t.is( vesting.print(opts), printContract( @@ -48,82 +50,82 @@ function testAPIEquivalence(title: string, opts?: VestingOptions) { // Snapshot tests // -testVesting("custom name", { +testVesting('custom name', { name: CUSTOM_NAME, }); -testVesting("custom start date", { +testVesting('custom start date', { startDate: CUSTOM_DATE, }); -testVesting("custom duration", { +testVesting('custom duration', { duration: CUSTOM_DURATION, }); -testVesting("custom cliff", { +testVesting('custom cliff', { duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, }); -testVesting("custom schedule", { - schedule: "custom", +testVesting('custom schedule', { + schedule: 'custom', }); -testVesting("all custom settings", { +testVesting('all custom settings', { startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: "custom", + schedule: 'custom', }); // // API tests // -testAPIEquivalence("API custom name", { +testAPIEquivalence('API custom name', { ...defaults, name: CUSTOM_NAME, }); -testAPIEquivalence("API custom start date", { +testAPIEquivalence('API custom start date', { ...defaults, startDate: CUSTOM_DATE, }); -testAPIEquivalence("API custom duration", { +testAPIEquivalence('API custom duration', { ...defaults, duration: CUSTOM_DURATION, }); -testAPIEquivalence("API custom cliff", { +testAPIEquivalence('API custom cliff', { ...defaults, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, }); -testAPIEquivalence("API custom schedule", { +testAPIEquivalence('API custom schedule', { ...defaults, - schedule: "custom", + schedule: 'custom', }); -testAPIEquivalence("API all custom settings", { +testAPIEquivalence('API all custom settings', { ...defaults, startDate: CUSTOM_DATE, duration: CUSTOM_DURATION, cliffDuration: CUSTOM_CLIFF, - schedule: "custom", + schedule: 'custom', }); -test("cliff too high", async (t) => { +test('cliff too high', async t => { const error = t.throws(() => buildVesting({ ...defaults, - duration: "20 days", - cliffDuration: "21 days", + duration: '20 days', + cliffDuration: '21 days', }), ); t.is( (error as OptionsError).messages.cliffDuration, - "Cliff duration must be less than or equal to the total duration", + 'Cliff duration must be less than or equal to the total duration', ); }); diff --git a/packages/core/cairo/src/vesting.ts b/packages/core/cairo/src/vesting.ts index 398a509a9..451c42a80 100644 --- a/packages/core/cairo/src/vesting.ts +++ b/packages/core/cairo/src/vesting.ts @@ -1,22 +1,24 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from "./contract"; -import { contractDefaults as commonDefaults } from "./common-options"; -import { setAccessControl } from "./set-access-control"; -import { setUpgradeable } from "./set-upgradeable"; -import { Info, setInfo } from "./set-info"; -import { defineComponents } from "./utils/define-components"; -import { printContract } from "./print"; -import { OptionsError } from "./error"; -import { durationToTimestamp } from "./utils/duration"; -import { toUint, validateUint } from "./utils/convert-strings"; - -export type VestingSchedule = "linear" | "custom"; +import type { BaseImplementedTrait, Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { contractDefaults as commonDefaults } from './common-options'; +import { setAccessControl } from './set-access-control'; +import { setUpgradeable } from './set-upgradeable'; +import type { Info } from './set-info'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { printContract } from './print'; +import { OptionsError } from './error'; +import { durationToTimestamp } from './utils/duration'; +import { toUint, validateUint } from './utils/convert-strings'; + +export type VestingSchedule = 'linear' | 'custom'; export const defaults: Required = { - name: "VestingWallet", - startDate: "", - duration: "0 day", - cliffDuration: "0 day", - schedule: "custom", + name: 'VestingWallet', + startDate: '', + duration: '0 day', + cliffDuration: '0 day', + schedule: 'custom', info: commonDefaults.info, } as const; @@ -53,7 +55,7 @@ export function buildVesting(opts: VestingOptions): Contract { setInfo(c, allOpts.info); // Vesting component depends on Ownable component - const access = "ownable"; + const access = 'ownable'; setAccessControl(c, access); // Must be non-upgradable to guarantee vesting according to the schedule @@ -63,107 +65,94 @@ export function buildVesting(opts: VestingOptions): Contract { } function addBase(c: ContractBuilder, opts: VestingOptions) { - c.addUseClause("starknet", "ContractAddress"); + c.addUseClause('starknet', 'ContractAddress'); const startDate = getVestingStart(opts); const totalDuration = getVestingDuration(opts); const cliffDuration = getCliffDuration(opts); validateDurations(totalDuration, cliffDuration); if (startDate !== undefined) { c.addConstant({ - name: "START", - type: "u64", + name: 'START', + type: 'u64', value: startDate.timestampInSec.toString(), comment: startDate.formattedDate, inlineComment: true, }); } else { c.addConstant({ - name: "START", - type: "u64", - value: "0", + name: 'START', + type: 'u64', + value: '0', }); } c.addConstant({ - name: "DURATION", - type: "u64", + name: 'DURATION', + type: 'u64', value: totalDuration.toString(), comment: opts.duration, inlineComment: true, }); c.addConstant({ - name: "CLIFF_DURATION", - type: "u64", + name: 'CLIFF_DURATION', + type: 'u64', value: cliffDuration.toString(), comment: opts.cliffDuration, inlineComment: true, }); - const initParams = [ - { lit: "START" }, - { lit: "DURATION" }, - { lit: "CLIFF_DURATION" }, - ]; + const initParams = [{ lit: 'START' }, { lit: 'DURATION' }, { lit: 'CLIFF_DURATION' }]; c.addComponent(components.VestingComponent, initParams, true); } function addSchedule(c: ContractBuilder, opts: VestingOptions) { switch (opts.schedule) { - case "linear": - c.addUseClause("openzeppelin::finance::vesting", "LinearVestingSchedule"); + case 'linear': + c.addUseClause('openzeppelin::finance::vesting', 'LinearVestingSchedule'); return; - case "custom": { + case 'custom': { const scheduleTrait: BaseImplementedTrait = { name: `VestingSchedule`, - of: "VestingComponent::VestingScheduleTrait", + of: 'VestingComponent::VestingScheduleTrait', tags: [], priority: 0, }; c.addImplementedTrait(scheduleTrait); c.addFunction(scheduleTrait, { - name: "calculate_vested_amount", - returns: "u256", + name: 'calculate_vested_amount', + returns: 'u256', args: [ { - name: "self", + name: 'self', type: `@VestingComponent::ComponentState`, }, - { name: "token", type: "ContractAddress" }, - { name: "total_allocation", type: "u256" }, - { name: "timestamp", type: "u64" }, - { name: "start", type: "u64" }, - { name: "duration", type: "u64" }, - { name: "cliff", type: "u64" }, - ], - code: [ - "// TODO: Must be implemented according to the desired vesting schedule", - "0", + { name: 'token', type: 'ContractAddress' }, + { name: 'total_allocation', type: 'u256' }, + { name: 'timestamp', type: 'u64' }, + { name: 'start', type: 'u64' }, + { name: 'duration', type: 'u64' }, + { name: 'cliff', type: 'u64' }, ], + code: ['// TODO: Must be implemented according to the desired vesting schedule', '0'], }); return; } } } -function getVestingStart( - opts: VestingOptions, -): { timestampInSec: bigint; formattedDate: string } | undefined { - if (opts.startDate === "" || opts.startDate === "NaN") { +function getVestingStart(opts: VestingOptions): { timestampInSec: bigint; formattedDate: string } | undefined { + if (opts.startDate === '' || opts.startDate === 'NaN') { return undefined; } const startDate = new Date(`${opts.startDate}Z`); const timestampInMillis = startDate.getTime(); - const timestampInSec = toUint( - Math.floor(timestampInMillis / 1000), - "startDate", - "u64", - ); - const formattedDate = startDate.toLocaleString("en-GB", { - day: "2-digit", - month: "short", - year: "numeric", - hour: "2-digit", - minute: "2-digit", + const timestampInSec = toUint(Math.floor(timestampInMillis / 1000), 'startDate', 'u64'); + const formattedDate = startDate.toLocaleString('en-GB', { + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', hour12: false, - timeZone: "UTC", + timeZone: 'UTC', }); return { timestampInSec, formattedDate: `${formattedDate} (UTC)` }; } @@ -197,8 +186,8 @@ function getCliffDuration(opts: VestingOptions): number { } function validateDurations(duration: number, cliffDuration: number): void { - validateUint(duration, "duration", "u64"); - validateUint(cliffDuration, "cliffDuration", "u64"); + validateUint(duration, 'duration', 'u64'); + validateUint(cliffDuration, 'cliffDuration', 'u64'); if (cliffDuration > duration) { throw new OptionsError({ cliffDuration: `Cliff duration must be less than or equal to the total duration`, @@ -208,24 +197,24 @@ function validateDurations(duration: number, cliffDuration: number): void { const components = defineComponents({ VestingComponent: { - path: "openzeppelin::finance::vesting", + path: 'openzeppelin::finance::vesting', substorage: { - name: "vesting", - type: "VestingComponent::Storage", + name: 'vesting', + type: 'VestingComponent::Storage', }, event: { - name: "VestingEvent", - type: "VestingComponent::Event", + name: 'VestingEvent', + type: 'VestingComponent::Event', }, impls: [ { - name: "VestingImpl", - value: "VestingComponent::VestingImpl", + name: 'VestingImpl', + value: 'VestingComponent::VestingImpl', }, { - name: "VestingInternalImpl", + name: 'VestingInternalImpl', embed: false, - value: "VestingComponent::InternalImpl", + value: 'VestingComponent::InternalImpl', }, ], }, diff --git a/packages/core/solidity/ava.config.js b/packages/core/solidity/ava.config.js index a39075959..e39146f7a 100644 --- a/packages/core/solidity/ava.config.js +++ b/packages/core/solidity/ava.config.js @@ -1,9 +1,9 @@ module.exports = { - extensions: ["ts"], - require: ["ts-node/register"], + extensions: ['ts'], + require: ['ts-node/register'], watchmode: { - ignoreChanges: ["contracts", "artifacts", "cache"], + ignoreChanges: ['contracts', 'artifacts', 'cache'], }, - timeout: "10m", + timeout: '10m', workerThreads: false, }; diff --git a/packages/core/solidity/get-imports.d.ts b/packages/core/solidity/get-imports.d.ts index d89032e9e..4c0a44175 100644 --- a/packages/core/solidity/get-imports.d.ts +++ b/packages/core/solidity/get-imports.d.ts @@ -1 +1 @@ -export * from "./src/get-imports"; +export * from './src/get-imports'; diff --git a/packages/core/solidity/get-imports.js b/packages/core/solidity/get-imports.js index 52dd2841a..bc85ea526 100644 --- a/packages/core/solidity/get-imports.js +++ b/packages/core/solidity/get-imports.js @@ -1 +1 @@ -module.exports = require("./dist/get-imports"); +module.exports = require('./dist/get-imports'); diff --git a/packages/core/solidity/hardhat.config.js b/packages/core/solidity/hardhat.config.js index 01edf1775..fe8394ddd 100644 --- a/packages/core/solidity/hardhat.config.js +++ b/packages/core/solidity/hardhat.config.js @@ -1,24 +1,21 @@ -const { task } = require("hardhat/config"); -const { HardhatError } = require("hardhat/internal/core/errors"); -const { ERRORS } = require("hardhat/internal/core/errors-list"); +const { task } = require('hardhat/config'); +const { HardhatError } = require('hardhat/internal/core/errors'); +const { ERRORS } = require('hardhat/internal/core/errors-list'); const { TASK_COMPILE_SOLIDITY_CHECK_ERRORS, TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, -} = require("hardhat/builtin-tasks/task-names"); -const SOLIDITY_VERSION = require("./src/solidity-version.json"); +} = require('hardhat/builtin-tasks/task-names'); +const SOLIDITY_VERSION = require('./src/solidity-version.json'); // Unused parameter warnings are caused by OpenZeppelin Upgradeable Contracts. -const WARN_UNUSED_PARAMETER = "5667"; -const WARN_CODE_SIZE = "5574"; +const WARN_UNUSED_PARAMETER = '5667'; +const WARN_CODE_SIZE = '5574'; const IGNORED_WARNINGS = [WARN_UNUSED_PARAMETER, WARN_CODE_SIZE]; // Overriding this task so that warnings are considered errors. task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { - const errors = - (output.errors && - output.errors.filter((e) => !IGNORED_WARNINGS.includes(e.errorCode))) || - []; + const errors = (output.errors && output.errors.filter(e => !IGNORED_WARNINGS.includes(e.errorCode))) || []; await run(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS, { output: { ...output, errors }, @@ -30,20 +27,15 @@ task(TASK_COMPILE_SOLIDITY_CHECK_ERRORS, async ({ output, quiet }, { run }) => { } }); -task( - TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, - async ({ compilationJobs }, _, runSuper) => { - const CHUNK_SIZE = 100; - const chunks = []; - for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { - chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); - } - const mergedChunks = await Promise.all( - chunks.map((cj) => runSuper({ compilationJobs: cj })), - ); - return mergedChunks.flat(); - }, -); +task(TASK_COMPILE_SOLIDITY_MERGE_COMPILATION_JOBS, async ({ compilationJobs }, _, runSuper) => { + const CHUNK_SIZE = 100; + const chunks = []; + for (let i = 0; i < compilationJobs.length - 1; i += CHUNK_SIZE) { + chunks.push(compilationJobs.slice(i, i + CHUNK_SIZE)); + } + const mergedChunks = await Promise.all(chunks.map(cj => runSuper({ compilationJobs: cj }))); + return mergedChunks.flat(); +}); /** * @type import('hardhat/config').HardhatUserConfig diff --git a/packages/core/solidity/print-versioned.js b/packages/core/solidity/print-versioned.js index c3861b0fa..2bc0b8aa3 100644 --- a/packages/core/solidity/print-versioned.js +++ b/packages/core/solidity/print-versioned.js @@ -1 +1 @@ -module.exports = require("./dist/print-versioned"); +module.exports = require('./dist/print-versioned'); diff --git a/packages/core/solidity/print-versioned.ts b/packages/core/solidity/print-versioned.ts index ee52117b8..95629a774 100644 --- a/packages/core/solidity/print-versioned.ts +++ b/packages/core/solidity/print-versioned.ts @@ -1 +1 @@ -export * from "./src/print-versioned"; +export * from './src/print-versioned'; diff --git a/packages/core/solidity/src/add-pausable.ts b/packages/core/solidity/src/add-pausable.ts index e6fedab08..12fe36288 100644 --- a/packages/core/solidity/src/add-pausable.ts +++ b/packages/core/solidity/src/add-pausable.ts @@ -1,40 +1,37 @@ -import type { ContractBuilder, BaseFunction } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder, BaseFunction } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; -export function addPausable( - c: ContractBuilder, - access: Access, - pausableFns: BaseFunction[], -) { +export function addPausable(c: ContractBuilder, access: Access, pausableFns: BaseFunction[]) { c.addParent({ - name: "Pausable", - path: "@openzeppelin/contracts/utils/Pausable.sol", + name: 'Pausable', + path: '@openzeppelin/contracts/utils/Pausable.sol', }); for (const fn of pausableFns) { - c.addModifier("whenNotPaused", fn); + c.addModifier('whenNotPaused', fn); } addPauseFunctions(c, access); } export function addPauseFunctions(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.pause, access, "PAUSER", "pauser"); - c.addFunctionCode("_pause();", functions.pause); + requireAccessControl(c, functions.pause, access, 'PAUSER', 'pauser'); + c.addFunctionCode('_pause();', functions.pause); - requireAccessControl(c, functions.unpause, access, "PAUSER", "pauser"); - c.addFunctionCode("_unpause();", functions.unpause); + requireAccessControl(c, functions.unpause, access, 'PAUSER', 'pauser'); + c.addFunctionCode('_unpause();', functions.unpause); } const functions = defineFunctions({ pause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, unpause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, }); diff --git a/packages/core/solidity/src/api.ts b/packages/core/solidity/src/api.ts index e7c8f1941..43ce36e6d 100644 --- a/packages/core/solidity/src/api.ts +++ b/packages/core/solidity/src/api.ts @@ -1,40 +1,40 @@ -import type { CommonOptions } from "./common-options"; +import type { CommonOptions } from './common-options'; +import type { ERC20Options } from './erc20'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, - ERC20Options, -} from "./erc20"; +} from './erc20'; +import type { ERC721Options } from './erc721'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, - ERC721Options, -} from "./erc721"; +} from './erc721'; +import type { ERC1155Options } from './erc1155'; import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, - ERC1155Options, -} from "./erc1155"; +} from './erc1155'; +import type { StablecoinOptions } from './stablecoin'; import { printStablecoin, defaults as stablecoinDefaults, isAccessControlRequired as stablecoinIsAccessControlRequired, - StablecoinOptions, -} from "./stablecoin"; +} from './stablecoin'; +import type { GovernorOptions } from './governor'; import { printGovernor, defaults as governorDefaults, isAccessControlRequired as governorIsAccessControlRequired, - GovernorOptions, -} from "./governor"; +} from './governor'; +import type { CustomOptions } from './custom'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, - CustomOptions, -} from "./custom"; +} from './custom'; export interface WizardContractAPI { /** diff --git a/packages/core/solidity/src/build-generic.ts b/packages/core/solidity/src/build-generic.ts index 8487be2e3..fe64543fa 100644 --- a/packages/core/solidity/src/build-generic.ts +++ b/packages/core/solidity/src/build-generic.ts @@ -1,49 +1,55 @@ -import { CustomOptions, buildCustom } from "./custom"; -import { ERC20Options, buildERC20 } from "./erc20"; -import { ERC721Options, buildERC721 } from "./erc721"; -import { ERC1155Options, buildERC1155 } from "./erc1155"; -import { StablecoinOptions, buildStablecoin } from "./stablecoin"; -import { GovernorOptions, buildGovernor } from "./governor"; -import { Contract } from "./contract"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import type { StablecoinOptions } from './stablecoin'; +import { buildStablecoin } from './stablecoin'; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import type { Contract } from './contract'; export interface KindedOptions { - ERC20: { kind: "ERC20" } & ERC20Options; - ERC721: { kind: "ERC721" } & ERC721Options; - ERC1155: { kind: "ERC1155" } & ERC1155Options; - Stablecoin: { kind: "Stablecoin" } & StablecoinOptions; - RealWorldAsset: { kind: "RealWorldAsset" } & StablecoinOptions; - Governor: { kind: "Governor" } & GovernorOptions; - Custom: { kind: "Custom" } & CustomOptions; + ERC20: { kind: 'ERC20' } & ERC20Options; + ERC721: { kind: 'ERC721' } & ERC721Options; + ERC1155: { kind: 'ERC1155' } & ERC1155Options; + Stablecoin: { kind: 'Stablecoin' } & StablecoinOptions; + RealWorldAsset: { kind: 'RealWorldAsset' } & StablecoinOptions; + Governor: { kind: 'Governor' } & GovernorOptions; + Custom: { kind: 'Custom' } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; export function buildGeneric(opts: GenericOptions): Contract { switch (opts.kind) { - case "ERC20": + case 'ERC20': return buildERC20(opts); - case "ERC721": + case 'ERC721': return buildERC721(opts); - case "ERC1155": + case 'ERC1155': return buildERC1155(opts); - case "Stablecoin": + case 'Stablecoin': return buildStablecoin(opts); - case "RealWorldAsset": + case 'RealWorldAsset': return buildStablecoin(opts); - case "Governor": + case 'Governor': return buildGovernor(opts); - case "Custom": + case 'Custom': return buildCustom(opts); default: { const _: never = opts; - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } } diff --git a/packages/core/solidity/src/common-functions.ts b/packages/core/solidity/src/common-functions.ts index 7647afdad..cf311c898 100644 --- a/packages/core/solidity/src/common-functions.ts +++ b/packages/core/solidity/src/common-functions.ts @@ -1,9 +1,9 @@ -import type { BaseFunction } from "./contract"; +import type { BaseFunction } from './contract'; export const supportsInterface: BaseFunction = { - name: "supportsInterface", - kind: "public", - args: [{ name: "interfaceId", type: "bytes4" }], - returns: ["bool"], - mutability: "view", + name: 'supportsInterface', + kind: 'public', + args: [{ name: 'interfaceId', type: 'bytes4' }], + returns: ['bool'], + mutability: 'view', }; diff --git a/packages/core/solidity/src/common-options.ts b/packages/core/solidity/src/common-options.ts index 9e3e59e9c..b001ae5f6 100644 --- a/packages/core/solidity/src/common-options.ts +++ b/packages/core/solidity/src/common-options.ts @@ -1,7 +1,7 @@ -import type { Access } from "./set-access-control"; -import type { Info } from "./set-info"; -import { defaults as infoDefaults } from "./set-info"; -import type { Upgradeable } from "./set-upgradeable"; +import type { Access } from './set-access-control'; +import type { Info } from './set-info'; +import { defaults as infoDefaults } from './set-info'; +import type { Upgradeable } from './set-upgradeable'; export const defaults: Required = { access: false, @@ -15,9 +15,7 @@ export interface CommonOptions { info?: Info; } -export function withCommonDefaults( - opts: CommonOptions, -): Required { +export function withCommonDefaults(opts: CommonOptions): Required { return { access: opts.access ?? false, upgradeable: opts.upgradeable ?? false, diff --git a/packages/core/solidity/src/contract.test.ts b/packages/core/solidity/src/contract.test.ts index 1c74412f0..0a1824cee 100644 --- a/packages/core/solidity/src/contract.test.ts +++ b/packages/core/solidity/src/contract.test.ts @@ -1,8 +1,8 @@ -import test from "ava"; +import test from 'ava'; -import { ContractBuilder } from "./contract"; -import { printContract } from "./print"; -import { TAG_SECURITY_CONTACT } from "./set-info"; +import { ContractBuilder } from './contract'; +import { printContract } from './print'; +import { TAG_SECURITY_CONTACT } from './set-info'; const toContractReference = (name: string) => { return { @@ -17,153 +17,153 @@ const toParentContract = (name: string, path: string) => { }; }; -test("contract basics", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract basics', t => { + const Foo = new ContractBuilder('Foo'); t.snapshot(printContract(Foo)); }); -test("contract with a parent", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); +test('contract with a parent', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); Foo.addParent(Bar); t.snapshot(printContract(Foo)); }); -test("contract with two parents", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - const Quux = toParentContract("Quux", "./Quux.sol"); +test('contract with two parents', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + const Quux = toParentContract('Quux', './Quux.sol'); Foo.addParent(Bar); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test("contract with a parent with parameters", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - Foo.addParent(Bar, ["param1", "param2"]); +test('contract with a parent with parameters', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + Foo.addParent(Bar, ['param1', 'param2']); t.snapshot(printContract(Foo)); }); -test("contract with two parents only one with parameters", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - const Quux = toParentContract("Quux", "./Quux.sol"); - Foo.addParent(Bar, ["param1", "param2"]); +test('contract with two parents only one with parameters', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + const Quux = toParentContract('Quux', './Quux.sol'); + Foo.addParent(Bar, ['param1', 'param2']); Foo.addParent(Quux); t.snapshot(printContract(Foo)); }); -test("contract with one override", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with one override', t => { + const Foo = new ContractBuilder('Foo'); const _beforeTokenTransfer = { - name: "_beforeTokenTransfer", - kind: "internal" as const, + name: '_beforeTokenTransfer', + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }; - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test("contract with two overrides", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("ERC20Snapshot"), _beforeTokenTransfer); +test('contract with two overrides', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20Snapshot'), _beforeTokenTransfer); t.snapshot(printContract(Foo)); }); -test("contract with two different overrides", (t) => { - const Foo = new ContractBuilder("Foo"); +test('contract with two different overrides', t => { + const Foo = new ContractBuilder('Foo'); - Foo.addOverride(toContractReference("ERC20"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("OtherParent"), _beforeTokenTransfer); - Foo.addOverride(toContractReference("ERC20"), _otherFunction); - Foo.addOverride(toContractReference("OtherParent"), _otherFunction); + Foo.addOverride(toContractReference('ERC20'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('OtherParent'), _beforeTokenTransfer); + Foo.addOverride(toContractReference('ERC20'), _otherFunction); + Foo.addOverride(toContractReference('OtherParent'), _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with a modifier", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addModifier("whenNotPaused", _otherFunction); +test('contract with a modifier', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addModifier('whenNotPaused', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with a modifier and override", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addModifier("whenNotPaused", _otherFunction); +test('contract with a modifier and override', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addModifier('whenNotPaused', _otherFunction); - Foo.addOverride(toContractReference("ERC20"), _otherFunction); - Foo.addOverride(toContractReference("OtherParent"), _otherFunction); + Foo.addOverride(toContractReference('ERC20'), _otherFunction); + Foo.addOverride(toContractReference('OtherParent'), _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with constructor code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); +test('contract with constructor code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); t.snapshot(printContract(Foo)); }); -test("contract with constructor code and a parent", (t) => { - const Foo = new ContractBuilder("Foo"); - const Bar = toParentContract("Bar", "./Bar.sol"); - Foo.addParent(Bar, ["param1", "param2"]); - Foo.addConstructorCode("_mint(msg.sender, 10 ether);"); +test('contract with constructor code and a parent', t => { + const Foo = new ContractBuilder('Foo'); + const Bar = toParentContract('Bar', './Bar.sol'); + Foo.addParent(Bar, ['param1', 'param2']); + Foo.addConstructorCode('_mint(msg.sender, 10 ether);'); t.snapshot(printContract(Foo)); }); -test("contract with function code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); +test('contract with function code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with overridden function with code", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addOverride(toContractReference("Bar"), _otherFunction); - Foo.addFunctionCode("_mint(msg.sender, 10 ether);", _otherFunction); +test('contract with overridden function with code', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addOverride(toContractReference('Bar'), _otherFunction); + Foo.addFunctionCode('_mint(msg.sender, 10 ether);', _otherFunction); t.snapshot(printContract(Foo)); }); -test("contract with one variable", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addVariable("uint value = 42;"); +test('contract with one variable', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addVariable('uint value = 42;'); t.snapshot(printContract(Foo)); }); -test("contract with two variables", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addVariable("uint value = 42;"); +test('contract with two variables', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addVariable('uint value = 42;'); Foo.addVariable('string name = "john";'); t.snapshot(printContract(Foo)); }); -test("name with special characters", (t) => { - const Foo = new ContractBuilder("foo bar baz"); +test('name with special characters', t => { + const Foo = new ContractBuilder('foo bar baz'); t.snapshot(printContract(Foo)); }); -test("contract with info", (t) => { - const Foo = new ContractBuilder("Foo"); - Foo.addNatspecTag(TAG_SECURITY_CONTACT, "security@example.com"); +test('contract with info', t => { + const Foo = new ContractBuilder('Foo'); + Foo.addNatspecTag(TAG_SECURITY_CONTACT, 'security@example.com'); t.snapshot(printContract(Foo)); }); const _beforeTokenTransfer = { - name: "_beforeTokenTransfer", - kind: "internal" as const, + name: '_beforeTokenTransfer', + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }; const _otherFunction = { - name: "_otherFunction", - kind: "internal" as const, + name: '_otherFunction', + kind: 'internal' as const, args: [], }; diff --git a/packages/core/solidity/src/contract.ts b/packages/core/solidity/src/contract.ts index 560bf4cae..6ed276124 100644 --- a/packages/core/solidity/src/contract.ts +++ b/packages/core/solidity/src/contract.ts @@ -1,4 +1,4 @@ -import { toIdentifier } from "./utils/to-identifier"; +import { toIdentifier } from './utils/to-identifier'; export interface Contract { name: string; @@ -13,11 +13,7 @@ export interface Contract { upgradeable: boolean; } -export type Value = - | string - | number - | { lit: string } - | { note: string; value: Value }; +export type Value = string | number | { lit: string } | { note: string; value: Value }; export interface Parent { contract: ImportContract; @@ -56,19 +52,14 @@ export interface ContractFunction extends BaseFunction { comments: string[]; } -export type FunctionKind = "internal" | "public"; +export type FunctionKind = 'internal' | 'public'; export type FunctionMutability = (typeof mutabilityRank)[number]; // Order is important -const mutabilityRank = ["pure", "view", "nonpayable", "payable"] as const; - -function maxMutability( - a: FunctionMutability, - b: FunctionMutability, -): FunctionMutability { - return mutabilityRank[ - Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b)) - ]!; +const mutabilityRank = ['pure', 'view', 'nonpayable', 'payable'] as const; + +function maxMutability(a: FunctionMutability, b: FunctionMutability): FunctionMutability { + return mutabilityRank[Math.max(mutabilityRank.indexOf(a), mutabilityRank.indexOf(b))]!; } export interface FunctionArgument { @@ -83,7 +74,7 @@ export interface NatspecTag { export class ContractBuilder implements Contract { readonly name: string; - license: string = "MIT"; + license: string = 'MIT'; upgradeable = false; readonly using: Using[] = []; @@ -102,11 +93,11 @@ export class ContractBuilder implements Contract { get parents(): Parent[] { return [...this.parentMap.values()] - .filter((p) => !p.importOnly) + .filter(p => !p.importOnly) .sort((a, b) => { - if (a.contract.name === "Initializable") { + if (a.contract.name === 'Initializable') { return -1; - } else if (b.contract.name === "Initializable") { + } else if (b.contract.name === 'Initializable') { return 1; } else { return 0; @@ -115,10 +106,7 @@ export class ContractBuilder implements Contract { } get imports(): ImportContract[] { - return [ - ...[...this.parentMap.values()].map((p) => p.contract), - ...this.using.map((u) => u.library), - ]; + return [...[...this.parentMap.values()].map(p => p.contract), ...this.using.map(u => u.library)]; } get functions(): ContractFunction[] { @@ -145,11 +133,7 @@ export class ContractBuilder implements Contract { return !present; } - addOverride( - parent: ReferencedContract, - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + addOverride(parent: ReferencedContract, baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); fn.override.add(parent); if (mutability) { @@ -164,18 +148,12 @@ export class ContractBuilder implements Contract { addNatspecTag(key: string, value: string) { // eslint-disable-next-line no-useless-escape - if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) - throw new Error(`Invalid natspec key: ${key}`); + if (!/^(@custom:)?[a-z][a-z\-]*$/.exec(key)) throw new Error(`Invalid natspec key: ${key}`); this.natspecTags.push({ key, value }); } private addFunction(baseFn: BaseFunction): ContractFunction { - const signature = [ - baseFn.name, - "(", - ...baseFn.args.map((a) => a.name), - ")", - ].join(""); + const signature = [baseFn.name, '(', ...baseFn.args.map(a => a.name), ')'].join(''); const got = this.functionMap.get(signature); if (got !== undefined) { return got; @@ -184,7 +162,7 @@ export class ContractBuilder implements Contract { override: new Set(), modifiers: [], code: [], - mutability: "nonpayable", + mutability: 'nonpayable', final: false, comments: [], ...baseFn, @@ -202,11 +180,7 @@ export class ContractBuilder implements Contract { this.constructorCode.push(code); } - addFunctionCode( - code: string, - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + addFunctionCode(code: string, baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); if (fn.final) { throw new Error(`Function ${baseFn.name} is already finalized`); @@ -217,11 +191,7 @@ export class ContractBuilder implements Contract { } } - setFunctionBody( - code: string[], - baseFn: BaseFunction, - mutability?: FunctionMutability, - ) { + setFunctionBody(code: string[], baseFn: BaseFunction, mutability?: FunctionMutability) { const fn = this.addFunction(baseFn); if (fn.code.length > 0) { throw new Error(`Function ${baseFn.name} has additional code`); diff --git a/packages/core/solidity/src/custom.test.ts b/packages/core/solidity/src/custom.test.ts index 3299f1cbe..751b40277 100644 --- a/packages/core/solidity/src/custom.test.ts +++ b/packages/core/solidity/src/custom.test.ts @@ -1,13 +1,14 @@ -import test from "ava"; -import { custom } from "."; +import test from 'ava'; +import { custom } from '.'; -import { buildCustom, CustomOptions } from "./custom"; -import { printContract } from "./print"; +import type { CustomOptions } from './custom'; +import { buildCustom } from './custom'; +import { printContract } from './print'; function testCustom(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }); t.snapshot(printContract(c)); @@ -18,12 +19,12 @@ function testCustom(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: CustomOptions) { - test(title, (t) => { + test(title, t => { t.is( custom.print(opts), printContract( buildCustom({ - name: "MyContract", + name: 'MyContract', ...opts, }), ), @@ -31,66 +32,66 @@ function testAPIEquivalence(title: string, opts?: CustomOptions) { }); } -testCustom("custom", {}); +testCustom('custom', {}); -testCustom("pausable", { +testCustom('pausable', { pausable: true, }); -testCustom("upgradeable transparent", { - upgradeable: "transparent", +testCustom('upgradeable transparent', { + upgradeable: 'transparent', }); -testCustom("upgradeable uups", { - upgradeable: "uups", +testCustom('upgradeable uups', { + upgradeable: 'uups', }); -testCustom("access control disabled", { +testCustom('access control disabled', { access: false, }); -testCustom("access control ownable", { - access: "ownable", +testCustom('access control ownable', { + access: 'ownable', }); -testCustom("access control roles", { - access: "roles", +testCustom('access control roles', { + access: 'roles', }); -testCustom("access control managed", { - access: "managed", +testCustom('access control managed', { + access: 'managed', }); -testCustom("upgradeable uups with access control disabled", { +testCustom('upgradeable uups with access control disabled', { // API should override access to true since it is required for UUPS access: false, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("custom API default"); +testAPIEquivalence('custom API default'); -testAPIEquivalence("custom API basic", { name: "CustomContract" }); +testAPIEquivalence('custom API basic', { name: 'CustomContract' }); -testAPIEquivalence("custom API full upgradeable", { - name: "CustomContract", - access: "roles", +testAPIEquivalence('custom API full upgradeable', { + name: 'CustomContract', + access: 'roles', pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("custom API full upgradeable with managed", { - name: "CustomContract", - access: "managed", +testAPIEquivalence('custom API full upgradeable with managed', { + name: 'CustomContract', + access: 'managed', pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("custom API assert defaults", async (t) => { +test('custom API assert defaults', async t => { t.is(custom.print(custom.defaults), custom.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(custom.isAccessControlRequired({ pausable: true }), true); - t.is(custom.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(custom.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(custom.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(custom.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/custom.ts b/packages/core/solidity/src/custom.ts index b167015ed..5baaf31b5 100644 --- a/packages/core/solidity/src/custom.ts +++ b/packages/core/solidity/src/custom.ts @@ -1,14 +1,12 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { setAccessControl } from "./set-access-control"; -import { addPausable } from "./add-pausable"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { setAccessControl } from './set-access-control'; +import { addPausable } from './add-pausable'; +import { printContract } from './print'; export interface CustomOptions extends CommonOptions { name: string; @@ -16,7 +14,7 @@ export interface CustomOptions extends CommonOptions { } export const defaults: Required = { - name: "MyContract", + name: 'MyContract', pausable: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, @@ -36,7 +34,7 @@ export function printCustom(opts: CustomOptions = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.pausable || opts.upgradeable === "uups"; + return opts.pausable || opts.upgradeable === 'uups'; } export function buildCustom(opts: CustomOptions): Contract { diff --git a/packages/core/solidity/src/erc1155.test.ts b/packages/core/solidity/src/erc1155.test.ts index 91e20ceae..bea4ae13e 100644 --- a/packages/core/solidity/src/erc1155.test.ts +++ b/packages/core/solidity/src/erc1155.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc1155 } from "."; +import test from 'ava'; +import { erc1155 } from '.'; -import { buildERC1155, ERC1155Options } from "./erc1155"; -import { printContract } from "./print"; +import type { ERC1155Options } from './erc1155'; +import { buildERC1155 } from './erc1155'; +import { printContract } from './print'; function testERC1155(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC1155({ - name: "MyToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", + name: 'MyToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC1155(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC1155Options) { - test(title, (t) => { + test(title, t => { t.is( erc1155.print(opts), printContract( buildERC1155({ - name: "MyToken", - uri: "", + name: 'MyToken', + uri: '', ...opts, }), ), @@ -33,104 +34,98 @@ function testAPIEquivalence(title: string, opts?: ERC1155Options) { }); } -testERC1155("basic", {}); +testERC1155('basic', {}); -testERC1155("basic + roles", { - access: "roles", +testERC1155('basic + roles', { + access: 'roles', }); -testERC1155("basic + managed", { - access: "managed", +testERC1155('basic + managed', { + access: 'managed', }); -testERC1155("no updatable uri", { +testERC1155('no updatable uri', { updatableUri: false, }); -testERC1155("burnable", { +testERC1155('burnable', { burnable: true, }); -testERC1155("pausable", { +testERC1155('pausable', { pausable: true, }); -testERC1155("mintable", { +testERC1155('mintable', { mintable: true, }); -testERC1155("mintable + roles", { +testERC1155('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC1155("mintable + managed", { +testERC1155('mintable + managed', { mintable: true, - access: "managed", + access: 'managed', }); -testERC1155("supply tracking", { +testERC1155('supply tracking', { supply: true, }); -testERC1155("full upgradeable transparent", { +testERC1155('full upgradeable transparent', { mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC1155("full upgradeable uups", { +testERC1155('full upgradeable uups', { mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC1155("full upgradeable transparent with managed", { +testERC1155('full upgradeable transparent with managed', { mintable: true, - access: "managed", + access: 'managed', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { - name: "CustomToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testAPIEquivalence('API basic', { + name: 'CustomToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', }); -testAPIEquivalence("API full upgradeable", { - name: "CustomToken", - uri: "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testAPIEquivalence('API full upgradeable', { + name: 'CustomToken', + uri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', mintable: true, - access: "roles", + access: 'roles', burnable: true, pausable: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc1155.print(erc1155.defaults), erc1155.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), - true, - ); - t.is( - erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), - true, - ); +test('API isAccessControlRequired', async t => { + t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); t.is( erc1155.isAccessControlRequired({ updatableUri: false, - upgradeable: "uups", + upgradeable: 'uups', }), true, ); diff --git a/packages/core/solidity/src/erc1155.ts b/packages/core/solidity/src/erc1155.ts index 1ec08a189..9bb93ded1 100644 --- a/packages/core/solidity/src/erc1155.ts +++ b/packages/core/solidity/src/erc1155.ts @@ -1,20 +1,15 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { supportsInterface } from "./common-functions"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { supportsInterface } from './common-functions'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; export interface ERC1155Options extends CommonOptions { name: string; @@ -27,8 +22,8 @@ export interface ERC1155Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - uri: "", + name: 'MyToken', + uri: '', burnable: false, pausable: false, mintable: false, @@ -55,15 +50,8 @@ export function printERC1155(opts: ERC1155Options = defaults): string { return printContract(buildERC1155(opts)); } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return ( - opts.mintable || - opts.pausable || - opts.updatableUri !== false || - opts.upgradeable === "uups" - ); +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable || opts.pausable || opts.updatableUri !== false || opts.upgradeable === 'uups'; } export function buildERC1155(opts: ERC1155Options): Contract { @@ -104,8 +92,8 @@ export function buildERC1155(opts: ERC1155Options): Contract { function addBase(c: ContractBuilder, uri: string) { const ERC1155 = { - name: "ERC1155", - path: "@openzeppelin/contracts/token/ERC1155/ERC1155.sol", + name: 'ERC1155', + path: '@openzeppelin/contracts/token/ERC1155/ERC1155.sol', }; c.addParent(ERC1155, [uri]); @@ -115,8 +103,8 @@ function addBase(c: ContractBuilder, uri: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC1155Pausable = { - name: "ERC1155Pausable", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol", + name: 'ERC1155Pausable', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol', }; c.addParent(ERC1155Pausable); c.addOverride(ERC1155Pausable, functions._update); @@ -126,27 +114,27 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC1155Burnable", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol", + name: 'ERC1155Burnable', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol', }); } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, "MINTER", "minter"); - requireAccessControl(c, functions.mintBatch, access, "MINTER", "minter"); - c.addFunctionCode("_mint(account, id, amount, data);", functions.mint); - c.addFunctionCode("_mintBatch(to, ids, amounts, data);", functions.mintBatch); + requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); + requireAccessControl(c, functions.mintBatch, access, 'MINTER', 'minter'); + c.addFunctionCode('_mint(account, id, amount, data);', functions.mint); + c.addFunctionCode('_mintBatch(to, ids, amounts, data);', functions.mintBatch); } function addSetUri(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.setURI, access, "URI_SETTER", undefined); - c.addFunctionCode("_setURI(newuri);", functions.setURI); + requireAccessControl(c, functions.setURI, access, 'URI_SETTER', undefined); + c.addFunctionCode('_setURI(newuri);', functions.setURI); } function addSupply(c: ContractBuilder) { const ERC1155Supply = { - name: "ERC1155Supply", - path: "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol", + name: 'ERC1155Supply', + path: '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol', }; c.addParent(ERC1155Supply); c.addOverride(ERC1155Supply, functions._update); @@ -154,37 +142,37 @@ function addSupply(c: ContractBuilder) { const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "ids", type: "uint256[] memory" }, - { name: "values", type: "uint256[] memory" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'ids', type: 'uint256[] memory' }, + { name: 'values', type: 'uint256[] memory' }, ], }, setURI: { - kind: "public" as const, - args: [{ name: "newuri", type: "string memory" }], + kind: 'public' as const, + args: [{ name: 'newuri', type: 'string memory' }], }, mint: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "account", type: "address" }, - { name: "id", type: "uint256" }, - { name: "amount", type: "uint256" }, - { name: "data", type: "bytes memory" }, + { name: 'account', type: 'address' }, + { name: 'id', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + { name: 'data', type: 'bytes memory' }, ], }, mintBatch: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "to", type: "address" }, - { name: "ids", type: "uint256[] memory" }, - { name: "amounts", type: "uint256[] memory" }, - { name: "data", type: "bytes memory" }, + { name: 'to', type: 'address' }, + { name: 'ids', type: 'uint256[] memory' }, + { name: 'amounts', type: 'uint256[] memory' }, + { name: 'data', type: 'bytes memory' }, ], }, }); diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 4a1cbb586..84409b78d 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc20 } from "."; +import test from 'ava'; +import { erc20 } from '.'; -import { buildERC20, ERC20Options } from "./erc20"; -import { printContract } from "./print"; +import type { ERC20Options } from './erc20'; +import { buildERC20 } from './erc20'; +import { printContract } from './print'; function testERC20(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC20(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC20Options) { - test(title, (t) => { + test(title, t => { t.is( erc20.print(opts), printContract( buildERC20({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -33,131 +34,131 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { }); } -testERC20("basic erc20", {}); +testERC20('basic erc20', {}); -testERC20("erc20 burnable", { +testERC20('erc20 burnable', { burnable: true, }); -testERC20("erc20 pausable", { +testERC20('erc20 pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 pausable with roles", { +testERC20('erc20 pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 pausable with managed", { +testERC20('erc20 pausable with managed', { pausable: true, - access: "managed", + access: 'managed', }); -testERC20("erc20 burnable pausable", { +testERC20('erc20 burnable pausable', { burnable: true, pausable: true, }); -testERC20("erc20 preminted", { - premint: "1000", +testERC20('erc20 preminted', { + premint: '1000', }); -testERC20("erc20 premint of 0", { - premint: "0", +testERC20('erc20 premint of 0', { + premint: '0', }); -testERC20("erc20 mintable", { +testERC20('erc20 mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testERC20("erc20 mintable with roles", { +testERC20('erc20 mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC20("erc20 permit", { +testERC20('erc20 permit', { permit: true, }); -testERC20("erc20 votes", { +testERC20('erc20 votes', { votes: true, }); -testERC20("erc20 votes + blocknumber", { - votes: "blocknumber", +testERC20('erc20 votes + blocknumber', { + votes: 'blocknumber', }); -testERC20("erc20 votes + timestamp", { - votes: "timestamp", +testERC20('erc20 votes + timestamp', { + votes: 'timestamp', }); -testERC20("erc20 flashmint", { +testERC20('erc20 flashmint', { flashmint: true, }); -testERC20("erc20 full upgradeable transparent", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable transparent', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC20("erc20 full upgradeable uups", { - premint: "2000", - access: "roles", +testERC20('erc20 full upgradeable uups', { + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC20("erc20 full upgradeable uups managed", { - premint: "2000", - access: "managed", +testERC20('erc20 full upgradeable uups managed', { + premint: '2000', + access: 'managed', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testAPIEquivalence("erc20 API default"); +testAPIEquivalence('erc20 API default'); -testAPIEquivalence("erc20 API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('erc20 API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("erc20 API full upgradeable", { - name: "CustomToken", - symbol: "CTK", - premint: "2000", - access: "roles", +testAPIEquivalence('erc20 API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("erc20 API assert defaults", async (t) => { +test('erc20 API assert defaults', async t => { t.is(erc20.print(erc20.defaults), erc20.print()); }); -test("erc20 API isAccessControlRequired", async (t) => { +test('erc20 API isAccessControlRequired', async t => { t.is(erc20.isAccessControlRequired({ mintable: true }), true); t.is(erc20.isAccessControlRequired({ pausable: true }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(erc20.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(erc20.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(erc20.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 8a2643e09..3b0f3419b 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -1,20 +1,15 @@ -import { ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; -import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; +import type { ClockMode } from './set-clock-mode'; +import { clockModeDefault, setClockMode } from './set-clock-mode'; export interface ERC20Options extends CommonOptions { name: string; @@ -33,11 +28,11 @@ export interface ERC20Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', burnable: false, pausable: false, - premint: "0", + premint: '0', mintable: false, permit: true, votes: false, @@ -66,7 +61,7 @@ export function printERC20(opts: ERC20Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === "uups"; + return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; } export function buildERC20(opts: ERC20Options): ContractBuilder { @@ -117,8 +112,8 @@ export function buildERC20(opts: ERC20Options): ContractBuilder { function addBase(c: ContractBuilder, name: string, symbol: string) { const ERC20 = { - name: "ERC20", - path: "@openzeppelin/contracts/token/ERC20/ERC20.sol", + name: 'ERC20', + path: '@openzeppelin/contracts/token/ERC20/ERC20.sol', }; c.addParent(ERC20, [name, symbol]); @@ -128,8 +123,8 @@ function addBase(c: ContractBuilder, name: string, symbol: string) { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC20Pausable = { - name: "ERC20Pausable", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol", + name: 'ERC20Pausable', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', }; c.addParent(ERC20Pausable); c.addOverride(ERC20Pausable, functions._update); @@ -139,8 +134,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC20Burnable", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol", + name: 'ERC20Burnable', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', }); } @@ -149,55 +144,54 @@ export const premintPattern = /^(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addPremint(c: ContractBuilder, amount: string) { const m = amount.match(premintPattern); if (m) { - const integer = m[1]?.replace(/^0+/, "") ?? ""; - const decimals = m[2]?.replace(/0+$/, "") ?? ""; + const integer = m[1]?.replace(/^0+/, '') ?? ''; + const decimals = m[2]?.replace(/0+$/, '') ?? ''; const exponent = Number(m[3] ?? 0); if (Number(integer + decimals) > 0) { const decimalPlace = decimals.length - exponent; - const zeroes = new Array(Math.max(0, -decimalPlace)).fill("0").join(""); + const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); const units = integer + decimals + zeroes; - const exp = - decimalPlace <= 0 ? "decimals()" : `(decimals() - ${decimalPlace})`; - c.addConstructorArgument({ type: "address", name: "recipient" }); + const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; + c.addConstructorArgument({ type: 'address', name: 'recipient' }); c.addConstructorCode(`_mint(recipient, ${units} * 10 ** ${exp});`); } } } function addMintable(c: ContractBuilder, access: Access) { - requireAccessControl(c, functions.mint, access, "MINTER", "minter"); - c.addFunctionCode("_mint(to, amount);", functions.mint); + requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); + c.addFunctionCode('_mint(to, amount);', functions.mint); } function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { - name: "ERC20Permit", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol", + name: 'ERC20Permit', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol', }; c.addParent(ERC20Permit, [name]); c.addOverride(ERC20Permit, functions.nonces); } function addVotes(c: ContractBuilder, clockMode: ClockMode) { - if (!c.parents.some((p) => p.contract.name === "ERC20Permit")) { - throw new Error("Missing ERC20Permit requirement for ERC20Votes"); + if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) { + throw new Error('Missing ERC20Permit requirement for ERC20Votes'); } const ERC20Votes = { - name: "ERC20Votes", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol", + name: 'ERC20Votes', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol', }; c.addParent(ERC20Votes); c.addOverride(ERC20Votes, functions._update); c.addImportOnly({ - name: "Nonces", - path: "@openzeppelin/contracts/utils/Nonces.sol", + name: 'Nonces', + path: '@openzeppelin/contracts/utils/Nonces.sol', }); c.addOverride( { - name: "Nonces", + name: 'Nonces', }, functions.nonces, ); @@ -207,58 +201,58 @@ function addVotes(c: ContractBuilder, clockMode: ClockMode) { function addFlashMint(c: ContractBuilder) { c.addParent({ - name: "ERC20FlashMint", - path: "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol", + name: 'ERC20FlashMint', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol', }); } export const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "value", type: "uint256" }, + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' }, ], }, _approve: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "owner", type: "address" }, - { name: "spender", type: "address" }, - { name: "value", type: "uint256" }, - { name: "emitEvent", type: "bool" }, + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + { name: 'value', type: 'uint256' }, + { name: 'emitEvent', type: 'bool' }, ], }, mint: { - kind: "public" as const, + kind: 'public' as const, args: [ - { name: "to", type: "address" }, - { name: "amount", type: "uint256" }, + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, ], }, pause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, unpause: { - kind: "public" as const, + kind: 'public' as const, args: [], }, snapshot: { - kind: "public" as const, + kind: 'public' as const, args: [], }, nonces: { - kind: "public" as const, - args: [{ name: "owner", type: "address" }], - returns: ["uint256"], - mutability: "view" as const, + kind: 'public' as const, + args: [{ name: 'owner', type: 'address' }], + returns: ['uint256'], + mutability: 'view' as const, }, }); diff --git a/packages/core/solidity/src/erc721.test.ts b/packages/core/solidity/src/erc721.test.ts index 96b608678..1b26718e0 100644 --- a/packages/core/solidity/src/erc721.test.ts +++ b/packages/core/solidity/src/erc721.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { erc721 } from "."; +import test from 'ava'; +import { erc721 } from '.'; -import { buildERC721, ERC721Options } from "./erc721"; -import { printContract } from "./print"; +import type { ERC721Options } from './erc721'; +import { buildERC721 } from './erc721'; +import { printContract } from './print'; function testERC721(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testERC721(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: ERC721Options) { - test(title, (t) => { + test(title, t => { t.is( erc721.print(opts), printContract( buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', ...opts, }), ), @@ -33,126 +34,125 @@ function testAPIEquivalence(title: string, opts?: ERC721Options) { }); } -testERC721("basic", {}); +testERC721('basic', {}); -testERC721("base uri", { - baseUri: - "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/", +testERC721('base uri', { + baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', }); -testERC721("enumerable", { +testERC721('enumerable', { enumerable: true, }); -testERC721("uri storage", { +testERC721('uri storage', { uriStorage: true, }); -testERC721("mintable + uri storage", { +testERC721('mintable + uri storage', { mintable: true, uriStorage: true, }); -testERC721("mintable + uri storage + incremental", { +testERC721('mintable + uri storage + incremental', { mintable: true, uriStorage: true, incremental: true, }); -testERC721("burnable", { +testERC721('burnable', { burnable: true, }); -testERC721("burnable + uri storage", { +testERC721('burnable + uri storage', { uriStorage: true, burnable: true, }); -testERC721("pausable", { +testERC721('pausable', { pausable: true, }); -testERC721("mintable", { +testERC721('mintable', { mintable: true, }); -testERC721("mintable + roles", { +testERC721('mintable + roles', { mintable: true, - access: "roles", + access: 'roles', }); -testERC721("mintable + managed", { +testERC721('mintable + managed', { mintable: true, - access: "managed", + access: 'managed', }); -testERC721("mintable + incremental", { +testERC721('mintable + incremental', { mintable: true, incremental: true, }); -testERC721("votes", { +testERC721('votes', { votes: true, }); -testERC721("votes + blocknumber", { - votes: "blocknumber", +testERC721('votes + blocknumber', { + votes: 'blocknumber', }); -testERC721("votes + timestamp", { - votes: "timestamp", +testERC721('votes + timestamp', { + votes: 'timestamp', }); -testERC721("full upgradeable transparent", { +testERC721('full upgradeable transparent', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "transparent", + upgradeable: 'transparent', }); -testERC721("full upgradeable uups", { +testERC721('full upgradeable uups', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", + upgradeable: 'uups', }); -testERC721("full upgradeable uups + managed", { +testERC721('full upgradeable uups + managed', { mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", - access: "managed", + upgradeable: 'uups', + access: 'managed', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { name: "CustomToken", symbol: "CTK" }); +testAPIEquivalence('API basic', { name: 'CustomToken', symbol: 'CTK' }); -testAPIEquivalence("API full upgradeable", { - name: "CustomToken", - symbol: "CTK", +testAPIEquivalence('API full upgradeable', { + name: 'CustomToken', + symbol: 'CTK', mintable: true, enumerable: true, pausable: true, burnable: true, votes: true, - upgradeable: "uups", + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(erc721.print(erc721.defaults), erc721.print()); }); -test("API isAccessControlRequired", async (t) => { +test('API isAccessControlRequired', async t => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: "uups" }), true); - t.is(erc721.isAccessControlRequired({ upgradeable: "transparent" }), false); + t.is(erc721.isAccessControlRequired({ upgradeable: 'uups' }), true); + t.is(erc721.isAccessControlRequired({ upgradeable: 'transparent' }), false); }); diff --git a/packages/core/solidity/src/erc721.ts b/packages/core/solidity/src/erc721.ts index 123bc91f4..cfa285274 100644 --- a/packages/core/solidity/src/erc721.ts +++ b/packages/core/solidity/src/erc721.ts @@ -1,21 +1,17 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { addPauseFunctions } from "./add-pausable"; -import { supportsInterface } from "./common-functions"; -import { defineFunctions } from "./utils/define-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { setUpgradeable } from "./set-upgradeable"; -import { setInfo } from "./set-info"; -import { printContract } from "./print"; -import { ClockMode, clockModeDefault, setClockMode } from "./set-clock-mode"; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { addPauseFunctions } from './add-pausable'; +import { supportsInterface } from './common-functions'; +import { defineFunctions } from './utils/define-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { printContract } from './print'; +import type { ClockMode } from './set-clock-mode'; +import { clockModeDefault, setClockMode } from './set-clock-mode'; export interface ERC721Options extends CommonOptions { name: string; @@ -35,9 +31,9 @@ export interface ERC721Options extends CommonOptions { } export const defaults: Required = { - name: "MyToken", - symbol: "MTK", - baseUri: "", + name: 'MyToken', + symbol: 'MTK', + baseUri: '', enumerable: false, uriStorage: false, burnable: false, @@ -70,7 +66,7 @@ export function printERC721(opts: ERC721Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === "uups"; + return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; } export function buildERC721(opts: ERC721Options): Contract { @@ -120,8 +116,8 @@ export function buildERC721(opts: ERC721Options): Contract { function addPausableExtension(c: ContractBuilder, access: Access) { const ERC721Pausable = { - name: "ERC721Pausable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol", + name: 'ERC721Pausable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol', }; c.addParent(ERC721Pausable); c.addOverride(ERC721Pausable, functions._update); @@ -130,8 +126,8 @@ function addPausableExtension(c: ContractBuilder, access: Access) { } const ERC721 = { - name: "ERC721", - path: "@openzeppelin/contracts/token/ERC721/ERC721.sol", + name: 'ERC721', + path: '@openzeppelin/contracts/token/ERC721/ERC721.sol', }; function addBase(c: ContractBuilder, name: string, symbol: string) { @@ -150,8 +146,8 @@ function addBaseURI(c: ContractBuilder, baseUri: string) { function addEnumerable(c: ContractBuilder) { const ERC721Enumerable = { - name: "ERC721Enumerable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol", + name: 'ERC721Enumerable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol', }; c.addParent(ERC721Enumerable); @@ -162,8 +158,8 @@ function addEnumerable(c: ContractBuilder) { function addURIStorage(c: ContractBuilder) { const ERC721URIStorage = { - name: "ERC721URIStorage", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol", + name: 'ERC721URIStorage', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol', }; c.addParent(ERC721URIStorage); @@ -173,43 +169,38 @@ function addURIStorage(c: ContractBuilder) { function addBurnable(c: ContractBuilder) { c.addParent({ - name: "ERC721Burnable", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol", + name: 'ERC721Burnable', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol', }); } -function addMintable( - c: ContractBuilder, - access: Access, - incremental = false, - uriStorage = false, -) { +function addMintable(c: ContractBuilder, access: Access, incremental = false, uriStorage = false) { const fn = getMintFunction(incremental, uriStorage); - requireAccessControl(c, fn, access, "MINTER", "minter"); + requireAccessControl(c, fn, access, 'MINTER', 'minter'); if (incremental) { - c.addVariable("uint256 private _nextTokenId;"); - c.addFunctionCode("uint256 tokenId = _nextTokenId++;", fn); - c.addFunctionCode("_safeMint(to, tokenId);", fn); + c.addVariable('uint256 private _nextTokenId;'); + c.addFunctionCode('uint256 tokenId = _nextTokenId++;', fn); + c.addFunctionCode('_safeMint(to, tokenId);', fn); } else { - c.addFunctionCode("_safeMint(to, tokenId);", fn); + c.addFunctionCode('_safeMint(to, tokenId);', fn); } if (uriStorage) { - c.addFunctionCode("_setTokenURI(tokenId, uri);", fn); + c.addFunctionCode('_setTokenURI(tokenId, uri);', fn); } } function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const EIP712 = { - name: "EIP712", - path: "@openzeppelin/contracts/utils/cryptography/EIP712.sol", + name: 'EIP712', + path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol', }; - c.addParent(EIP712, [name, "1"]); + c.addParent(EIP712, [name, '1']); const ERC721Votes = { - name: "ERC721Votes", - path: "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol", + name: 'ERC721Votes', + path: '@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol', }; c.addParent(ERC721Votes); @@ -221,51 +212,51 @@ function addVotes(c: ContractBuilder, name: string, clockMode: ClockMode) { const functions = defineFunctions({ _update: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "to", type: "address" }, - { name: "tokenId", type: "uint256" }, - { name: "auth", type: "address" }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: 'auth', type: 'address' }, ], - returns: ["address"], + returns: ['address'], }, tokenURI: { - kind: "public" as const, - args: [{ name: "tokenId", type: "uint256" }], - returns: ["string memory"], - mutability: "view" as const, + kind: 'public' as const, + args: [{ name: 'tokenId', type: 'uint256' }], + returns: ['string memory'], + mutability: 'view' as const, }, _baseURI: { - kind: "internal" as const, + kind: 'internal' as const, args: [], - returns: ["string memory"], - mutability: "pure" as const, + returns: ['string memory'], + mutability: 'pure' as const, }, _increaseBalance: { - kind: "internal" as const, + kind: 'internal' as const, args: [ - { name: "account", type: "address" }, - { name: "value", type: "uint128" }, + { name: 'account', type: 'address' }, + { name: 'value', type: 'uint128' }, ], }, }); function getMintFunction(incremental: boolean, uriStorage: boolean) { const fn = { - name: "safeMint", - kind: "public" as const, - args: [{ name: "to", type: "address" }], + name: 'safeMint', + kind: 'public' as const, + args: [{ name: 'to', type: 'address' }], }; if (!incremental) { - fn.args.push({ name: "tokenId", type: "uint256" }); + fn.args.push({ name: 'tokenId', type: 'uint256' }); } if (uriStorage) { - fn.args.push({ name: "uri", type: "string memory" }); + fn.args.push({ name: 'uri', type: 'string memory' }); } return fn; diff --git a/packages/core/solidity/src/error.ts b/packages/core/solidity/src/error.ts index 2235dfd68..96eb23489 100644 --- a/packages/core/solidity/src/error.ts +++ b/packages/core/solidity/src/error.ts @@ -2,6 +2,6 @@ export type OptionsErrorMessages = { [prop in string]?: string }; export class OptionsError extends Error { constructor(readonly messages: OptionsErrorMessages) { - super("Invalid options for Governor"); + super('Invalid options for Governor'); } } diff --git a/packages/core/solidity/src/generate/alternatives.ts b/packages/core/solidity/src/generate/alternatives.ts index c4b282064..b66e733cf 100644 --- a/packages/core/solidity/src/generate/alternatives.ts +++ b/packages/core/solidity/src/generate/alternatives.ts @@ -4,9 +4,7 @@ type Alternatives = { [k in keyof B]: B[k][number]; }; -export function* generateAlternatives( - blueprint: B, -): Generator> { +export function* generateAlternatives(blueprint: B): Generator> { const entries = Object.entries(blueprint).map(([key, values]) => ({ key, values, @@ -15,9 +13,7 @@ export function* generateAlternatives( })); for (; !done(); advance()) { - yield Object.fromEntries( - entries.map((e) => [e.key, e.values[e.current % e.limit]]), - ) as Alternatives; + yield Object.fromEntries(entries.map(e => [e.key, e.values[e.current % e.limit]])) as Alternatives; } function done() { diff --git a/packages/core/solidity/src/generate/custom.ts b/packages/core/solidity/src/generate/custom.ts index 884032068..40207666a 100644 --- a/packages/core/solidity/src/generate/custom.ts +++ b/packages/core/solidity/src/generate/custom.ts @@ -1,13 +1,13 @@ -import type { CustomOptions } from "../custom"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { CustomOptions } from '../custom'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyContract"], + name: ['MyContract'], pausable: booleans, access: accessOptions, upgradeable: upgradeableOptions, diff --git a/packages/core/solidity/src/generate/erc1155.ts b/packages/core/solidity/src/generate/erc1155.ts index d340602a9..fdb9c09fe 100644 --- a/packages/core/solidity/src/generate/erc1155.ts +++ b/packages/core/solidity/src/generate/erc1155.ts @@ -1,14 +1,14 @@ -import type { ERC1155Options } from "../erc1155"; -import { accessOptions } from "../set-access-control"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC1155Options } from '../erc1155'; +import { accessOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - uri: ["https://example.com/"], + name: ['MyToken'], + uri: ['https://example.com/'], burnable: booleans, pausable: booleans, mintable: booleans, diff --git a/packages/core/solidity/src/generate/erc20.ts b/packages/core/solidity/src/generate/erc20.ts index 5c3d6eb48..3db65e95f 100644 --- a/packages/core/solidity/src/generate/erc20.ts +++ b/packages/core/solidity/src/generate/erc20.ts @@ -1,22 +1,22 @@ -import type { ERC20Options } from "../erc20"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC20Options } from '../erc20'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], + name: ['MyToken'], + symbol: ['MTK'], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ["1"], + premint: ['1'], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core/solidity/src/generate/erc721.ts b/packages/core/solidity/src/generate/erc721.ts index 06d1ca530..1f91b7c30 100644 --- a/packages/core/solidity/src/generate/erc721.ts +++ b/packages/core/solidity/src/generate/erc721.ts @@ -1,16 +1,16 @@ -import type { ERC721Options } from "../erc721"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { ERC721Options } from '../erc721'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyToken"], - symbol: ["MTK"], - baseUri: ["https://example.com/"], + name: ['MyToken'], + symbol: ['MTK'], + baseUri: ['https://example.com/'], enumerable: booleans, uriStorage: booleans, burnable: booleans, diff --git a/packages/core/solidity/src/generate/governor.ts b/packages/core/solidity/src/generate/governor.ts index 66679caae..765f4e69f 100644 --- a/packages/core/solidity/src/generate/governor.ts +++ b/packages/core/solidity/src/generate/governor.ts @@ -1,27 +1,23 @@ -import { - defaults, - GovernorOptions, - timelockOptions, - votesOptions, -} from "../governor"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { GovernorOptions } from '../governor'; +import { defaults, timelockOptions, votesOptions } from '../governor'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyGovernor"], - delay: ["1 week"], - period: ["1 week"], + name: ['MyGovernor'], + delay: ['1 week'], + period: ['1 week'], blockTime: [defaults.blockTime], - proposalThreshold: ["0", "1000"], + proposalThreshold: ['0', '1000'], decimals: [18], - quorumMode: ["percent", "absolute"] as const, + quorumMode: ['percent', 'absolute'] as const, quorumPercent: [4], - quorumAbsolute: ["1000"], + quorumAbsolute: ['1000'], votes: votesOptions, clockMode: clockModeOptions, timelock: timelockOptions, @@ -32,8 +28,6 @@ const blueprint = { info: infoOptions, }; -export function* generateGovernorOptions(): Generator< - Required -> { +export function* generateGovernorOptions(): Generator> { yield* generateAlternatives(blueprint); } diff --git a/packages/core/solidity/src/generate/sources.ts b/packages/core/solidity/src/generate/sources.ts index 3ee3ad00c..b25f6c444 100644 --- a/packages/core/solidity/src/generate/sources.ts +++ b/packages/core/solidity/src/generate/sources.ts @@ -1,63 +1,64 @@ -import { promises as fs } from "fs"; -import path from "path"; -import crypto from "crypto"; - -import { generateERC20Options } from "./erc20"; -import { generateERC721Options } from "./erc721"; -import { generateERC1155Options } from "./erc1155"; -import { generateStablecoinOptions } from "./stablecoin"; -import { generateGovernorOptions } from "./governor"; -import { generateCustomOptions } from "./custom"; -import { buildGeneric, GenericOptions, KindedOptions } from "../build-generic"; -import { printContract } from "../print"; -import { OptionsError } from "../error"; -import { findCover } from "../utils/find-cover"; -import type { Contract } from "../contract"; - -type Subset = "all" | "minimal-cover"; +import { promises as fs } from 'fs'; +import path from 'path'; +import crypto from 'crypto'; + +import { generateERC20Options } from './erc20'; +import { generateERC721Options } from './erc721'; +import { generateERC1155Options } from './erc1155'; +import { generateStablecoinOptions } from './stablecoin'; +import { generateGovernorOptions } from './governor'; +import { generateCustomOptions } from './custom'; +import type { GenericOptions, KindedOptions } from '../build-generic'; +import { buildGeneric } from '../build-generic'; +import { printContract } from '../print'; +import { OptionsError } from '../error'; +import { findCover } from '../utils/find-cover'; +import type { Contract } from '../contract'; + +type Subset = 'all' | 'minimal-cover'; type Kind = keyof KindedOptions; export function* generateOptions(kind?: Kind): Generator { - if (!kind || kind === "ERC20") { + if (!kind || kind === 'ERC20') { for (const kindOpts of generateERC20Options()) { - yield { kind: "ERC20", ...kindOpts }; + yield { kind: 'ERC20', ...kindOpts }; } } - if (!kind || kind === "ERC721") { + if (!kind || kind === 'ERC721') { for (const kindOpts of generateERC721Options()) { - yield { kind: "ERC721", ...kindOpts }; + yield { kind: 'ERC721', ...kindOpts }; } } - if (!kind || kind === "ERC1155") { + if (!kind || kind === 'ERC1155') { for (const kindOpts of generateERC1155Options()) { - yield { kind: "ERC1155", ...kindOpts }; + yield { kind: 'ERC1155', ...kindOpts }; } } - if (!kind || kind === "Stablecoin") { + if (!kind || kind === 'Stablecoin') { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: "Stablecoin", ...kindOpts }; + yield { kind: 'Stablecoin', ...kindOpts }; } } - if (!kind || kind === "RealWorldAsset") { + if (!kind || kind === 'RealWorldAsset') { for (const kindOpts of generateStablecoinOptions()) { - yield { kind: "RealWorldAsset", ...kindOpts }; + yield { kind: 'RealWorldAsset', ...kindOpts }; } } - if (!kind || kind === "Governor") { + if (!kind || kind === 'Governor') { for (const kindOpts of generateGovernorOptions()) { - yield { kind: "Governor", ...kindOpts }; + yield { kind: 'Governor', ...kindOpts }; } } - if (!kind || kind === "Custom") { + if (!kind || kind === 'Custom') { for (const kindOpts of generateCustomOptions()) { - yield { kind: "Custom", ...kindOpts }; + yield { kind: 'Custom', ...kindOpts }; } } } @@ -72,18 +73,11 @@ interface GeneratedSource extends GeneratedContract { source: string; } -function generateContractSubset( - subset: Subset, - kind?: Kind, -): GeneratedContract[] { +function generateContractSubset(subset: Subset, kind?: Kind): GeneratedContract[] { const contracts = []; for (const options of generateOptions(kind)) { - const id = crypto - .createHash("sha1") - .update(JSON.stringify(options)) - .digest() - .toString("hex"); + const id = crypto.createHash('sha1').update(JSON.stringify(options)).digest().toString('hex'); try { const contract = buildGeneric(options); @@ -97,42 +91,34 @@ function generateContractSubset( } } - if (subset === "all") { + if (subset === 'all') { return contracts; } else { - const getParents = (c: GeneratedContract) => - c.contract.parents.map((p) => p.contract.path); + const getParents = (c: GeneratedContract) => c.contract.parents.map(p => p.contract.path); return [ ...findCover( - contracts.filter((c) => c.options.upgradeable), + contracts.filter(c => c.options.upgradeable), getParents, ), ...findCover( - contracts.filter((c) => !c.options.upgradeable), + contracts.filter(c => !c.options.upgradeable), getParents, ), ]; } } -export function* generateSources( - subset: Subset, - kind?: Kind, -): Generator { +export function* generateSources(subset: Subset, kind?: Kind): Generator { for (const c of generateContractSubset(subset, kind)) { const source = printContract(c.contract); yield { ...c, source }; } } -export async function writeGeneratedSources( - dir: string, - subset: Subset, - kind?: Kind, -): Promise { +export async function writeGeneratedSources(dir: string, subset: Subset, kind?: Kind): Promise { await fs.mkdir(dir, { recursive: true }); for (const { id, source } of generateSources(subset, kind)) { - await fs.writeFile(path.format({ dir, name: id, ext: ".sol" }), source); + await fs.writeFile(path.format({ dir, name: id, ext: '.sol' }), source); } } diff --git a/packages/core/solidity/src/generate/stablecoin.ts b/packages/core/solidity/src/generate/stablecoin.ts index e20841856..8cbcd1adc 100644 --- a/packages/core/solidity/src/generate/stablecoin.ts +++ b/packages/core/solidity/src/generate/stablecoin.ts @@ -1,32 +1,30 @@ -import type { StablecoinOptions } from "../stablecoin"; -import { accessOptions } from "../set-access-control"; -import { clockModeOptions } from "../set-clock-mode"; -import { infoOptions } from "../set-info"; -import { upgradeableOptions } from "../set-upgradeable"; -import { generateAlternatives } from "./alternatives"; +import type { StablecoinOptions } from '../stablecoin'; +import { accessOptions } from '../set-access-control'; +import { clockModeOptions } from '../set-clock-mode'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; const booleans = [true, false]; const blueprint = { - name: ["MyStablecoin"], - symbol: ["MST"], + name: ['MyStablecoin'], + symbol: ['MST'], burnable: booleans, pausable: booleans, mintable: booleans, permit: booleans, - limitations: [false, "allowlist", "blocklist"] as const, + limitations: [false, 'allowlist', 'blocklist'] as const, votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, - premint: ["1"], + premint: ['1'], custodian: booleans, access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, }; -export function* generateStablecoinOptions(): Generator< - Required -> { +export function* generateStablecoinOptions(): Generator> { for (const opts of generateAlternatives(blueprint)) { yield { ...opts, upgradeable: false }; } diff --git a/packages/core/solidity/src/get-imports.test.ts b/packages/core/solidity/src/get-imports.test.ts index 089ce3678..d6f1509bb 100644 --- a/packages/core/solidity/src/get-imports.test.ts +++ b/packages/core/solidity/src/get-imports.test.ts @@ -1,29 +1,29 @@ -import test from "ava"; +import test from 'ava'; -import { getImports } from "./get-imports"; -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { generateSources } from "./generate/sources"; -import { buildGeneric } from "./build-generic"; +import { getImports } from './get-imports'; +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { generateSources } from './generate/sources'; +import { buildGeneric } from './build-generic'; -test("erc20 basic", (t) => { - const c = buildERC20({ name: "MyToken", symbol: "MTK", permit: false }); +test('erc20 basic', t => { + const c = buildERC20({ name: 'MyToken', symbol: 'MTK', permit: false }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/token/ERC20/ERC20.sol", - "@openzeppelin/contracts/token/ERC20/IERC20.sol", - "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol", - "@openzeppelin/contracts/utils/Context.sol", + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC20/ERC20.sol', + '@openzeppelin/contracts/token/ERC20/IERC20.sol', + '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol', + '@openzeppelin/contracts/utils/Context.sol', ]); }); -test("erc721 auto increment", (t) => { +test('erc721 auto increment', t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', mintable: true, incremental: true, }); @@ -31,65 +31,65 @@ test("erc721 auto increment", (t) => { const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts/access/Ownable.sol", - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/token/ERC721/ERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", - "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", - "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", - "@openzeppelin/contracts/utils/Context.sol", - "@openzeppelin/contracts/utils/Panic.sol", - "@openzeppelin/contracts/utils/Strings.sol", - "@openzeppelin/contracts/utils/introspection/ERC165.sol", - "@openzeppelin/contracts/utils/introspection/IERC165.sol", - "@openzeppelin/contracts/utils/math/Math.sol", - "@openzeppelin/contracts/utils/math/SafeCast.sol", - "@openzeppelin/contracts/utils/math/SignedMath.sol", + '@openzeppelin/contracts/access/Ownable.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/token/ERC721/ERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Context.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/ERC165.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', ]); }); -test("erc721 auto increment uups", (t) => { +test('erc721 auto increment uups', t => { const c = buildERC721({ - name: "MyToken", - symbol: "MTK", + name: 'MyToken', + symbol: 'MTK', mintable: true, incremental: true, - upgradeable: "uups", + upgradeable: 'uups', }); const sources = getImports(c); const files = Object.keys(sources).sort(); t.deepEqual(files, [ - "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", - "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol", - "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol", - "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol", - "@openzeppelin/contracts/interfaces/IERC1967.sol", - "@openzeppelin/contracts/interfaces/draft-IERC1822.sol", - "@openzeppelin/contracts/interfaces/draft-IERC6093.sol", - "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol", - "@openzeppelin/contracts/proxy/beacon/IBeacon.sol", - "@openzeppelin/contracts/token/ERC721/IERC721.sol", - "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol", - "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol", - "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol", - "@openzeppelin/contracts/utils/Address.sol", - "@openzeppelin/contracts/utils/Errors.sol", - "@openzeppelin/contracts/utils/Panic.sol", - "@openzeppelin/contracts/utils/StorageSlot.sol", - "@openzeppelin/contracts/utils/Strings.sol", - "@openzeppelin/contracts/utils/introspection/IERC165.sol", - "@openzeppelin/contracts/utils/math/Math.sol", - "@openzeppelin/contracts/utils/math/SafeCast.sol", - "@openzeppelin/contracts/utils/math/SignedMath.sol", + '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol', + '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol', + '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol', + '@openzeppelin/contracts/interfaces/IERC1967.sol', + '@openzeppelin/contracts/interfaces/draft-IERC1822.sol', + '@openzeppelin/contracts/interfaces/draft-IERC6093.sol', + '@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol', + '@openzeppelin/contracts/proxy/beacon/IBeacon.sol', + '@openzeppelin/contracts/token/ERC721/IERC721.sol', + '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol', + '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol', + '@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol', + '@openzeppelin/contracts/utils/Address.sol', + '@openzeppelin/contracts/utils/Errors.sol', + '@openzeppelin/contracts/utils/Panic.sol', + '@openzeppelin/contracts/utils/StorageSlot.sol', + '@openzeppelin/contracts/utils/Strings.sol', + '@openzeppelin/contracts/utils/introspection/IERC165.sol', + '@openzeppelin/contracts/utils/math/Math.sol', + '@openzeppelin/contracts/utils/math/SafeCast.sol', + '@openzeppelin/contracts/utils/math/SignedMath.sol', ]); }); -test("can get imports for all combinations", (t) => { - for (const { options } of generateSources("all")) { +test('can get imports for all combinations', t => { + for (const { options } of generateSources('all')) { const c = buildGeneric(options); getImports(c); } diff --git a/packages/core/solidity/src/get-imports.ts b/packages/core/solidity/src/get-imports.ts index 4a5ca111b..c7e390907 100644 --- a/packages/core/solidity/src/get-imports.ts +++ b/packages/core/solidity/src/get-imports.ts @@ -1,8 +1,8 @@ -import type { Contract } from "./contract"; -import { reachable } from "./utils/transitive-closure"; +import type { Contract } from './contract'; +import { reachable } from './utils/transitive-closure'; -import contracts from "../openzeppelin-contracts"; -import { withHelpers } from "./options"; +import contracts from '../openzeppelin-contracts'; +import { withHelpers } from './options'; export interface SolcInputSources { [source: string]: { @@ -24,10 +24,10 @@ export function getImports(c: Contract): SolcInputSources { const result: SolcInputSources = {}; - const fileName = c.name + ".sol"; + const fileName = c.name + '.sol'; const dependencies = { - [fileName]: c.imports.map((i) => transformImport(i).path), + [fileName]: c.imports.map(i => transformImport(i).path), ...contracts.dependencies, }; diff --git a/packages/core/solidity/src/governor.test.ts b/packages/core/solidity/src/governor.test.ts index b22c706d0..8b84b468c 100644 --- a/packages/core/solidity/src/governor.test.ts +++ b/packages/core/solidity/src/governor.test.ts @@ -1,15 +1,16 @@ -import test from "ava"; -import { governor } from "."; +import test from 'ava'; +import { governor } from '.'; -import { buildGovernor, GovernorOptions } from "./governor"; -import { printContract } from "./print"; +import type { GovernorOptions } from './governor'; +import { buildGovernor } from './governor'; +import { printContract } from './print'; function testGovernor(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildGovernor({ - name: "MyGovernor", - delay: "1 week", - period: "1 week", + name: 'MyGovernor', + delay: '1 week', + period: '1 week', settings: false, ...opts, }); @@ -21,14 +22,14 @@ function testGovernor(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: GovernorOptions) { - test(title, (t) => { + test(title, t => { t.is( governor.print(opts), printContract( buildGovernor({ - name: "MyGovernor", - delay: "1 day", - period: "1 week", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', ...opts, }), ), @@ -36,131 +37,131 @@ function testAPIEquivalence(title: string, opts?: GovernorOptions) { }); } -testGovernor("governor with proposal threshold", { - proposalThreshold: "1", +testGovernor('governor with proposal threshold', { + proposalThreshold: '1', }); -testGovernor("governor with custom block time", { +testGovernor('governor with custom block time', { blockTime: 6, }); -testGovernor("governor with custom decimals", { +testGovernor('governor with custom decimals', { decimals: 6, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "1", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '1', }); -testGovernor("governor with 0 decimals", { +testGovernor('governor with 0 decimals', { decimals: 0, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "1", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '1', }); -testGovernor("governor with settings", { +testGovernor('governor with settings', { settings: true, - proposalThreshold: "1", + proposalThreshold: '1', }); -testGovernor("governor with storage", { +testGovernor('governor with storage', { storage: true, }); -testGovernor("governor with erc20votes", { - votes: "erc20votes", +testGovernor('governor with erc20votes', { + votes: 'erc20votes', }); -testGovernor("governor with erc721votes", { - votes: "erc721votes", +testGovernor('governor with erc721votes', { + votes: 'erc721votes', }); -testGovernor("governor with erc721votes omit decimals", { - votes: "erc721votes", +testGovernor('governor with erc721votes omit decimals', { + votes: 'erc721votes', decimals: 6, - proposalThreshold: "1", - quorumMode: "absolute", - quorumAbsolute: "5", + proposalThreshold: '1', + quorumMode: 'absolute', + quorumAbsolute: '5', }); -testGovernor("governor with erc721votes settings omit decimals", { - votes: "erc721votes", +testGovernor('governor with erc721votes settings omit decimals', { + votes: 'erc721votes', decimals: 6, - proposalThreshold: "10", + proposalThreshold: '10', settings: true, }); -testGovernor("governor with percent quorum", { - quorumMode: "percent", +testGovernor('governor with percent quorum', { + quorumMode: 'percent', quorumPercent: 6, }); -testGovernor("governor with fractional percent quorum", { - quorumMode: "percent", +testGovernor('governor with fractional percent quorum', { + quorumMode: 'percent', quorumPercent: 0.5, }); -testGovernor("governor with openzeppelin timelock", { - timelock: "openzeppelin", +testGovernor('governor with openzeppelin timelock', { + timelock: 'openzeppelin', }); -testGovernor("governor with compound timelock", { - timelock: "compound", +testGovernor('governor with compound timelock', { + timelock: 'compound', }); -testGovernor("governor with blocknumber, updatable settings", { - clockMode: "blocknumber", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with blocknumber, updatable settings', { + clockMode: 'blocknumber', + delay: '1 day', + period: '2 weeks', settings: true, }); -testGovernor("governor with blocknumber, non-updatable settings", { - clockMode: "blocknumber", - delay: "1 block", - period: "2 weeks", +testGovernor('governor with blocknumber, non-updatable settings', { + clockMode: 'blocknumber', + delay: '1 block', + period: '2 weeks', settings: false, }); -testGovernor("governor with timestamp clock mode, updatable settings", { - clockMode: "timestamp", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with timestamp clock mode, updatable settings', { + clockMode: 'timestamp', + delay: '1 day', + period: '2 weeks', settings: true, }); -testGovernor("governor with timestamp clock mode, non-updatable settings", { - clockMode: "timestamp", - delay: "1 day", - period: "2 weeks", +testGovernor('governor with timestamp clock mode, non-updatable settings', { + clockMode: 'timestamp', + delay: '1 day', + period: '2 weeks', settings: false, }); -testGovernor("governor with erc20votes, upgradable", { - votes: "erc20votes", - upgradeable: "uups", +testGovernor('governor with erc20votes, upgradable', { + votes: 'erc20votes', + upgradeable: 'uups', }); -testAPIEquivalence("API default"); +testAPIEquivalence('API default'); -testAPIEquivalence("API basic", { - name: "CustomGovernor", - delay: "2 weeks", - period: "2 week", +testAPIEquivalence('API basic', { + name: 'CustomGovernor', + delay: '2 weeks', + period: '2 week', }); -testAPIEquivalence("API basic upgradeable", { - name: "CustomGovernor", - delay: "2 weeks", - period: "2 week", - upgradeable: "uups", +testAPIEquivalence('API basic upgradeable', { + name: 'CustomGovernor', + delay: '2 weeks', + period: '2 week', + upgradeable: 'uups', }); -test("API assert defaults", async (t) => { +test('API assert defaults', async t => { t.is(governor.print(governor.defaults), governor.print()); }); -test("API isAccessControlRequired", async (t) => { - t.is(governor.isAccessControlRequired({ upgradeable: "uups" }), true); +test('API isAccessControlRequired', async t => { + t.is(governor.isAccessControlRequired({ upgradeable: 'uups' }), true); t.is(governor.isAccessControlRequired({}), false); }); diff --git a/packages/core/solidity/src/governor.ts b/packages/core/solidity/src/governor.ts index 4e32d38b3..2ffaa90e1 100644 --- a/packages/core/solidity/src/governor.ts +++ b/packages/core/solidity/src/governor.ts @@ -1,33 +1,31 @@ -import { supportsInterface } from "./common-functions"; -import { - CommonOptions, - withCommonDefaults, - defaults as commonDefaults, -} from "./common-options"; -import { ContractBuilder, Contract } from "./contract"; -import { OptionsError } from "./error"; -import { setAccessControl } from "./set-access-control"; -import { printContract } from "./print"; -import { setInfo } from "./set-info"; -import { setUpgradeable } from "./set-upgradeable"; -import { defineFunctions } from "./utils/define-functions"; -import { durationToBlocks, durationToTimestamp } from "./utils/duration"; -import { clockModeDefault, type ClockMode } from "./set-clock-mode"; +import { supportsInterface } from './common-functions'; +import type { CommonOptions } from './common-options'; +import { withCommonDefaults, defaults as commonDefaults } from './common-options'; +import type { Contract } from './contract'; +import { ContractBuilder } from './contract'; +import { OptionsError } from './error'; +import { setAccessControl } from './set-access-control'; +import { printContract } from './print'; +import { setInfo } from './set-info'; +import { setUpgradeable } from './set-upgradeable'; +import { defineFunctions } from './utils/define-functions'; +import { durationToBlocks, durationToTimestamp } from './utils/duration'; +import { clockModeDefault, type ClockMode } from './set-clock-mode'; export const defaults: Required = { - name: "MyGovernor", - delay: "1 day", - period: "1 week", + name: 'MyGovernor', + delay: '1 day', + period: '1 week', - votes: "erc20votes", + votes: 'erc20votes', clockMode: clockModeDefault, - timelock: "openzeppelin", + timelock: 'openzeppelin', blockTime: 12, decimals: 18, - proposalThreshold: "0", - quorumMode: "percent", + proposalThreshold: '0', + quorumMode: 'percent', quorumPercent: 4, - quorumAbsolute: "", + quorumAbsolute: '', storage: false, settings: true, @@ -36,10 +34,10 @@ export const defaults: Required = { info: commonDefaults.info, } as const; -export const votesOptions = ["erc20votes", "erc721votes"] as const; +export const votesOptions = ['erc20votes', 'erc721votes'] as const; export type VotesOptions = (typeof votesOptions)[number]; -export const timelockOptions = [false, "openzeppelin", "compound"] as const; +export const timelockOptions = [false, 'openzeppelin', 'compound'] as const; export type TimelockOptions = (typeof timelockOptions)[number]; export function printGovernor(opts: GovernorOptions = defaults): string { @@ -53,7 +51,7 @@ export interface GovernorOptions extends CommonOptions { blockTime?: number; proposalThreshold?: string; decimals?: number; - quorumMode?: "percent" | "absolute"; + quorumMode?: 'percent' | 'absolute'; quorumPercent?: number; quorumAbsolute?: string; votes?: VotesOptions; @@ -63,10 +61,8 @@ export interface GovernorOptions extends CommonOptions { settings?: boolean; } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return opts.upgradeable === "uups"; +export function isAccessControlRequired(opts: Partial): boolean { + return opts.upgradeable === 'uups'; } function withDefaults(opts: GovernorOptions): Required { @@ -111,8 +107,8 @@ export function buildGovernor(opts: GovernorOptions): Contract { function addBase(c: ContractBuilder, { name }: GovernorOptions) { const Governor = { - name: "Governor", - path: "@openzeppelin/contracts/governance/Governor.sol", + name: 'Governor', + path: '@openzeppelin/contracts/governance/Governor.sol', }; c.addParent(Governor, [name]); c.addOverride(Governor, functions.votingDelay); @@ -133,28 +129,26 @@ function addBase(c: ContractBuilder, { name }: GovernorOptions) { function addSettings(c: ContractBuilder, allOpts: Required) { if (allOpts.settings) { const GovernorSettings = { - name: "GovernorSettings", - path: "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol", + name: 'GovernorSettings', + path: '@openzeppelin/contracts/governance/extensions/GovernorSettings.sol', }; c.addParent(GovernorSettings, [ getVotingDelay(allOpts), getVotingPeriod(allOpts), { lit: getProposalThreshold(allOpts) }, ]); - c.addOverride(GovernorSettings, functions.votingDelay, "view"); - c.addOverride(GovernorSettings, functions.votingPeriod, "view"); - c.addOverride(GovernorSettings, functions.proposalThreshold, "view"); + c.addOverride(GovernorSettings, functions.votingDelay, 'view'); + c.addOverride(GovernorSettings, functions.votingPeriod, 'view'); + c.addOverride(GovernorSettings, functions.proposalThreshold, 'view'); } else { setVotingParameters(c, allOpts); setProposalThreshold(c, allOpts); } } -function getVotingDelay( - opts: Required, -): { lit: string } | { note: string; value: number } { +function getVotingDelay(opts: Required): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return { lit: durationToTimestamp(opts.delay), }; @@ -175,11 +169,9 @@ function getVotingDelay( } } -function getVotingPeriod( - opts: Required, -): { lit: string } | { note: string; value: number } { +function getVotingPeriod(opts: Required): { lit: string } | { note: string; value: number } { try { - if (opts.clockMode === "timestamp") { + if (opts.clockMode === 'timestamp') { return { lit: durationToTimestamp(opts.period), }; @@ -203,62 +195,42 @@ function getVotingPeriod( function validateDecimals(decimals: number) { if (!/^\d+$/.test(decimals.toString())) { throw new OptionsError({ - decimals: "Not a valid number", + decimals: 'Not a valid number', }); } } -function getProposalThreshold({ - proposalThreshold, - decimals, - votes, -}: Required): string { +function getProposalThreshold({ proposalThreshold, decimals, votes }: Required): string { if (!/^\d+$/.test(proposalThreshold)) { throw new OptionsError({ - proposalThreshold: "Not a valid number", + proposalThreshold: 'Not a valid number', }); } - if ( - /^0+$/.test(proposalThreshold) || - decimals === 0 || - votes === "erc721votes" - ) { + if (/^0+$/.test(proposalThreshold) || decimals === 0 || votes === 'erc721votes') { return proposalThreshold; } else { return `${proposalThreshold}e${decimals}`; } } -function setVotingParameters( - c: ContractBuilder, - opts: Required, -) { +function setVotingParameters(c: ContractBuilder, opts: Required) { const delayBlocks = getVotingDelay(opts); - if ("lit" in delayBlocks) { + if ('lit' in delayBlocks) { c.setFunctionBody([`return ${delayBlocks.lit};`], functions.votingDelay); } else { - c.setFunctionBody( - [`return ${delayBlocks.value}; // ${delayBlocks.note}`], - functions.votingDelay, - ); + c.setFunctionBody([`return ${delayBlocks.value}; // ${delayBlocks.note}`], functions.votingDelay); } const periodBlocks = getVotingPeriod(opts); - if ("lit" in periodBlocks) { + if ('lit' in periodBlocks) { c.setFunctionBody([`return ${periodBlocks.lit};`], functions.votingPeriod); } else { - c.setFunctionBody( - [`return ${periodBlocks.value}; // ${periodBlocks.note}`], - functions.votingPeriod, - ); + c.setFunctionBody([`return ${periodBlocks.value}; // ${periodBlocks.note}`], functions.votingPeriod); } } -function setProposalThreshold( - c: ContractBuilder, - opts: Required, -) { +function setProposalThreshold(c: ContractBuilder, opts: Required) { const threshold = getProposalThreshold(opts); const nonZeroThreshold = parseInt(threshold) !== 0; @@ -269,22 +241,22 @@ function setProposalThreshold( function addCounting(c: ContractBuilder) { c.addParent({ - name: "GovernorCountingSimple", - path: "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol", + name: 'GovernorCountingSimple', + path: '@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol', }); } function addVotes(c: ContractBuilder) { - const tokenArg = "_token"; + const tokenArg = '_token'; c.addImportOnly({ - name: "IVotes", + name: 'IVotes', path: `@openzeppelin/contracts/governance/utils/IVotes.sol`, transpiled: false, }); c.addConstructorArgument({ type: { - name: "IVotes", + name: 'IVotes', transpiled: false, }, name: tokenArg, @@ -292,7 +264,7 @@ function addVotes(c: ContractBuilder) { c.addParent( { - name: "GovernorVotes", + name: 'GovernorVotes', path: `@openzeppelin/contracts/governance/extensions/GovernorVotes.sol`, }, [{ lit: tokenArg }], @@ -302,67 +274,62 @@ function addVotes(c: ContractBuilder) { export const numberPattern = /^(?!$)(\d*)(?:\.(\d+))?(?:e(\d+))?$/; function addQuorum(c: ContractBuilder, opts: Required) { - if (opts.quorumMode === "percent") { + if (opts.quorumMode === 'percent') { if (opts.quorumPercent > 100) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } - const { quorumFractionNumerator, quorumFractionDenominator } = - getQuorumFractionComponents(opts.quorumPercent); + const { quorumFractionNumerator, quorumFractionDenominator } = getQuorumFractionComponents(opts.quorumPercent); const GovernorVotesQuorumFraction = { - name: "GovernorVotesQuorumFraction", - path: "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol", + name: 'GovernorVotesQuorumFraction', + path: '@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol', }; if (quorumFractionDenominator !== undefined) { c.addOverride(GovernorVotesQuorumFraction, functions.quorumDenominator); - c.setFunctionBody( - [`return ${quorumFractionDenominator};`], - functions.quorumDenominator, - "pure", - ); + c.setFunctionBody([`return ${quorumFractionDenominator};`], functions.quorumDenominator, 'pure'); } c.addParent(GovernorVotesQuorumFraction, [quorumFractionNumerator]); c.addOverride(GovernorVotesQuorumFraction, functions.quorum); - } else if (opts.quorumMode === "absolute") { + } else if (opts.quorumMode === 'absolute') { if (!numberPattern.test(opts.quorumAbsolute)) { throw new OptionsError({ - quorumAbsolute: "Not a valid number", + quorumAbsolute: 'Not a valid number', }); } const returnStatement = - opts.decimals === 0 || opts.votes === "erc721votes" + opts.decimals === 0 || opts.votes === 'erc721votes' ? `return ${opts.quorumAbsolute};` : `return ${opts.quorumAbsolute}e${opts.decimals};`; - c.setFunctionBody([returnStatement], functions.quorum, "pure"); + c.setFunctionBody([returnStatement], functions.quorum, 'pure'); } } const timelockModules = { openzeppelin: { timelockType: { - name: "TimelockController", + name: 'TimelockController', path: `@openzeppelin/contracts/governance/TimelockController.sol`, }, timelockParent: { - name: "GovernorTimelockControl", + name: 'GovernorTimelockControl', path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol`, }, }, compound: { timelockType: { - name: "ICompoundTimelock", + name: 'ICompoundTimelock', path: `@openzeppelin/contracts/vendor/compound/ICompoundTimelock.sol`, transpiled: false, }, timelockParent: { - name: "GovernorTimelockCompound", + name: 'GovernorTimelockCompound', path: `@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol`, }, }, @@ -375,37 +342,32 @@ function getQuorumFractionComponents(quorumPercent: number): { let quorumFractionNumerator = quorumPercent; let quorumFractionDenominator = undefined; - const quorumPercentSegments = quorumPercent.toString().split("."); + const quorumPercentSegments = quorumPercent.toString().split('.'); if (quorumPercentSegments.length > 2) { throw new OptionsError({ - quorumPercent: "Invalid percentage", + quorumPercent: 'Invalid percentage', }); } else if ( quorumPercentSegments.length == 2 && quorumPercentSegments[0] !== undefined && quorumPercentSegments[1] !== undefined ) { - quorumFractionNumerator = parseInt( - quorumPercentSegments[0].concat(quorumPercentSegments[1]), - ); + quorumFractionNumerator = parseInt(quorumPercentSegments[0].concat(quorumPercentSegments[1])); const decimals = quorumPercentSegments[1].length; - quorumFractionDenominator = "100"; + quorumFractionDenominator = '100'; while (quorumFractionDenominator.length < decimals + 3) { - quorumFractionDenominator += "0"; + quorumFractionDenominator += '0'; } } return { quorumFractionNumerator, quorumFractionDenominator }; } -function addTimelock( - c: ContractBuilder, - { timelock }: Required, -) { +function addTimelock(c: ContractBuilder, { timelock }: Required) { if (timelock === false) { return; } - const timelockArg = "_timelock"; + const timelockArg = '_timelock'; const { timelockType, timelockParent } = timelockModules[timelock]; c.addImportOnly(timelockType); @@ -426,8 +388,8 @@ function addTimelock( function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { if (storage) { const GovernorStorage = { - name: "GovernorStorage", - path: "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol", + name: 'GovernorStorage', + path: '@openzeppelin/contracts/governance/extensions/GovernorStorage.sol', }; c.addParent(GovernorStorage); c.addOverride(GovernorStorage, functions._propose); @@ -437,102 +399,102 @@ function addStorage(c: ContractBuilder, { storage }: GovernorOptions) { const functions = defineFunctions({ votingDelay: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, votingPeriod: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, proposalThreshold: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "pure", + returns: ['uint256'], + kind: 'public', + mutability: 'pure', }, proposalNeedsQueuing: { - args: [{ name: "proposalId", type: "uint256" }], - returns: ["bool"], - kind: "public", - mutability: "view", + args: [{ name: 'proposalId', type: 'uint256' }], + returns: ['bool'], + kind: 'public', + mutability: 'view', }, quorum: { - args: [{ name: "blockNumber", type: "uint256" }], - returns: ["uint256"], - kind: "public", - mutability: "view", + args: [{ name: 'blockNumber', type: 'uint256' }], + returns: ['uint256'], + kind: 'public', + mutability: 'view', }, quorumDenominator: { args: [], - returns: ["uint256"], - kind: "public", - mutability: "view", + returns: ['uint256'], + kind: 'public', + mutability: 'view', }, propose: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "description", type: "string memory" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'description', type: 'string memory' }, ], - returns: ["uint256"], - kind: "public", + returns: ['uint256'], + kind: 'public', }, _propose: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "description", type: "string memory" }, - { name: "proposer", type: "address" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'description', type: 'string memory' }, + { name: 'proposer', type: 'address' }, ], - returns: ["uint256"], - kind: "internal", + returns: ['uint256'], + kind: 'internal', }, _queueOperations: { args: [ - { name: "proposalId", type: "uint256" }, - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'proposalId', type: 'uint256' }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - kind: "internal", - returns: ["uint48"], + kind: 'internal', + returns: ['uint48'], }, _executeOperations: { args: [ - { name: "proposalId", type: "uint256" }, - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'proposalId', type: 'uint256' }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - kind: "internal", + kind: 'internal', }, _cancel: { args: [ - { name: "targets", type: "address[] memory" }, - { name: "values", type: "uint256[] memory" }, - { name: "calldatas", type: "bytes[] memory" }, - { name: "descriptionHash", type: "bytes32" }, + { name: 'targets', type: 'address[] memory' }, + { name: 'values', type: 'uint256[] memory' }, + { name: 'calldatas', type: 'bytes[] memory' }, + { name: 'descriptionHash', type: 'bytes32' }, ], - returns: ["uint256"], - kind: "internal", + returns: ['uint256'], + kind: 'internal', }, state: { - args: [{ name: "proposalId", type: "uint256" }], - returns: ["ProposalState"], - kind: "public", - mutability: "view", + args: [{ name: 'proposalId', type: 'uint256' }], + returns: ['ProposalState'], + kind: 'public', + mutability: 'view', }, _executor: { args: [], - returns: ["address"], - kind: "internal", - mutability: "view", + returns: ['address'], + kind: 'internal', + mutability: 'view', }, }); diff --git a/packages/core/solidity/src/index.ts b/packages/core/solidity/src/index.ts index 2cfc6b1ee..4b4bba141 100644 --- a/packages/core/solidity/src/index.ts +++ b/packages/core/solidity/src/index.ts @@ -1,32 +1,24 @@ -export type { GenericOptions, KindedOptions } from "./build-generic"; -export { buildGeneric } from "./build-generic"; +export type { GenericOptions, KindedOptions } from './build-generic'; +export { buildGeneric } from './build-generic'; -export type { Contract } from "./contract"; -export { ContractBuilder } from "./contract"; +export type { Contract } from './contract'; +export { ContractBuilder } from './contract'; -export { printContract } from "./print"; +export { printContract } from './print'; -export type { Access } from "./set-access-control"; -export type { Upgradeable } from "./set-upgradeable"; -export type { Info } from "./set-info"; +export type { Access } from './set-access-control'; +export type { Upgradeable } from './set-upgradeable'; +export type { Info } from './set-info'; -export { premintPattern } from "./erc20"; -export { defaults as infoDefaults } from "./set-info"; +export { premintPattern } from './erc20'; +export { defaults as infoDefaults } from './set-info'; -export type { OptionsErrorMessages } from "./error"; -export { OptionsError } from "./error"; +export type { OptionsErrorMessages } from './error'; +export { OptionsError } from './error'; -export type { Kind } from "./kind"; -export { sanitizeKind } from "./kind"; +export type { Kind } from './kind'; +export { sanitizeKind } from './kind'; -export { - erc20, - erc721, - erc1155, - stablecoin, - realWorldAsset, - governor, - custom, -} from "./api"; +export { erc20, erc721, erc1155, stablecoin, realWorldAsset, governor, custom } from './api'; -export { compatibleContractsSemver } from "./utils/version"; +export { compatibleContractsSemver } from './utils/version'; diff --git a/packages/core/solidity/src/infer-transpiled.test.ts b/packages/core/solidity/src/infer-transpiled.test.ts index 262d80660..cccffeeab 100644 --- a/packages/core/solidity/src/infer-transpiled.test.ts +++ b/packages/core/solidity/src/infer-transpiled.test.ts @@ -1,12 +1,12 @@ -import test from "ava"; -import { inferTranspiled } from "./infer-transpiled"; +import test from 'ava'; +import { inferTranspiled } from './infer-transpiled'; -test("infer transpiled", (t) => { - t.true(inferTranspiled({ name: "Foo" })); - t.true(inferTranspiled({ name: "Foo", transpiled: true })); - t.false(inferTranspiled({ name: "Foo", transpiled: false })); +test('infer transpiled', t => { + t.true(inferTranspiled({ name: 'Foo' })); + t.true(inferTranspiled({ name: 'Foo', transpiled: true })); + t.false(inferTranspiled({ name: 'Foo', transpiled: false })); - t.false(inferTranspiled({ name: "IFoo" })); - t.true(inferTranspiled({ name: "IFoo", transpiled: true })); - t.false(inferTranspiled({ name: "IFoo", transpiled: false })); + t.false(inferTranspiled({ name: 'IFoo' })); + t.true(inferTranspiled({ name: 'IFoo', transpiled: true })); + t.false(inferTranspiled({ name: 'IFoo', transpiled: false })); }); diff --git a/packages/core/solidity/src/infer-transpiled.ts b/packages/core/solidity/src/infer-transpiled.ts index 9089099e1..f338ea6a4 100644 --- a/packages/core/solidity/src/infer-transpiled.ts +++ b/packages/core/solidity/src/infer-transpiled.ts @@ -1,4 +1,4 @@ -import type { ReferencedContract } from "./contract"; +import type { ReferencedContract } from './contract'; export function inferTranspiled(c: ReferencedContract): boolean { return c.transpiled ?? !/^I[A-Z]/.test(c.name); diff --git a/packages/core/solidity/src/kind.ts b/packages/core/solidity/src/kind.ts index 844bfddde..26a775c4a 100644 --- a/packages/core/solidity/src/kind.ts +++ b/packages/core/solidity/src/kind.ts @@ -1,26 +1,26 @@ -import type { GenericOptions } from "./build-generic"; +import type { GenericOptions } from './build-generic'; -export type Kind = GenericOptions["kind"]; +export type Kind = GenericOptions['kind']; export function sanitizeKind(kind: unknown): Kind { - if (typeof kind === "string") { - const sanitized = kind.replace(/^(ERC|.)/i, (c) => c.toUpperCase()); + if (typeof kind === 'string') { + const sanitized = kind.replace(/^(ERC|.)/i, c => c.toUpperCase()); if (isKind(sanitized)) { return sanitized; } } - return "ERC20"; + return 'ERC20'; } function isKind(value: Kind | T): value is Kind { switch (value) { - case "ERC20": - case "ERC1155": - case "ERC721": - case "Stablecoin": - case "RealWorldAsset": - case "Governor": - case "Custom": + case 'ERC20': + case 'ERC1155': + case 'ERC721': + case 'Stablecoin': + case 'RealWorldAsset': + case 'Governor': + case 'Custom': return true; default: { diff --git a/packages/core/solidity/src/options.ts b/packages/core/solidity/src/options.ts index 7ae76a616..6e800715f 100644 --- a/packages/core/solidity/src/options.ts +++ b/packages/core/solidity/src/options.ts @@ -1,13 +1,13 @@ -import path from "path"; +import path from 'path'; -import type { Contract, ReferencedContract, ImportContract } from "./contract"; -import { inferTranspiled } from "./infer-transpiled"; +import type { Contract, ReferencedContract, ImportContract } from './contract'; +import { inferTranspiled } from './infer-transpiled'; const upgradeableName = (n: string) => { - if (n === "Initializable") { + if (n === 'Initializable') { return n; } else { - return n.replace(/(Upgradeable)?(?=\.|$)/, "Upgradeable"); + return n.replace(/(Upgradeable)?(?=\.|$)/, 'Upgradeable'); } }; @@ -19,10 +19,7 @@ const upgradeableImport = (p: ImportContract): ImportContract => { name: upgradeableName(p.name), // Contract name path: path.posix.format({ ext, - dir: dir.replace( - /^@openzeppelin\/contracts/, - "@openzeppelin/contracts-upgradeable", - ), + dir: dir.replace(/^@openzeppelin\/contracts/, '@openzeppelin/contracts-upgradeable'), name: upgradeableName(name), // Solidity file name }), }; @@ -40,15 +37,12 @@ export interface Helpers extends Required { export function withHelpers(contract: Contract, opts: Options = {}): Helpers { const contractUpgradeable = contract.upgradeable; const transformName = (n: ReferencedContract) => - contractUpgradeable && inferTranspiled(n) - ? upgradeableName(n.name) - : n.name; + contractUpgradeable && inferTranspiled(n) ? upgradeableName(n.name) : n.name; return { upgradeable: contractUpgradeable, transformName, - transformImport: (p1) => { - const p2 = - contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; + transformImport: p1 => { + const p2 = contractUpgradeable && inferTranspiled(p1) ? upgradeableImport(p1) : p1; return opts.transformImport?.(p2) ?? p2; }, }; diff --git a/packages/core/solidity/src/print-versioned.ts b/packages/core/solidity/src/print-versioned.ts index 4f2a6a59d..d4bde0aad 100644 --- a/packages/core/solidity/src/print-versioned.ts +++ b/packages/core/solidity/src/print-versioned.ts @@ -1,16 +1,13 @@ -import contracts from "../openzeppelin-contracts"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; +import contracts from '../openzeppelin-contracts'; +import type { Contract } from './contract'; +import { printContract } from './print'; export function printContractVersioned(contract: Contract): string { return printContract(contract, { - transformImport: (p) => { + transformImport: p => { return { ...p, - path: p.path.replace( - /^@openzeppelin\/contracts(-upgradeable)?/, - `$&@${contracts.version}`, - ), + path: p.path.replace(/^@openzeppelin\/contracts(-upgradeable)?/, `$&@${contracts.version}`), }; }, }); diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index c910b9888..b81651e68 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -6,23 +6,23 @@ import type { Value, NatspecTag, ImportContract, -} from "./contract"; -import { Options, Helpers, withHelpers } from "./options"; +} from './contract'; +import type { Options, Helpers } from './options'; +import { withHelpers } from './options'; -import { formatLines, spaceBetween, Lines } from "./utils/format-lines"; -import { mapValues } from "./utils/map-values"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import { inferTranspiled } from "./infer-transpiled"; -import { compatibleContractsSemver } from "./utils/version"; +import type { Lines } from './utils/format-lines'; +import { formatLines, spaceBetween } from './utils/format-lines'; +import { mapValues } from './utils/map-values'; +import SOLIDITY_VERSION from './solidity-version.json'; +import { inferTranspiled } from './infer-transpiled'; +import { compatibleContractsSemver } from './utils/version'; export function printContract(contract: Contract, opts?: Options): string { const helpers = withHelpers(contract, opts); - const fns = mapValues(sortedFunctions(contract), (fns) => - fns.map((fn) => printFunction(fn, helpers)), - ); + const fns = mapValues(sortedFunctions(contract), fns => fns.map(fn => printFunction(fn, helpers))); - const hasOverrides = fns.override.some((l) => l.length > 0); + const hasOverrides = fns.override.some(l => l.length > 0); return formatLines( ...spaceBetween( @@ -36,20 +36,14 @@ export function printContract(contract: Contract, opts?: Options): string { [ ...printNatspecTags(contract.natspecTags), - [ - `contract ${contract.name}`, - ...printInheritance(contract, helpers), - "{", - ].join(" "), + [`contract ${contract.name}`, ...printInheritance(contract, helpers), '{'].join(' '), spaceBetween( contract.variables, printConstructor(contract, helpers), ...fns.code, ...fns.modifiers, - hasOverrides - ? [`// The following functions are overrides required by Solidity.`] - : [], + hasOverrides ? [`// The following functions are overrides required by Solidity.`] : [], ...fns.override, ), @@ -59,23 +53,19 @@ export function printContract(contract: Contract, opts?: Options): string { ); } -function printInheritance( - contract: Contract, - { transformName }: Helpers, -): [] | [string] { +function printInheritance(contract: Contract, { transformName }: Helpers): [] | [string] { if (contract.parents.length > 0) { - return [ - "is " + contract.parents.map((p) => transformName(p.contract)).join(", "), - ]; + return ['is ' + contract.parents.map(p => transformName(p.contract)).join(', ')]; } else { return []; } } function printConstructor(contract: Contract, helpers: Helpers): Lines[] { - const hasParentParams = contract.parents.some((p) => p.params.length > 0); + const hasParentParams = contract.parents.some(p => p.params.length > 0); const hasConstructorCode = contract.constructorCode.length > 0; const parentsWithInitializers = contract.parents.filter(hasInitializer); +<<<<<<< HEAD if ( hasParentParams || hasConstructorCode || @@ -86,13 +76,19 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { ); const modifiers = helpers.upgradeable ? ["public initializer"] : parents; const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); +======= + if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) { + const parents = parentsWithInitializers.flatMap(p => printParentConstructor(p, helpers)); + const modifiers = helpers.upgradeable ? ['initializer public'] : parents; + const args = contract.constructorArgs.map(a => printArgument(a, helpers)); +>>>>>>> ce5fcd3 (Add consistent-type-imports rule) const body = helpers.upgradeable ? spaceBetween( - parents.map((p) => p + ";"), + parents.map(p => p + ';'), contract.constructorCode, ) : contract.constructorCode; - const head = helpers.upgradeable ? "function initialize" : "constructor"; + const head = helpers.upgradeable ? 'function initialize' : 'constructor'; const constructor = printFunction2([], head, args, modifiers, body); if (!helpers.upgradeable) { return constructor; @@ -107,23 +103,20 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { } const DISABLE_INITIALIZERS = [ - "/// @custom:oz-upgrades-unsafe-allow constructor", - "constructor() {", - ["_disableInitializers();"], - "}", + '/// @custom:oz-upgrades-unsafe-allow constructor', + 'constructor() {', + ['_disableInitializers();'], + '}', ]; function hasInitializer(parent: Parent) { // CAUTION // This list is validated by compilation of SafetyCheck.sol. // Always keep this list and that file in sync. - return !["Initializable"].includes(parent.contract.name); + return !['Initializable'].includes(parent.contract.name); } -type SortedFunctions = Record< - "code" | "modifiers" | "override", - ContractFunction[] ->; +type SortedFunctions = Record<'code' | 'modifiers' | 'override', ContractFunction[]>; // Functions with code first, then those with modifiers, then the rest function sortedFunctions(contract: Contract): SortedFunctions { @@ -142,29 +135,26 @@ function sortedFunctions(contract: Contract): SortedFunctions { return fns; } -function printParentConstructor( - { contract, params }: Parent, - helpers: Helpers, -): [] | [string] { +function printParentConstructor({ contract, params }: Parent, helpers: Helpers): [] | [string] { const useTranspiled = helpers.upgradeable && inferTranspiled(contract); const fn = useTranspiled ? `__${contract.name}_init` : contract.name; if (useTranspiled || params.length > 0) { - return [fn + "(" + params.map(printValue).join(", ") + ")"]; + return [fn + '(' + params.map(printValue).join(', ') + ')']; } else { return []; } } export function printValue(value: Value): string { - if (typeof value === "object") { - if ("lit" in value) { + if (typeof value === 'object') { + if ('lit' in value) { return value.lit; - } else if ("note" in value) { + } else if ('note' in value) { return `${printValue(value.value)} /* ${value.note} */`; } else { - throw Error("Unknown value type"); + throw Error('Unknown value type'); } - } else if (typeof value === "number") { + } else if (typeof value === 'number') { if (Number.isSafeInteger(value)) { return value.toFixed(0); } else { @@ -178,46 +168,49 @@ export function printValue(value: Value): string { function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { const { transformName } = helpers; - if ( - fn.override.size <= 1 && - fn.modifiers.length === 0 && - fn.code.length === 0 && - !fn.final - ) { + if (fn.override.size <= 1 && fn.modifiers.length === 0 && fn.code.length === 0 && !fn.final) { return []; } const modifiers: string[] = [fn.kind]; +<<<<<<< HEAD if (fn.mutability !== "nonpayable") { modifiers.push(fn.mutability); +======= + if (fn.mutability !== 'nonpayable') { + modifiers.splice(1, 0, fn.mutability); +>>>>>>> ce5fcd3 (Add consistent-type-imports rule) } if (fn.override.size === 1) { modifiers.push(`override`); } else if (fn.override.size > 1) { - modifiers.push( - `override(${[...fn.override].map(transformName).join(", ")})`, - ); + modifiers.push(`override(${[...fn.override].map(transformName).join(', ')})`); } modifiers.push(...fn.modifiers); if (fn.returns?.length) { - modifiers.push(`returns (${fn.returns.join(", ")})`); + modifiers.push(`returns (${fn.returns.join(', ')})`); } const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { +<<<<<<< HEAD const superCall = `super.${fn.name}(${fn.args.map((a) => a.name).join(", ")});`; code.push(fn.returns?.length ? "return " + superCall : superCall); +======= + const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; + code.push(fn.returns?.length ? 'return ' + superCall : superCall); +>>>>>>> ce5fcd3 (Add consistent-type-imports rule) } if (modifiers.length + fn.code.length > 1) { return printFunction2( fn.comments, - "function " + fn.name, - fn.args.map((a) => printArgument(a, helpers)), + 'function ' + fn.name, + fn.args.map(a => printArgument(a, helpers)), modifiers, code, ); @@ -237,33 +230,26 @@ function printFunction2( ): Lines[] { const fn: Lines[] = [...comments]; - const headingLength = [kindedName, ...args, ...modifiers] - .map((s) => s.length) - .reduce((a, b) => a + b); + const headingLength = [kindedName, ...args, ...modifiers].map(s => s.length).reduce((a, b) => a + b); - const braces = code.length > 0 ? "{" : "{}"; + const braces = code.length > 0 ? '{' : '{}'; if (headingLength <= 72) { - fn.push( - [`${kindedName}(${args.join(", ")})`, ...modifiers, braces].join(" "), - ); + fn.push([`${kindedName}(${args.join(', ')})`, ...modifiers, braces].join(' ')); } else { - fn.push(`${kindedName}(${args.join(", ")})`, modifiers, braces); + fn.push(`${kindedName}(${args.join(', ')})`, modifiers, braces); } if (code.length > 0) { - fn.push(code, "}"); + fn.push(code, '}'); } return fn; } -function printArgument( - arg: FunctionArgument, - { transformName }: Helpers, -): string { +function printArgument(arg: FunctionArgument, { transformName }: Helpers): string { let type: string; - if (typeof arg.type === "string") { + if (typeof arg.type === 'string') { if (/^[A-Z]/.test(arg.type)) { // eslint-disable-next-line @typescript-eslint/no-unused-expressions `Type ${arg.type} is not a primitive type. Define it as a ContractReference`; @@ -273,7 +259,7 @@ function printArgument( type = transformName(arg.type); } - return [type, arg.name].join(" "); + return [type, arg.name].join(' '); } function printNatspecTags(tags: NatspecTag[]): string[] { @@ -289,11 +275,9 @@ function printImports(imports: ImportContract[], helpers: Helpers): string[] { }); const lines: string[] = []; - imports.map((p) => { + imports.map(p => { const importContract = helpers.transformImport(p); - lines.push( - `import {${importContract.name}} from "${importContract.path}";`, - ); + lines.push(`import {${importContract.name}} from "${importContract.path}";`); }); return lines; diff --git a/packages/core/solidity/src/scripts/prepare.ts b/packages/core/solidity/src/scripts/prepare.ts index 50974bc2d..e65e08d9b 100644 --- a/packages/core/solidity/src/scripts/prepare.ts +++ b/packages/core/solidity/src/scripts/prepare.ts @@ -1,50 +1,44 @@ -import { promises as fs } from "fs"; -import path from "path"; -import hre from "hardhat"; -import type { BuildInfo } from "hardhat/types"; -import { findAll } from "solidity-ast/utils"; -import { rimraf } from "rimraf"; -import { version } from "@openzeppelin/contracts/package.json"; - -import type { OpenZeppelinContracts } from "../../openzeppelin-contracts"; -import { writeGeneratedSources } from "../generate/sources"; -import { mapValues } from "../utils/map-values"; -import { transitiveClosure } from "../utils/transitive-closure"; +import { promises as fs } from 'fs'; +import path from 'path'; +import hre from 'hardhat'; +import type { BuildInfo } from 'hardhat/types'; +import { findAll } from 'solidity-ast/utils'; +import { rimraf } from 'rimraf'; +import { version } from '@openzeppelin/contracts/package.json'; + +import type { OpenZeppelinContracts } from '../../openzeppelin-contracts'; +import { writeGeneratedSources } from '../generate/sources'; +import { mapValues } from '../utils/map-values'; +import { transitiveClosure } from '../utils/transitive-closure'; async function main() { - const generatedSourcesPath = path.join(hre.config.paths.sources, "generated"); + const generatedSourcesPath = path.join(hre.config.paths.sources, 'generated'); await rimraf(generatedSourcesPath); - await writeGeneratedSources(generatedSourcesPath, "minimal-cover"); - await hre.run("compile"); + await writeGeneratedSources(generatedSourcesPath, 'minimal-cover'); + await hre.run('compile'); const dependencies: Record> = {}; const sources: Record = {}; for (const buildInfoPath of await hre.artifacts.getBuildInfoPaths()) { - const buildInfo: BuildInfo = JSON.parse( - await fs.readFile(buildInfoPath, "utf8"), - ); + const buildInfo: BuildInfo = JSON.parse(await fs.readFile(buildInfoPath, 'utf8')); - for (const [sourceFile, { ast }] of Object.entries( - buildInfo.output.sources, - )) { + for (const [sourceFile, { ast }] of Object.entries(buildInfo.output.sources)) { if ( - sourceFile.startsWith("@openzeppelin/contracts") || - sourceFile.startsWith("@openzeppelin/community-contracts") + sourceFile.startsWith('@openzeppelin/contracts') || + sourceFile.startsWith('@openzeppelin/community-contracts') ) { const sourceDependencies = (dependencies[sourceFile] ??= new Set()); - for (const imp of findAll("ImportDirective", ast)) { + for (const imp of findAll('ImportDirective', ast)) { sourceDependencies.add(imp.absolutePath); } } } - for (const [sourceFile, { content }] of Object.entries( - buildInfo.input.sources, - )) { + for (const [sourceFile, { content }] of Object.entries(buildInfo.input.sources)) { if ( - sourceFile.startsWith("@openzeppelin/contracts") || - sourceFile.startsWith("@openzeppelin/community-contracts") + sourceFile.startsWith('@openzeppelin/contracts') || + sourceFile.startsWith('@openzeppelin/community-contracts') ) { sources[sourceFile] = content; } @@ -54,18 +48,13 @@ async function main() { const contracts: OpenZeppelinContracts = { version, sources, - dependencies: mapValues(transitiveClosure(dependencies), (d) => - Array.from(d), - ), + dependencies: mapValues(transitiveClosure(dependencies), d => Array.from(d)), }; - await fs.writeFile( - "openzeppelin-contracts.json", - JSON.stringify(contracts, null, 2), - ); + await fs.writeFile('openzeppelin-contracts.json', JSON.stringify(contracts, null, 2)); } -main().catch((e) => { +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/packages/core/solidity/src/set-access-control.ts b/packages/core/solidity/src/set-access-control.ts index 0147594f2..a4779231d 100644 --- a/packages/core/solidity/src/set-access-control.ts +++ b/packages/core/solidity/src/set-access-control.ts @@ -1,7 +1,7 @@ -import type { ContractBuilder, BaseFunction } from "./contract"; -import { supportsInterface } from "./common-functions"; +import type { ContractBuilder, BaseFunction } from './contract'; +import { supportsInterface } from './common-functions'; -export const accessOptions = [false, "ownable", "roles", "managed"] as const; +export const accessOptions = [false, 'ownable', 'roles', 'managed'] as const; export type Access = (typeof accessOptions)[number]; @@ -10,31 +10,31 @@ export type Access = (typeof accessOptions)[number]; */ export function setAccessControl(c: ContractBuilder, access: Access) { switch (access) { - case "ownable": { - if (c.addParent(parents.Ownable, [{ lit: "initialOwner" }])) { + case 'ownable': { + if (c.addParent(parents.Ownable, [{ lit: 'initialOwner' }])) { c.addConstructorArgument({ - type: "address", - name: "initialOwner", + type: 'address', + name: 'initialOwner', }); } break; } - case "roles": { + case 'roles': { if (c.addParent(parents.AccessControl)) { c.addConstructorArgument({ - type: "address", - name: "defaultAdmin", + type: 'address', + name: 'defaultAdmin', }); - c.addConstructorCode("_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);"); + c.addConstructorCode('_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);'); } c.addOverride(parents.AccessControl, supportsInterface); break; } - case "managed": { - if (c.addParent(parents.AccessManaged, [{ lit: "initialAuthority" }])) { + case 'managed': { + if (c.addParent(parents.AccessManaged, [{ lit: 'initialAuthority' }])) { c.addConstructorArgument({ - type: "address", - name: "initialAuthority", + type: 'address', + name: 'initialAuthority', }); } break; @@ -53,30 +53,28 @@ export function requireAccessControl( roleOwner: string | undefined, ) { if (access === false) { - access = "ownable"; + access = 'ownable'; } setAccessControl(c, access); switch (access) { - case "ownable": { - c.addModifier("onlyOwner", fn); + case 'ownable': { + c.addModifier('onlyOwner', fn); break; } - case "roles": { - const roleId = roleIdPrefix + "_ROLE"; - const addedConstant = c.addVariable( - `bytes32 public constant ${roleId} = keccak256("${roleId}");`, - ); + case 'roles': { + const roleId = roleIdPrefix + '_ROLE'; + const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); if (roleOwner && addedConstant) { - c.addConstructorArgument({ type: "address", name: roleOwner }); + c.addConstructorArgument({ type: 'address', name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } c.addModifier(`onlyRole(${roleId})`, fn); break; } - case "managed": { - c.addModifier("restricted", fn); + case 'managed': { + c.addModifier('restricted', fn); break; } } @@ -84,15 +82,15 @@ export function requireAccessControl( const parents = { Ownable: { - name: "Ownable", - path: "@openzeppelin/contracts/access/Ownable.sol", + name: 'Ownable', + path: '@openzeppelin/contracts/access/Ownable.sol', }, AccessControl: { - name: "AccessControl", - path: "@openzeppelin/contracts/access/AccessControl.sol", + name: 'AccessControl', + path: '@openzeppelin/contracts/access/AccessControl.sol', }, AccessManaged: { - name: "AccessManaged", - path: "@openzeppelin/contracts/access/manager/AccessManaged.sol", + name: 'AccessManaged', + path: '@openzeppelin/contracts/access/manager/AccessManaged.sol', }, }; diff --git a/packages/core/solidity/src/set-clock-mode.ts b/packages/core/solidity/src/set-clock-mode.ts index 3569b1dfe..189a952cc 100644 --- a/packages/core/solidity/src/set-clock-mode.ts +++ b/packages/core/solidity/src/set-clock-mode.ts @@ -1,39 +1,32 @@ -import type { ContractBuilder, ReferencedContract } from "./contract"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder, ReferencedContract } from './contract'; +import { defineFunctions } from './utils/define-functions'; -export const clockModeOptions = ["blocknumber", "timestamp"] as const; -export const clockModeDefault = "blocknumber" as const; +export const clockModeOptions = ['blocknumber', 'timestamp'] as const; +export const clockModeDefault = 'blocknumber' as const; export type ClockMode = (typeof clockModeOptions)[number]; const functions = defineFunctions({ clock: { - kind: "public" as const, + kind: 'public' as const, args: [], - returns: ["uint48"], - mutability: "view" as const, + returns: ['uint48'], + mutability: 'view' as const, }, CLOCK_MODE: { - kind: "public" as const, + kind: 'public' as const, args: [], - returns: ["string memory"], - mutability: "pure" as const, + returns: ['string memory'], + mutability: 'pure' as const, }, }); -export function setClockMode( - c: ContractBuilder, - parent: ReferencedContract, - votes: ClockMode, -) { - if (votes === "timestamp") { +export function setClockMode(c: ContractBuilder, parent: ReferencedContract, votes: ClockMode) { + if (votes === 'timestamp') { c.addOverride(parent, functions.clock); - c.setFunctionBody(["return uint48(block.timestamp);"], functions.clock); + c.setFunctionBody(['return uint48(block.timestamp);'], functions.clock); - c.setFunctionComments( - ["// solhint-disable-next-line func-name-mixedcase"], - functions.CLOCK_MODE, - ); + c.setFunctionComments(['// solhint-disable-next-line func-name-mixedcase'], functions.CLOCK_MODE); c.addOverride(parent, functions.CLOCK_MODE); c.setFunctionBody(['return "mode=timestamp";'], functions.CLOCK_MODE); } diff --git a/packages/core/solidity/src/set-info.ts b/packages/core/solidity/src/set-info.ts index 9821cd29a..61e3713f4 100644 --- a/packages/core/solidity/src/set-info.ts +++ b/packages/core/solidity/src/set-info.ts @@ -1,13 +1,10 @@ -import type { ContractBuilder } from "./contract"; +import type { ContractBuilder } from './contract'; export const TAG_SECURITY_CONTACT = `@custom:security-contact`; -export const infoOptions = [ - {}, - { securityContact: "security@example.com", license: "WTFPL" }, -] as const; +export const infoOptions = [{}, { securityContact: 'security@example.com', license: 'WTFPL' }] as const; -export const defaults: Info = { license: "MIT" }; +export const defaults: Info = { license: 'MIT' }; export type Info = { securityContact?: string; diff --git a/packages/core/solidity/src/set-upgradeable.ts b/packages/core/solidity/src/set-upgradeable.ts index 71073d743..ff6502805 100644 --- a/packages/core/solidity/src/set-upgradeable.ts +++ b/packages/core/solidity/src/set-upgradeable.ts @@ -1,16 +1,13 @@ -import type { ContractBuilder } from "./contract"; -import { Access, requireAccessControl } from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; +import type { ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; -export const upgradeableOptions = [false, "transparent", "uups"] as const; +export const upgradeableOptions = [false, 'transparent', 'uups'] as const; export type Upgradeable = (typeof upgradeableOptions)[number]; -export function setUpgradeable( - c: ContractBuilder, - upgradeable: Upgradeable, - access: Access, -) { +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { if (upgradeable === false) { return; } @@ -18,25 +15,19 @@ export function setUpgradeable( c.upgradeable = true; c.addParent({ - name: "Initializable", - path: "@openzeppelin/contracts/proxy/utils/Initializable.sol", + name: 'Initializable', + path: '@openzeppelin/contracts/proxy/utils/Initializable.sol', }); switch (upgradeable) { - case "transparent": + case 'transparent': break; - case "uups": { - requireAccessControl( - c, - functions._authorizeUpgrade, - access, - "UPGRADER", - "upgrader", - ); + case 'uups': { + requireAccessControl(c, functions._authorizeUpgrade, access, 'UPGRADER', 'upgrader'); const UUPSUpgradeable = { - name: "UUPSUpgradeable", - path: "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol", + name: 'UUPSUpgradeable', + path: '@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol', }; c.addParent(UUPSUpgradeable); c.addOverride(UUPSUpgradeable, functions._authorizeUpgrade); @@ -46,14 +37,14 @@ export function setUpgradeable( default: { const _: never = upgradeable; - throw new Error("Unknown value for `upgradeable`"); + throw new Error('Unknown value for `upgradeable`'); } } } const functions = defineFunctions({ _authorizeUpgrade: { - args: [{ name: "newImplementation", type: "address" }], - kind: "internal", + args: [{ name: 'newImplementation', type: 'address' }], + kind: 'internal', }, }); diff --git a/packages/core/solidity/src/stablecoin.test.ts b/packages/core/solidity/src/stablecoin.test.ts index 35d205e0c..b5a837863 100644 --- a/packages/core/solidity/src/stablecoin.test.ts +++ b/packages/core/solidity/src/stablecoin.test.ts @@ -1,14 +1,15 @@ -import test from "ava"; -import { stablecoin } from "."; +import test from 'ava'; +import { stablecoin } from '.'; -import { buildStablecoin, StablecoinOptions } from "./stablecoin"; -import { printContract } from "./print"; +import type { StablecoinOptions } from './stablecoin'; +import { buildStablecoin } from './stablecoin'; +import { printContract } from './print'; function testStablecoin(title: string, opts: Partial) { - test(title, (t) => { + test(title, t => { const c = buildStablecoin({ - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', ...opts, }); t.snapshot(printContract(c)); @@ -19,13 +20,13 @@ function testStablecoin(title: string, opts: Partial) { * Tests external API for equivalence with internal API */ function testAPIEquivalence(title: string, opts?: StablecoinOptions) { - test(title, (t) => { + test(title, t => { t.is( stablecoin.print(opts), printContract( buildStablecoin({ - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', ...opts, }), ), @@ -33,111 +34,111 @@ function testAPIEquivalence(title: string, opts?: StablecoinOptions) { }); } -testStablecoin("basic stablecoin", {}); +testStablecoin('basic stablecoin', {}); -testStablecoin("stablecoin burnable", { +testStablecoin('stablecoin burnable', { burnable: true, }); -testStablecoin("stablecoin pausable", { +testStablecoin('stablecoin pausable', { pausable: true, - access: "ownable", + access: 'ownable', }); -testStablecoin("stablecoin pausable with roles", { +testStablecoin('stablecoin pausable with roles', { pausable: true, - access: "roles", + access: 'roles', }); -testStablecoin("stablecoin pausable with managed", { +testStablecoin('stablecoin pausable with managed', { pausable: true, - access: "managed", + access: 'managed', }); -testStablecoin("stablecoin burnable pausable", { +testStablecoin('stablecoin burnable pausable', { burnable: true, pausable: true, }); -testStablecoin("stablecoin preminted", { - premint: "1000", +testStablecoin('stablecoin preminted', { + premint: '1000', }); -testStablecoin("stablecoin premint of 0", { - premint: "0", +testStablecoin('stablecoin premint of 0', { + premint: '0', }); -testStablecoin("stablecoin mintable", { +testStablecoin('stablecoin mintable', { mintable: true, - access: "ownable", + access: 'ownable', }); -testStablecoin("stablecoin mintable with roles", { +testStablecoin('stablecoin mintable with roles', { mintable: true, - access: "roles", + access: 'roles', }); -testStablecoin("stablecoin permit", { +testStablecoin('stablecoin permit', { permit: true, }); -testStablecoin("stablecoin custodian", { +testStablecoin('stablecoin custodian', { custodian: true, }); -testStablecoin("stablecoin allowlist", { - limitations: "allowlist", +testStablecoin('stablecoin allowlist', { + limitations: 'allowlist', }); -testStablecoin("stablecoin blocklist", { - limitations: "blocklist", +testStablecoin('stablecoin blocklist', { + limitations: 'blocklist', }); -testStablecoin("stablecoin votes", { +testStablecoin('stablecoin votes', { votes: true, }); -testStablecoin("stablecoin votes + blocknumber", { - votes: "blocknumber", +testStablecoin('stablecoin votes + blocknumber', { + votes: 'blocknumber', }); -testStablecoin("stablecoin votes + timestamp", { - votes: "timestamp", +testStablecoin('stablecoin votes + timestamp', { + votes: 'timestamp', }); -testStablecoin("stablecoin flashmint", { +testStablecoin('stablecoin flashmint', { flashmint: true, }); -testAPIEquivalence("stablecoin API default"); +testAPIEquivalence('stablecoin API default'); -testAPIEquivalence("stablecoin API basic", { - name: "CustomStablecoin", - symbol: "CST", +testAPIEquivalence('stablecoin API basic', { + name: 'CustomStablecoin', + symbol: 'CST', }); -testAPIEquivalence("stablecoin API full", { - name: "CustomStablecoin", - symbol: "CST", - premint: "2000", - access: "roles", +testAPIEquivalence('stablecoin API full', { + name: 'CustomStablecoin', + symbol: 'CST', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, permit: true, votes: true, flashmint: true, - limitations: "allowlist", + limitations: 'allowlist', custodian: true, }); -test("stablecoin API assert defaults", async (t) => { +test('stablecoin API assert defaults', async t => { t.is(stablecoin.print(stablecoin.defaults), stablecoin.print()); }); -test("stablecoin API isAccessControlRequired", async (t) => { +test('stablecoin API isAccessControlRequired', async t => { t.is(stablecoin.isAccessControlRequired({ mintable: true }), true); t.is(stablecoin.isAccessControlRequired({ pausable: true }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: "allowlist" }), true); - t.is(stablecoin.isAccessControlRequired({ limitations: "blocklist" }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: 'allowlist' }), true); + t.is(stablecoin.isAccessControlRequired({ limitations: 'blocklist' }), true); }); diff --git a/packages/core/solidity/src/stablecoin.ts b/packages/core/solidity/src/stablecoin.ts index af0dec852..7510d6755 100644 --- a/packages/core/solidity/src/stablecoin.ts +++ b/packages/core/solidity/src/stablecoin.ts @@ -1,28 +1,25 @@ -import { Contract, ContractBuilder } from "./contract"; -import { - Access, - setAccessControl, - requireAccessControl, -} from "./set-access-control"; -import { defineFunctions } from "./utils/define-functions"; -import { printContract } from "./print"; +import type { Contract, ContractBuilder } from './contract'; +import type { Access } from './set-access-control'; +import { setAccessControl, requireAccessControl } from './set-access-control'; +import { defineFunctions } from './utils/define-functions'; +import { printContract } from './print'; +import type { ERC20Options } from './erc20'; import { buildERC20, - ERC20Options, defaults as erc20defaults, withDefaults as withERC20Defaults, functions as erc20functions, -} from "./erc20"; +} from './erc20'; export interface StablecoinOptions extends ERC20Options { - limitations?: false | "allowlist" | "blocklist"; + limitations?: false | 'allowlist' | 'blocklist'; custodian?: boolean; } export const defaults: Required = { ...erc20defaults, - name: "MyStablecoin", - symbol: "MST", + name: 'MyStablecoin', + symbol: 'MST', limitations: false, custodian: false, } as const; @@ -41,16 +38,8 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string { return printContract(buildStablecoin(opts)); } -export function isAccessControlRequired( - opts: Partial, -): boolean { - return ( - opts.mintable || - opts.limitations !== false || - opts.custodian || - opts.pausable || - opts.upgradeable === "uups" - ); +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable || opts.limitations !== false || opts.custodian || opts.pausable || opts.upgradeable === 'uups'; } export function buildStablecoin(opts: StablecoinOptions): Contract { @@ -72,15 +61,11 @@ export function buildStablecoin(opts: StablecoinOptions): Contract { return c; } -function addLimitations( - c: ContractBuilder, - access: Access, - mode: boolean | "allowlist" | "blocklist", -) { - const type = mode === "allowlist"; +function addLimitations(c: ContractBuilder, access: Access, mode: boolean | 'allowlist' | 'blocklist') { + const type = mode === 'allowlist'; const ERC20Limitation = { - name: type ? "ERC20Allowlist" : "ERC20Blocklist", - path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? "ERC20Allowlist" : "ERC20Blocklist"}.sol`, + name: type ? 'ERC20Allowlist' : 'ERC20Blocklist', + path: `@openzeppelin/community-contracts/contracts/token/ERC20/extensions/${type ? 'ERC20Allowlist' : 'ERC20Blocklist'}.sol`, }; c.addParent(ERC20Limitation); @@ -91,20 +76,17 @@ function addLimitations( ? [functions.allowUser, functions.disallowUser] : [functions.blockUser, functions.unblockUser]; - requireAccessControl(c, addFn, access, "LIMITER", "limiter"); - c.addFunctionCode(`_${type ? "allowUser" : "blockUser"}(user);`, addFn); + requireAccessControl(c, addFn, access, 'LIMITER', 'limiter'); + c.addFunctionCode(`_${type ? 'allowUser' : 'blockUser'}(user);`, addFn); - requireAccessControl(c, removeFn, access, "LIMITER", "limiter"); - c.addFunctionCode( - `_${type ? "disallowUser" : "unblockUser"}(user);`, - removeFn, - ); + requireAccessControl(c, removeFn, access, 'LIMITER', 'limiter'); + c.addFunctionCode(`_${type ? 'disallowUser' : 'unblockUser'}(user);`, removeFn); } function addCustodian(c: ContractBuilder, access: Access) { const ERC20Custodian = { - name: "ERC20Custodian", - path: "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol", + name: 'ERC20Custodian', + path: '@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Custodian.sol', }; c.addParent(ERC20Custodian); @@ -112,35 +94,30 @@ function addCustodian(c: ContractBuilder, access: Access) { c.addOverride(ERC20Custodian, functions._isCustodian); if (access === false) { - access = "ownable"; + access = 'ownable'; } setAccessControl(c, access); switch (access) { - case "ownable": { + case 'ownable': { c.setFunctionBody([`return user == owner();`], functions._isCustodian); break; } - case "roles": { - const roleOwner = "custodian"; - const roleId = "CUSTODIAN_ROLE"; - const addedConstant = c.addVariable( - `bytes32 public constant ${roleId} = keccak256("${roleId}");`, - ); + case 'roles': { + const roleOwner = 'custodian'; + const roleId = 'CUSTODIAN_ROLE'; + const addedConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); if (roleOwner && addedConstant) { - c.addConstructorArgument({ type: "address", name: roleOwner }); + c.addConstructorArgument({ type: 'address', name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } - c.setFunctionBody( - [`return hasRole(CUSTODIAN_ROLE, user);`], - functions._isCustodian, - ); + c.setFunctionBody([`return hasRole(CUSTODIAN_ROLE, user);`], functions._isCustodian); break; } - case "managed": { + case 'managed': { c.addImportOnly({ - name: "AuthorityUtils", + name: 'AuthorityUtils', path: `@openzeppelin/contracts/access/manager/AuthorityUtils.sol`, }); const logic = [ @@ -157,30 +134,30 @@ const functions = { ...erc20functions, ...defineFunctions({ _isCustodian: { - kind: "internal" as const, - args: [{ name: "user", type: "address" }], - returns: ["bool"], - mutability: "view" as const, + kind: 'internal' as const, + args: [{ name: 'user', type: 'address' }], + returns: ['bool'], + mutability: 'view' as const, }, allowUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, disallowUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, blockUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, unblockUser: { - kind: "public" as const, - args: [{ name: "user", type: "address" }], + kind: 'public' as const, + args: [{ name: 'user', type: 'address' }], }, }), }; diff --git a/packages/core/solidity/src/test.ts b/packages/core/solidity/src/test.ts index e0e7211cb..1d7105f86 100644 --- a/packages/core/solidity/src/test.ts +++ b/packages/core/solidity/src/test.ts @@ -1,11 +1,12 @@ -import { promises as fs } from "fs"; -import _test, { TestFn, ExecutionContext } from "ava"; -import hre from "hardhat"; -import path from "path"; +import { promises as fs } from 'fs'; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; +import hre from 'hardhat'; +import path from 'path'; -import { generateSources, writeGeneratedSources } from "./generate/sources"; -import type { GenericOptions, KindedOptions } from "./build-generic"; -import { custom, erc1155, stablecoin, erc20, erc721, governor } from "./api"; +import { generateSources, writeGeneratedSources } from './generate/sources'; +import type { GenericOptions, KindedOptions } from './build-generic'; +import { custom, erc1155, stablecoin, erc20, erc721, governor } from './api'; interface Context { generatedSourcesPath: string; @@ -13,37 +14,34 @@ interface Context { const test = _test as TestFn; -test.serial("erc20 result compiles", async (t) => { - await testCompile(t, "ERC20"); +test.serial('erc20 result compiles', async t => { + await testCompile(t, 'ERC20'); }); -test.serial("erc721 result compiles", async (t) => { - await testCompile(t, "ERC721"); +test.serial('erc721 result compiles', async t => { + await testCompile(t, 'ERC721'); }); -test.serial("erc1155 result compiles", async (t) => { - await testCompile(t, "ERC1155"); +test.serial('erc1155 result compiles', async t => { + await testCompile(t, 'ERC1155'); }); -test.serial("stablecoin result compiles", async (t) => { - await testCompile(t, "Stablecoin"); +test.serial('stablecoin result compiles', async t => { + await testCompile(t, 'Stablecoin'); }); -test.serial("governor result compiles", async (t) => { - await testCompile(t, "Governor"); +test.serial('governor result compiles', async t => { + await testCompile(t, 'Governor'); }); -test.serial("custom result compiles", async (t) => { - await testCompile(t, "Custom"); +test.serial('custom result compiles', async t => { + await testCompile(t, 'Custom'); }); -async function testCompile( - t: ExecutionContext, - kind: keyof KindedOptions, -) { +async function testCompile(t: ExecutionContext, kind: keyof KindedOptions) { const generatedSourcesPath = path.join(hre.config.paths.sources, `generated`); await fs.rm(generatedSourcesPath, { force: true, recursive: true }); - await writeGeneratedSources(generatedSourcesPath, "all", kind); + await writeGeneratedSources(generatedSourcesPath, 'all', kind); // We only want to check that contracts compile and we don't care about any // of the outputs. Setting empty outputSelection causes compilation to go a @@ -52,48 +50,40 @@ async function testCompile( settings.outputSelection = {}; } - await hre.run("compile"); + await hre.run('compile'); t.pass(); } function isAccessControlRequired(opts: GenericOptions) { switch (opts.kind) { - case "ERC20": + case 'ERC20': return erc20.isAccessControlRequired(opts); - case "ERC721": + case 'ERC721': return erc721.isAccessControlRequired(opts); - case "ERC1155": + case 'ERC1155': return erc1155.isAccessControlRequired(opts); - case "Stablecoin": + case 'Stablecoin': return stablecoin.isAccessControlRequired(opts); - case "RealWorldAsset": + case 'RealWorldAsset': return stablecoin.isAccessControlRequired(opts); - case "Governor": + case 'Governor': return governor.isAccessControlRequired(opts); - case "Custom": + case 'Custom': return custom.isAccessControlRequired(opts); default: - throw new Error("No such kind"); + throw new Error('No such kind'); } } -test("is access control required", async (t) => { - for (const contract of generateSources("all")) { +test('is access control required', async t => { + for (const contract of generateSources('all')) { const regexOwnable = /import.*Ownable(Upgradeable)?.sol.*/gm; if (!contract.options.access) { if (isAccessControlRequired(contract.options)) { - t.regex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); } else { - t.notRegex( - contract.source, - regexOwnable, - JSON.stringify(contract.options), - ); + t.notRegex(contract.source, regexOwnable, JSON.stringify(contract.options)); } } } diff --git a/packages/core/solidity/src/utils/define-functions.ts b/packages/core/solidity/src/utils/define-functions.ts index c05316bce..3c89e6c76 100644 --- a/packages/core/solidity/src/utils/define-functions.ts +++ b/packages/core/solidity/src/utils/define-functions.ts @@ -1,18 +1,9 @@ -import type { BaseFunction } from "../contract"; +import type { BaseFunction } from '../contract'; -type ImplicitNameFunction = Omit; +type ImplicitNameFunction = Omit; -export function defineFunctions( - fns: Record, -): Record; +export function defineFunctions(fns: Record): Record; -export function defineFunctions( - fns: Record, -): Record { - return Object.fromEntries( - Object.entries(fns).map(([name, fn]) => [ - name, - Object.assign({ name }, fn), - ]), - ); +export function defineFunctions(fns: Record): Record { + return Object.fromEntries(Object.entries(fns).map(([name, fn]) => [name, Object.assign({ name }, fn)])); } diff --git a/packages/core/solidity/src/utils/duration.ts b/packages/core/solidity/src/utils/duration.ts index 32c273fff..f74ff1c1d 100644 --- a/packages/core/solidity/src/utils/duration.ts +++ b/packages/core/solidity/src/utils/duration.ts @@ -1,17 +1,6 @@ -const durationUnits = [ - "block", - "second", - "minute", - "hour", - "day", - "week", - "month", - "year", -] as const; +const durationUnits = ['block', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year'] as const; type DurationUnit = (typeof durationUnits)[number]; -export const durationPattern = new RegExp( - `^(\\d+(?:\\.\\d+)?) +(${durationUnits.join("|")})s?$`, -); +export const durationPattern = new RegExp(`^(\\d+(?:\\.\\d+)?) +(${durationUnits.join('|')})s?$`); const second = 1; const minute = 60 * second; @@ -26,15 +15,15 @@ export function durationToBlocks(duration: string, blockTime: number): number { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = parseFloat(match[1]!); const unit = match[2]! as DurationUnit; - if (unit === "block") { + if (unit === 'block') { if (!Number.isInteger(value)) { - throw new Error("Invalid number of blocks"); + throw new Error('Invalid number of blocks'); } return value; @@ -48,15 +37,15 @@ export function durationToTimestamp(duration: string): string { const match = duration.trim().match(durationPattern); if (!match) { - throw new Error("Bad duration format"); + throw new Error('Bad duration format'); } const value = match[1]!; const unit = match[2]! as DurationUnit; // unit must be a Solidity supported time unit - if (unit === "block" || unit === "month" || unit === "year") { - throw new Error("Invalid unit for timestamp"); + if (unit === 'block' || unit === 'month' || unit === 'year') { + throw new Error('Invalid unit for timestamp'); } return `${value} ${unit}s`; diff --git a/packages/core/solidity/src/utils/find-cover.ts b/packages/core/solidity/src/utils/find-cover.ts index 0cc7f0bb6..939ed9240 100644 --- a/packages/core/solidity/src/utils/find-cover.ts +++ b/packages/core/solidity/src/utils/find-cover.ts @@ -1,14 +1,11 @@ -import { sortedBy } from "./sorted-by"; +import { sortedBy } from './sorted-by'; // Greedy approximation of minimum set cover. -export function findCover( - sets: T[], - getElements: (set: T) => unknown[], -): T[] { +export function findCover(sets: T[], getElements: (set: T) => unknown[]): T[] { const sortedSets = sortedBy( - sets.map((set) => ({ set, elems: getElements(set) })), - (s) => -s.elems.length, + sets.map(set => ({ set, elems: getElements(set) })), + s => -s.elems.length, ); const seen = new Set(); diff --git a/packages/core/solidity/src/utils/format-lines.ts b/packages/core/solidity/src/utils/format-lines.ts index a0ab46e87..6ab9aa445 100644 --- a/packages/core/solidity/src/utils/format-lines.ts +++ b/packages/core/solidity/src/utils/format-lines.ts @@ -1,37 +1,30 @@ export type Lines = string | typeof whitespace | Lines[]; -const whitespace = Symbol("whitespace"); +const whitespace = Symbol('whitespace'); export function formatLines(...lines: Lines[]): string { return formatLinesWithSpaces(4, ...lines); } -export function formatLinesWithSpaces( - spacesPerIndent: number, - ...lines: Lines[] -): string { - return [...indentEach(0, lines, spacesPerIndent)].join("\n") + "\n"; +export function formatLinesWithSpaces(spacesPerIndent: number, ...lines: Lines[]): string { + return [...indentEach(0, lines, spacesPerIndent)].join('\n') + '\n'; } -function* indentEach( - indent: number, - lines: Lines[], - spacesPerIndent: number, -): Generator { +function* indentEach(indent: number, lines: Lines[], spacesPerIndent: number): Generator { for (const line of lines) { if (line === whitespace) { - yield ""; + yield ''; } else if (Array.isArray(line)) { yield* indentEach(indent + 1, line, spacesPerIndent); } else { - yield " ".repeat(indent * spacesPerIndent) + line; + yield ' '.repeat(indent * spacesPerIndent) + line; } } } export function spaceBetween(...lines: Lines[][]): Lines[] { return lines - .filter((l) => l.length > 0) - .flatMap((l) => [whitespace, ...l]) + .filter(l => l.length > 0) + .flatMap(l => [whitespace, ...l]) .slice(1); } diff --git a/packages/core/solidity/src/utils/map-values.ts b/packages/core/solidity/src/utils/map-values.ts index b177c69a4..1e42bad36 100644 --- a/packages/core/solidity/src/utils/map-values.ts +++ b/packages/core/solidity/src/utils/map-values.ts @@ -1,8 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function mapValues( - obj: Record, - fn: (val: V) => W, -): Record { +export function mapValues(obj: Record, fn: (val: V) => W): Record { const res = {} as Record; for (const key in obj) { res[key] = fn(obj[key]); diff --git a/packages/core/solidity/src/utils/to-identifier.test.ts b/packages/core/solidity/src/utils/to-identifier.test.ts index 870f062ff..6af3b3a11 100644 --- a/packages/core/solidity/src/utils/to-identifier.test.ts +++ b/packages/core/solidity/src/utils/to-identifier.test.ts @@ -1,20 +1,20 @@ -import test from "ava"; +import test from 'ava'; -import { toIdentifier } from "./to-identifier"; +import { toIdentifier } from './to-identifier'; -test("unmodified", (t) => { - t.is(toIdentifier("abc"), "abc"); +test('unmodified', t => { + t.is(toIdentifier('abc'), 'abc'); }); -test("remove leading specials", (t) => { - t.is(toIdentifier("--abc"), "abc"); +test('remove leading specials', t => { + t.is(toIdentifier('--abc'), 'abc'); }); -test("remove specials and upcase next char", (t) => { - t.is(toIdentifier("abc-def"), "abcDef"); - t.is(toIdentifier("abc--def"), "abcDef"); +test('remove specials and upcase next char', t => { + t.is(toIdentifier('abc-def'), 'abcDef'); + t.is(toIdentifier('abc--def'), 'abcDef'); }); -test("capitalize", (t) => { - t.is(toIdentifier("abc", true), "Abc"); +test('capitalize', t => { + t.is(toIdentifier('abc', true), 'Abc'); }); diff --git a/packages/core/solidity/src/utils/to-identifier.ts b/packages/core/solidity/src/utils/to-identifier.ts index eb49e670f..70e7c9310 100644 --- a/packages/core/solidity/src/utils/to-identifier.ts +++ b/packages/core/solidity/src/utils/to-identifier.ts @@ -1,8 +1,8 @@ export function toIdentifier(str: string, capitalize = false): string { return str - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") // remove accents - .replace(/^[^a-zA-Z$_]+/, "") - .replace(/^(.)/, (c) => (capitalize ? c.toUpperCase() : c)) + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') // remove accents + .replace(/^[^a-zA-Z$_]+/, '') + .replace(/^(.)/, c => (capitalize ? c.toUpperCase() : c)) .replace(/[^\w$]+(.?)/g, (_, c) => c.toUpperCase()); } diff --git a/packages/core/solidity/src/utils/transitive-closure.ts b/packages/core/solidity/src/utils/transitive-closure.ts index 1b56e79fc..f2e04ba2d 100644 --- a/packages/core/solidity/src/utils/transitive-closure.ts +++ b/packages/core/solidity/src/utils/transitive-closure.ts @@ -1,8 +1,6 @@ type T = string; -export function transitiveClosure( - obj: Record>, -): Record> { +export function transitiveClosure(obj: Record>): Record> { const closure = {} as Record>; for (const key in obj) { diff --git a/packages/core/solidity/src/utils/version.test.ts b/packages/core/solidity/src/utils/version.test.ts index bc114df8c..5a90bc410 100644 --- a/packages/core/solidity/src/utils/version.test.ts +++ b/packages/core/solidity/src/utils/version.test.ts @@ -1,11 +1,11 @@ -import test from "ava"; +import test from 'ava'; -import semver from "semver"; +import semver from 'semver'; -import { compatibleContractsSemver } from "./version"; -import contracts from "../../openzeppelin-contracts"; +import { compatibleContractsSemver } from './version'; +import contracts from '../../openzeppelin-contracts'; -test("installed contracts satisfies compatible range", (t) => { +test('installed contracts satisfies compatible range', t => { t.true( semver.satisfies(contracts.version, compatibleContractsSemver), `Installed contracts version ${contracts.version} does not satisfy compatible range ${compatibleContractsSemver}. diff --git a/packages/core/solidity/src/utils/version.ts b/packages/core/solidity/src/utils/version.ts index 31bf323b7..3daddc218 100644 --- a/packages/core/solidity/src/utils/version.ts +++ b/packages/core/solidity/src/utils/version.ts @@ -1,4 +1,4 @@ /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = "^5.0.0"; +export const compatibleContractsSemver = '^5.0.0'; diff --git a/packages/core/solidity/src/zip-foundry.test.ts b/packages/core/solidity/src/zip-foundry.test.ts index ae1a640fc..c1214fa24 100644 --- a/packages/core/solidity/src/zip-foundry.test.ts +++ b/packages/core/solidity/src/zip-foundry.test.ts @@ -1,21 +1,22 @@ -import _test, { TestFn, ExecutionContext } from "ava"; - -import { zipFoundry } from "./zip-foundry"; - -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { buildERC1155 } from "./erc1155"; -import { buildCustom } from "./custom"; -import { promises as fs } from "fs"; -import path from "path"; -import os from "os"; -import util from "util"; -import child from "child_process"; -import type { Contract } from "./contract"; -import { rimraf } from "rimraf"; -import type { JSZipObject } from "jszip"; -import type JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; + +import { zipFoundry } from './zip-foundry'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import { buildCustom } from './custom'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; +import util from 'util'; +import child from 'child_process'; +import type { Contract } from './contract'; +import { rimraf } from 'rimraf'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; interface Context { tempFolder: string; @@ -23,23 +24,21 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async (t) => { - t.context.tempFolder = await fs.mkdtemp( - path.join(os.tmpdir(), "openzeppelin-wizard-"), - ); +test.beforeEach(async t => { + t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); }); -test.afterEach.always(async (t) => { +test.afterEach.always(async t => { await rimraf(t.context.tempFolder); }); -test.serial("erc20 full", async (t) => { +test.serial('erc20 full', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - premint: "2000", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, @@ -51,74 +50,70 @@ test.serial("erc20 full", async (t) => { await runTest(c, t, opts); }); -test.serial("erc20 uups, roles", async (t) => { +test.serial('erc20 uups, roles', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', + access: 'roles', }; const c = buildERC20(opts); await runTest(c, t, opts); }); -test.serial("erc721 uups, ownable", async (t) => { +test.serial('erc721 uups, ownable', async t => { const opts: GenericOptions = { - kind: "ERC721", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", - access: "ownable", + kind: 'ERC721', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', + access: 'ownable', }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial("erc1155 basic", async (t) => { +test.serial('erc1155 basic', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("erc1155 transparent, ownable", async (t) => { +test.serial('erc1155 transparent, ownable', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", - upgradeable: "transparent", - access: "ownable", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', + upgradeable: 'transparent', + access: 'ownable', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("custom basic", async (t) => { - const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; +test.serial('custom basic', async t => { + const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial("custom transparent, managed", async (t) => { +test.serial('custom transparent, managed', async t => { const opts: GenericOptions = { - kind: "Custom", - name: "My Contract", - upgradeable: "transparent", - access: "managed", + kind: 'Custom', + name: 'My Contract', + upgradeable: 'transparent', + access: 'managed', }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest( - c: Contract, - t: ExecutionContext, - opts: GenericOptions, -) { +async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { const zip = await zipFoundry(c, opts); assertLayout(zip, c, t); @@ -128,25 +123,21 @@ async function runTest( function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { const sorted = Object.values(zip.files) - .map((f) => f.name) + .map(f => f.name) .sort(); t.deepEqual(sorted, [ - "README.md", - "script/", + 'README.md', + 'script/', `script/${c.name}.s.sol`, - "setup.sh", - "src/", + 'setup.sh', + 'src/', `src/${c.name}.sol`, - "test/", + 'test/', `test/${c.name}.t.sol`, ]); } -async function extractAndRunPackage( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -156,22 +147,16 @@ async function extractAndRunPackage( if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile( - path.join(tempFolder, item.name), - await asString(item), - ); + await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); } } - const setGitUser = - 'git init && git config user.email "test@test.test" && git config user.name "Test"'; - const setup = "bash setup.sh"; - const test = "forge test" + (c.upgradeable ? " --force" : ""); - const script = - `forge script script/${c.name}.s.sol` + (c.upgradeable ? " --force" : ""); + const setGitUser = 'git init && git config user.email "test@test.test" && git config user.name "Test"'; + const setup = 'bash setup.sh'; + const test = 'forge test' + (c.upgradeable ? ' --force' : ''); + const script = `forge script script/${c.name}.s.sol` + (c.upgradeable ? ' --force' : ''); - const exec = (cmd: string) => - util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: "" } }); + const exec = (cmd: string) => util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: '' } }); const command = `cd "${tempFolder}" && ${setGitUser} && ${setup} && ${test} && ${script}`; const result = await exec(command); @@ -190,13 +175,8 @@ async function extractAndRunPackage( t.regex(rerunResult.stdout, /Foundry project already initialized\./); } -async function assertContents( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { - const normalizeVersion = (text: string) => - text.replace(/\bv\d+\.\d+\.\d+\b/g, "vX.Y.Z"); +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { + const normalizeVersion = (text: string) => text.replace(/\bv\d+\.\d+\.\d+\b/g, 'vX.Y.Z'); const contentComparison = [ normalizeVersion(await getItemString(zip, `setup.sh`)), @@ -218,5 +198,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async("arraybuffer")).toString(); + return Buffer.from(await item.async('arraybuffer')).toString(); } diff --git a/packages/core/solidity/src/zip-foundry.ts b/packages/core/solidity/src/zip-foundry.ts index 4a0e5c8af..81850a8d0 100644 --- a/packages/core/solidity/src/zip-foundry.ts +++ b/packages/core/solidity/src/zip-foundry.ts @@ -1,34 +1,23 @@ -import JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import contracts from "../openzeppelin-contracts"; -import { - formatLinesWithSpaces, - Lines, - spaceBetween, -} from "./utils/format-lines"; +import JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; +import type { Contract } from './contract'; +import { printContract } from './print'; +import SOLIDITY_VERSION from './solidity-version.json'; +import contracts from '../openzeppelin-contracts'; +import type { Lines } from './utils/format-lines'; +import { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; function getHeader(c: Contract) { - return [ - `// SPDX-License-Identifier: ${c.license}`, - `pragma solidity ^${SOLIDITY_VERSION};`, - ]; + return [`// SPDX-License-Identifier: ${c.license}`, `pragma solidity ^${SOLIDITY_VERSION};`]; } const test = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getHeader(c), getImports(c), getTestCase(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getHeader(c), getImports(c), getTestCase(c))); function getImports(c: Contract) { const result = ['import {Test} from "forge-std/Test.sol";']; if (c.upgradeable) { - result.push( - 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', - ); + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -40,55 +29,39 @@ const test = (c: Contract, opts?: GenericOptions) => { `contract ${c.name}Test is Test {`, spaceBetween( [`${c.name} public instance;`], - [ - "function setUp() public {", - getAddressVariables(c, args), - getDeploymentCode(c, args), - "}", - ], + ['function setUp() public {', getAddressVariables(c, args), getDeploymentCode(c, args), '}'], getContractSpecificTestFunction(), ), - "}", + '}', ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === "transparent") { + if (opts?.upgradeable === 'transparent') { return [ `address proxy = Upgrades.deployTransparentProxy(`, - [ - `"${c.name}.sol",`, - `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `initialOwner,`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `instance = ${c.name}(proxy);`, ]; } else { return [ `address proxy = Upgrades.deployUUPSProxy(`, - [ - `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `instance = ${c.name}(proxy);`, ]; } } else { - return [`instance = new ${c.name}(${args.join(", ")});`]; + return [`instance = new ${c.name}(${args.join(', ')});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; let i = 1; // private key index starts from 1 since it must be non-zero - if ( - c.upgradeable && - opts?.upgradeable === "transparent" && - !args.includes("initialOwner") - ) { + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { vars.push(`address initialOwner = vm.addr(${i++});`); } for (const arg of args) { @@ -100,31 +73,19 @@ const test = (c: Contract, opts?: GenericOptions) => { function getContractSpecificTestFunction(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case "ERC20": - case "ERC721": - return [ - "function testName() public view {", - [`assertEq(instance.name(), "${opts.name}");`], - "}", - ]; - - case "ERC1155": - return [ - "function testUri() public view {", - [`assertEq(instance.uri(0), "${opts.uri}");`], - "}", - ]; - - case "Governor": - case "Custom": - return [ - "function testSomething() public {", - ["// Add your test here"], - "}", - ]; + case 'ERC20': + case 'ERC721': + return ['function testName() public view {', [`assertEq(instance.name(), "${opts.name}");`], '}']; + + case 'ERC1155': + return ['function testUri() public view {', [`assertEq(instance.uri(0), "${opts.uri}");`], '}']; + + case 'Governor': + case 'Custom': + return ['function testSomething() public {', ['// Add your test here'], '}']; default: - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } return []; @@ -134,7 +95,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === "address") { + if (constructorArg.type === 'address') { args.push(constructorArg.name); } } @@ -142,20 +103,12 @@ function getAddressArgs(c: Contract): string[] { } const script = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getHeader(c), getImports(c), getScript(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getHeader(c), getImports(c), getScript(c))); function getImports(c: Contract) { - const result = [ - 'import {Script} from "forge-std/Script.sol";', - 'import {console} from "forge-std/console.sol";', - ]; + const result = ['import {Script} from "forge-std/Script.sol";', 'import {console} from "forge-std/console.sol";']; if (c.upgradeable) { - result.push( - 'import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";', - ); + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); } result.push(`import {${c.name}} from "src/${c.name}.sol";`); return result; @@ -164,65 +117,48 @@ const script = (c: Contract, opts?: GenericOptions) => { function getScript(c: Contract) { const args = getAddressArgs(c); const deploymentLines = [ - "vm.startBroadcast();", + 'vm.startBroadcast();', ...getAddressVariables(c, args), ...getDeploymentCode(c, args), - `console.log("${c.upgradeable ? "Proxy" : "Contract"} deployed to %s", address(instance));`, - "vm.stopBroadcast();", + `console.log("${c.upgradeable ? 'Proxy' : 'Contract'} deployed to %s", address(instance));`, + 'vm.stopBroadcast();', ]; return [ `contract ${c.name}Script is Script {`, spaceBetween( - ["function setUp() public {}"], - [ - "function run() public {", - args.length > 0 - ? addTodoAndCommentOut(deploymentLines) - : deploymentLines, - "}", - ], + ['function setUp() public {}'], + ['function run() public {', args.length > 0 ? addTodoAndCommentOut(deploymentLines) : deploymentLines, '}'], ), - "}", + '}', ]; } function getDeploymentCode(c: Contract, args: string[]): Lines[] { if (c.upgradeable) { - if (opts?.upgradeable === "transparent") { + if (opts?.upgradeable === 'transparent') { return [ `address proxy = Upgrades.deployTransparentProxy(`, - [ - `"${c.name}.sol",`, - `initialOwner,`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `initialOwner,`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `${c.name} instance = ${c.name}(proxy);`, ]; } else { return [ `address proxy = Upgrades.deployUUPSProxy(`, - [ - `"${c.name}.sol",`, - `abi.encodeCall(${c.name}.initialize, (${args.join(", ")}))`, - ], - ");", + [`"${c.name}.sol",`, `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))`], + ');', `${c.name} instance = ${c.name}(proxy);`, ]; } } else { - return [`${c.name} instance = new ${c.name}(${args.join(", ")});`]; + return [`${c.name} instance = new ${c.name}(${args.join(', ')});`]; } } function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; - if ( - c.upgradeable && - opts?.upgradeable === "transparent" && - !args.includes("initialOwner") - ) { - vars.push("address initialOwner = ;"); + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + vars.push('address initialOwner = ;'); } for (const arg of args) { vars.push(`address ${arg} = ;`); @@ -232,10 +168,10 @@ const script = (c: Contract, opts?: GenericOptions) => { function addTodoAndCommentOut(lines: Lines[]) { return [ - "// TODO: Set addresses for the variables below, then uncomment the following section:", - "/*", + '// TODO: Set addresses for the variables below, then uncomment the following section:', + '/*', ...lines, - "*/", + '*/', ]; } }; @@ -341,7 +277,7 @@ bash setup.sh ## Testing the contract \`\`\` -forge test${c.upgradeable ? " --force" : ""} +forge test${c.upgradeable ? ' --force' : ''} \`\`\` ## Deploying the contract @@ -349,7 +285,7 @@ forge test${c.upgradeable ? " --force" : ""} You can simulate a deployment by running the script: \`\`\` -forge script script/${c.name}.s.sol${c.upgradeable ? " --force" : ""} +forge script script/${c.name}.s.sol${c.upgradeable ? ' --force' : ''} \`\`\` See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information. @@ -361,8 +297,8 @@ export async function zipFoundry(c: Contract, opts?: GenericOptions) { zip.file(`src/${c.name}.sol`, printContract(c)); zip.file(`test/${c.name}.t.sol`, test(c, opts)); zip.file(`script/${c.name}.s.sol`, script(c, opts)); - zip.file("setup.sh", setupSh(c)); - zip.file("README.md", readme(c)); + zip.file('setup.sh', setupSh(c)); + zip.file('README.md', readme(c)); return zip; } diff --git a/packages/core/solidity/src/zip-hardhat.test.ts b/packages/core/solidity/src/zip-hardhat.test.ts index 30c1380f8..5e08d7010 100644 --- a/packages/core/solidity/src/zip-hardhat.test.ts +++ b/packages/core/solidity/src/zip-hardhat.test.ts @@ -1,21 +1,22 @@ -import _test, { TestFn, ExecutionContext } from "ava"; - -import { zipHardhat } from "./zip-hardhat"; - -import { buildERC20 } from "./erc20"; -import { buildERC721 } from "./erc721"; -import { buildERC1155 } from "./erc1155"; -import { buildCustom } from "./custom"; -import { promises as fs } from "fs"; -import path from "path"; -import os from "os"; -import util from "util"; -import child from "child_process"; -import type { Contract } from "./contract"; -import { rimraf } from "rimraf"; -import type { JSZipObject } from "jszip"; -import type JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; +import type { TestFn, ExecutionContext } from 'ava'; +import _test from 'ava'; + +import { zipHardhat } from './zip-hardhat'; + +import { buildERC20 } from './erc20'; +import { buildERC721 } from './erc721'; +import { buildERC1155 } from './erc1155'; +import { buildCustom } from './custom'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; +import util from 'util'; +import child from 'child_process'; +import type { Contract } from './contract'; +import { rimraf } from 'rimraf'; +import type { JSZipObject } from 'jszip'; +import type JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; interface Context { tempFolder: string; @@ -23,23 +24,21 @@ interface Context { const test = _test as TestFn; -test.beforeEach(async (t) => { - t.context.tempFolder = await fs.mkdtemp( - path.join(os.tmpdir(), "openzeppelin-wizard-"), - ); +test.beforeEach(async t => { + t.context.tempFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'openzeppelin-wizard-')); }); -test.afterEach.always(async (t) => { +test.afterEach.always(async t => { await rimraf(t.context.tempFolder); }); -test.serial("erc20 full", async (t) => { +test.serial('erc20 full', async t => { const opts: GenericOptions = { - kind: "ERC20", - name: "My Token", - symbol: "MTK", - premint: "2000", - access: "roles", + kind: 'ERC20', + name: 'My Token', + symbol: 'MTK', + premint: '2000', + access: 'roles', burnable: true, mintable: true, pausable: true, @@ -51,48 +50,44 @@ test.serial("erc20 full", async (t) => { await runTest(c, t, opts); }); -test.serial("erc721 upgradeable", async (t) => { +test.serial('erc721 upgradeable', async t => { const opts: GenericOptions = { - kind: "ERC721", - name: "My Token", - symbol: "MTK", - upgradeable: "uups", + kind: 'ERC721', + name: 'My Token', + symbol: 'MTK', + upgradeable: 'uups', }; const c = buildERC721(opts); await runTest(c, t, opts); }); -test.serial("erc1155 basic", async (t) => { +test.serial('erc1155 basic', async t => { const opts: GenericOptions = { - kind: "ERC1155", - name: "My Token", - uri: "https://myuri/{id}", + kind: 'ERC1155', + name: 'My Token', + uri: 'https://myuri/{id}', }; const c = buildERC1155(opts); await runTest(c, t, opts); }); -test.serial("custom basic", async (t) => { - const opts: GenericOptions = { kind: "Custom", name: "My Contract" }; +test.serial('custom basic', async t => { + const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; const c = buildCustom(opts); await runTest(c, t, opts); }); -test.serial("custom upgradeable", async (t) => { +test.serial('custom upgradeable', async t => { const opts: GenericOptions = { - kind: "Custom", - name: "My Contract", - upgradeable: "transparent", + kind: 'Custom', + name: 'My Contract', + upgradeable: 'transparent', }; const c = buildCustom(opts); await runTest(c, t, opts); }); -async function runTest( - c: Contract, - t: ExecutionContext, - opts: GenericOptions, -) { +async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { const zip = await zipHardhat(c, opts); assertLayout(zip, c, t); @@ -102,29 +97,25 @@ async function runTest( function assertLayout(zip: JSZip, c: Contract, t: ExecutionContext) { const sorted = Object.values(zip.files) - .map((f) => f.name) + .map(f => f.name) .sort(); t.deepEqual(sorted, [ - ".gitignore", - "README.md", - "contracts/", + '.gitignore', + 'README.md', + 'contracts/', `contracts/${c.name}.sol`, - "hardhat.config.ts", - "package-lock.json", - "package.json", - "scripts/", - "scripts/deploy.ts", - "test/", - "test/test.ts", - "tsconfig.json", + 'hardhat.config.ts', + 'package-lock.json', + 'package.json', + 'scripts/', + 'scripts/deploy.ts', + 'test/', + 'test/test.ts', + 'tsconfig.json', ]); } -async function extractAndRunPackage( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext) { const files = Object.values(zip.files); const tempFolder = t.context.tempFolder; @@ -134,17 +125,14 @@ async function extractAndRunPackage( if (item.dir) { await fs.mkdir(path.join(tempFolder, item.name)); } else { - await fs.writeFile( - path.join(tempFolder, item.name), - await asString(item), - ); + await fs.writeFile(path.join(tempFolder, item.name), await asString(item)); } } let command = `cd "${tempFolder}" && npm install && npm test`; if (c.constructorArgs === undefined) { // only test deploying the contract if there are no constructor args needed - command += " && npx hardhat run scripts/deploy.ts"; + command += ' && npx hardhat run scripts/deploy.ts'; } const exec = util.promisify(child.exec); @@ -156,17 +144,13 @@ async function extractAndRunPackage( } } -async function assertContents( - zip: JSZip, - c: Contract, - t: ExecutionContext, -) { +async function assertContents(zip: JSZip, c: Contract, t: ExecutionContext) { const contentComparison = [ await getItemString(zip, `contracts/${c.name}.sol`), - await getItemString(zip, "hardhat.config.ts"), - await getItemString(zip, "package.json"), - await getItemString(zip, "scripts/deploy.ts"), - await getItemString(zip, "test/test.ts"), + await getItemString(zip, 'hardhat.config.ts'), + await getItemString(zip, 'package.json'), + await getItemString(zip, 'scripts/deploy.ts'), + await getItemString(zip, 'test/test.ts'), ]; t.snapshot(contentComparison); @@ -181,5 +165,5 @@ async function getItemString(zip: JSZip, key: string) { } async function asString(item: JSZipObject) { - return Buffer.from(await item.async("arraybuffer")).toString(); + return Buffer.from(await item.async('arraybuffer')).toString(); } diff --git a/packages/core/solidity/src/zip-hardhat.ts b/packages/core/solidity/src/zip-hardhat.ts index f9092b67f..893f9cf79 100644 --- a/packages/core/solidity/src/zip-hardhat.ts +++ b/packages/core/solidity/src/zip-hardhat.ts @@ -1,18 +1,15 @@ -import JSZip from "jszip"; -import type { GenericOptions } from "./build-generic"; -import type { Contract } from "./contract"; -import { printContract } from "./print"; -import SOLIDITY_VERSION from "./solidity-version.json"; -import { - formatLinesWithSpaces, - Lines, - spaceBetween, -} from "./utils/format-lines"; +import JSZip from 'jszip'; +import type { GenericOptions } from './build-generic'; +import type { Contract } from './contract'; +import { printContract } from './print'; +import SOLIDITY_VERSION from './solidity-version.json'; +import type { Lines } from './utils/format-lines'; +import { formatLinesWithSpaces, spaceBetween } from './utils/format-lines'; const hardhatConfig = (upgradeable: boolean) => `\ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; -${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ""} +${upgradeable ? `import "@openzeppelin/hardhat-upgrades";` : ''} const config: HardhatUserConfig = { solidity: { @@ -56,10 +53,7 @@ artifacts `; const test = (c: Contract, opts?: GenericOptions) => { - return formatLinesWithSpaces( - 2, - ...spaceBetween(getImports(c), getTestCase(c)), - ); + return formatLinesWithSpaces(2, ...spaceBetween(getImports(c), getTestCase(c))); function getTestCase(c: Contract) { const args = getAddressArgs(c); @@ -68,45 +62,37 @@ const test = (c: Contract, opts?: GenericOptions) => { [ 'it("Test contract", async function () {', spaceBetween( - [ - `const ContractFactory = await ethers.getContractFactory("${c.name}");`, - ], + [`const ContractFactory = await ethers.getContractFactory("${c.name}");`], getAddressVariables(args), - [ - `const instance = await ${getDeploymentCall(c, args)};`, - "await instance.waitForDeployment();", - ], + [`const instance = await ${getDeploymentCall(c, args)};`, 'await instance.waitForDeployment();'], getExpects(), ), - "});", + '});', ], - "});", + '});', ]; } function getImports(c: Contract) { - return [ - 'import { expect } from "chai";', - `import { ${getHardhatPlugins(c).join(", ")} } from "hardhat";`, - ]; + return ['import { expect } from "chai";', `import { ${getHardhatPlugins(c).join(', ')} } from "hardhat";`]; } function getExpects(): Lines[] { if (opts !== undefined) { switch (opts.kind) { - case "ERC20": - case "ERC721": + case 'ERC20': + case 'ERC721': return [`expect(await instance.name()).to.equal("${opts.name}");`]; - case "ERC1155": + case 'ERC1155': return [`expect(await instance.uri(0)).to.equal("${opts.uri}");`]; - case "Governor": - case "Custom": + case 'Governor': + case 'Custom': break; default: - throw new Error("Unknown ERC"); + throw new Error('Unknown ERC'); } } return []; @@ -115,9 +101,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressVariables(args: string[]): Lines[] { const vars = []; for (let i = 0; i < args.length; i++) { - vars.push( - `const ${args[i]} = (await ethers.getSigners())[${i}].address;`, - ); + vars.push(`const ${args[i]} = (await ethers.getSigners())[${i}].address;`); } return vars; } @@ -126,7 +110,7 @@ const test = (c: Contract, opts?: GenericOptions) => { function getAddressArgs(c: Contract): string[] { const args = []; for (const constructorArg of c.constructorArgs) { - if (constructorArg.type === "address") { + if (constructorArg.type === 'address') { args.push(constructorArg.name); } } @@ -135,29 +119,23 @@ function getAddressArgs(c: Contract): string[] { function getDeploymentCall(c: Contract, args: string[]): string { return c.upgradeable - ? `upgrades.deployProxy(ContractFactory, [${args.join(", ")}])` - : `ContractFactory.deploy(${args.join(", ")})`; + ? `upgrades.deployProxy(ContractFactory, [${args.join(', ')}])` + : `ContractFactory.deploy(${args.join(', ')})`; } const script = (c: Contract) => { const args = getAddressArgs(c); return `\ -import { ${getHardhatPlugins(c).join(", ")} } from "hardhat"; +import { ${getHardhatPlugins(c).join(', ')} } from "hardhat"; async function main() { const ContractFactory = await ethers.getContractFactory("${c.name}"); - ${ - args.length > 0 - ? "// TODO: Set addresses for the contract arguments below" - : "" - } + ${args.length > 0 ? '// TODO: Set addresses for the contract arguments below' : ''} const instance = await ${getDeploymentCall(c, args)}; await instance.waitForDeployment(); - console.log(\`${ - c.upgradeable ? "Proxy" : "Contract" - } deployed to \${await instance.getAddress()}\`); + console.log(\`${c.upgradeable ? 'Proxy' : 'Contract'} deployed to \${await instance.getAddress()}\`); } // We recommend this pattern to be able to use async/await everywhere @@ -196,9 +174,9 @@ npx hardhat run --network scripts/deploy.ts `; function getHardhatPlugins(c: Contract) { - const plugins = ["ethers"]; + const plugins = ['ethers']; if (c.upgradeable) { - plugins.push("upgrades"); + plugins.push('upgrades'); } return plugins; } @@ -207,24 +185,24 @@ export async function zipHardhat(c: Contract, opts?: GenericOptions) { const zip = new JSZip(); const { default: packageJson } = c.upgradeable - ? await import("./environments/hardhat/upgradeable/package.json") - : await import("./environments/hardhat/package.json"); + ? await import('./environments/hardhat/upgradeable/package.json') + : await import('./environments/hardhat/package.json'); packageJson.license = c.license; const { default: packageLock } = c.upgradeable - ? await import("./environments/hardhat/upgradeable/package-lock.json") - : await import("./environments/hardhat/package-lock.json"); - packageLock.packages[""].license = c.license; + ? await import('./environments/hardhat/upgradeable/package-lock.json') + : await import('./environments/hardhat/package-lock.json'); + packageLock.packages[''].license = c.license; zip.file(`contracts/${c.name}.sol`, printContract(c)); - zip.file("test/test.ts", test(c, opts)); - zip.file("scripts/deploy.ts", script(c)); - zip.file(".gitignore", gitIgnore); - zip.file("hardhat.config.ts", hardhatConfig(c.upgradeable)); - zip.file("package.json", JSON.stringify(packageJson, null, 2)); + zip.file('test/test.ts', test(c, opts)); + zip.file('scripts/deploy.ts', script(c)); + zip.file('.gitignore', gitIgnore); + zip.file('hardhat.config.ts', hardhatConfig(c.upgradeable)); + zip.file('package.json', JSON.stringify(packageJson, null, 2)); zip.file(`package-lock.json`, JSON.stringify(packageLock, null, 2)); - zip.file("README.md", readme); - zip.file("tsconfig.json", tsConfig); + zip.file('README.md', readme); + zip.file('tsconfig.json', tsConfig); return zip; } diff --git a/packages/core/solidity/zip-env-foundry.js b/packages/core/solidity/zip-env-foundry.js index 5cfb903a7..836eb4d8e 100644 --- a/packages/core/solidity/zip-env-foundry.js +++ b/packages/core/solidity/zip-env-foundry.js @@ -1 +1 @@ -module.exports = require("./dist/zip-foundry"); +module.exports = require('./dist/zip-foundry'); diff --git a/packages/core/solidity/zip-env-foundry.ts b/packages/core/solidity/zip-env-foundry.ts index 7b167f759..b28db5f46 100644 --- a/packages/core/solidity/zip-env-foundry.ts +++ b/packages/core/solidity/zip-env-foundry.ts @@ -1 +1 @@ -export * from "./src/zip-foundry"; +export * from './src/zip-foundry'; diff --git a/packages/core/solidity/zip-env-hardhat.js b/packages/core/solidity/zip-env-hardhat.js index 5ce54ca53..2d13c70df 100644 --- a/packages/core/solidity/zip-env-hardhat.js +++ b/packages/core/solidity/zip-env-hardhat.js @@ -1 +1 @@ -module.exports = require("./dist/zip-hardhat"); +module.exports = require('./dist/zip-hardhat'); diff --git a/packages/core/solidity/zip-env-hardhat.ts b/packages/core/solidity/zip-env-hardhat.ts index 3934d6d7f..8786bfeae 100644 --- a/packages/core/solidity/zip-env-hardhat.ts +++ b/packages/core/solidity/zip-env-hardhat.ts @@ -1 +1 @@ -export * from "./src/zip-hardhat"; +export * from './src/zip-hardhat'; diff --git a/packages/ui/api/ai.ts b/packages/ui/api/ai.ts index 938e5370d..2332bea44 100644 --- a/packages/ui/api/ai.ts +++ b/packages/ui/api/ai.ts @@ -1,5 +1,5 @@ -import OpenAI from "https://esm.sh/openai@4.11.0"; -import { OpenAIStream, StreamingTextResponse } from "https://esm.sh/ai@2.2.16"; +import OpenAI from 'https://esm.sh/openai@4.11.0'; +import { OpenAIStream, StreamingTextResponse } from 'https://esm.sh/ai@2.2.16'; import { erc20Function, erc721Function, @@ -8,19 +8,19 @@ import { realWorldAssetFunction, governorFunction, customFunction, -} from "../src/solidity/wiz-functions.ts"; -import { Redis } from "https://esm.sh/@upstash/redis@1.25.1"; +} from '../src/solidity/wiz-functions.ts'; +import { Redis } from 'https://esm.sh/@upstash/redis@1.25.1'; export default async (req: Request) => { try { const data = await req.json(); - const apiKey = Deno.env.get("OPENAI_API_KEY"); + const apiKey = Deno.env.get('OPENAI_API_KEY'); - const redisUrl = Deno.env.get("REDIS_URL"); - const redisToken = Deno.env.get("REDIS_TOKEN"); + const redisUrl = Deno.env.get('REDIS_URL'); + const redisToken = Deno.env.get('REDIS_TOKEN'); if (!redisUrl || !redisToken) { - throw new Error("missing redis credentials"); + throw new Error('missing redis credentials'); } const redis = new Redis({ @@ -32,15 +32,13 @@ export default async (req: Request) => { apiKey: apiKey, }); - const validatedMessages = data.messages.filter( - (message: { role: string; content: string }) => { - return message.content.length < 500; - }, - ); + const validatedMessages = data.messages.filter((message: { role: string; content: string }) => { + return message.content.length < 500; + }); const messages = [ { - role: "system", + role: 'system', content: ` You are a smart contract assistant built by OpenZeppelin to help users using OpenZeppelin Contracts Wizard. The current options are ${JSON.stringify(data.currentOpts)}. @@ -51,7 +49,7 @@ export default async (req: Request) => { ]; const response = await openai.chat.completions.create({ - model: "gpt-4-1106-preview", + model: 'gpt-4-1106-preview', messages, functions: [ erc20Function, @@ -77,7 +75,7 @@ export default async (req: Request) => { ...messages, { content: completion, - role: "assistant", + role: 'assistant', }, ], }; @@ -91,9 +89,9 @@ export default async (req: Request) => { }); return new StreamingTextResponse(stream); } catch (e) { - console.error("Could not retrieve results:", e); + console.error('Could not retrieve results:', e); return Response.json({ - error: "Could not retrieve results.", + error: 'Could not retrieve results.', }); } }; diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js index 0b17b641f..01dfd298e 100644 --- a/packages/ui/postcss.config.js +++ b/packages/ui/postcss.config.js @@ -1,6 +1,6 @@ -const nesting = require("tailwindcss/nesting"); -const tailwindcss = require("tailwindcss"); -const autoprefixer = require("autoprefixer"); +const nesting = require('tailwindcss/nesting'); +const tailwindcss = require('tailwindcss'); +const autoprefixer = require('autoprefixer'); module.exports = { plugins: [nesting, tailwindcss, autoprefixer], diff --git a/packages/ui/rollup.config.mjs b/packages/ui/rollup.config.mjs index 65f0d7635..e920328fd 100644 --- a/packages/ui/rollup.config.mjs +++ b/packages/ui/rollup.config.mjs @@ -1,27 +1,27 @@ -import svelte from "rollup-plugin-svelte"; -import commonjs from "@rollup/plugin-commonjs"; -import json from "@rollup/plugin-json"; -import resolve from "@rollup/plugin-node-resolve"; -import replace from "@rollup/plugin-replace"; -import alias from "@rollup/plugin-alias"; -import livereload from "rollup-plugin-livereload"; -import { terser } from "rollup-plugin-terser"; -import typescript from "@rollup/plugin-typescript"; -import styles from "rollup-plugin-styles"; -import proc from "child_process"; -import events from "events"; -import serve from "./rollup.server.mjs"; +import svelte from 'rollup-plugin-svelte'; +import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; +import resolve from '@rollup/plugin-node-resolve'; +import replace from '@rollup/plugin-replace'; +import alias from '@rollup/plugin-alias'; +import livereload from 'rollup-plugin-livereload'; +import { terser } from 'rollup-plugin-terser'; +import typescript from '@rollup/plugin-typescript'; +import styles from 'rollup-plugin-styles'; +import proc from 'child_process'; +import events from 'events'; +import serve from './rollup.server.mjs'; const production = !process.env.ROLLUP_WATCH; -process.env.NODE_ENV = production ? "production" : "development"; +process.env.NODE_ENV = production ? 'production' : 'development'; // Watch the `public` directory and refresh the // browser on changes when not in production const livereloader = !production && livereload({ - watch: "public", + watch: 'public', port: 35731, }); @@ -31,10 +31,10 @@ function onStartRun(cmd, ...args) { async buildStart() { if (ran) return; const child = proc.spawn(cmd, args, { - stdio: "inherit", - shell: process.platform == "win32", + stdio: 'inherit', + shell: process.platform == 'win32', }); - const [code, signal] = await events.once(child, "exit"); + const [code, signal] = await events.once(child, 'exit'); if (code || signal) { throw new Error(`Command \`${cmd}\` failed`); } @@ -46,37 +46,37 @@ function onStartRun(cmd, ...args) { /** @type import('rollup').RollupOptions */ export default [ { - input: "src/standalone.js", + input: 'src/standalone.js', output: { - dir: "public/build", - assetFileNames: "[name][extname]", + dir: 'public/build', + assetFileNames: '[name][extname]', sourcemap: true, }, onwarn(warning, warn) { - if (warning.code !== "EMPTY_BUNDLE") { + if (warning.code !== 'EMPTY_BUNDLE') { warn(warning); } }, plugins: [ styles({ - include: "src/standalone.css", - mode: ["extract"], + include: 'src/standalone.css', + mode: ['extract'], url: false, sourceMap: true, }), ], }, { - input: "src/embed.ts", + input: 'src/embed.ts', output: { sourcemap: true, - format: "iife", - name: "embed", - file: "public/build/embed.js", + format: 'iife', + name: 'embed', + file: 'public/build/embed.js', }, plugins: [ typescript({ - include: ["src/**/*.ts"], + include: ['src/**/*.ts'], sourceMap: true, inlineSources: true, }), @@ -90,45 +90,44 @@ export default [ }, { preserveEntrySignatures: false, - input: "src/main.ts", + input: 'src/main.ts', output: { sourcemap: true, - format: "es", - dir: "public/build", - chunkFileNames: "[name].js", - assetFileNames: "[name][extname]", + format: 'es', + dir: 'public/build', + chunkFileNames: '[name].js', + assetFileNames: '[name][extname]', }, plugins: [ // Generate openzeppelin-contracts.js data file - onStartRun(..."yarn --cwd ../core/solidity prepare".split(" ")), + onStartRun(...'yarn --cwd ../core/solidity prepare'.split(' ')), - svelte(await import("./svelte.config.js")), + svelte(await import('./svelte.config.js')), styles({ - mode: ["extract", "bundle.css"], + mode: ['extract', 'bundle.css'], sourceMap: true, }), alias({ entries: { - path: "path-browserify", - "highlight.js/lib/languages/python": - "../../node_modules/highlight.js/lib/languages/python.js", + path: 'path-browserify', + 'highlight.js/lib/languages/python': '../../node_modules/highlight.js/lib/languages/python.js', }, }), resolve({ browser: true, - dedupe: ["svelte"], - mainFields: ["ts:main", "module", "main"], + dedupe: ['svelte'], + mainFields: ['ts:main', 'module', 'main'], preferBuiltins: false, }), replace({ preventAssignment: true, - include: "../../**/node_modules/**/*", - "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV), - "process.env.NODE_DEBUG": JSON.stringify(process.env.NODE_DEBUG), + include: '../../**/node_modules/**/*', + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), }), json(), @@ -136,7 +135,7 @@ export default [ commonjs(), typescript({ - include: ["src/**/*.ts", "../core/*/src/**/*.ts"], + include: ['src/**/*.ts', '../core/*/src/**/*.ts'], sourceMap: true, inlineSources: true, }), diff --git a/packages/ui/rollup.server.mjs b/packages/ui/rollup.server.mjs index e456b3d2e..ff2266018 100644 --- a/packages/ui/rollup.server.mjs +++ b/packages/ui/rollup.server.mjs @@ -1,4 +1,4 @@ -import proc from "child_process"; +import proc from 'child_process'; const state = (global.ROLLUP_SERVER = global.ROLLUP_SERVER || { server: undefined, @@ -12,13 +12,13 @@ export default function serve() { return { writeBundle() { if (state.server) return; - state.server = proc.spawn("npm", ["run", "start", "--", "--dev"], { - stdio: ["ignore", "inherit", "inherit"], + state.server = proc.spawn('npm', ['run', 'start', '--', '--dev'], { + stdio: ['ignore', 'inherit', 'inherit'], shell: true, }); - process.on("SIGTERM", toExit); - process.on("exit", toExit); + process.on('SIGTERM', toExit); + process.on('exit', toExit); }, }; } diff --git a/packages/ui/src/cairo/highlightjs.ts b/packages/ui/src/cairo/highlightjs.ts index cfc81aa15..eb4e7c319 100644 --- a/packages/ui/src/cairo/highlightjs.ts +++ b/packages/ui/src/cairo/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from "highlight.js/lib/core"; +import hljs from 'highlight.js/lib/core'; // @ts-expect-error missing type declaration -import hljsDefineCairo from "highlightjs-cairo"; +import hljsDefineCairo from 'highlightjs-cairo'; hljsDefineCairo(hljs); export default hljs; diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index 6f3becaf5..a83929d2e 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -1,39 +1,33 @@ /* eslint-disable no-useless-escape */ -import { contractsVersionTag } from "@openzeppelin/wizard-cairo/src"; +import { contractsVersionTag } from '@openzeppelin/wizard-cairo/src'; export function injectHyperlinks(code: string) { - const importRegex = - /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; + const importRegex = /use<\/span> (openzeppelin)::([^A-Z]*)(::[a-zA-Z0-9]+|::{)/g; let result = code; let match = importRegex.exec(code); while (match != null) { const [line, libraryPrefix, libraryPath, suffix] = match; - if ( - line !== undefined && - libraryPrefix !== undefined && - libraryPath !== undefined && - suffix !== undefined - ) { + if (line !== undefined && libraryPrefix !== undefined && libraryPath !== undefined && suffix !== undefined) { const githubPrefix = `https://github.com/OpenZeppelin/cairo-contracts/blob/${contractsVersionTag}/packages/`; - const libraryPathSegments = libraryPath.split("::"); - libraryPathSegments.splice(1, 0, "src"); + const libraryPathSegments = libraryPath.split('::'); + libraryPathSegments.splice(1, 0, 'src'); if (libraryPathSegments !== undefined && libraryPathSegments.length > 0) { let replacement; - if (suffix === "::{") { + if (suffix === '::{') { // Multiple components are imported, so remove components and link to the parent .cairo file replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Exclude suffix from link } else { // Single component is imported // If a mapping exists, link to the mapped file, otherwise remove the component and link to the parent .cairo file const componentName = suffix.substring(2, suffix.length); const mapping = componentMappings[componentName]; - const urlSuffix = mapping ? `/${mapping}.cairo` : ".cairo"; + const urlSuffix = mapping ? `/${mapping}.cairo` : '.cairo'; replacement = `use<\/span> ${libraryPrefix}::${libraryPath}${suffix}`; // Include suffix (component) in link } @@ -46,6 +40,6 @@ export function injectHyperlinks(code: string) { } const componentMappings: { [key: string]: string } = { - AccountComponent: "account", - UpgradeableComponent: "upgradeable", + AccountComponent: 'account', + UpgradeableComponent: 'upgradeable', } as const; diff --git a/packages/ui/src/common/error-tooltip.ts b/packages/ui/src/common/error-tooltip.ts index cd6b96741..804eb5be0 100644 --- a/packages/ui/src/common/error-tooltip.ts +++ b/packages/ui/src/common/error-tooltip.ts @@ -1,13 +1,13 @@ -import tippy from "tippy.js"; +import tippy from 'tippy.js'; -const klass = "has-error"; +const klass = 'has-error'; export function error(node: HTMLElement, content?: string) { let shown = false; const t = tippy(node, { - placement: "right", - theme: "light-red border", + placement: 'right', + theme: 'light-red border', showOnCreate: false, onShow: () => { shown = true; diff --git a/packages/ui/src/common/post-config.ts b/packages/ui/src/common/post-config.ts index 365cd3c80..b917f6d40 100644 --- a/packages/ui/src/common/post-config.ts +++ b/packages/ui/src/common/post-config.ts @@ -1,5 +1,5 @@ -import type { GenericOptions as SolidityOptions } from "@openzeppelin/wizard"; -import type { GenericOptions as CairoOptions } from "@openzeppelin/wizard-cairo"; +import type { GenericOptions as SolidityOptions } from '@openzeppelin/wizard'; +import type { GenericOptions as CairoOptions } from '@openzeppelin/wizard-cairo'; declare global { interface Window { @@ -7,21 +7,15 @@ declare global { } } -export type Action = - | "copy" - | "remix" - | "download-file" - | "download-hardhat" - | "download-foundry" - | "defender"; -export type Language = "solidity" | "cairo" | "stylus" | "stellar"; +export type Action = 'copy' | 'remix' | 'download-file' | 'download-hardhat' | 'download-foundry' | 'defender'; +export type Language = 'solidity' | 'cairo' | 'stylus' | 'stellar'; export async function postConfig( opts: Required | Required, action: Action, language: Language, ) { - window.gtag?.("event", "wizard_action", { + window.gtag?.('event', 'wizard_action', { ...opts, action, wizard_lang: language, diff --git a/packages/ui/src/common/post-message.ts b/packages/ui/src/common/post-message.ts index c63bba812..c5ddb81d9 100644 --- a/packages/ui/src/common/post-message.ts +++ b/packages/ui/src/common/post-message.ts @@ -1,47 +1,41 @@ -import type { SolcInputSources } from "@openzeppelin/wizard/get-imports"; +import type { SolcInputSources } from '@openzeppelin/wizard/get-imports'; -export type Message = - | ResizeMessage - | TabChangeMessage - | UnsupportedVersionMessage - | DefenderDeployMessage; +export type Message = ResizeMessage | TabChangeMessage | UnsupportedVersionMessage | DefenderDeployMessage; export interface ResizeMessage { - kind: "oz-wizard-resize"; + kind: 'oz-wizard-resize'; height: number; } export interface TabChangeMessage { - kind: "oz-wizard-tab-change"; + kind: 'oz-wizard-tab-change'; tab: string; } export interface UnsupportedVersionMessage { - kind: "oz-wizard-unsupported-version"; + kind: 'oz-wizard-unsupported-version'; } export interface DefenderDeployMessage { - kind: "oz-wizard-defender-deploy"; + kind: 'oz-wizard-defender-deploy'; sources: SolcInputSources; } export function postMessage(msg: Message) { if (parent) { - parent.postMessage(msg, "*"); + parent.postMessage(msg, '*'); } } -export function postMessageToIframe(id: "defender-deploy", msg: Message) { - const iframe: HTMLIFrameElement | null = document.getElementById( - id, - ) as HTMLIFrameElement; +export function postMessageToIframe(id: 'defender-deploy', msg: Message) { + const iframe: HTMLIFrameElement | null = document.getElementById(id) as HTMLIFrameElement; if (iframe) { - iframe.contentWindow?.postMessage(msg, "*"); + iframe.contentWindow?.postMessage(msg, '*'); // in case the iframe is still loading, waits // a second to fully load and tries again iframe.onload = () => { setTimeout(() => { - iframe?.contentWindow?.postMessage(msg, "*"); + iframe?.contentWindow?.postMessage(msg, '*'); }, 1000); }; } diff --git a/packages/ui/src/common/resize-to-fit.ts b/packages/ui/src/common/resize-to-fit.ts index 52b9097ed..6326f1d18 100644 --- a/packages/ui/src/common/resize-to-fit.ts +++ b/packages/ui/src/common/resize-to-fit.ts @@ -1,33 +1,26 @@ export function resizeToFit(node: HTMLInputElement) { const resize = () => { - if (node.value === "") { + if (node.value === '') { return; } const style = window.getComputedStyle(node); const textWidth = measureTextWidth(node.value, style); const minWidth = measureTextWidth(node.placeholder, style); - const padding = [ - "padding-left", - "padding-right", - "border-left-width", - "border-right-width", - ].map((p) => style.getPropertyValue(p)); - const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join( - " + ", - )})`; - node.style.setProperty("width", result); + const padding = ['padding-left', 'padding-right', 'border-left-width', 'border-right-width'].map(p => + style.getPropertyValue(p), + ); + const result = `calc(5px + max(${minWidth}, ${textWidth}) + ${padding.join(' + ')})`; + node.style.setProperty('width', result); }; resize(); - node.addEventListener("input", resize); + node.addEventListener('input', resize); } function measureTextWidth(text: string, style: CSSStyleDeclaration): string { - const font = ["font-size", "font-family"] - .map((p) => style.getPropertyValue(p)) - .join(" "); - const ctx = document.createElement("canvas").getContext("2d")!; + const font = ['font-size', 'font-family'].map(p => style.getPropertyValue(p)).join(' '); + const ctx = document.createElement('canvas').getContext('2d')!; ctx.font = font; - return ctx.measureText(text).width + "px"; + return ctx.measureText(text).width + 'px'; } diff --git a/packages/ui/src/embed.ts b/packages/ui/src/embed.ts index 0ea4c6d52..2ac1e629d 100644 --- a/packages/ui/src/embed.ts +++ b/packages/ui/src/embed.ts @@ -1,7 +1,7 @@ -import type { Message } from "./common/post-message"; +import type { Message } from './common/post-message'; -if (!document.currentScript || !("src" in document.currentScript)) { - throw new Error("Unknown script URL"); +if (!document.currentScript || !('src' in document.currentScript)) { + throw new Error('Unknown script URL'); } const currentScript = new URL(document.currentScript.src); @@ -9,61 +9,59 @@ const currentScript = new URL(document.currentScript.src); const iframes = new WeakMap(); let unsupportedVersion: boolean = false; -const unsupportedVersionFrameHeight = "auto"; +const unsupportedVersionFrameHeight = 'auto'; -window.addEventListener("message", function (e: MessageEvent) { +window.addEventListener('message', function (e: MessageEvent) { if (e.source) { - if (e.data.kind === "oz-wizard-unsupported-version") { + if (e.data.kind === 'oz-wizard-unsupported-version') { unsupportedVersion = true; const iframe = iframes.get(e.source); if (iframe) { iframe.style.height = unsupportedVersionFrameHeight; } - } else if (e.data.kind === "oz-wizard-resize") { + } else if (e.data.kind === 'oz-wizard-resize') { const iframe = iframes.get(e.source); if (iframe) { - iframe.style.height = unsupportedVersion - ? unsupportedVersionFrameHeight - : "calc(100vh - 100px)"; + iframe.style.height = unsupportedVersion ? unsupportedVersionFrameHeight : 'calc(100vh - 100px)'; } } } }); onDOMContentLoaded(function () { - const wizards = document.querySelectorAll("oz-wizard"); + const wizards = document.querySelectorAll('oz-wizard'); for (const w of wizards) { - w.style.display = "block"; + w.style.display = 'block'; - const src = new URL("embed", currentScript.origin); + const src = new URL('embed', currentScript.origin); - setSearchParam(w, src.searchParams, "data-lang", "lang"); - setSearchParam(w, src.searchParams, "data-tab", "tab"); - setSearchParam(w, src.searchParams, "version", "version"); + setSearchParam(w, src.searchParams, 'data-lang', 'lang'); + setSearchParam(w, src.searchParams, 'data-tab', 'tab'); + setSearchParam(w, src.searchParams, 'version', 'version'); - const sync = w.getAttribute("data-sync-url"); + const sync = w.getAttribute('data-sync-url'); - if (sync === "fragment") { + if (sync === 'fragment') { // Uses format: #tab&key=value&key=value... - const fragments = window.location.hash.replace("#", "").split("&"); + const fragments = window.location.hash.replace('#', '').split('&'); for (const fragment of fragments) { - const [key, value] = fragment.split("=", 2); + const [key, value] = fragment.split('=', 2); if (key && value) { src.searchParams.set(key, value); } else { - src.searchParams.set("tab", fragment); + src.searchParams.set('tab', fragment); } } } - const iframe = document.createElement("iframe"); + const iframe = document.createElement('iframe'); iframe.src = src.toString(); - iframe.style.display = "block"; - iframe.style.border = "0"; - iframe.style.width = "100%"; - iframe.style.height = "calc(100vh - 100px)"; - iframe.allow = "clipboard-write"; + iframe.style.display = 'block'; + iframe.style.border = '0'; + iframe.style.width = '100%'; + iframe.style.height = 'calc(100vh - 100px)'; + iframe.allow = 'clipboard-write'; w.appendChild(iframe); @@ -71,9 +69,9 @@ onDOMContentLoaded(function () { iframes.set(iframe.contentWindow, iframe); } - if (sync === "fragment") { - window.addEventListener("message", (e: MessageEvent) => { - if (e.source && e.data.kind === "oz-wizard-tab-change") { + if (sync === 'fragment') { + window.addEventListener('message', (e: MessageEvent) => { + if (e.source && e.data.kind === 'oz-wizard-tab-change') { if (iframe === iframes.get(e.source)) { window.location.hash = e.data.tab; } @@ -83,12 +81,7 @@ onDOMContentLoaded(function () { } }); -function setSearchParam( - w: HTMLElement, - searchParams: URLSearchParams, - dataParam: string, - param: string, -) { +function setSearchParam(w: HTMLElement, searchParams: URLSearchParams, dataParam: string, param: string) { const value = w.getAttribute(dataParam) ?? w.getAttribute(param); if (value) { searchParams.set(param, value); @@ -96,8 +89,8 @@ function setSearchParam( } function onDOMContentLoaded(callback: () => void) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", callback); + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback); } else { callback(); } diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index e8afa5c09..346c3d8ea 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -1,18 +1,18 @@ -import "./common/styles/global.css"; +import './common/styles/global.css'; -import type {} from "svelte"; -import SolidityApp from "./solidity/App.svelte"; -import CairoApp from "./cairo/App.svelte"; -import { postMessage } from "./common/post-message"; -import UnsupportedVersion from "./common/UnsupportedVersion.svelte"; -import semver from "semver"; -import { compatibleContractsSemver as compatibleSolidityContractsSemver } from "@openzeppelin/wizard"; -import { compatibleContractsSemver as compatibleCairoContractsSemver } from "@openzeppelin/wizard-cairo"; -import type { InitialOptions } from "./common/initial-options.ts"; +import type {} from 'svelte'; +import SolidityApp from './solidity/App.svelte'; +import CairoApp from './cairo/App.svelte'; +import { postMessage } from './common/post-message'; +import UnsupportedVersion from './common/UnsupportedVersion.svelte'; +import semver from 'semver'; +import { compatibleContractsSemver as compatibleSolidityContractsSemver } from '@openzeppelin/wizard'; +import { compatibleContractsSemver as compatibleCairoContractsSemver } from '@openzeppelin/wizard-cairo'; +import type { InitialOptions } from './common/initial-options.ts'; function postResize() { const { height } = document.documentElement.getBoundingClientRect(); - postMessage({ kind: "oz-wizard-resize", height }); + postMessage({ kind: 'oz-wizard-resize', height }); } window.onload = postResize; @@ -22,40 +22,34 @@ resizeObserver.observe(document.body); const params = new URLSearchParams(window.location.search); -const initialTab = params.get("tab") ?? undefined; -const lang = params.get("lang") ?? undefined; -const requestedVersion = params.get("version") ?? undefined; +const initialTab = params.get('tab') ?? undefined; +const lang = params.get('lang') ?? undefined; +const requestedVersion = params.get('version') ?? undefined; const initialOpts: InitialOptions = { - name: params.get("name") ?? undefined, - symbol: params.get("symbol") ?? undefined, - premint: params.get("premint") ?? undefined, + name: params.get('name') ?? undefined, + symbol: params.get('symbol') ?? undefined, + premint: params.get('premint') ?? undefined, }; -const compatibleVersionSemver = - lang === "cairo" - ? compatibleCairoContractsSemver - : compatibleSolidityContractsSemver; +const compatibleVersionSemver = lang === 'cairo' ? compatibleCairoContractsSemver : compatibleSolidityContractsSemver; let app; -if ( - requestedVersion && - !semver.satisfies(requestedVersion, compatibleVersionSemver) -) { - postMessage({ kind: "oz-wizard-unsupported-version" }); +if (requestedVersion && !semver.satisfies(requestedVersion, compatibleVersionSemver)) { + postMessage({ kind: 'oz-wizard-unsupported-version' }); app = new UnsupportedVersion({ target: document.body, props: { requestedVersion, compatibleVersionSemver }, }); } else { switch (lang) { - case "cairo": + case 'cairo': app = new CairoApp({ target: document.body, props: { initialTab, initialOpts }, }); break; - case "solidity": + case 'solidity': default: app = new SolidityApp({ target: document.body, @@ -65,8 +59,8 @@ if ( } } -app.$on("tab-change", (e: CustomEvent) => { - postMessage({ kind: "oz-wizard-tab-change", tab: e.detail.toLowerCase() }); +app.$on('tab-change', (e: CustomEvent) => { + postMessage({ kind: 'oz-wizard-tab-change', tab: e.detail.toLowerCase() }); }); export default app; diff --git a/packages/ui/src/solidity/highlightjs.ts b/packages/ui/src/solidity/highlightjs.ts index b387baaa6..5772decd1 100644 --- a/packages/ui/src/solidity/highlightjs.ts +++ b/packages/ui/src/solidity/highlightjs.ts @@ -1,7 +1,7 @@ -import hljs from "highlight.js/lib/core"; +import hljs from 'highlight.js/lib/core'; // @ts-expect-error missing type declaration file -import hljsDefineSolidity from "highlightjs-solidity"; +import hljsDefineSolidity from 'highlightjs-solidity'; hljsDefineSolidity(hljs); export default hljs; diff --git a/packages/ui/src/solidity/inject-hyperlinks.ts b/packages/ui/src/solidity/inject-hyperlinks.ts index 8f5b9305c..d8ae1ef13 100644 --- a/packages/ui/src/solidity/inject-hyperlinks.ts +++ b/packages/ui/src/solidity/inject-hyperlinks.ts @@ -1,4 +1,4 @@ -import { version as contractsVersion } from "@openzeppelin/contracts/package.json"; +import { version as contractsVersion } from '@openzeppelin/contracts/package.json'; export function injectHyperlinks(code: string) { // We are modifying HTML, so use HTML escaped chars. The pattern excludes paths that include /../ in the URL. diff --git a/packages/ui/src/solidity/remix.ts b/packages/ui/src/solidity/remix.ts index ee479a157..a43087074 100644 --- a/packages/ui/src/solidity/remix.ts +++ b/packages/ui/src/solidity/remix.ts @@ -1,8 +1,8 @@ export function remixURL(code: string, upgradeable = false): URL { - const remix = new URL("https://remix.ethereum.org"); - remix.searchParams.set("code", btoa(code).replace(/=*$/, "")); + const remix = new URL('https://remix.ethereum.org'); + remix.searchParams.set('code', btoa(code).replace(/=*$/, '')); if (upgradeable) { - remix.searchParams.set("deployProxy", upgradeable.toString()); + remix.searchParams.set('deployProxy', upgradeable.toString()); } return remix; } diff --git a/packages/ui/src/solidity/wiz-functions.ts b/packages/ui/src/solidity/wiz-functions.ts index be6877bd3..0b0c9efab 100644 --- a/packages/ui/src/solidity/wiz-functions.ts +++ b/packages/ui/src/solidity/wiz-functions.ts @@ -1,31 +1,31 @@ const commonOptions = { // 'false' gets converted to false access: { - type: "string", - enum: ["false", "ownable", "roles", "managed"], + type: 'string', + enum: ['false', 'ownable', 'roles', 'managed'], description: - "The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.", + 'The type of access control to provision. Ownable is a simple mechanism with a single account authorized for all privileged actions. Roles is a flexible mechanism with a separate role for each privileged action. A role can have many authorized accounts. Managed enables a central contract to define a policy that allows certain callers to access certain functions.', }, // 'false' gets converted to false upgradeable: { - type: "string", - enum: ["false", "transparent", "uups"], + type: 'string', + enum: ['false', 'transparent', 'uups'], description: - "Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.", + 'Whether the smart contract is upgradeable. Transparent uses more complex proxy with higher overhead, requires less changes in your contract.Can also be used with beacons. UUPS uses simpler proxy with less overhead, requires including extra code in your contract. Allows flexibility for authorizing upgrades.', }, info: { - type: "object", - description: "Metadata about the contract and author", + type: 'object', + description: 'Metadata about the contract and author', properties: { securityContact: { - type: "string", + type: 'string', description: - "Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.", + 'Email where people can contact you to report security issues. Will only be visible if contract metadata is verified.', }, license: { - type: "string", + type: 'string', description: 'The license used by the contract, default is "MIT"', }, }, @@ -33,260 +33,249 @@ const commonOptions = { }; const repeatedOptions = { - name: { type: "string", description: "The name of the contract" }, - symbol: { type: "string", description: "The short symbol for the token" }, + name: { type: 'string', description: 'The name of the contract' }, + symbol: { type: 'string', description: 'The short symbol for the token' }, burnable: { - type: "boolean", - description: "Whether token holders will be able to destroy their tokens", + type: 'boolean', + description: 'Whether token holders will be able to destroy their tokens', }, pausable: { - type: "boolean", + type: 'boolean', description: - "Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.", + 'Whether privileged accounts will be able to pause the functionality marked as whenNotPaused. Useful for emergency response.', }, mintable: { - type: "boolean", - description: - "Whether privileged accounts will be able to create more supply or emit more tokens", + type: 'boolean', + description: 'Whether privileged accounts will be able to create more supply or emit more tokens', }, }; export const erc20Function = { - name: "erc20", - description: "Make a fungible token per the ERC-20 standard", + name: 'erc20', + description: 'Make a fungible token per the ERC-20 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, premint: { - type: "number", - description: "The number of tokens to premint for the deployer.", + type: 'number', + description: 'The number of tokens to premint for the deployer.', }, mintable: repeatedOptions.mintable, permit: { - type: "boolean", + type: 'boolean', description: - "Whether without paying gas, token holders will be able to allow third parties to transfer from their account.", + 'Whether without paying gas, token holders will be able to allow third parties to transfer from their account.', }, // 'false' gets converted to false votes: { - type: "string", - enum: ["false", "blocknumber", "timestamp"], + type: 'string', + enum: ['false', 'blocknumber', 'timestamp'], description: - "Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + 'Whether to keep track of historical balances for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.', }, flashmint: { - type: "boolean", + type: 'boolean', description: "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", }, ...commonOptions, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const erc721Function = { - name: "erc721", - description: "Make a non-fungible token per the ERC-721 standard", + name: 'erc721', + description: 'Make a non-fungible token per the ERC-721 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, symbol: repeatedOptions.symbol, - baseUri: { type: "string", description: "A base uri for the token" }, + baseUri: { type: 'string', description: 'A base uri for the token' }, enumerable: { - type: "boolean", + type: 'boolean', description: - "Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.", + 'Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.', }, uriStorage: { - type: "boolean", - description: "Allows updating token URIs for individual token IDs", + type: 'boolean', + description: 'Allows updating token URIs for individual token IDs', }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, incremental: { - type: "boolean", - description: - "Whether new tokens will be automatically assigned an incremental id", + type: 'boolean', + description: 'Whether new tokens will be automatically assigned an incremental id', }, // 'false' gets converted to false votes: { - type: "string", - enum: ["false", "blocknumber", "timestamp"], + type: 'string', + enum: ['false', 'blocknumber', 'timestamp'], description: - "Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.", + 'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.', }, ...commonOptions, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const erc1155Function = { - name: "erc1155", - description: "Make a non-fungible token per the ERC-1155 standard", + name: 'erc1155', + description: 'Make a non-fungible token per the ERC-1155 standard', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, uri: { - type: "string", + type: 'string', description: - "The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.", + 'The Location of the metadata for the token. Clients will replace any instance of {id} in this string with the tokenId.', }, burnable: repeatedOptions.burnable, pausable: repeatedOptions.pausable, mintable: repeatedOptions.mintable, supply: { - type: "boolean", - description: "Whether to keep track of total supply of tokens", + type: 'boolean', + description: 'Whether to keep track of total supply of tokens', }, updatableUri: { - type: "boolean", - description: - "Whether privileged accounts will be able to set a new URI for all token types", + type: 'boolean', + description: 'Whether privileged accounts will be able to set a new URI for all token types', }, ...commonOptions, }, - required: ["name", "uri"], + required: ['name', 'uri'], }, }; export const stablecoinFunction = { - name: "stablecoin", + name: 'stablecoin', description: - "Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", + 'Make a stablecoin token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', parameters: { - type: "object", + type: 'object', properties: { ...erc20Function.parameters.properties, custodian: { - type: "boolean", + type: 'boolean', description: - "Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.", + 'Whether authorized accounts can freeze and unfreeze accounts for regulatory or security purposes. This feature is experimental, not audited and is subject to change.', }, // 'false' gets converted to false limitations: { - type: "string", - enum: ["false", "allowlist", "blocklist"], + type: 'string', + enum: ['false', 'allowlist', 'blocklist'], description: - "Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.", + 'Whether to restrict certain users from transferring tokens, either via allowing or blocking them. This feature is experimental, not audited and is subject to change.', }, upgradeable: { - type: "string", - enum: ["false"], - description: - "Upgradeability is not yet available for features that use @openzeppelin/community-contracts", + type: 'string', + enum: ['false'], + description: 'Upgradeability is not yet available for features that use @openzeppelin/community-contracts', }, }, - required: ["name", "symbol"], + required: ['name', 'symbol'], }, }; export const realWorldAssetFunction = { - name: "realworldasset", + name: 'realworldasset', description: - "Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.", + 'Make a real-world asset token that uses the ERC-20 standard. Emphasize that this is experimental, and some features are not audited and subject to change.', parameters: stablecoinFunction.parameters, }; export const governorFunction = { - name: "governor", - description: "Make a contract to implement governance, such as for a DAO", + name: 'governor', + description: 'Make a contract to implement governance, such as for a DAO', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, delay: { - type: "string", - description: - 'The delay since proposal is created until voting starts, default is "1 day"', + type: 'string', + description: 'The delay since proposal is created until voting starts, default is "1 day"', }, period: { - type: "string", - description: - 'The length of period during which people can cast their vote, default is "1 week"', + type: 'string', + description: 'The length of period during which people can cast their vote, default is "1 week"', }, blockTime: { - type: "number", - description: "The number of seconds assumed for a block, default is 12", + type: 'number', + description: 'The number of seconds assumed for a block, default is 12', }, // gets converted to a string to follow the API proposalThreshold: { - type: "number", - description: - "Minimum number of votes an account must have to create a proposal, default is 0.", + type: 'number', + description: 'Minimum number of votes an account must have to create a proposal, default is 0.', }, decimals: { - type: "number", + type: 'number', description: - "The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)", + 'The number of decimals to use for the contract, default is 18 for ERC20Votes and 0 for ERC721Votes (because it does not apply to ERC721Votes)', }, quorumMode: { - type: "string", - enum: ["percent", "absolute"], - description: "The type of quorum mode to use", + type: 'string', + enum: ['percent', 'absolute'], + description: 'The type of quorum mode to use', }, quorumPercent: { - type: "number", - description: - "The percent required, in cases of quorumMode equals percent", + type: 'number', + description: 'The percent required, in cases of quorumMode equals percent', }, // gets converted to a string to follow the API quorumAbsolute: { - type: "number", - description: - "The absolute quorum required, in cases of quorumMode equals absolute", + type: 'number', + description: 'The absolute quorum required, in cases of quorumMode equals absolute', }, votes: { - type: "string", - enum: ["erc20votes", "erc721votes"], - description: "The type of voting to use", + type: 'string', + enum: ['erc20votes', 'erc721votes'], + description: 'The type of voting to use', }, clockMode: { - type: "string", - enum: ["blocknumber", "timestamp"], + type: 'string', + enum: ['blocknumber', 'timestamp'], description: - "The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.", + 'The clock mode used by the voting token. For Governor, this must be chosen to match what the ERC20 or ERC721 voting token uses.', }, // 'false' gets converted to false timelock: { - type: "string", - enum: ["false", "openzeppelin", "compound"], - description: "The type of timelock to use", + type: 'string', + enum: ['false', 'openzeppelin', 'compound'], + description: 'The type of timelock to use', }, storage: { - type: "boolean", - description: - "Enable storage of proposal details and enumerability of proposals", + type: 'boolean', + description: 'Enable storage of proposal details and enumerability of proposals', }, settings: { - type: "boolean", - description: - "Allow governance to update voting settings (delay, period, proposal threshold)", + type: 'boolean', + description: 'Allow governance to update voting settings (delay, period, proposal threshold)', }, ...commonOptions, }, - required: ["name", "delay", "period"], + required: ['name', 'delay', 'period'], }, }; export const customFunction = { - name: "custom", - description: "Make a custom smart contract", + name: 'custom', + description: 'Make a custom smart contract', parameters: { - type: "object", + type: 'object', properties: { name: repeatedOptions.name, pausable: repeatedOptions.pausable, ...commonOptions, }, - required: ["name"], + required: ['name'], }, }; diff --git a/packages/ui/src/standalone.js b/packages/ui/src/standalone.js index 66b97fc1d..fe423caf7 100644 --- a/packages/ui/src/standalone.js +++ b/packages/ui/src/standalone.js @@ -1,3 +1,3 @@ // Used as Rollup entry point to preprocess CSS for wizard.openzeppelin.com -import "./standalone.css"; +import './standalone.css'; diff --git a/packages/ui/svelte.config.js b/packages/ui/svelte.config.js index f043d9dd9..dc6f828f6 100644 --- a/packages/ui/svelte.config.js +++ b/packages/ui/svelte.config.js @@ -1,4 +1,4 @@ -const sveltePreprocess = require("svelte-preprocess"); +const sveltePreprocess = require('svelte-preprocess'); const production = !process.env.ROLLUP_WATCH; diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js index cafb69e25..579a11b5e 100644 --- a/packages/ui/tailwind.config.js +++ b/packages/ui/tailwind.config.js @@ -1,36 +1,36 @@ module.exports = { content: [ - "./src/**/*.{html,svelte}", + './src/**/*.{html,svelte}', // Using glob patterns results in infinite loop - "./public/index.html", - "./public/cairo.html", - "./public/stylus.html", - "./public/stellar.html", - "./public/embed.html", + './public/index.html', + './public/cairo.html', + './public/stylus.html', + './public/stellar.html', + './public/embed.html', ], theme: { extend: { keyframes: { - "fade-in": { - "0%": { opacity: "0" }, - "100%": { opacity: "1" }, + 'fade-in': { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, }, - "fade-up": { - "0%": { opacity: "0", transform: "translateY(1rem)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, + 'fade-up': { + '0%': { opacity: '0', transform: 'translateY(1rem)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, }, - "fade-down": { - "0%": { opacity: "0", transform: "translateY(-1rem)" }, - "100%": { opacity: "1", transform: "translateY(0)" }, + 'fade-down': { + '0%': { opacity: '0', transform: 'translateY(-1rem)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, }, }, animation: { - "fade-in": "fade-in 0.3s ease-out", - "fade-up": "fade-up 0.2s ease-out", - "fade-down": "fade-down 0.5s ease-out", - "spin-slow": "spin 2s linear infinite", + 'fade-in': 'fade-in 0.3s ease-out', + 'fade-up': 'fade-up 0.2s ease-out', + 'fade-down': 'fade-down 0.5s ease-out', + 'spin-slow': 'spin 2s linear infinite', }, }, }, diff --git a/scripts/bump-changelog.js b/scripts/bump-changelog.js index de6f39a3d..fb756d7a2 100644 --- a/scripts/bump-changelog.js +++ b/scripts/bump-changelog.js @@ -1,22 +1,19 @@ #!/usr/bin/env node -const { version } = require(process.cwd() + "/package.json"); -const [date] = new Date().toISOString().split("T"); +const { version } = require(process.cwd() + '/package.json'); +const [date] = new Date().toISOString().split('T'); -const fs = require("fs"); -const changelog = fs.readFileSync("CHANGELOG.md", "utf8"); +const fs = require('fs'); +const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); const unreleased = /^## Unreleased$/im; if (!unreleased.test(changelog)) { - console.error("Missing changelog entry"); + console.error('Missing changelog entry'); process.exit(1); } -fs.writeFileSync( - "CHANGELOG.md", - changelog.replace(unreleased, `## ${version} (${date})`), -); +fs.writeFileSync('CHANGELOG.md', changelog.replace(unreleased, `## ${version} (${date})`)); -const proc = require("child_process"); -proc.execSync("git add CHANGELOG.md", { stdio: "inherit" }); +const proc = require('child_process'); +proc.execSync('git add CHANGELOG.md', { stdio: 'inherit' }); From 98bd8af02b124e06b7513e217b901292d0aa21fe Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:03:46 -0500 Subject: [PATCH 08/16] Add lint step in ci action --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b5ab7d6c..f429b8720 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,6 +31,8 @@ jobs: - name: Check Svelte run: yarn svelte-check working-directory: packages/ui + - name: Run linter + run: yarn lint - name: Run tests run: yarn test working-directory: packages/core/${{matrix.package}} From a9098d0dd173302d510744db2bd856d293b9c6fa Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:12:38 -0500 Subject: [PATCH 09/16] resolve prettier conflict --- packages/core/solidity/src/print.ts | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/packages/core/solidity/src/print.ts b/packages/core/solidity/src/print.ts index b81651e68..11fce8d12 100644 --- a/packages/core/solidity/src/print.ts +++ b/packages/core/solidity/src/print.ts @@ -65,23 +65,10 @@ function printConstructor(contract: Contract, helpers: Helpers): Lines[] { const hasParentParams = contract.parents.some(p => p.params.length > 0); const hasConstructorCode = contract.constructorCode.length > 0; const parentsWithInitializers = contract.parents.filter(hasInitializer); -<<<<<<< HEAD - if ( - hasParentParams || - hasConstructorCode || - (helpers.upgradeable && parentsWithInitializers.length > 0) - ) { - const parents = parentsWithInitializers.flatMap((p) => - printParentConstructor(p, helpers), - ); - const modifiers = helpers.upgradeable ? ["public initializer"] : parents; - const args = contract.constructorArgs.map((a) => printArgument(a, helpers)); -======= if (hasParentParams || hasConstructorCode || (helpers.upgradeable && parentsWithInitializers.length > 0)) { const parents = parentsWithInitializers.flatMap(p => printParentConstructor(p, helpers)); - const modifiers = helpers.upgradeable ? ['initializer public'] : parents; + const modifiers = helpers.upgradeable ? ['public initializer'] : parents; const args = contract.constructorArgs.map(a => printArgument(a, helpers)); ->>>>>>> ce5fcd3 (Add consistent-type-imports rule) const body = helpers.upgradeable ? spaceBetween( parents.map(p => p + ';'), @@ -173,13 +160,8 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { } const modifiers: string[] = [fn.kind]; -<<<<<<< HEAD - if (fn.mutability !== "nonpayable") { - modifiers.push(fn.mutability); -======= if (fn.mutability !== 'nonpayable') { - modifiers.splice(1, 0, fn.mutability); ->>>>>>> ce5fcd3 (Add consistent-type-imports rule) + modifiers.push(fn.mutability); } if (fn.override.size === 1) { @@ -197,13 +179,8 @@ function printFunction(fn: ContractFunction, helpers: Helpers): Lines[] { const code = [...fn.code]; if (fn.override.size > 0 && !fn.final) { -<<<<<<< HEAD - const superCall = `super.${fn.name}(${fn.args.map((a) => a.name).join(", ")});`; - code.push(fn.returns?.length ? "return " + superCall : superCall); -======= const superCall = `super.${fn.name}(${fn.args.map(a => a.name).join(', ')});`; code.push(fn.returns?.length ? 'return ' + superCall : superCall); ->>>>>>> ce5fcd3 (Add consistent-type-imports rule) } if (modifiers.length + fn.code.length > 1) { From 574a7393f8c24df04f0fb29647fafd8ddf4f5675 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 09:25:55 -0500 Subject: [PATCH 10/16] Remove .vscode directory from Git tracking --- .gitignore | 2 +- .vscode/settings.json | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 63a8b9661..bb1d9b041 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ node_modules .env .env.local -.vscode \ No newline at end of file +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 07fa5d824..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[svelte]": { - "editor.defaultFormatter": "svelte.svelte-vscode" - }, -} \ No newline at end of file From c0e9002a2790e715ef908e93011647816d1ebeec Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 15:03:40 -0500 Subject: [PATCH 11/16] move linter action in it's own job --- .github/workflows/test.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f429b8720..f3d93c93c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,20 @@ on: pull_request: {} jobs: + lint: + name: Lint Codebase + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18.x + cache: 'yarn' + - name: Install dependencies + run: yarn install --network-concurrency 1 + - name: Run linter + run: yarn lint + build: strategy: matrix: @@ -31,8 +45,6 @@ jobs: - name: Check Svelte run: yarn svelte-check working-directory: packages/ui - - name: Run linter - run: yarn lint - name: Run tests run: yarn test working-directory: packages/core/${{matrix.package}} From 86c65dc4dc2c2dc48b410f407bd0495d07526b22 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Fri, 21 Feb 2025 15:12:36 -0500 Subject: [PATCH 12/16] add lint note in readme --- README.md | 1 + eslint.config.mjs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d7b59113..2c5496b63 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Install dependencies with `yarn install`. `packages/core` contains the code generation logic for each language under separately named subfolders. From each language's subfolder: - Run `yarn test` to run the tests. - Run `yarn test:update-snapshots` to update AVA snapshots and run the tests. +- Run `yarn lint` to run the linter across the codebase (optionally `yarn lint --fix` will automatically fix fixable issues, like formatting issues). `packages/ui` is the interface built in Svelte. From the `packages/ui` directory, run `yarn dev` to spin up a local server to develop the UI. diff --git a/eslint.config.mjs b/eslint.config.mjs index c0969cd4d..aa9b737bf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,7 +15,7 @@ export default typescriptEslint.config( }, rules: { '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], - '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/consistent-type-imports': 'error', 'prettier/prettier': [ 'error', From a1111d3111ead122e100d377fc62a05ffad8e79a Mon Sep 17 00:00:00 2001 From: Cove Marquis-Bortoli <44323490+CoveMB@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:51:51 -0500 Subject: [PATCH 13/16] Update .github/workflows/test.yml Co-authored-by: Eric Lau --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3d93c93c..99c10659b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,6 @@ on: jobs: lint: - name: Lint Codebase runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 16438c33a3ac3a3a78d0865c98dde0afd17345a3 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Mon, 26 May 2025 14:03:34 -0400 Subject: [PATCH 14/16] add codeowners --- .github/CODEOWNERS | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..74fbfd460 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +# Path Owners +* @ericglau @CoveMB +/packages/core/solidity @contracts +/packages/ui/src/solidity @contracts +/packages/core/cairo @ericnordelo @immrsd +/packages/ui/src/cairo @ericnordelo @immrsd +/packages/core/stylus @0xNeshi @bidzyyys @qalisander +/packages/ui/src/stylus @0xNeshi @bidzyyys @qalisander +/packages/core/stellar @ozgunozerk @brozorec +/packages/ui/src/stellar @ozgunozerk @brozorec \ No newline at end of file From ce8a53497ab6da7e2d9232932a36c18dc6bb4b7e Mon Sep 17 00:00:00 2001 From: CoveMB Date: Mon, 26 May 2025 14:06:30 -0400 Subject: [PATCH 15/16] add cairo_alpha --- .github/CODEOWNERS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 74fbfd460..92aac5dff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,11 @@ # Path Owners * @ericglau @CoveMB -/packages/core/solidity @contracts -/packages/ui/src/solidity @contracts +/packages/core/solidity @OpenZeppelin/contracts +/packages/ui/src/solidity @OpenZeppelin/contracts /packages/core/cairo @ericnordelo @immrsd +/packages/core/cairo_alpha @ericnordelo @immrsd /packages/ui/src/cairo @ericnordelo @immrsd +/packages/ui/src/cairo_alpha @ericnordelo @immrsd /packages/core/stylus @0xNeshi @bidzyyys @qalisander /packages/ui/src/stylus @0xNeshi @bidzyyys @qalisander /packages/core/stellar @ozgunozerk @brozorec From 500e5f89bd19040d5d8cecb0bb878bddded9153f Mon Sep 17 00:00:00 2001 From: CoveMB Date: Tue, 17 Jun 2025 14:48:36 -0400 Subject: [PATCH 16/16] Use team references --- .github/CODEOWNERS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 92aac5dff..1cee5d76e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,12 +1,12 @@ # Path Owners -* @ericglau @CoveMB +* @OpenZeppelin/wizard /packages/core/solidity @OpenZeppelin/contracts /packages/ui/src/solidity @OpenZeppelin/contracts -/packages/core/cairo @ericnordelo @immrsd -/packages/core/cairo_alpha @ericnordelo @immrsd -/packages/ui/src/cairo @ericnordelo @immrsd -/packages/ui/src/cairo_alpha @ericnordelo @immrsd -/packages/core/stylus @0xNeshi @bidzyyys @qalisander -/packages/ui/src/stylus @0xNeshi @bidzyyys @qalisander -/packages/core/stellar @ozgunozerk @brozorec -/packages/ui/src/stellar @ozgunozerk @brozorec \ No newline at end of file +/packages/core/cairo @OpenZeppelin/cairo +/packages/core/cairo_alpha @OpenZeppelin/cairo +/packages/ui/src/cairo @OpenZeppelin/cairo +/packages/ui/src/cairo_alpha @OpenZeppelin/cairo +/packages/core/stylus @OpenZeppelin/stylus +/packages/ui/src/stylus @OpenZeppelin/stylus +/packages/core/stellar @OpenZeppelin/stellar +/packages/ui/src/stellar @OpenZeppelin/stellar \ No newline at end of file