diff --git a/packages/core-cairo/CHANGELOG.md b/packages/core-cairo/CHANGELOG.md index b2457f7f0..96e2767b8 100644 --- a/packages/core-cairo/CHANGELOG.md +++ b/packages/core-cairo/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.11.0 (2024-04-17) + +- **Breaking changes**: + - Set `upgradeable` to `true` by default. ([#334](https://github.com/OpenZeppelin/contracts-wizard/pull/334)) + ## 0.10.2 (2024-04-03) - Use OpenZeppelin Contracts for Cairo v0.11.0. ([#351](https://github.com/OpenZeppelin/contracts-wizard/pull/351)) diff --git a/packages/core-cairo/package.json b/packages/core-cairo/package.json index c850420ee..84b968555 100644 --- a/packages/core-cairo/package.json +++ b/packages/core-cairo/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/wizard-cairo", - "version": "0.10.2", + "version": "0.11.0", "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/common-options.ts b/packages/core-cairo/src/common-options.ts index 6961816d0..183087728 100644 --- a/packages/core-cairo/src/common-options.ts +++ b/packages/core-cairo/src/common-options.ts @@ -6,7 +6,7 @@ import type { Upgradeable } from "./set-upgradeable"; export const defaults: Required = { access: false, - upgradeable: false, + upgradeable: true, info: infoDefaults, } as const; diff --git a/packages/core-cairo/src/custom.test.ts b/packages/core-cairo/src/custom.test.ts index 1b3addabf..ae5b28cf0 100644 --- a/packages/core-cairo/src/custom.test.ts +++ b/packages/core-cairo/src/custom.test.ts @@ -26,7 +26,11 @@ function testCustom(title: string, opts: Partial) { }); } -testCustom('custom', {}); +testCustom('custom non-upgradeable', { + upgradeable: false, +}); + +testCustom('custom defaults', {}); testCustom('pausable', { pausable: true, @@ -37,6 +41,7 @@ testCustom('upgradeable', { }); testCustom('access control disabled', { + upgradeable: false, access: false, }); @@ -52,6 +57,7 @@ testCustom('pausable with access control disabled', { // API should override access to true since it is required for pausable access: false, pausable: true, + upgradeable: false, }); testAPIEquivalence('custom API default'); diff --git a/packages/core-cairo/src/custom.test.ts.md b/packages/core-cairo/src/custom.test.ts.md index 9c497d1ee..c236b8320 100644 --- a/packages/core-cairo/src/custom.test.ts.md +++ b/packages/core-cairo/src/custom.test.ts.md @@ -4,7 +4,7 @@ The actual snapshot is saved in `custom.test.ts.snap`. Generated by [AVA](https://avajs.dev). -## custom +## custom non-upgradeable > Snapshot 1 @@ -19,6 +19,62 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## custom defaults + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ + ␊ + #[starknet::contract]␊ + mod MyContract {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + ## pausable > Snapshot 1 @@ -30,10 +86,14 @@ Generated by [AVA](https://avajs.dev). mod MyContract {␊ use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl PausableImpl = PausableComponent::PausableImpl;␊ @@ -42,6 +102,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -49,6 +110,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -58,6 +121,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -80,6 +145,14 @@ Generated by [AVA](https://avajs.dev). self.pausable._unpause();␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -164,19 +237,26 @@ Generated by [AVA](https://avajs.dev). #[starknet::contract]␊ mod MyContract {␊ use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -184,12 +264,22 @@ Generated by [AVA](https://avajs.dev). enum Event {␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.ownable.initializer(owner);␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -200,20 +290,28 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ + ␊ #[starknet::contract]␊ mod MyContract {␊ use openzeppelin::access::accesscontrol::AccessControlComponent;␊ use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ + use super::{UPGRADER_ROLE};␊ ␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl AccessControlMixinImpl = AccessControlComponent::AccessControlMixinImpl;␊ ␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -221,6 +319,8 @@ Generated by [AVA](https://avajs.dev). accesscontrol: AccessControlComponent::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -230,13 +330,28 @@ Generated by [AVA](https://avajs.dev). AccessControlEvent: AccessControlComponent::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, default_admin: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ self.accesscontrol.initializer();␊ ␊ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ + }␊ + ␊ + #[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/custom.test.ts.snap b/packages/core-cairo/src/custom.test.ts.snap index e9d14bcaa..3775085b1 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 index 8231e7986..266548201 100644 --- a/packages/core-cairo/src/erc1155.test.ts +++ b/packages/core-cairo/src/erc1155.test.ts @@ -28,6 +28,10 @@ function testERC1155(title: string, opts: Partial) { }); } +testERC1155('basic non-upgradeable', { + upgradeable: false, +}); + testERC1155('basic', {}); testERC1155('basic + roles', { @@ -55,6 +59,14 @@ testERC1155('mintable + roles', { access: 'roles', }); +testERC1155('full non-upgradeable', { + mintable: true, + access: 'roles', + burnable: true, + pausable: true, + upgradeable: false, +}); + testERC1155('full upgradeable', { mintable: true, access: 'roles', diff --git a/packages/core-cairo/src/erc1155.test.ts.md b/packages/core-cairo/src/erc1155.test.ts.md index 1970fb54f..cd6b38314 100644 --- a/packages/core-cairo/src/erc1155.test.ts.md +++ b/packages/core-cairo/src/erc1155.test.ts.md @@ -4,6 +4,76 @@ The actual snapshot is saved in `erc1155.test.ts.snap`. Generated by [AVA](https://avajs.dev). +## basic non-upgradeable + +> 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 > Snapshot 1 @@ -16,11 +86,15 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -29,6 +103,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -38,6 +113,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -49,6 +126,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -71,6 +150,14 @@ Generated by [AVA](https://avajs.dev). self.set_base_uri(baseUri);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -82,6 +169,7 @@ Generated by [AVA](https://avajs.dev). // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const URI_SETTER_ROLE: felt252 = selector!("URI_SETTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -89,12 +177,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{URI_SETTER_ROLE};␊ + use super::{URI_SETTER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -105,6 +197,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -114,6 +207,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -125,6 +220,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -132,12 +229,14 @@ Generated by [AVA](https://avajs.dev). ref self: ContractState,␊ default_admin: 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(URI_SETTER_ROLE, uri_setter);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ }␊ ␊ #[generate_trait]␊ @@ -154,6 +253,14 @@ Generated by [AVA](https://avajs.dev). 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);␊ + }␊ + }␊ }␊ ` @@ -166,16 +273,27 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ ␊ component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + 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 UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -183,6 +301,10 @@ Generated by [AVA](https://avajs.dev). erc1155: ERC1155Component::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -192,11 +314,24 @@ Generated by [AVA](https://avajs.dev). ERC1155Event: ERC1155Component::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState) {␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ }␊ }␊ ` @@ -213,12 +348,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ 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);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -227,6 +366,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -236,6 +376,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -247,6 +389,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -302,6 +446,14 @@ Generated by [AVA](https://avajs.dev). self.set_base_uri(baseUri);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -319,12 +471,16 @@ Generated by [AVA](https://avajs.dev). 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;␊ ␊ 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);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MetadataURIImpl = ERC1155Component::ERC1155MetadataURIImpl;␊ @@ -338,6 +494,7 @@ Generated by [AVA](https://avajs.dev). impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -349,6 +506,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -362,6 +521,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -481,6 +642,14 @@ Generated by [AVA](https://avajs.dev). self.set_base_uri(baseUri);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -496,11 +665,15 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -509,6 +682,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -518,6 +692,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -529,6 +705,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -586,6 +764,14 @@ Generated by [AVA](https://avajs.dev). self.set_base_uri(baseUri);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -598,6 +784,7 @@ Generated by [AVA](https://avajs.dev). ␊ 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 {␊ @@ -605,12 +792,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{MINTER_ROLE, URI_SETTER_ROLE};␊ + use super::{MINTER_ROLE, URI_SETTER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -621,6 +812,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -630,6 +822,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -641,6 +835,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -649,6 +845,7 @@ Generated by [AVA](https://avajs.dev). default_admin: ContractAddress,␊ minter: ContractAddress,␊ uri_setter: ContractAddress,␊ + upgrader: ContractAddress,␊ ) {␊ self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ self.accesscontrol.initializer();␊ @@ -656,6 +853,7 @@ Generated by [AVA](https://avajs.dev). 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);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ }␊ ␊ #[generate_trait]␊ @@ -707,6 +905,285 @@ Generated by [AVA](https://avajs.dev). 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);␊ + }␊ + }␊ + }␊ + ` + +## full non-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");␊ + ␊ + #[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 starknet::ContractAddress;␊ + use starknet::get_caller_address;␊ + use super::{PAUSER_ROLE, MINTER_ROLE, URI_SETTER_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);␊ + ␊ + #[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;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + minter: ContractAddress,␊ + 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(PAUSER_ROLE, pauser);␊ + self.accesscontrol._grant_role(MINTER_ROLE, minter);␊ + self.accesscontrol._grant_role(URI_SETTER_ROLE, uri_setter);␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ }␊ ` diff --git a/packages/core-cairo/src/erc1155.test.ts.snap b/packages/core-cairo/src/erc1155.test.ts.snap index a999e4803..f2e038d20 100644 Binary files a/packages/core-cairo/src/erc1155.test.ts.snap and b/packages/core-cairo/src/erc1155.test.ts.snap differ diff --git a/packages/core-cairo/src/erc20.test.ts b/packages/core-cairo/src/erc20.test.ts index 87e9776ea..9b81f13cd 100644 --- a/packages/core-cairo/src/erc20.test.ts +++ b/packages/core-cairo/src/erc20.test.ts @@ -29,6 +29,10 @@ function testAPIEquivalence(title: string, opts?: ERC20Options) { }); } +testERC20('basic erc20, non-upgradeable', { + upgradeable: false, +}); + testERC20('basic erc20', {}); testERC20('erc20 burnable', { @@ -68,6 +72,15 @@ testERC20('erc20 mintable with roles', { access: 'roles', }); +testERC20('erc20 full, non-upgradeable', { + premint: '2000', + access: 'ownable', + burnable: true, + mintable: true, + pausable: true, + upgradeable: false, +}); + testERC20('erc20 full upgradeable', { premint: '2000', access: 'ownable', diff --git a/packages/core-cairo/src/erc20.test.ts.md b/packages/core-cairo/src/erc20.test.ts.md index 658ee3cb6..393bac5e9 100644 --- a/packages/core-cairo/src/erc20.test.ts.md +++ b/packages/core-cairo/src/erc20.test.ts.md @@ -4,7 +4,7 @@ The actual snapshot is saved in `erc20.test.ts.snap`. Generated by [AVA](https://avajs.dev). -## basic erc20 +## basic erc20, non-upgradeable > Snapshot 1 @@ -42,6 +42,72 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## basic erc20 + +> 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::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc20: ERC20Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC20Event: ERC20Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc20.initializer("MyToken", "MTK");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + ## erc20 burnable > Snapshot 1 @@ -51,20 +117,35 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ #[substorage(v0)]␊ erc20: ERC20Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -72,11 +153,16 @@ Generated by [AVA](https://avajs.dev). enum Event {␊ #[flat]␊ ERC20Event: ERC20Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState) {␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.erc20.initializer("MyToken", "MTK");␊ + self.ownable.initializer(owner);␊ }␊ ␊ #[generate_trait]␊ @@ -88,6 +174,14 @@ Generated by [AVA](https://avajs.dev). self.erc20._burn(caller, value);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -104,11 +198,15 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl;␊ @@ -120,6 +218,7 @@ Generated by [AVA](https://avajs.dev). impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -129,6 +228,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -140,6 +241,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -218,6 +321,14 @@ Generated by [AVA](https://avajs.dev). self.pausable._unpause();␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -229,6 +340,7 @@ Generated by [AVA](https://avajs.dev). // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -238,13 +350,17 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{PAUSER_ROLE};␊ + use super::{PAUSER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl;␊ @@ -256,6 +372,7 @@ Generated by [AVA](https://avajs.dev). impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -267,6 +384,8 @@ Generated by [AVA](https://avajs.dev). accesscontrol: AccessControlComponent::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -280,15 +399,23 @@ Generated by [AVA](https://avajs.dev). AccessControlEvent: AccessControlComponent::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, default_admin: ContractAddress, pauser: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + pauser: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ self.erc20.initializer("MyToken", "MTK");␊ self.accesscontrol.initializer();␊ ␊ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ self.accesscontrol._grant_role(PAUSER_ROLE, pauser);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ }␊ ␊ #[abi(embed_v0)]␊ @@ -361,6 +488,14 @@ Generated by [AVA](https://avajs.dev). self.pausable._unpause();␊ }␊ }␊ + ␊ + #[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);␊ + }␊ + }␊ }␊ ` @@ -377,12 +512,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::security::pausable::PausableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ use openzeppelin::token::erc20::interface;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl;␊ @@ -394,6 +533,7 @@ Generated by [AVA](https://avajs.dev). impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -403,6 +543,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -414,6 +556,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -499,6 +643,14 @@ Generated by [AVA](https://avajs.dev). self.erc20._burn(caller, value);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -511,20 +663,34 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ #[substorage(v0)]␊ erc20: ERC20Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -532,14 +698,27 @@ Generated by [AVA](https://avajs.dev). enum Event {␊ #[flat]␊ ERC20Event: ERC20Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, recipient: ContractAddress) {␊ + fn constructor(ref self: ContractState, recipient: ContractAddress, owner: ContractAddress) {␊ self.erc20.initializer("MyToken", "MTK");␊ + self.ownable.initializer(owner);␊ ␊ self.erc20._mint(recipient, 1000000000000000000000);␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -552,19 +731,34 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ #[substorage(v0)]␊ erc20: ERC20Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -572,11 +766,24 @@ Generated by [AVA](https://avajs.dev). enum Event {␊ #[flat]␊ ERC20Event: ERC20Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState) {␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.erc20.initializer("MyToken", "MTK");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ }␊ }␊ ` @@ -592,10 +799,14 @@ Generated by [AVA](https://avajs.dev). mod MyToken {␊ use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ @@ -604,6 +815,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -611,6 +823,8 @@ Generated by [AVA](https://avajs.dev). erc20: ERC20Component::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -620,6 +834,8 @@ Generated by [AVA](https://avajs.dev). ERC20Event: ERC20Component::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -637,6 +853,14 @@ Generated by [AVA](https://avajs.dev). self.erc20._mint(recipient, amount);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -648,6 +872,7 @@ Generated by [AVA](https://avajs.dev). // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -655,12 +880,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{MINTER_ROLE};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;␊ @@ -669,6 +898,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -678,6 +908,8 @@ Generated by [AVA](https://avajs.dev). accesscontrol: AccessControlComponent::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -689,15 +921,23 @@ Generated by [AVA](https://avajs.dev). AccessControlEvent: AccessControlComponent::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, default_admin: ContractAddress, minter: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ self.erc20.initializer("MyToken", "MTK");␊ self.accesscontrol.initializer();␊ ␊ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ self.accesscontrol._grant_role(MINTER_ROLE, minter);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ }␊ ␊ #[generate_trait]␊ @@ -709,6 +949,161 @@ Generated by [AVA](https://avajs.dev). self.erc20._mint(recipient, amount);␊ }␊ }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## erc20 full, non-upgradeable + +> 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::security::pausable::PausableComponent;␊ + use openzeppelin::token::erc20::ERC20Component;␊ + use openzeppelin::token::erc20::interface;␊ + use starknet::ContractAddress;␊ + use starknet::get_caller_address;␊ + ␊ + component!(path: ERC20Component, storage: erc20, event: ERC20Event);␊ + component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC20InternalImpl = ERC20Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc20: ERC20Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC20Event: ERC20Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, recipient: ContractAddress, owner: ContractAddress) {␊ + self.erc20.initializer("MyToken", "MTK");␊ + self.ownable.initializer(owner);␊ + ␊ + self.erc20._mint(recipient, 2000000000000000000000);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC20Impl of interface::IERC20 {␊ + fn total_supply(self: @ContractState) -> u256 {␊ + self.erc20.total_supply()␊ + }␊ + ␊ + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {␊ + self.erc20.balance_of(account)␊ + }␊ + ␊ + fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 {␊ + self.erc20.allowance(owner, spender)␊ + }␊ + ␊ + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool {␊ + self.pausable.assert_not_paused();␊ + self.erc20.transfer(recipient, amount)␊ + }␊ + ␊ + fn transfer_from(␊ + ref self: ContractState,␊ + sender: ContractAddress,␊ + recipient: ContractAddress,␊ + amount: u256,␊ + ) -> bool {␊ + self.pausable.assert_not_paused();␊ + self.erc20.transfer_from(sender, recipient, amount)␊ + }␊ + ␊ + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool {␊ + self.pausable.assert_not_paused();␊ + self.erc20.approve(spender, amount)␊ + }␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC20CamelOnlyImpl of interface::IERC20CamelOnly {␊ + fn totalSupply(self: @ContractState) -> u256 {␊ + self.total_supply()␊ + }␊ + ␊ + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {␊ + self.balance_of(account)␊ + }␊ + ␊ + fn transferFrom(␊ + ref self: ContractState,␊ + sender: ContractAddress,␊ + recipient: ContractAddress,␊ + amount: u256,␊ + ) -> bool {␊ + self.transfer_from(sender, recipient, amount)␊ + }␊ + }␊ + ␊ + #[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 burn(ref self: ContractState, value: u256) {␊ + self.pausable.assert_not_paused();␊ + let caller = get_caller_address();␊ + self.erc20._burn(caller, value);␊ + }␊ + ␊ + #[external(v0)]␊ + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.assert_not_paused();␊ + self.erc20._mint(recipient, amount);␊ + }␊ + }␊ }␊ ` diff --git a/packages/core-cairo/src/erc20.test.ts.snap b/packages/core-cairo/src/erc20.test.ts.snap index 755c46465..253597796 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/erc721.test.ts b/packages/core-cairo/src/erc721.test.ts index b9cf264bc..a7ba11467 100644 --- a/packages/core-cairo/src/erc721.test.ts +++ b/packages/core-cairo/src/erc721.test.ts @@ -29,6 +29,10 @@ function testERC721(title: string, opts: Partial) { }); } +testERC721('basic non-upgradeable', { + upgradeable: false, +}); + testERC721('basic', {}); testERC721('base uri', { @@ -52,6 +56,13 @@ testERC721('mintable + roles', { access: 'roles', }); +testERC721('full non-upgradeable', { + mintable: true, + pausable: true, + burnable: true, + upgradeable: false, +}); + testERC721('full upgradeable', { mintable: true, pausable: true, diff --git a/packages/core-cairo/src/erc721.test.ts.md b/packages/core-cairo/src/erc721.test.ts.md index a26b9e7b4..cbaac769a 100644 --- a/packages/core-cairo/src/erc721.test.ts.md +++ b/packages/core-cairo/src/erc721.test.ts.md @@ -4,7 +4,7 @@ The actual snapshot is saved in `erc721.test.ts.snap`. Generated by [AVA](https://avajs.dev). -## basic +## basic non-upgradeable > Snapshot 1 @@ -48,6 +48,78 @@ 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::erc721::ERC721Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + ␊ + component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + ## base uri > Snapshot 1 @@ -57,16 +129,27 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -74,6 +157,10 @@ Generated by [AVA](https://avajs.dev). erc721: ERC721Component::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -83,11 +170,24 @@ Generated by [AVA](https://avajs.dev). ERC721Event: ERC721Component::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState) {␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.erc721.initializer("MyToken", "MTK", "https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ }␊ }␊ ` @@ -101,17 +201,28 @@ Generated by [AVA](https://avajs.dev). ␊ #[starknet::contract]␊ mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ ␊ impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -119,6 +230,10 @@ Generated by [AVA](https://avajs.dev). erc721: ERC721Component::Storage,␊ #[substorage(v0)]␊ src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -128,11 +243,16 @@ Generated by [AVA](https://avajs.dev). ERC721Event: ERC721Component::Event,␊ #[flat]␊ SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState) {␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ self.erc721.initializer("MyToken", "MTK", "");␊ + self.ownable.initializer(owner);␊ }␊ ␊ #[generate_trait]␊ @@ -145,6 +265,14 @@ Generated by [AVA](https://avajs.dev). self.erc721._burn(token_id);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -162,12 +290,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::security::pausable::PausableComponent;␊ 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;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl;␊ @@ -183,6 +315,7 @@ Generated by [AVA](https://avajs.dev). impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -194,6 +327,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -207,6 +342,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -322,6 +459,14 @@ Generated by [AVA](https://avajs.dev). self.pausable._unpause();␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -337,11 +482,15 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::ownable::OwnableComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ @@ -350,6 +499,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -359,6 +509,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -370,6 +522,8 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ @@ -402,6 +556,14 @@ Generated by [AVA](https://avajs.dev). self.safe_mint(recipient, tokenId, data);␊ }␊ }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable._upgrade(new_class_hash);␊ + }␊ + }␊ }␊ ` @@ -413,6 +575,7 @@ Generated by [AVA](https://avajs.dev). // Compatible with OpenZeppelin Contracts for Cairo ^0.11.0␊ ␊ const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");␊ + const UPGRADER_ROLE: felt252 = selector!("UPGRADER_ROLE");␊ ␊ #[starknet::contract]␊ mod MyToken {␊ @@ -420,12 +583,16 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{MINTER_ROLE};␊ + use super::{MINTER_ROLE, UPGRADER_ROLE};␊ ␊ component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ ␊ #[abi(embed_v0)]␊ impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ @@ -436,6 +603,7 @@ Generated by [AVA](https://avajs.dev). ␊ impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -445,6 +613,8 @@ Generated by [AVA](https://avajs.dev). src5: SRC5Component::Storage,␊ #[substorage(v0)]␊ accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ }␊ ␊ #[event]␊ @@ -456,15 +626,23 @@ Generated by [AVA](https://avajs.dev). SRC5Event: SRC5Component::Event,␊ #[flat]␊ AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, default_admin: ContractAddress, minter: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + minter: ContractAddress,␊ + upgrader: ContractAddress,␊ + ) {␊ self.erc721.initializer("MyToken", "MTK", "");␊ self.accesscontrol.initializer();␊ ␊ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ self.accesscontrol._grant_role(MINTER_ROLE, minter);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ }␊ ␊ #[generate_trait]␊ @@ -491,6 +669,222 @@ Generated by [AVA](https://avajs.dev). self.safe_mint(recipient, tokenId, data);␊ }␊ }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## full non-upgradeable + +> 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::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::interface;␊ + use starknet::ContractAddress;␊ + use starknet::get_caller_address;␊ + ␊ + component!(path: ERC721Component, storage: erc721, event: ERC721Event);␊ + 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 ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl;␊ + #[abi(embed_v0)]␊ + impl ERC721MetadataCamelOnly = ERC721Component::ERC721MetadataCamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + #[abi(embed_v0)]␊ + impl PausableImpl = PausableComponent::PausableImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl PausableInternalImpl = PausableComponent::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + pausable: PausableComponent::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + PausableEvent: PausableComponent::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.ownable.initializer(owner);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721Impl of interface::IERC721 {␊ + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {␊ + self.erc721.balance_of(account)␊ + }␊ + ␊ + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress {␊ + self.erc721.owner_of(token_id)␊ + }␊ + ␊ + fn safe_transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_id: u256,␊ + data: Span,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc721.safe_transfer_from(from, to, token_id, data);␊ + }␊ + ␊ + fn transfer_from(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + token_id: u256,␊ + ) {␊ + self.pausable.assert_not_paused();␊ + self.erc721.transfer_from(from, to, token_id);␊ + }␊ + ␊ + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) {␊ + self.pausable.assert_not_paused();␊ + self.erc721.approve(to, token_id);␊ + }␊ + ␊ + fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.pausable.assert_not_paused();␊ + self.erc721.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress {␊ + self.erc721.get_approved(token_id)␊ + }␊ + ␊ + fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool {␊ + self.erc721.is_approved_for_all(owner, operator)␊ + }␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721CamelOnlyImpl of interface::IERC721CamelOnly {␊ + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 {␊ + self.balance_of(account)␊ + }␊ + ␊ + fn ownerOf(self: @ContractState, tokenId: u256) -> ContractAddress {␊ + self.owner_of(tokenId)␊ + }␊ + ␊ + fn safeTransferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenId: u256,␊ + data: Span,␊ + ) {␊ + self.safe_transfer_from(from, to, tokenId, data);␊ + }␊ + ␊ + fn transferFrom(␊ + ref self: ContractState,␊ + from: ContractAddress,␊ + to: ContractAddress,␊ + tokenId: u256,␊ + ) {␊ + self.transfer_from(from, to, tokenId);␊ + }␊ + ␊ + fn setApprovalForAll(ref self: ContractState, operator: ContractAddress, approved: bool) {␊ + self.set_approval_for_all(operator, approved);␊ + }␊ + ␊ + fn getApproved(self: @ContractState, tokenId: u256) -> ContractAddress {␊ + self.get_approved(tokenId)␊ + }␊ + ␊ + 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 burn(ref self: ContractState, token_id: u256) {␊ + self.pausable.assert_not_paused();␊ + let caller = get_caller_address();␊ + assert(self.erc721._is_approved_or_owner(caller, token_id), ERC721Component::Errors::UNAUTHORIZED);␊ + self.erc721._burn(token_id);␊ + }␊ + ␊ + #[external(v0)]␊ + fn safe_mint(␊ + ref self: ContractState,␊ + recipient: ContractAddress,␊ + token_id: u256,␊ + data: Span,␊ + ) {␊ + self.ownable.assert_only_owner();␊ + self.pausable.assert_not_paused();␊ + self.erc721._safe_mint(recipient, token_id, data);␊ + }␊ + ␊ + #[external(v0)]␊ + fn safeMint(␊ + ref self: ContractState,␊ + recipient: ContractAddress,␊ + tokenId: u256,␊ + data: Span,␊ + ) {␊ + self.safe_mint(recipient, tokenId, data);␊ + }␊ + }␊ }␊ ` diff --git a/packages/core-cairo/src/erc721.test.ts.snap b/packages/core-cairo/src/erc721.test.ts.snap index 26e94e20f..97d34fc9a 100644 Binary files a/packages/core-cairo/src/erc721.test.ts.snap and b/packages/core-cairo/src/erc721.test.ts.snap differ