diff --git a/.changeset/add-erc6909.md b/.changeset/add-erc6909.md new file mode 100644 index 000000000..6ff34fe57 --- /dev/null +++ b/.changeset/add-erc6909.md @@ -0,0 +1,5 @@ +--- +"@openzeppelin/wizard-common": patch +--- + +Cairo: add ERC6909 description for AI prompts diff --git a/packages/common/src/ai/descriptions/cairo.ts b/packages/common/src/ai/descriptions/cairo.ts index ac16c9f8f..fc4bdc929 100644 --- a/packages/common/src/ai/descriptions/cairo.ts +++ b/packages/common/src/ai/descriptions/cairo.ts @@ -5,6 +5,7 @@ export const cairoPrompts = { ERC20: 'Make a fungible token per the ERC-20 standard.', ERC721: 'Make a non-fungible token per the ERC-721 standard.', ERC1155: 'Make a non-fungible token per the ERC-1155 standard.', + ERC6909: 'Make a minimal multi-token per the ERC-6909 standard.', Governor: 'Make a contract to implement governance, such as for a DAO.', Multisig: 'Make a multi-signature smart contract, requiring a quorum of registered signers to approve and collectively execute transactions.', @@ -73,6 +74,8 @@ export const cairoERC1155Descriptions = { supply: 'Whether to keep track of total supply of tokens.', }; +export const cairoERC6909Descriptions = {}; + export const cairoGovernorDescriptions = { delay: 'The delay since proposal is created until voting starts, in readable date time format matching /^(\\d+(?:\\.\\d+)?) +(second|minute|hour|day|week|month|year)s?$/, default is "1 day".', diff --git a/packages/core/cairo_alpha/CHANGELOG.md b/packages/core/cairo_alpha/CHANGELOG.md index 05ea7fc16..915b47e9f 100644 --- a/packages/core/cairo_alpha/CHANGELOG.md +++ b/packages/core/cairo_alpha/CHANGELOG.md @@ -2,9 +2,10 @@ ## Unreleased +- Add ERC6909 ([#771](https://github.com/OpenZeppelin/contracts-wizard/pull/771)) - Add ERC1155 supply tracking extension ([#765](https://github.com/OpenZeppelin/contracts-wizard/pull/765)) - Add ERC721 wrapper extension ([#764](https://github.com/OpenZeppelin/contracts-wizard/pull/764)) -- Add ERC721Consecutive extension ([##769](https://github.com/OpenZeppelin/contracts-wizard/pull/#769)) +- Add ERC721Consecutive extension ([#769](https://github.com/OpenZeppelin/contracts-wizard/pull/769)) - Add ERC20 wrapper extension ([#763](https://github.com/OpenZeppelin/contracts-wizard/pull/763)) - **Breaking changes**: diff --git a/packages/core/cairo_alpha/src/api.ts b/packages/core/cairo_alpha/src/api.ts index b88fe3ebb..5e83a7a4e 100644 --- a/packages/core/cairo_alpha/src/api.ts +++ b/packages/core/cairo_alpha/src/api.ts @@ -17,6 +17,12 @@ import { defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, } from './erc1155'; +import type { ERC6909Options } from './erc6909'; +import { + printERC6909, + defaults as erc6909defaults, + isAccessControlRequired as erc6909IsAccessControlRequired, +} from './erc6909'; import type { AccountOptions } from './account'; import { printAccount, defaults as accountDefaults } from './account'; import type { GovernorOptions } from './governor'; @@ -67,6 +73,7 @@ export interface AccessControlAPI { export type ERC20 = WizardContractAPI & AccessControlAPI; export type ERC721 = WizardContractAPI & AccessControlAPI; export type ERC1155 = WizardContractAPI & AccessControlAPI; +export type ERC6909 = WizardContractAPI & AccessControlAPI; export type Account = WizardAccountAPI; export type Multisig = WizardContractAPI; export type Governor = WizardContractAPI; @@ -88,6 +95,11 @@ export const erc1155: ERC1155 = { defaults: erc1155defaults, isAccessControlRequired: erc1155IsAccessControlRequired, }; +export const erc6909: ERC6909 = { + print: printERC6909, + defaults: erc6909defaults, + isAccessControlRequired: erc6909IsAccessControlRequired, +}; export const account: Account = { print: printAccount, defaults: accountDefaults, diff --git a/packages/core/cairo_alpha/src/build-generic.ts b/packages/core/cairo_alpha/src/build-generic.ts index 2162210b3..1bf964818 100644 --- a/packages/core/cairo_alpha/src/build-generic.ts +++ b/packages/core/cairo_alpha/src/build-generic.ts @@ -4,6 +4,8 @@ import type { ERC721Options } from './erc721'; import { buildERC721 } from './erc721'; import type { ERC1155Options } from './erc1155'; import { buildERC1155 } from './erc1155'; +import type { ERC6909Options } from './erc6909'; +import { buildERC6909 } from './erc6909'; import type { CustomOptions } from './custom'; import { buildCustom } from './custom'; import type { AccountOptions } from './account'; @@ -19,6 +21,7 @@ export interface KindedOptions { ERC20: { kind: 'ERC20' } & ERC20Options; ERC721: { kind: 'ERC721' } & ERC721Options; ERC1155: { kind: 'ERC1155' } & ERC1155Options; + ERC6909: { kind: 'ERC6909' } & ERC6909Options; Account: { kind: 'Account' } & AccountOptions; Multisig: { kind: 'Multisig' } & MultisigOptions; Governor: { kind: 'Governor' } & GovernorOptions; @@ -39,6 +42,9 @@ export function buildGeneric(opts: GenericOptions) { case 'ERC1155': return buildERC1155(opts); + case 'ERC6909': + return buildERC6909(opts); + case 'Account': return buildAccount(opts); diff --git a/packages/core/cairo_alpha/src/erc6909.ts b/packages/core/cairo_alpha/src/erc6909.ts new file mode 100644 index 000000000..43ead6533 --- /dev/null +++ b/packages/core/cairo_alpha/src/erc6909.ts @@ -0,0 +1,183 @@ +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'; + +export const defaults: Required = { + name: 'MyToken', + burnable: false, + pausable: false, + mintable: false, + access: commonDefaults.access, + upgradeable: commonDefaults.upgradeable, + info: commonDefaults.info, + macros: commonDefaults.macros, +} as const; + +export function printERC6909(opts: ERC6909Options = defaults): string { + return printContract(buildERC6909(opts)); +} + +export interface ERC6909Options extends CommonContractOptions { + name: string; + burnable?: boolean; + pausable?: boolean; + mintable?: boolean; +} + +function withDefaults(opts: ERC6909Options): Required { + return { + ...opts, + ...withCommonContractDefaults(opts), + burnable: opts.burnable ?? defaults.burnable, + pausable: opts.pausable ?? defaults.pausable, + mintable: opts.mintable ?? defaults.mintable, + }; +} + +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; +} + +export function buildERC6909(opts: ERC6909Options): Contract { + const allOpts = withDefaults(opts); + const c = new ContractBuilder(allOpts.name, allOpts.macros); + + addBase(c); + addERC6909Mixin(c); + + if (allOpts.pausable) { + addPausable(c, allOpts.access); + } + + if (allOpts.burnable) { + addBurnable(c); + } + + if (allOpts.mintable) { + addMintable(c, allOpts.access); + } + + setAccessControl(c, allOpts.access); + setUpgradeable(c, allOpts.upgradeable, allOpts.access); + setInfo(c, allOpts.info); + + addHooks(c, allOpts); + + return c; +} + +function addHooks(c: ContractBuilder, allOpts: Required) { + const usesCustomHooks = allOpts.pausable; + if (usesCustomHooks) { + const hooksTrait = { + name: 'ERC6909HooksImpl', + of: 'ERC6909Component::ERC6909HooksTrait', + tags: [], + priority: 1, + }; + c.addImplementedTrait(hooksTrait); + c.addUseClause('starknet', 'ContractAddress'); + + c.addFunction(hooksTrait, { + name: 'before_update', + args: [ + { + name: 'ref self', + type: `ERC6909Component::ComponentState`, + }, + { name: 'from', type: 'ContractAddress' }, + { name: 'recipient', type: 'ContractAddress' }, + { name: 'id', type: 'u256' }, + { name: 'amount', type: 'u256' }, + ], + code: ['let contract_state = self.get_contract()', 'contract_state.pausable.assert_not_paused()'], + }); + } else { + c.addUseClause('openzeppelin_token::erc6909', 'ERC6909HooksEmptyImpl'); + } +} + +function addERC6909Mixin(c: ContractBuilder) { + c.addImplToComponent(components.ERC6909Component, { + name: 'ERC6909MixinImpl', + embed: true, + value: 'ERC6909Component::ERC6909MixinImpl', + }); + c.addInterfaceFlag('ISRC5'); + addSRC5Component(c); +} + +function addBase(c: ContractBuilder) { + c.addComponent(components.ERC6909Component, [], true); +} + +function addBurnable(c: ContractBuilder) { + c.addUseClause('starknet', 'ContractAddress'); + 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.mint, access, 'MINTER', 'minter'); +} + +const components = defineComponents({ + ERC6909Component: { + path: 'openzeppelin_token::erc6909', + substorage: { + name: 'erc6909', + type: 'ERC6909Component::Storage', + }, + event: { + name: 'ERC6909Event', + type: 'ERC6909Component::Event', + }, + impls: [ + { + name: 'ERC6909InternalImpl', + embed: false, + value: 'ERC6909Component::InternalImpl', + }, + ], + }, +}); + +const functions = defineFunctions({ + burn: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'id', type: 'u256' }, + { name: 'amount', type: 'u256' }, + ], + code: [ + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE)', + '}', + 'self.erc6909.burn(account, id, amount);', + ], + }, + mint: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'id', type: 'u256' }, + { name: 'amount', type: 'u256' }, + ], + code: ['self.erc6909.mint(account, id, amount);'], + }, +}); diff --git a/packages/core/cairo_alpha/src/generate/erc6909.ts b/packages/core/cairo_alpha/src/generate/erc6909.ts new file mode 100644 index 000000000..4d85ce020 --- /dev/null +++ b/packages/core/cairo_alpha/src/generate/erc6909.ts @@ -0,0 +1,33 @@ +import type { ERC6909Options } from '../erc6909'; +import type { AccessSubset } from '../set-access-control'; +import { resolveAccessControlOptions } from '../set-access-control'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import type { MacrosSubset } from '../set-macros'; +import { resolveMacrosOptions } from '../set-macros'; +import { generateAlternatives } from './alternatives'; + +const booleans = [true, false]; + +type GeneratorOptions = { + access: AccessSubset; + macros: MacrosSubset; +}; + +function prepareBlueprint(opts: GeneratorOptions) { + return { + name: ['MyToken'], + burnable: booleans, + pausable: booleans, + mintable: booleans, + upgradeable: upgradeableOptions, + access: resolveAccessControlOptions(opts.access), + info: infoOptions, + macros: resolveMacrosOptions(opts.macros), + }; +} + +export function* generateERC6909Options(opts: GeneratorOptions): Generator> { + const blueprint = prepareBlueprint(opts); + yield* generateAlternatives(blueprint); +} diff --git a/packages/core/cairo_alpha/src/generate/sources.ts b/packages/core/cairo_alpha/src/generate/sources.ts index 0d458374e..c9f369610 100644 --- a/packages/core/cairo_alpha/src/generate/sources.ts +++ b/packages/core/cairo_alpha/src/generate/sources.ts @@ -5,6 +5,7 @@ import crypto from 'crypto'; import { generateERC20Options } from './erc20'; import { generateERC721Options } from './erc721'; import { generateERC1155Options } from './erc1155'; +import { generateERC6909Options } from './erc6909'; import { generateAccountOptions } from './account'; import { generateCustomOptions } from './custom'; import { generateGovernorOptions } from './governor'; @@ -49,6 +50,12 @@ export function* generateOptions(params: { } } + if (kind === 'all' || kind === 'ERC6909') { + for (const kindOpts of generateERC6909Options({ access, macros })) { + yield { kind: 'ERC6909', ...kindOpts }; + } + } + if (kind === 'all' || kind === 'Account') { for (const kindOpts of generateAccountOptions({ macros })) { yield { kind: 'Account', ...kindOpts }; @@ -126,6 +133,7 @@ function generateContractSubset(params: { case 'ERC20': case 'ERC721': case 'ERC1155': + case 'ERC6909': case 'Account': case 'Multisig': case 'Governor': @@ -215,6 +223,7 @@ function resolveKindLabel(kind: KindSubset): string { case 'ERC20': case 'ERC721': case 'ERC1155': + case 'ERC6909': case 'Account': case 'Multisig': case 'Governor': @@ -235,6 +244,7 @@ function resolveAccessLabel(kind: KindSubset, access: AccessSubset): string | un case 'ERC20': case 'ERC721': case 'ERC1155': + case 'ERC6909': return `access: ${access}`; case 'Account': case 'Multisig': @@ -259,6 +269,7 @@ function resolveRoyaltyInfoLabel(kind: KindSubset, royaltyInfo: RoyaltyInfoSubse case 'ERC1155': return `royalty: ${royaltyInfo}`; case 'ERC20': + case 'ERC6909': case 'Account': case 'Custom': case 'Multisig': diff --git a/packages/core/cairo_alpha/src/index.ts b/packages/core/cairo_alpha/src/index.ts index 0c376fb8b..27bd959f3 100644 --- a/packages/core/cairo_alpha/src/index.ts +++ b/packages/core/cairo_alpha/src/index.ts @@ -27,11 +27,12 @@ export { sanitizeKind } from './kind'; export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; -export { erc20, erc721, erc1155, account, multisig, governor, vesting, custom } from './api'; +export { erc20, erc721, erc1155, erc6909, account, multisig, governor, vesting, custom } from './api'; export type { ERC20Options } from './erc20'; export type { ERC721Options } from './erc721'; export type { ERC1155Options } from './erc1155'; +export type { ERC6909Options } from './erc6909'; export type { AccountOptions } from './account'; export type { MultisigOptions } from './multisig'; export type { GovernorOptions } from './governor'; diff --git a/packages/core/cairo_alpha/src/kind.ts b/packages/core/cairo_alpha/src/kind.ts index 2710ecab3..1410fb42d 100644 --- a/packages/core/cairo_alpha/src/kind.ts +++ b/packages/core/cairo_alpha/src/kind.ts @@ -17,6 +17,7 @@ function isKind(value: Kind | T): value is Kind { case 'ERC20': case 'ERC721': case 'ERC1155': + case 'ERC6909': case 'Account': case 'Multisig': case 'Governor': diff --git a/packages/core/cairo_alpha/src/test.ts b/packages/core/cairo_alpha/src/test.ts index f160df6b7..c49e32ee1 100644 --- a/packages/core/cairo_alpha/src/test.ts +++ b/packages/core/cairo_alpha/src/test.ts @@ -10,7 +10,7 @@ import type { RoyaltyInfoSubset } from './set-royalty-info'; import type { GenericOptions } from './build-generic'; import type { MacrosSubset } from './set-macros'; import { generateSources, writeGeneratedSources } from './generate/sources'; -import { custom, erc20, erc721, erc1155 } from './api'; +import { custom, erc20, erc721, erc1155, erc6909 } from './api'; interface Context { generatedSourcesPath: string; @@ -30,6 +30,10 @@ test.serial('erc1155 results generated', async ctx => { await testGenerate({ ctx, kind: 'ERC1155', access: 'all', royaltyInfo: 'all' }); }); +test.serial('erc6909 results generated', async ctx => { + await testGenerate({ ctx, kind: 'ERC6909', access: 'all' }); +}); + test.serial('account results generated', async ctx => { await testGenerate({ ctx, kind: 'Account' }); }); @@ -83,6 +87,8 @@ function isAccessControlRequired(opts: GenericOptions) { return erc721.isAccessControlRequired(opts); case 'ERC1155': return erc1155.isAccessControlRequired(opts); + case 'ERC6909': + return erc6909.isAccessControlRequired(opts); case 'Custom': return custom.isAccessControlRequired(opts); case 'Account': @@ -119,6 +125,7 @@ test('is access control required', async t => { case 'ERC20': case 'ERC721': case 'ERC1155': + case 'ERC6909': case 'Custom': if (!contract.options.access?.type) { if (isAccessControlRequired(contract.options)) { diff --git a/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts new file mode 100644 index 000000000..7589f1c8e --- /dev/null +++ b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts @@ -0,0 +1,136 @@ +import test from 'ava'; +import { erc6909 } from '../../..'; + +import type { ERC6909Options } from '../../../erc6909'; +import { buildERC6909 } from '../../../erc6909'; +import { printContract } from '../../../print'; +import { AccessControl, darDefaultOpts, darCustomOpts } from '../../../set-access-control'; + +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; + +const allFeaturesON: Partial = { + mintable: true, + burnable: true, + pausable: true, + upgradeable: true, +} as const; + +function testERC6909(title: string, opts: Partial) { + test(title, t => { + const c = buildERC6909({ + name: NAME, + ...opts, + }); + t.snapshot(printContract(c)); + }); +} + +/** + * Tests external API for equivalence with internal API + */ +function testAPIEquivalence(title: string, opts?: ERC6909Options) { + test(title, t => { + t.is( + erc6909.print(opts), + printContract( + buildERC6909({ + name: NAME, + ...opts, + }), + ), + ); + }); +} + +testERC6909('basic non-upgradeable', { + upgradeable: false, +}); + +testERC6909('basic', {}); + +testERC6909('burnable', { + burnable: true, +}); + +testERC6909('pausable', { + pausable: true, +}); + +testERC6909('mintable', { + mintable: true, +}); + +testERC6909('mintable + roles', { + mintable: true, + access: AccessControl.Roles(), +}); + +testERC6909('mintable + roles-DAR (default opts)', { + mintable: true, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), +}); + +testERC6909('mintable + roles-DAR (custom opts)', { + mintable: true, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), +}); + +testERC6909('full non-upgradeable roles', { + ...allFeaturesON, + access: AccessControl.Roles(), + upgradeable: false, +}); + +testERC6909('full upgradeable roles', { + ...allFeaturesON, + access: AccessControl.Roles(), + upgradeable: true, +}); + +testERC6909('full non-upgradeable roles-DAR (default opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), + upgradeable: false, +}); + +testERC6909('full non-upgradeable roles-DAR (custom opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), + upgradeable: false, +}); + +testERC6909('full upgradeable roles-DAR (default opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), + upgradeable: true, +}); + +testERC6909('full upgradeable roles-DAR (custom opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), + upgradeable: true, +}); + +testAPIEquivalence('API default'); + +testAPIEquivalence('API basic', { name: CUSTOM_NAME }); + +testAPIEquivalence('API full upgradeable', { + ...allFeaturesON, + name: CUSTOM_NAME, + access: AccessControl.Roles(), + upgradeable: true, +}); + +test('API assert defaults', async t => { + t.is(erc6909.print(erc6909.defaults), erc6909.print()); +}); + +test('API isAccessControlRequired', async t => { + t.is(erc6909.isAccessControlRequired({ mintable: true }), true); + t.is(erc6909.isAccessControlRequired({ pausable: true }), true); + t.is(erc6909.isAccessControlRequired({ upgradeable: true }), true); + t.is(erc6909.isAccessControlRequired({ burnable: true }), false); + t.is(erc6909.isAccessControlRequired({}), false); +}); diff --git a/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.md b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.md new file mode 100644 index 000000000..7a9ce923c --- /dev/null +++ b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.md @@ -0,0 +1,1605 @@ +# Snapshot report for `src/tests/with_components_off/erc6909/erc6909.test.ts` + +The actual snapshot is saved in `erc6909.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## basic non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState) {␊ + self.erc6909.initializer();␊ + }␊ + }␊ + ` + +## basic + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::ownable::OwnableComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## burnable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::ownable::OwnableComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## pausable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::ownable::OwnableComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.unpause();␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::ownable::OwnableComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.ownable.assert_only_owner();␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + access_control: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + self.access_control._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::{␊ + AccessControlDefaultAdminRulesComponent,␊ + DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig␊ + };␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_token::erc6909::{ERC6909Component, ERC6909HooksEmptyImpl};␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control: AccessControlComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + self.access_control._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::{␊ + AccessControlDefaultAdminRulesComponent,␊ + DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig␊ + };␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::{␊ + AccessControlDefaultAdminRulesComponent,␊ + DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig␊ + };␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_introspection::src5::SRC5Component;␊ + use openzeppelin_security::pausable::PausableComponent;␊ + use openzeppelin_token::erc6909::ERC6909Component;␊ + use openzeppelin_upgrades::UpgradeableComponent;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + component!(path: ERC6909Component, storage: erc6909, event: ERC6909Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlDefaultAdminRulesComponent, storage: access_control_dar, event: AccessControlDefaultAdminRulesEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + // Internal␊ + impl ERC6909InternalImpl = ERC6909Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlDefaultAdminRulesInternalImpl = AccessControlDefaultAdminRulesComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc6909: ERC6909Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + access_control_dar: AccessControlDefaultAdminRulesComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC6909Event: ERC6909Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlDefaultAdminRulesEvent: AccessControlDefaultAdminRulesComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` diff --git a/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.snap b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.snap new file mode 100644 index 000000000..11a9be194 Binary files /dev/null and b/packages/core/cairo_alpha/src/tests/with_components_off/erc6909/erc6909.test.ts.snap differ diff --git a/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts new file mode 100644 index 000000000..f4fc9354b --- /dev/null +++ b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts @@ -0,0 +1,132 @@ +import test from 'ava'; +import { erc6909 } from '../../..'; + +import type { ERC6909Options } from '../../../erc6909'; +import { buildERC6909, defaults } from '../../../erc6909'; +import { printContract } from '../../../print'; +import { AccessControl, darDefaultOpts, darCustomOpts } from '../../../set-access-control'; + +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; + +const withComponentsMacroON = { withComponents: true }; + +const allFeaturesON: Partial = { + mintable: true, + burnable: true, + pausable: true, + upgradeable: true, +} as const; + +function testERC6909(title: string, opts: Partial) { + test(title, t => { + const c = buildERC6909({ + name: NAME, + ...opts, + macros: withComponentsMacroON, + }); + t.snapshot(printContract(c)); + }); +} + +/** + * Tests external API for equivalence with internal API + */ +function testAPIEquivalence(title: string, opts?: ERC6909Options) { + const options = { ...(opts === undefined ? defaults : opts), macros: withComponentsMacroON }; + test(title, t => { + t.is(erc6909.print(options), printContract(buildERC6909(options))); + }); +} + +testERC6909('basic non-upgradeable', { + upgradeable: false, +}); + +testERC6909('basic', {}); + +testERC6909('burnable', { + burnable: true, +}); + +testERC6909('pausable', { + pausable: true, +}); + +testERC6909('mintable', { + mintable: true, +}); + +testERC6909('mintable + roles', { + mintable: true, + access: AccessControl.Roles(), +}); + +testERC6909('mintable + roles-DAR (default opts)', { + mintable: true, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), +}); + +testERC6909('mintable + roles-DAR (custom opts)', { + mintable: true, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), +}); + +testERC6909('full non-upgradeable roles', { + ...allFeaturesON, + access: AccessControl.Roles(), + upgradeable: false, +}); + +testERC6909('full upgradeable roles', { + ...allFeaturesON, + access: AccessControl.Roles(), + upgradeable: true, +}); + +testERC6909('full non-upgradeable roles-DAR (default opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), + upgradeable: false, +}); + +testERC6909('full non-upgradeable roles-DAR (custom opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), + upgradeable: false, +}); + +testERC6909('full upgradeable roles-DAR (default opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darDefaultOpts), + upgradeable: true, +}); + +testERC6909('full upgradeable roles-DAR (custom opts)', { + ...allFeaturesON, + access: AccessControl.RolesDefaultAdminRules(darCustomOpts), + upgradeable: true, +}); + +testAPIEquivalence('API default'); + +testAPIEquivalence('API basic', { name: CUSTOM_NAME }); + +testAPIEquivalence('API full upgradeable', { + ...allFeaturesON, + name: CUSTOM_NAME, + access: AccessControl.Roles(), + upgradeable: true, +}); + +test('API assert defaults', async t => { + t.is(erc6909.print(erc6909.defaults), erc6909.print()); +}); + +test('API isAccessControlRequired', async t => { + t.is(erc6909.isAccessControlRequired({ mintable: true }), true); + t.is(erc6909.isAccessControlRequired({ pausable: true }), true); + t.is(erc6909.isAccessControlRequired({ upgradeable: true }), true); + t.is(erc6909.isAccessControlRequired({ burnable: true }), false); + t.is(erc6909.isAccessControlRequired({}), false); +}); diff --git a/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.md b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.md new file mode 100644 index 000000000..eb47070b9 --- /dev/null +++ b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.md @@ -0,0 +1,1105 @@ +# Snapshot report for `src/tests/with_components_on/erc6909/erc6909.test.ts` + +The actual snapshot is saved in `erc6909.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## basic non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5)]␊ + mod MyToken {␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState) {␊ + self.erc6909.initializer();␊ + }␊ + }␊ + ` + +## basic + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Upgradeable, Ownable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## burnable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Upgradeable, Ownable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## pausable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, Ownable, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.unpause();␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Ownable, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc6909.initializer();␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.ownable.assert_only_owner();␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, AccessControl, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + self.access_control._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, AccessControlDefaultAdminRules, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, AccessControlDefaultAdminRules, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use openzeppelin_token::erc6909::ERC6909HooksEmptyImpl;␊ + use starknet::{ClassHash, ContractAddress};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControl)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControl, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control.initializer();␊ + ␊ + self.access_control._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.access_control._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control._grant_role(MINTER_ROLE, minter);␊ + self.access_control._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControlDefaultAdminRules)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig;␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControlDefaultAdminRules)]␊ + mod MyToken {␊ + use starknet::{ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles-DAR (default opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControlDefaultAdminRules, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_access::accesscontrol::extensions::DefaultConfig as AccessControlDefaultAdminRulesDefaultConfig;␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 86400; // 1 day␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable roles-DAR (custom opts) + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo 4.0.0-alpha.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + #[with_components(ERC6909, SRC5, Pausable, AccessControlDefaultAdminRules, Upgradeable)]␊ + mod MyToken {␊ + use openzeppelin_interfaces::upgrades::IUpgradeable;␊ + use starknet::{ClassHash, ContractAddress, get_caller_address};␊ + use super::{MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE};␊ + ␊ + const INITIAL_DELAY: u64 = 172800; // 2 days␊ + ␊ + // External␊ + #[abi(embed_v0)]␊ + impl ERC6909MixinImpl = ERC6909Component::ERC6909MixinImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlDefaultAdminRulesComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlDefaultAdminRulesImpl = AccessControlDefaultAdminRulesComponent::AccessControlDefaultAdminRulesImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlDefaultAdminRulesComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlWithDelayImpl = AccessControlDefaultAdminRulesComponent::AccessControlWithDelayImpl;␊ + ␊ + #[storage]␊ + struct Storage {}␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + initial_default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc6909.initializer();␊ + self.access_control_dar.initializer(INITIAL_DELAY, initial_default_admin);␊ + ␊ + self.access_control_dar._grant_role(PAUSER_ROLE, pauser);␊ + self.access_control_dar._grant_role(MINTER_ROLE, minter);␊ + self.access_control_dar._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + impl ERC6909HooksImpl of ERC6909Component::ERC6909HooksTrait {␊ + fn before_update(␊ + ref self: ERC6909Component::ComponentState,␊ + from: ContractAddress,␊ + recipient: ContractAddress,␊ + id: u256,␊ + amount: u256,␊ + ) {␊ + let contract_state = self.get_contract();␊ + contract_state.pausable.assert_not_paused();␊ + }␊ + }␊ + ␊ + impl AccessControlDefaultAdminRulesImmutableConfig of AccessControlDefaultAdminRulesComponent::ImmutableConfig {␊ + const DEFAULT_ADMIN_DELAY_INCREASE_WAIT: u64 = 604800; // 1 week␊ + const MAXIMUM_DEFAULT_ADMIN_TRANSFER_DELAY: u64 = 1209600; // 2 weeks␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.access_control_dar.assert_only_role(PAUSER_ROLE);␊ + self.pausable.unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc6909.is_operator(account, caller), ERC6909Component::Errors::INSUFFICIENT_ALLOWANCE);␊ + }␊ + self.erc6909.burn(account, id, amount);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, account: ContractAddress, id: u256, amount: u256) {␊ + self.access_control_dar.assert_only_role(MINTER_ROLE);␊ + self.erc6909.mint(account, id, amount);␊ + }␊ + }␊ + ␊ + //␊ + // Upgradeable␊ + //␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.access_control_dar.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` diff --git a/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.snap b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.snap new file mode 100644 index 000000000..754b7d2bf Binary files /dev/null and b/packages/core/cairo_alpha/src/tests/with_components_on/erc6909/erc6909.test.ts.snap differ diff --git a/packages/ui/src/cairo_alpha/App.svelte b/packages/ui/src/cairo_alpha/App.svelte index 0f7e5e566..cd9f6e5a1 100644 --- a/packages/ui/src/cairo_alpha/App.svelte +++ b/packages/ui/src/cairo_alpha/App.svelte @@ -6,6 +6,7 @@ import ERC20Controls from './ERC20Controls.svelte'; import ERC721Controls from './ERC721Controls.svelte'; import ERC1155Controls from './ERC1155Controls.svelte'; + import ERC6909Controls from './ERC6909Controls.svelte'; import CustomControls from './CustomControls.svelte'; import AccountControls from './AccountControls.svelte'; import MultisigControls from './MultisigControls.svelte'; @@ -79,6 +80,7 @@ opts.symbol = initialOpts.symbol ?? opts.symbol; break; case 'ERC1155': + case 'ERC6909': case 'Account': case 'Multisig': case 'Governor': @@ -148,6 +150,7 @@ + @@ -199,6 +202,9 @@
+
+ +
diff --git a/packages/ui/src/cairo_alpha/ERC6909Controls.svelte b/packages/ui/src/cairo_alpha/ERC6909Controls.svelte new file mode 100644 index 000000000..c12ad39b9 --- /dev/null +++ b/packages/ui/src/cairo_alpha/ERC6909Controls.svelte @@ -0,0 +1,73 @@ + + +
+

Settings

+ + +
+ +
+

Features

+ +
+ + + + +
+
+ + + + + +