Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-erc6909.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openzeppelin/wizard-common": patch
---

Cairo: add ERC6909 description for AI prompts
3 changes: 3 additions & 0 deletions packages/common/src/ai/descriptions/cairo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down Expand Up @@ -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".',
Expand Down
3 changes: 2 additions & 1 deletion packages/core/cairo_alpha/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:
Expand Down
12 changes: 12 additions & 0 deletions packages/core/cairo_alpha/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -67,6 +73,7 @@ export interface AccessControlAPI<Options extends CommonContractOptions> {
export type ERC20 = WizardContractAPI<ERC20Options> & AccessControlAPI<ERC20Options>;
export type ERC721 = WizardContractAPI<ERC721Options> & AccessControlAPI<ERC721Options>;
export type ERC1155 = WizardContractAPI<ERC1155Options> & AccessControlAPI<ERC1155Options>;
export type ERC6909 = WizardContractAPI<ERC6909Options> & AccessControlAPI<ERC6909Options>;
export type Account = WizardAccountAPI<AccountOptions>;
export type Multisig = WizardContractAPI<MultisigOptions>;
export type Governor = WizardContractAPI<GovernorOptions>;
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/cairo_alpha/src/build-generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -39,6 +42,9 @@ export function buildGeneric(opts: GenericOptions) {
case 'ERC1155':
return buildERC1155(opts);

case 'ERC6909':
return buildERC6909(opts);

case 'Account':
return buildAccount(opts);

Expand Down
183 changes: 183 additions & 0 deletions packages/core/cairo_alpha/src/erc6909.ts
Original file line number Diff line number Diff line change
@@ -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<ERC6909Options> = {
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<ERC6909Options> {
return {
...opts,
...withCommonContractDefaults(opts),
burnable: opts.burnable ?? defaults.burnable,
pausable: opts.pausable ?? defaults.pausable,
mintable: opts.mintable ?? defaults.mintable,
};
}

export function isAccessControlRequired(opts: Partial<ERC6909Options>): 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<ERC6909Options>) {
const usesCustomHooks = allOpts.pausable;
if (usesCustomHooks) {
const hooksTrait = {
name: 'ERC6909HooksImpl',
of: 'ERC6909Component::ERC6909HooksTrait<ContractState>',
tags: [],
priority: 1,
};
c.addImplementedTrait(hooksTrait);
c.addUseClause('starknet', 'ContractAddress');

c.addFunction(hooksTrait, {
name: 'before_update',
args: [
{
name: 'ref self',
type: `ERC6909Component::ComponentState<ContractState>`,
},
{ 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<ContractState>',
});
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<ContractState>',
},
],
},
});

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);'],
},
});
33 changes: 33 additions & 0 deletions packages/core/cairo_alpha/src/generate/erc6909.ts
Original file line number Diff line number Diff line change
@@ -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<Required<ERC6909Options>> {
const blueprint = prepareBlueprint(opts);
yield* generateAlternatives(blueprint);
}
11 changes: 11 additions & 0 deletions packages/core/cairo_alpha/src/generate/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -126,6 +133,7 @@ function generateContractSubset(params: {
case 'ERC20':
case 'ERC721':
case 'ERC1155':
case 'ERC6909':
case 'Account':
case 'Multisig':
case 'Governor':
Expand Down Expand Up @@ -215,6 +223,7 @@ function resolveKindLabel(kind: KindSubset): string {
case 'ERC20':
case 'ERC721':
case 'ERC1155':
case 'ERC6909':
case 'Account':
case 'Multisig':
case 'Governor':
Expand All @@ -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':
Expand All @@ -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':
Expand Down
3 changes: 2 additions & 1 deletion packages/core/cairo_alpha/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions packages/core/cairo_alpha/src/kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function isKind<T>(value: Kind | T): value is Kind {
case 'ERC20':
case 'ERC721':
case 'ERC1155':
case 'ERC6909':
case 'Account':
case 'Multisig':
case 'Governor':
Expand Down
Loading
Loading