diff --git a/packages/core-cairo/CHANGELOG.md b/packages/core-cairo/CHANGELOG.md index a54fc197a..b2457f7f0 100644 --- a/packages/core-cairo/CHANGELOG.md +++ b/packages/core-cairo/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.10.2 (2024-04-03) + +- Use OpenZeppelin Contracts for Cairo v0.11.0. ([#351](https://github.com/OpenZeppelin/contracts-wizard/pull/351)) +- Add ERC1155. +- Remove redundant not paused assertions for camel case functions. +- Fix use of ERC20 mixin. +- Sort imports alphabetically. + ## 0.10.1 (2024-03-27) - Use mixins. ([#348](https://github.com/OpenZeppelin/contracts-wizard/pull/348)) diff --git a/packages/core-cairo/README.md b/packages/core-cairo/README.md index ed51b7ced..ad3bd62e7 100644 --- a/packages/core-cairo/README.md +++ b/packages/core-cairo/README.md @@ -80,17 +80,6 @@ Calculates the initial supply that would be used in an ERC20 contract based on a Returns `premint` with zeros padded or removed based on `decimals`. Throws an error if `premint` has more than one decimal character or is more precise than allowed by the `decimals` argument. -### Utility functions - -#### `utils.toUint256` - -Returns Uint256 components for low and high bits based on a given number in string format. - -- `num` Number in string format - -Returns an object with lowBits and highBits. -Throws an error if the provided number is larger than 256 bits. - ### Examples Import the contract type(s) or function categories (for example, `erc20` or `utils`) that you want to use from the `@openzeppelin/wizard-cairo` package: diff --git a/packages/core-cairo/package.json b/packages/core-cairo/package.json index 42577228c..c850420ee 100644 --- a/packages/core-cairo/package.json +++ b/packages/core-cairo/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/wizard-cairo", - "version": "0.10.1", + "version": "0.10.2", "description": "A boilerplate generator to get started with OpenZeppelin Contracts for Cairo", "license": "MIT", "repository": "github:OpenZeppelin/contracts-wizard", diff --git a/packages/core-cairo/src/api.ts b/packages/core-cairo/src/api.ts index 26fdcb7db..95fdf1a9f 100644 --- a/packages/core-cairo/src/api.ts +++ b/packages/core-cairo/src/api.ts @@ -1,6 +1,7 @@ import type { CommonOptions } from './common-options'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; +import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; export interface WizardContractAPI { @@ -23,6 +24,7 @@ export interface WizardContractAPI { export type ERC20 = WizardContractAPI; export type ERC721 = WizardContractAPI; +export type ERC1155 = WizardContractAPI; export type Custom = WizardContractAPI; export const erc20: ERC20 = { @@ -35,6 +37,11 @@ export const erc721: ERC721 = { defaults: erc721defaults, isAccessControlRequired: erc721IsAccessControlRequired } +export const erc1155: ERC1155 = { + print: printERC1155, + defaults: erc1155defaults, + isAccessControlRequired: erc1155IsAccessControlRequired +} export const custom: Custom = { print: printCustom, defaults: customDefaults, diff --git a/packages/core-cairo/src/build-generic.ts b/packages/core-cairo/src/build-generic.ts index 3a28e2083..b15eadbe8 100644 --- a/packages/core-cairo/src/build-generic.ts +++ b/packages/core-cairo/src/build-generic.ts @@ -1,10 +1,12 @@ import { ERC20Options, buildERC20 } from './erc20'; import { ERC721Options, buildERC721 } from './erc721'; +import { ERC1155Options, buildERC1155 } from './erc1155'; import { CustomOptions, buildCustom } from './custom'; export interface KindedOptions { ERC20: { kind: 'ERC20' } & ERC20Options; ERC721: { kind: 'ERC721' } & ERC721Options; + ERC1155: { kind: 'ERC1155' } & ERC1155Options; Custom: { kind: 'Custom' } & CustomOptions; } @@ -18,6 +20,9 @@ export function buildGeneric(opts: GenericOptions) { case 'ERC721': return buildERC721(opts); + case 'ERC1155': + return buildERC1155(opts); + case 'Custom': return buildCustom(opts); diff --git a/packages/core-cairo/src/contract.test.ts.md b/packages/core-cairo/src/contract.test.ts.md index 18eec541d..1a89694ab 100644 --- a/packages/core-cairo/src/contract.test.ts.md +++ b/packages/core-cairo/src/contract.test.ts.md @@ -9,7 +9,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -24,7 +24,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -44,7 +44,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -64,7 +64,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -89,7 +89,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -114,7 +114,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ @@ -152,12 +152,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod Foo {␊ - use some::path::FooComponent;␊ use some::library::SomeLibrary;␊ + use some::path::FooComponent;␊ ␊ component!(path: FooComponent, storage: foo, event: FooEvent);␊ ␊ diff --git a/packages/core-cairo/src/contract.test.ts.snap b/packages/core-cairo/src/contract.test.ts.snap index 4631a7480..f20d83266 100644 Binary files a/packages/core-cairo/src/contract.test.ts.snap and b/packages/core-cairo/src/contract.test.ts.snap differ diff --git a/packages/core-cairo/src/custom.test.ts.md b/packages/core-cairo/src/custom.test.ts.md index 72b3bfba5..9c497d1ee 100644 --- a/packages/core-cairo/src/custom.test.ts.md +++ b/packages/core-cairo/src/custom.test.ts.md @@ -9,7 +9,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ @@ -24,12 +24,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ - use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::security::pausable::PausableComponent;␊ use starknet::ContractAddress;␊ ␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ @@ -88,12 +88,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ - use openzeppelin::upgrades::UpgradeableComponent;␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ use openzeppelin::upgrades::interface::IUpgradeable;␊ use starknet::ClassHash;␊ use starknet::ContractAddress;␊ @@ -144,7 +144,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ @@ -159,7 +159,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ @@ -198,13 +198,13 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ - use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ use starknet::ContractAddress;␊ ␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ @@ -246,12 +246,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyContract {␊ - use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::security::pausable::PausableComponent;␊ use starknet::ContractAddress;␊ ␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ diff --git a/packages/core-cairo/src/custom.test.ts.snap b/packages/core-cairo/src/custom.test.ts.snap index 0abf27056..e9d14bcaa 100644 Binary files a/packages/core-cairo/src/custom.test.ts.snap and b/packages/core-cairo/src/custom.test.ts.snap differ diff --git a/packages/core-cairo/src/erc1155.test.ts b/packages/core-cairo/src/erc1155.test.ts new file mode 100644 index 000000000..8231e7986 --- /dev/null +++ b/packages/core-cairo/src/erc1155.test.ts @@ -0,0 +1,91 @@ +import test from 'ava'; +import { erc1155 } from '.'; + +import { buildERC1155, ERC1155Options } from './erc1155'; +import { printContract } from './print'; + +function testERC1155(title: string, opts: Partial) { + test(title, t => { + const c = buildERC1155({ + name: 'MyToken', + baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + ...opts, + }); + t.snapshot(printContract(c)); + }); +} + +/** + * Tests external API for equivalence with internal API + */ + function testAPIEquivalence(title: string, opts?: ERC1155Options) { + test(title, t => { + t.is(erc1155.print(opts), printContract(buildERC1155({ + name: 'MyToken', + baseUri: '', + ...opts, + }))); + }); +} + +testERC1155('basic', {}); + +testERC1155('basic + roles', { + access: 'roles', +}); + +testERC1155('no updatable uri', { + updatableUri: false, +}); + +testERC1155('burnable', { + burnable: true, +}); + +testERC1155('pausable', { + pausable: true, +}); + +testERC1155('mintable', { + mintable: true, +}); + +testERC1155('mintable + roles', { + mintable: true, + access: 'roles', +}); + +testERC1155('full upgradeable', { + mintable: true, + access: 'roles', + burnable: true, + pausable: true, + upgradeable: true, +}); + +testAPIEquivalence('API default'); + +testAPIEquivalence('API basic', { name: 'CustomToken', baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/' }); + +testAPIEquivalence('API full upgradeable', { + name: 'CustomToken', + baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + mintable: true, + access: 'roles', + burnable: true, + pausable: true, + upgradeable: true, +}); + +test('API assert defaults', async t => { + t.is(erc1155.print(erc1155.defaults), erc1155.print()); +}); + +test('API isAccessControlRequired', async t => { + t.is(erc1155.isAccessControlRequired({ updatableUri: false, mintable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, pausable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false, upgradeable: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: true }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false}), false); + t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default +}); \ No newline at end of file diff --git a/packages/core-cairo/src/erc1155.test.ts.md b/packages/core-cairo/src/erc1155.test.ts.md new file mode 100644 index 000000000..1970fb54f --- /dev/null +++ b/packages/core-cairo/src/erc1155.test.ts.md @@ -0,0 +1,1002 @@ +# Snapshot report for `src/erc1155.test.ts` + +The actual snapshot is saved in `erc1155.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## basic + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## basic + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + const URI_SETTER_ROLE: felt252 = selector!("URI_SETTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::accesscontrol::AccessControlComponent;␊ + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use starknet::ContractAddress;␊ + use super::{URI_SETTER_ROLE};␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + uri_setter: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.accesscontrol.initializer();␊ + ␊ + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(URI_SETTER_ROLE, uri_setter);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.accesscontrol.assert_only_role(URI_SETTER_ROLE);␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## no updatable uri + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + }␊ + }␊ + ` + +## burnable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use starknet::ContractAddress;␊ + use starknet::get_caller_address;␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, token_id: u256, value: u256) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED);␊ + }␊ + self.erc1155.burn(account, token_id, value);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batch_burn(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + ) {␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED);␊ + }␊ + self.erc1155.batch_burn(account, token_ids, values);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batchBurn(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + ) {␊ + self.batch_burn(account, tokenIds, values);␊ + }␊ + ␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## pausable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::interface;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MetadataURIImpl = ERC1155Component::ERC1155MetadataURIImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155Impl of interface::IERC1155 {␊ + fn balance_of(self: @ContractState, account: ContractAddress, token_id: u256) -> u256 {␊ + self.erc1155.balance_of(account, token_id)␊ + }␊ + ␊ + fn balance_of_batch(self: @ContractState, accounts: Span, token_ids: Span) -> Span {␊ + self.erc1155.balance_of_batch(accounts, token_ids)␊ + }␊ + ␊ + fn safe_transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_id: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.safe_transfer_from(from, to, token_id, value, data);␊ + }␊ + ␊ + fn safe_batch_transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.safe_batch_transfer_from(from, to, token_ids, values, data);␊ + }␊ + ␊ + fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool {␊ + self.erc1155.is_approved_for_all(owner, operator)␊ + }␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155CamelImpl of interface::IERC1155Camel {␊ + fn balanceOf(self: @ContractState, account: ContractAddress, tokenId: u256) -> u256 {␊ + self.balance_of(account, tokenId)␊ + }␊ + ␊ + fn balanceOfBatch(self: @ContractState, accounts: Span, tokenIds: Span) -> Span {␊ + self.balance_of_batch(accounts, tokenIds)␊ + }␊ + ␊ + fn safeTransferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenId: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.safe_transfer_from(from, to, tokenId, value, data);␊ + }␊ + ␊ + fn safeBatchTransferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.safe_batch_transfer_from(from, to, tokenIds, values, data);␊ + }␊ + ␊ + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn isApprovedForAll(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool {␊ + self.is_approved_for_all(owner, operator)␊ + }␊ + }␊ + ␊ + #[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();␊ + }␊ + ␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## mintable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_id: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.mint_with_acceptance_check(account, token_id, value, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batch_mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batchMint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.batch_mint(account, tokenIds, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.ownable.assert_only_owner();␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## mintable + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const URI_SETTER_ROLE: felt252 = selector!("URI_SETTER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::accesscontrol::AccessControlComponent;␊ + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use starknet::ContractAddress;␊ + use super::{MINTER_ROLE, URI_SETTER_ROLE};␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + uri_setter: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.accesscontrol.initializer();␊ + ␊ + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(MINTER_ROLE, minter);␊ + self.accesscontrol._grant_role(URI_SETTER_ROLE, uri_setter);␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_id: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.accesscontrol.assert_only_role(MINTER_ROLE);␊ + self.erc1155.mint_with_acceptance_check(account, token_id, value, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batch_mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.accesscontrol.assert_only_role(MINTER_ROLE);␊ + self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batchMint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.batch_mint(account, tokenIds, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.accesscontrol.assert_only_role(URI_SETTER_ROLE);␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + }␊ + ` + +## full upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const URI_SETTER_ROLE: felt252 = selector!("URI_SETTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::accesscontrol::AccessControlComponent;␊ + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::interface;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + use starknet::get_caller_address;␊ + use super::{PAUSER_ROLE, MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE};␊ + ␊ + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MetadataURIImpl = ERC1155Component::ERC1155MetadataURIImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::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,␊ + uri_setter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.accesscontrol.initializer();␊ + ␊ + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(PAUSER_ROLE, pauser);␊ + self.accesscontrol._grant_role(MINTER_ROLE, minter);␊ + self.accesscontrol._grant_role(URI_SETTER_ROLE, uri_setter);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155Impl of interface::IERC1155 {␊ + fn balance_of(self: @ContractState, account: ContractAddress, token_id: u256) -> u256 {␊ + self.erc1155.balance_of(account, token_id)␊ + }␊ + ␊ + fn balance_of_batch(self: @ContractState, accounts: Span, token_ids: Span) -> Span {␊ + self.erc1155.balance_of_batch(accounts, token_ids)␊ + }␊ + ␊ + fn safe_transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_id: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.safe_transfer_from(from, to, token_id, value, data);␊ + }␊ + ␊ + fn safe_batch_transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.safe_batch_transfer_from(from, to, token_ids, values, data);␊ + }␊ + ␊ + fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.pausable.assert_not_paused();␊ + self.erc1155.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool {␊ + self.erc1155.is_approved_for_all(owner, operator)␊ + }␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155CamelImpl of interface::IERC1155Camel {␊ + fn balanceOf(self: @ContractState, account: ContractAddress, tokenId: u256) -> u256 {␊ + self.balance_of(account, tokenId)␊ + }␊ + ␊ + fn balanceOfBatch(self: @ContractState, accounts: Span, tokenIds: Span) -> Span {␊ + self.balance_of_batch(accounts, tokenIds)␊ + }␊ + ␊ + fn safeTransferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenId: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.safe_transfer_from(from, to, tokenId, value, data);␊ + }␊ + ␊ + fn safeBatchTransferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.safe_batch_transfer_from(from, to, tokenIds, values, data);␊ + }␊ + ␊ + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn isApprovedForAll(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool {␊ + self.is_approved_for_all(owner, operator)␊ + }␊ + }␊ + ␊ + #[generate_trait]␊ + #[abi(per_item)]␊ + impl ExternalImpl of ExternalTrait {␊ + #[external(v0)]␊ + fn pause(ref self: ContractState) {␊ + self.accesscontrol.assert_only_role(PAUSER_ROLE);␊ + self.pausable._pause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn unpause(ref self: ContractState) {␊ + self.accesscontrol.assert_only_role(PAUSER_ROLE);␊ + self.pausable._unpause();␊ + }␊ + ␊ + #[external(v0)]␊ + fn burn(ref self: ContractState, account: ContractAddress, token_id: u256, value: u256) {␊ + self.pausable.assert_not_paused();␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED);␊ + }␊ + self.erc1155.burn(account, token_id, value);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batch_burn(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + let caller = get_caller_address();␊ + if account != caller {␊ + assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED);␊ + }␊ + self.erc1155.batch_burn(account, token_ids, values);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batchBurn(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + ) {␊ + self.batch_burn(account, tokenIds, values);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_id: u256,␊ + value: u256,␊ + data: Span,␊ + ) {␊ + self.accesscontrol.assert_only_role(MINTER_ROLE);␊ + self.pausable.assert_not_paused();␊ + self.erc1155.mint_with_acceptance_check(account, token_id, value, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batch_mint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + token_ids: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.accesscontrol.assert_only_role(MINTER_ROLE);␊ + self.pausable.assert_not_paused();␊ + self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn batchMint(␊ + ref self: ContractState,␊ + account: ContractAddress,␊ + tokenIds: Span,␊ + values: Span,␊ + data: Span,␊ + ) {␊ + self.batch_mint(account, tokenIds, values, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn set_base_uri(ref self: ContractState, base_uri: ByteArray) {␊ + self.accesscontrol.assert_only_role(URI_SETTER_ROLE);␊ + self.erc1155.set_base_uri(base_uri);␊ + }␊ + ␊ + #[external(v0)]␊ + fn setBaseUri(ref self: ContractState, baseUri: ByteArray) {␊ + self.set_base_uri(baseUri);␊ + }␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.accesscontrol.assert_only_role(UPGRADER_ROLE);␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` diff --git a/packages/core-cairo/src/erc1155.test.ts.snap b/packages/core-cairo/src/erc1155.test.ts.snap new file mode 100644 index 000000000..a999e4803 Binary files /dev/null and b/packages/core-cairo/src/erc1155.test.ts.snap differ diff --git a/packages/core-cairo/src/erc1155.ts b/packages/core-cairo/src/erc1155.ts new file mode 100644 index 000000000..62ef40e5b --- /dev/null +++ b/packages/core-cairo/src/erc1155.ts @@ -0,0 +1,436 @@ +import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; +import { Access, requireAccessControl, setAccessControl } from './set-access-control'; +import { addPausable, setPausable } from './add-pausable'; +import { defineFunctions } from './utils/define-functions'; +import { CommonOptions, withCommonDefaults, getSelfArg } from './common-options'; +import { setUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { defaults as commonDefaults } from './common-options'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; +import { externalTrait } from './external-trait'; +import { toStringLiteral } from './utils/convert-strings'; + +export const defaults: Required = { + name: 'MyToken', + baseUri: '', + burnable: false, + pausable: false, + mintable: false, + updatableUri: true, + access: commonDefaults.access, + upgradeable: commonDefaults.upgradeable, + info: commonDefaults.info +} as const; + +export function printERC1155(opts: ERC1155Options = defaults): string { + return printContract(buildERC1155(opts)); +} + +export interface ERC1155Options extends CommonOptions { + name: string; + baseUri: string; + burnable?: boolean; + pausable?: boolean; + mintable?: boolean; + updatableUri?: boolean; +} + +function withDefaults(opts: ERC1155Options): Required { + return { + ...opts, + ...withCommonDefaults(opts), + burnable: opts.burnable ?? defaults.burnable, + pausable: opts.pausable ?? defaults.pausable, + mintable: opts.mintable ?? defaults.mintable, + updatableUri: opts.updatableUri ?? defaults.updatableUri, + }; +} + +export function isAccessControlRequired(opts: Partial): boolean { + return opts.mintable === true || opts.pausable === true || opts.updatableUri !== false || opts.upgradeable === true; +} + +export function buildERC1155(opts: ERC1155Options): Contract { + const c = new ContractBuilder(opts.name); + + const allOpts = withDefaults(opts); + + addBase(c, toStringLiteral(allOpts.baseUri)); + addERC1155MixinOrImpls(c, allOpts.pausable); + addSRC5Component(c); + + if (allOpts.pausable) { + addPausable(c, allOpts.access); + } + + if (allOpts.burnable) { + addBurnable(c); + if (allOpts.pausable) { + setPausable(c, externalTrait, functions.burn); + setPausable(c, externalTrait, functions.batch_burn); + } + } + + if (allOpts.mintable) { + addMintable(c, allOpts.access); + if (allOpts.pausable) { + setPausable(c, externalTrait, functions.mint); + setPausable(c, externalTrait, functions.batch_mint); + } + } + + if (allOpts.updatableUri) { + addSetBaseUri(c, allOpts.access); + } + + setAccessControl(c, allOpts.access); + setUpgradeable(c, allOpts.upgradeable, allOpts.access); + setInfo(c, allOpts.info); + + return c; +} + +function addERC1155Interface(c: ContractBuilder) { + c.addStandaloneImport('openzeppelin::token::erc1155::interface'); +} + +function addERC1155MixinOrImpls(c: ContractBuilder, pausable: boolean) { + if (pausable) { + addERC1155Interface(c); + + c.addImplToComponent(components.ERC1155Component, { + name: 'ERC1155MetadataURIImpl', + value: 'ERC1155Component::ERC1155MetadataURIImpl', + }); + + const ERC1155Impl: BaseImplementedTrait = { + name: 'ERC1155Impl', + of: 'interface::IERC1155', + tags: [ + 'abi(embed_v0)' + ], + } + c.addFunction(ERC1155Impl, functions.balance_of); + c.addFunction(ERC1155Impl, functions.balance_of_batch); + setPausable(c, ERC1155Impl, functions.safe_transfer_from); + setPausable(c, ERC1155Impl, functions.safe_batch_transfer_from); + setPausable(c, ERC1155Impl, functions.set_approval_for_all); + c.addFunction(ERC1155Impl, functions.is_approved_for_all); + + const ERC1155CamelImpl: BaseImplementedTrait = { + name: 'ERC1155CamelImpl', + of: 'interface::IERC1155Camel', + tags: [ + 'abi(embed_v0)' + ], + } + // Camel case versions of the functions above. Pausable is already set above. + c.addFunction(ERC1155CamelImpl, functions.balanceOf); + c.addFunction(ERC1155CamelImpl, functions.balanceOfBatch); + c.addFunction(ERC1155CamelImpl, functions.safeTransferFrom); + c.addFunction(ERC1155CamelImpl, functions.safeBatchTransferFrom); + c.addFunction(ERC1155CamelImpl, functions.setApprovalForAll); + c.addFunction(ERC1155CamelImpl, functions.isApprovedForAll); + } else { + c.addImplToComponent(components.ERC1155Component, { + name: 'ERC1155MixinImpl', + value: 'ERC1155Component::ERC1155MixinImpl', + }); + c.addInterfaceFlag('ISRC5'); + } +} + +function addBase(c: ContractBuilder, baseUri: string) { + c.addComponent( + components.ERC1155Component, + [ + baseUri, + ], + true, + ); +} + +function addBurnable(c: ContractBuilder) { + c.addStandaloneImport('starknet::get_caller_address'); + c.addFunction(externalTrait, functions.burn); + c.addFunction(externalTrait, functions.batch_burn); + c.addFunction(externalTrait, functions.batchBurn); +} + +function addMintable(c: ContractBuilder, access: Access) { + c.addStandaloneImport('starknet::ContractAddress'); + requireAccessControl(c, externalTrait, functions.mint, access, 'MINTER', 'minter'); + requireAccessControl(c, externalTrait, functions.batch_mint, access, 'MINTER', 'minter'); + + // Camel case version of batch_mint. Access control and pausable are already set on batch_mint. + c.addFunction(externalTrait, functions.batchMint); +} + +function addSetBaseUri(c: ContractBuilder, access: Access) { + requireAccessControl(c, externalTrait, functions.set_base_uri, access, 'URI_SETTER', 'uri_setter'); + + // Camel case version of set_base_uri. Access control is already set on set_base_uri. + c.addFunction(externalTrait, functions.setBaseUri); +} + +const components = defineComponents( { + ERC1155Component: { + path: 'openzeppelin::token::erc1155', + substorage: { + name: 'erc1155', + type: 'ERC1155Component::Storage', + }, + event: { + name: 'ERC1155Event', + type: 'ERC1155Component::Event', + }, + impls: [], + internalImpl: { + name: 'ERC1155InternalImpl', + value: 'ERC1155Component::InternalImpl', + }, + }, +}); + +const functions = defineFunctions({ + burn: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, + ], + code: [ + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.burn(account, token_id, value);' + ] + }, + batch_burn: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, + ], + code: [ + 'let caller = get_caller_address();', + 'if account != caller {', + ' assert(self.erc1155.is_approved_for_all(account, caller), ERC1155Component::Errors::UNAUTHORIZED)', + '}', + 'self.erc1155.batch_burn(account, token_ids, values);' + ] + }, + batchBurn: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, + ], + code: [ + 'self.batch_burn(account, tokenIds, values);' + ] + }, + mint: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.erc1155.mint_with_acceptance_check(account, token_id, value, data);', + ] + }, + batch_mint: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.erc1155.batch_mint_with_acceptance_check(account, token_ids, values, data);', + ] + }, + batchMint: { + args: [ + getSelfArg(), + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.batch_mint(account, tokenIds, values, data);', + ] + }, + set_base_uri: { + args: [ + getSelfArg(), + { name: 'base_uri', type: 'ByteArray' }, + ], + code: [ + 'self.erc1155.set_base_uri(base_uri);' + ] + }, + setBaseUri: { + args: [ + getSelfArg(), + { name: 'baseUri', type: 'ByteArray' }, + ], + code: [ + 'self.set_base_uri(baseUri);' + ] + }, + + // Re-implements ERC1155Impl + balance_of: { + args: [ + getSelfArg('view'), + { name: 'account', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + ], + returns: 'u256', + code: [ + 'self.erc1155.balance_of(account, token_id)' + ] + }, + balance_of_batch: { + args: [ + getSelfArg('view'), + { name: 'accounts', type: 'Span' }, + { name: 'token_ids', type: 'Span' }, + ], + returns: 'Span', + code: [ + 'self.erc1155.balance_of_batch(accounts, token_ids)' + ] + }, + safe_transfer_from: { + args: [ + getSelfArg(), + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_id', type: 'u256' }, + { name: 'value', type: 'u256' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.erc1155.safe_transfer_from(from, to, token_id, value, data)' + ] + }, + safe_batch_transfer_from: { + args: [ + getSelfArg(), + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'token_ids', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.erc1155.safe_batch_transfer_from(from, to, token_ids, values, data)' + ] + }, + set_approval_for_all: { + args: [ + getSelfArg(), + { name: 'operator', type: 'ContractAddress' }, + { name: 'approved', type: 'bool' }, + ], + code: [ + 'self.erc1155.set_approval_for_all(operator, approved)' + ] + }, + is_approved_for_all: { + args: [ + getSelfArg('view'), + { name: 'owner', type: 'ContractAddress' }, + { name: 'operator', type: 'ContractAddress' }, + ], + returns: 'bool', + code: [ + 'self.erc1155.is_approved_for_all(owner, operator)' + ] + }, + + // Re-implements ERC1155CamelImpl + + balanceOf: { + args: [ + getSelfArg('view'), + { name: 'account', type: 'ContractAddress' }, + { name: 'tokenId', type: 'u256' }, + ], + returns: 'u256', + code: [ + 'self.balance_of(account, tokenId)' + ] + }, + balanceOfBatch: { + args: [ + getSelfArg('view'), + { name: 'accounts', type: 'Span' }, + { name: 'tokenIds', type: 'Span' }, + ], + returns: 'Span', + code: [ + 'self.balance_of_batch(accounts, tokenIds)' + ] + }, + safeTransferFrom: { + args: [ + getSelfArg(), + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'tokenId', type: 'u256' }, + { name: 'value', type: 'u256' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.safe_transfer_from(from, to, tokenId, value, data)' + ] + }, + safeBatchTransferFrom: { + args: [ + getSelfArg(), + { name: 'from', type: 'ContractAddress' }, + { name: 'to', type: 'ContractAddress' }, + { name: 'tokenIds', type: 'Span' }, + { name: 'values', type: 'Span' }, + { name: 'data', type: 'Span' }, + ], + code: [ + 'self.safe_batch_transfer_from(from, to, tokenIds, values, data)' + ] + }, + setApprovalForAll: { + args: [ + getSelfArg(), + { name: 'operator', type: 'ContractAddress' }, + { name: 'approved', type: 'bool' }, + ], + code: [ + 'self.set_approval_for_all(operator, approved)' + ] + }, + isApprovedForAll: { + args: [ + getSelfArg('view'), + { name: 'owner', type: 'ContractAddress' }, + { name: 'operator', type: 'ContractAddress' }, + ], + returns: 'bool', + code: [ + 'self.is_approved_for_all(owner, operator)' + ] + }, +}); diff --git a/packages/core-cairo/src/erc20.test.ts.md b/packages/core-cairo/src/erc20.test.ts.md index 616174577..658ee3cb6 100644 --- a/packages/core-cairo/src/erc20.test.ts.md +++ b/packages/core-cairo/src/erc20.test.ts.md @@ -9,7 +9,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -18,7 +18,7 @@ Generated by [AVA](https://avajs.dev). component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ ␊ @@ -47,7 +47,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -57,7 +57,7 @@ Generated by [AVA](https://avajs.dev). component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ ␊ @@ -96,14 +96,14 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ - use openzeppelin::security::pausable::PausableComponent;␊ - use openzeppelin::access::ownable::OwnableComponent;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ @@ -199,7 +199,6 @@ Generated by [AVA](https://avajs.dev). recipient: ContractAddress,␊ amount: u256,␊ ) -> bool {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(sender, recipient, amount)␊ }␊ }␊ @@ -227,18 +226,18 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc20::ERC20Component;␊ - use openzeppelin::token::erc20::interface;␊ - use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ - use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::token::erc20::interface;␊ use starknet::ContractAddress;␊ use super::{PAUSER_ROLE};␊ ␊ @@ -343,7 +342,6 @@ Generated by [AVA](https://avajs.dev). recipient: ContractAddress,␊ amount: u256,␊ ) -> bool {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(sender, recipient, amount)␊ }␊ }␊ @@ -371,14 +369,14 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ - use openzeppelin::security::pausable::PausableComponent;␊ - use openzeppelin::access::ownable::OwnableComponent;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ ␊ @@ -475,7 +473,6 @@ Generated by [AVA](https://avajs.dev). recipient: ContractAddress,␊ amount: u256,␊ ) -> bool {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(sender, recipient, amount)␊ }␊ }␊ @@ -510,7 +507,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -520,7 +517,7 @@ Generated by [AVA](https://avajs.dev). component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ ␊ @@ -551,7 +548,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -560,7 +557,7 @@ Generated by [AVA](https://avajs.dev). component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ ␊ @@ -589,19 +586,19 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::token::erc20::ERC20Component;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ #[abi(embed_v0)]␊ impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ @@ -648,16 +645,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ - use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc20::ERC20Component;␊ use starknet::ContractAddress;␊ use super::{MINTER_ROLE};␊ ␊ @@ -666,7 +663,7 @@ Generated by [AVA](https://avajs.dev). component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ ␊ #[abi(embed_v0)]␊ - impl ERC20MixinImpl = ERC20Component::ERC20ABI;␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ #[abi(embed_v0)]␊ impl AccessControlMixinImpl = AccessControlComponent::AccessControlMixinImpl;␊ ␊ @@ -720,19 +717,19 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ - use openzeppelin::security::pausable::PausableComponent;␊ - use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::upgrades::UpgradeableComponent;␊ use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ - use starknet::ClassHash;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ @@ -835,7 +832,6 @@ Generated by [AVA](https://avajs.dev). recipient: ContractAddress,␊ amount: u256,␊ ) -> bool {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(sender, recipient, amount)␊ }␊ }␊ @@ -885,7 +881,7 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ @@ -893,17 +889,17 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc20::ERC20Component;␊ - use openzeppelin::token::erc20::interface;␊ - use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::token::erc20::interface;␊ use openzeppelin::upgrades::UpgradeableComponent;␊ - use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ - use starknet::ClassHash;␊ use super::{PAUSER_ROLE, MINTER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ @@ -1023,7 +1019,6 @@ Generated by [AVA](https://avajs.dev). recipient: ContractAddress,␊ amount: u256,␊ ) -> bool {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(sender, recipient, amount)␊ }␊ }␊ diff --git a/packages/core-cairo/src/erc20.test.ts.snap b/packages/core-cairo/src/erc20.test.ts.snap index 83514ebd9..755c46465 100644 Binary files a/packages/core-cairo/src/erc20.test.ts.snap and b/packages/core-cairo/src/erc20.test.ts.snap differ diff --git a/packages/core-cairo/src/erc20.ts b/packages/core-cairo/src/erc20.ts index 1ea8b39b2..ef3f3cfa1 100644 --- a/packages/core-cairo/src/erc20.ts +++ b/packages/core-cairo/src/erc20.ts @@ -123,13 +123,14 @@ function addERC20MixinOrImpls(c: ContractBuilder, pausable: boolean) { 'abi(embed_v0)' ], } + // Camel case versions of the functions above. Pausable is already set above. c.addFunction(ERC20CamelOnlyImpl, functions.totalSupply); c.addFunction(ERC20CamelOnlyImpl, functions.balanceOf); - setPausable(c, ERC20CamelOnlyImpl, functions.transferFrom); + c.addFunction(ERC20CamelOnlyImpl, functions.transferFrom); } else { c.addImplToComponent(components.ERC20Component, { name: 'ERC20MixinImpl', - value: 'ERC20Component::ERC20ABI', + value: 'ERC20Component::ERC20MixinImpl', }); } } diff --git a/packages/core-cairo/src/erc721.test.ts.md b/packages/core-cairo/src/erc721.test.ts.md index f29141501..a26b9e7b4 100644 --- a/packages/core-cairo/src/erc721.test.ts.md +++ b/packages/core-cairo/src/erc721.test.ts.md @@ -9,12 +9,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ @@ -53,12 +53,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ @@ -97,12 +97,12 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ use starknet::get_caller_address;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ @@ -153,15 +153,15 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ - use openzeppelin::token::erc721::interface;␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ - use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::interface;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ @@ -282,7 +282,6 @@ Generated by [AVA](https://avajs.dev). tokenId: u256,␊ data: Span,␊ ) {␊ - self.pausable.assert_not_paused();␊ self.safe_transfer_from(from, to, tokenId, data);␊ }␊ ␊ @@ -292,12 +291,10 @@ Generated by [AVA](https://avajs.dev). to: ContractAddress,␊ tokenId: u256,␊ ) {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(from, to, tokenId);␊ }␊ ␊ fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ - self.pausable.assert_not_paused();␊ self.set_approval_for_all(operator, approved);␊ }␊ ␊ @@ -333,13 +330,13 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ - use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ @@ -413,16 +410,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ - use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ use starknet::ContractAddress;␊ use super::{MINTER_ROLE};␊ ␊ @@ -502,20 +499,20 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 `// SPDX-License-Identifier: MIT␊ - // Compatible with OpenZeppelin Contracts for Cairo ^0.10.0␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ #[starknet::contract]␊ mod MyToken {␊ - use openzeppelin::token::erc721::ERC721Component;␊ - use openzeppelin::token::erc721::interface;␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ - use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::interface;␊ use openzeppelin::upgrades::UpgradeableComponent;␊ use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ - use starknet::ClassHash;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ @@ -641,7 +638,6 @@ Generated by [AVA](https://avajs.dev). tokenId: u256,␊ data: Span,␊ ) {␊ - self.pausable.assert_not_paused();␊ self.safe_transfer_from(from, to, tokenId, data);␊ }␊ ␊ @@ -651,12 +647,10 @@ Generated by [AVA](https://avajs.dev). to: ContractAddress,␊ tokenId: u256,␊ ) {␊ - self.pausable.assert_not_paused();␊ self.transfer_from(from, to, tokenId);␊ }␊ ␊ fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ - self.pausable.assert_not_paused();␊ self.set_approval_for_all(operator, approved);␊ }␊ ␊ diff --git a/packages/core-cairo/src/erc721.test.ts.snap b/packages/core-cairo/src/erc721.test.ts.snap index d077e0cc8..26e94e20f 100644 Binary files a/packages/core-cairo/src/erc721.test.ts.snap and b/packages/core-cairo/src/erc721.test.ts.snap differ diff --git a/packages/core-cairo/src/erc721.ts b/packages/core-cairo/src/erc721.ts index 658ba5a44..ef94a0626 100644 --- a/packages/core-cairo/src/erc721.ts +++ b/packages/core-cairo/src/erc721.ts @@ -126,11 +126,12 @@ function addERC721MixinOrImpls(c: ContractBuilder, pausable: boolean) { 'abi(embed_v0)' ], } + // Camel case versions of the functions above. Pausable is already set above. c.addFunction(ERC721CamelOnlyImpl, functions.balanceOf); c.addFunction(ERC721CamelOnlyImpl, functions.ownerOf); - setPausable(c, ERC721CamelOnlyImpl, functions.safeTransferFrom); - setPausable(c, ERC721CamelOnlyImpl, functions.transferFrom); - setPausable(c, ERC721CamelOnlyImpl, functions.setApprovalForAll); + c.addFunction(ERC721CamelOnlyImpl, functions.safeTransferFrom); + c.addFunction(ERC721CamelOnlyImpl, functions.transferFrom); + c.addFunction(ERC721CamelOnlyImpl, functions.setApprovalForAll); c.addFunction(ERC721CamelOnlyImpl, functions.getApproved); c.addFunction(ERC721CamelOnlyImpl, functions.isApprovedForAll); } else { diff --git a/packages/core-cairo/src/index.ts b/packages/core-cairo/src/index.ts index 9a01f17a0..7e360866a 100644 --- a/packages/core-cairo/src/index.ts +++ b/packages/core-cairo/src/index.ts @@ -22,4 +22,4 @@ export { sanitizeKind } from './kind'; export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; -export { erc20, erc721, custom } from './api'; +export { erc20, erc721, erc1155, custom } from './api'; diff --git a/packages/core-cairo/src/kind.ts b/packages/core-cairo/src/kind.ts index 8f816e99d..30544f33d 100644 --- a/packages/core-cairo/src/kind.ts +++ b/packages/core-cairo/src/kind.ts @@ -16,6 +16,7 @@ function isKind(value: Kind | T): value is Kind { switch (value) { case 'ERC20': case 'ERC721': + case 'ERC1155': case 'Custom': return true; diff --git a/packages/core-cairo/src/print.ts b/packages/core-cairo/src/print.ts index e7dc94579..04de9ff42 100644 --- a/packages/core-cairo/src/print.ts +++ b/packages/core-cairo/src/print.ts @@ -42,33 +42,17 @@ function printSuperVariables(contract: Contract) { function printImports(contract: Contract) { const lines: string[] = []; - getCategorizedImports(contract) - .forEach(category => category.forEach(i => lines.push(`use ${i}`))); + sortImports(contract).forEach(i => lines.push(`use ${i}`)); return withSemicolons(lines); } -function getCategorizedImports(contract: Contract) { +function sortImports(contract: Contract) { const componentImports = contract.components.flatMap(c => `${c.path}::${c.name}`); - const combined = componentImports.concat(contract.standaloneImports); - - const ozTokenImports = []; - const ozImports = []; - const otherImports = []; - const superImports = []; - - for (const importStatement of combined) { - if (importStatement.startsWith('openzeppelin::token')) { - ozTokenImports.push(importStatement); - } else if (importStatement.startsWith('openzeppelin')) { - ozImports.push(importStatement); - } else { - otherImports.push(importStatement); - } - } + const allImports = componentImports.concat(contract.standaloneImports); if (contract.superVariables.length > 0) { - superImports.push(`super::{${contract.superVariables.map(v => v.name).join(', ')}}`); + allImports.push(`super::{${contract.superVariables.map(v => v.name).join(', ')}}`); } - return [ ozTokenImports, ozImports, otherImports, superImports ]; + return allImports.sort(); } function printComponentDeclarations(contract: Contract) { @@ -152,8 +136,8 @@ function printFunction(fn: ContractFunction) { for (let i = 0; i < codeLines.length; i++) { const line = codeLines[i]; const shouldEndWithSemicolon = i < codeLines.length - 1 || fn.returns === undefined; - if (line !== undefined) { - if (shouldEndWithSemicolon && !line.endsWith(';')) { + if (line !== undefined && line.length > 0) { + if (shouldEndWithSemicolon && !['{', '}', ';'].includes(line.charAt(line.length - 1))) { codeLines[i] += ';'; } else if (!shouldEndWithSemicolon && line.endsWith(';')) { codeLines[i] = line.slice(0, line.length - 1); diff --git a/packages/core-cairo/src/utils/version.ts b/packages/core-cairo/src/utils/version.ts index 1081f30f6..819f88cb3 100644 --- a/packages/core-cairo/src/utils/version.ts +++ b/packages/core-cairo/src/utils/version.ts @@ -1,10 +1,10 @@ /** * The actual latest version to use in links. */ -export const contractsVersion = '0.10.0'; +export const contractsVersion = '0.11.0'; export const contractsVersionTag = `v${contractsVersion}`; /** * Semantic version string representing of the minimum compatible version of Contracts to display in output. */ -export const compatibleContractsSemver = '^0.10.0'; +export const compatibleContractsSemver = '^0.11.0'; diff --git a/packages/ui/src/cairo/App.svelte b/packages/ui/src/cairo/App.svelte index 4d042fafa..24a9f6cba 100644 --- a/packages/ui/src/cairo/App.svelte +++ b/packages/ui/src/cairo/App.svelte @@ -5,6 +5,7 @@ import ERC20Controls from './ERC20Controls.svelte'; import ERC721Controls from './ERC721Controls.svelte'; + import ERC1155Controls from './ERC1155Controls.svelte'; import CustomControls from './CustomControls.svelte'; import CopyIcon from '../icons/CopyIcon.svelte'; import CheckIcon from '../icons/CheckIcon.svelte'; @@ -90,9 +91,9 @@ - + @@ -135,6 +136,9 @@
+
+ +
diff --git a/packages/ui/src/cairo/ERC1155Controls.svelte b/packages/ui/src/cairo/ERC1155Controls.svelte new file mode 100644 index 000000000..13c528ea1 --- /dev/null +++ b/packages/ui/src/cairo/ERC1155Controls.svelte @@ -0,0 +1,75 @@ + + +
+

Settings

+ + + +
+ +
+

Features

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