From 2bf262ce88cee2fe6abe9b9d846db77160c25393 Mon Sep 17 00:00:00 2001 From: immrsd <103599616+immrsd@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:48:59 +0100 Subject: [PATCH] ERC2981 (Royalty Info) for Cairo (#413) --- packages/core-cairo/CHANGELOG.md | 4 + packages/core-cairo/package.json | 2 +- packages/core-cairo/src/contract.ts | 25 +- packages/core-cairo/src/custom.test.ts.md | 2 +- packages/core-cairo/src/custom.test.ts.snap | Bin 1324 -> 1320 bytes packages/core-cairo/src/erc1155.test.ts | 67 +- packages/core-cairo/src/erc1155.test.ts.md | 588 ++++++++++++++++++ packages/core-cairo/src/erc1155.test.ts.snap | Bin 2488 -> 3353 bytes packages/core-cairo/src/erc1155.ts | 11 +- packages/core-cairo/src/erc20.ts | 6 +- packages/core-cairo/src/erc721.test.ts | 81 ++- packages/core-cairo/src/erc721.test.ts.md | 513 ++++++++++++++- packages/core-cairo/src/erc721.test.ts.snap | Bin 2783 -> 3491 bytes packages/core-cairo/src/erc721.ts | 8 +- packages/core-cairo/src/generate/erc1155.ts | 4 +- packages/core-cairo/src/generate/erc721.ts | 2 + packages/core-cairo/src/index.ts | 2 + packages/core-cairo/src/print.ts | 47 +- packages/core-cairo/src/set-access-control.ts | 15 +- packages/core-cairo/src/set-info.ts | 2 +- packages/core-cairo/src/set-royalty-info.ts | 130 ++++ packages/core-cairo/src/set-upgradeable.ts | 4 +- .../core-cairo/src/utils/convert-strings.ts | 43 +- packages/ui/src/cairo/App.svelte | 2 +- packages/ui/src/cairo/ERC1155Controls.svelte | 7 +- packages/ui/src/cairo/ERC721Controls.svelte | 3 + .../ui/src/cairo/RoyaltyInfoSection.svelte | 42 ++ packages/ui/src/cairo/inject-hyperlinks.ts | 2 +- packages/ui/src/main.ts | 2 +- 29 files changed, 1536 insertions(+), 78 deletions(-) create mode 100644 packages/core-cairo/src/set-royalty-info.ts create mode 100644 packages/ui/src/cairo/RoyaltyInfoSection.svelte diff --git a/packages/core-cairo/CHANGELOG.md b/packages/core-cairo/CHANGELOG.md index 9a952b838..a2417913b 100644 --- a/packages/core-cairo/CHANGELOG.md +++ b/packages/core-cairo/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.19.0 (2024-11-27) + +- Add ERC2981 (RoyaltyInfo) for ERC721 and ERC1155 ([#413](https://github.com/OpenZeppelin/contracts-wizard/pull/413)) + ## 0.18.0 (2024-11-15) - **Breaking changes**: diff --git a/packages/core-cairo/package.json b/packages/core-cairo/package.json index f54540830..0f8d88a9a 100644 --- a/packages/core-cairo/package.json +++ b/packages/core-cairo/package.json @@ -1,6 +1,6 @@ { "name": "@openzeppelin/wizard-cairo", - "version": "0.18.0", + "version": "0.19.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/contract.ts b/packages/core-cairo/src/contract.ts index fa635d00b..431b3f321 100644 --- a/packages/core-cairo/src/contract.ts +++ b/packages/core-cairo/src/contract.ts @@ -13,7 +13,7 @@ export interface Contract { superVariables: Variable[]; } -export type Value = string | number | { lit: string } | { note: string, value: Value }; +export type Value = string | number | bigint | { lit: string } | { note: string, value: Value }; export interface Component { name: string; @@ -57,6 +57,7 @@ export interface BaseImplementedTrait { } export interface ImplementedTrait extends BaseImplementedTrait { + superVariables: Variable[]; functions: ContractFunction[]; } @@ -172,6 +173,7 @@ export class ContractBuilder implements Contract { name: baseTrait.name, of: baseTrait.of, tags: [ ...baseTrait.tags ], + superVariables: [], functions: [], priority: baseTrait.priority, }; @@ -180,6 +182,27 @@ export class ContractBuilder implements Contract { } } + addSuperVariableToTrait(baseTrait: BaseImplementedTrait, newVar: Variable): boolean { + const trait = this.addImplementedTrait(baseTrait); + for (const existingVar of trait.superVariables) { + if (existingVar.name === newVar.name) { + if (existingVar.type !== newVar.type) { + throw new Error( + `Tried to add duplicate super var ${newVar.name} with different type: ${newVar.type} instead of ${existingVar.type}.` + ); + } + if (existingVar.value !== newVar.value) { + throw new Error( + `Tried to add duplicate super var ${newVar.name} with different value: ${newVar.value} instead of ${existingVar.value}.` + ); + } + return false; // No need to add, already exists + } + } + trait.superVariables.push(newVar); + return true; + } + addFunction(baseTrait: BaseImplementedTrait, fn: BaseFunction): ContractFunction { const t = this.addImplementedTrait(baseTrait); diff --git a/packages/core-cairo/src/custom.test.ts.md b/packages/core-cairo/src/custom.test.ts.md index 998b9155d..ef3f7ffbb 100644 --- a/packages/core-cairo/src/custom.test.ts.md +++ b/packages/core-cairo/src/custom.test.ts.md @@ -301,7 +301,7 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::upgrades::interface::IUpgradeable;␊ use starknet::ClassHash;␊ use starknet::ContractAddress;␊ - use super::{UPGRADER_ROLE};␊ + use super::UPGRADER_ROLE;␊ ␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ diff --git a/packages/core-cairo/src/custom.test.ts.snap b/packages/core-cairo/src/custom.test.ts.snap index 4274266201478aa0fff69fd7df7839fb31bf3443..08994387bb602233ccfe7605e3d8daec92b51c58 100644 GIT binary patch literal 1320 zcmV+@1=spPRzV$1$@$RCRc00000000B+TFq|bHV{r*G(~)CpMcq-fCD%`T_A@t-2#S_b`2y=kRNo@ zA{A(96cdq2fu!=YMGn37HMZAYdhVq!&^PGI6p|>J)K8ogcGfnWOH65I$Zt5qZ#2?7 zzZY=#O8)jcD5?Uub|b0y05Q(iqwrD?4@fTnDj5l7pDX$6JI^DEy!rd1rB9aPzYjiM z`t-v;mo_)i$hRp=zWre*FM_YIAgYrG!#Bnp%)0X<*-!9x0ad0LQ05)co8z14;>Caw!o zV$5$?ylEI8ra$84a0X26)F|qmmKdK6_xuT3lqnECaRKA5VPU8^1l0+Ml&^{GSKibJ z+ny&t%AH0c<7Nw4ZH7ekF{)Z*3+a8w$cWn(f@@&PQrU~ItnE~rE_%!`tG$|=)S{U= z9et!nn=t5s=Ui{Cr7=hcVUS55&|Av7@r75Vx$^zWg}%-la-xWWZ}cRIJp)iT_C6zl zHe2N#j7_1E+2Se-7Rw8L(AuN(P|HOLn7CJv5YPgDG7}fzxZnkR8n4wpN$WYJmxSXY% zz(s1vXUMqL6yPHXfsc{3pRL$)@zGYu%{2pMthS*Yyg?aNlmzrGh-RXyQ|*3KhL{l@ zkGYS`!=w;q#`HeRVx=AggB!<WAs9AG9+rfq@_j93=?N<280s2*RNX2BwSlZjD2N`}z1ZR<`06 z^c_2!e3qnlt)Ir=jO_T>NGGz8KW2Rqd$tv3Q$1AOT|Lx9S@agBuSQ$+78bpQMQ>rz zTQC@9=4ykSeQwfI8zrYb50FO_=JMvYoqt^btew4J-Wg!tS?pzun|bHIJur`t^^2$S z1@jh&NEL`k@Bk`aF0ZUwH1yeIJN!vtptGazj@!F?$IkKL{vJj?1nTAXOY|y+4RO${ zHeXmWFXD4>EEJah*FM3xoeVqaa3nF+7}?$XwtcpL>a=$cy5A@3tusJl4H<%~D39Mf z2zMLfljF|z>@sOC5``czwzjQ~XREz#P^0DGoE$B+2btG$sbM#Ecy2BhdjI@wfihXV z+DV0weGH>p3I!Ifn`Cy`-OkNgfKOohBunj(n&C>JoJ93b8c{Xm2~_gVdn`BdN$My& z7tkMEIm8=KR;oi<#r-RnDyc}RV6h_5DqFJCphncW6oe@!ChBIHQ(E?d4eB3Z$~ux; zIh+|RpKOd~5mg2{{(|cEi>&XSRy~jHY@9FuYn534%;S^(6F;Ju=?@o1B+TOLf+=aS eKlgV4^Yi9@`X25VZ<(j`E%SfCKKdEIF8}~QM}XD< literal 1324 zcmV+{1=IRLRzVuYSUz4Y8mU!ZT$mnkGsGO3?9DeSCmIG32x%#h!3hTmwU z_kK6v?v?!QcTiLXaP3A?@jhaltwrIbARdrz08}y(${ttp*Y}=B6nXRaCrh6##eW}t zy7c+Se=coopp&EBH*5RU1twvw;{j9Drywvo=$tk-Hc*@QL!xLm0CYoD4;_ZUeu6NB zfHKtPObOyDiF__ln^3{gFPrOMeZ9WfXuSAYDk82JD2!b#c+u!{4;|cPis)SfA%tR4 zF32TZBtxl090& zN{smpi#HA9!}LeI9L|8L?HWbB(-Pye;hsN2i!uemCoW*zF)R!fhoIU4k@7W>y~>*! z;g07CkaD|`$hg@;o;5?FdKgtLvW4`%V`Ri_3&AxoWvT4NS5~*HO&2|8nAKj*O={81 zoQ^)yrA_E}!E>%RSJN1z{V>R+59lpro%q75(p>p|0GglAEnsGiK;jAu`l+JT7PH zCUB7&@)p2d`5`6(s?E3!<5*>Qozy$`CW6 z<1zP0k54TP0iuyJYBdddY+* zL{}3e))9H00n_ACa>{W3Yq{#_Xf|Jp&4?9?enNtsOc5|9+RoFs%1IL-?C};6lcj$v zrz;&aKH{%Z{~zVYqJEgp`awJ65*P@gz)^zGJYHiLg&-WNpl`Zp@76dZxSx+tV`VFD zLEo{n$!AG=*ZOG;&d83B^>iW&`9szhu_s$$Hr0LA-PL_fltpi0`f9XAZ(-3}So9Va zy#<3&X0A5K+2fO+xgc8z}ndh=A8lNoyA_pxS4nU+XM6XSig8G zUodZhh*W`y1P`F%2M@5)fm~``)=oK|J2#pJ?Q+9thdepjWuKluA)4C z^B~+Uj8Bf+TeHigxkwa(!1$fDZ-Y9ft@gV?j+TpadbHH;JsS;@w_pe;6q#~t~#fm_yaLG=C8d2v`5T=}%s+(n2Y1s=l$bW<> z>r8ItaAvT8vN4)vR2k^_ORC#1v(9^3_dK+>alZVoRb>4$kI(v#{ET9zKV2A+FpKXC irliUK)L#P3&%68Sd%Rz~XP(mc%>Mz02JpqfF8}~+G?C>1 diff --git a/packages/core-cairo/src/erc1155.test.ts b/packages/core-cairo/src/erc1155.test.ts index 266548201..9923de2d5 100644 --- a/packages/core-cairo/src/erc1155.test.ts +++ b/packages/core-cairo/src/erc1155.test.ts @@ -3,12 +3,25 @@ import { erc1155 } from '.'; import { buildERC1155, ERC1155Options } from './erc1155'; import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; + +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; + +const allFeaturesON: Partial = { + mintable: true, + burnable: true, + pausable: true, + royaltyInfo: royaltyInfoOptions.enabledDefault, + upgradeable: true +} as const; function testERC1155(title: string, opts: Partial) { test(title, t => { const c = buildERC1155({ - name: 'MyToken', - baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + name: NAME, + baseUri: BASE_URI, ...opts, }); t.snapshot(printContract(c)); @@ -18,10 +31,10 @@ function testERC1155(title: string, opts: Partial) { /** * Tests external API for equivalence with internal API */ - function testAPIEquivalence(title: string, opts?: ERC1155Options) { +function testAPIEquivalence(title: string, opts?: ERC1155Options) { test(title, t => { t.is(erc1155.print(opts), printContract(buildERC1155({ - name: 'MyToken', + name: NAME, baseUri: '', ...opts, }))); @@ -59,33 +72,51 @@ testERC1155('mintable + roles', { access: 'roles', }); +testERC1155('royalty info disabled', { + royaltyInfo: royaltyInfoOptions.disabled +}); + +testERC1155('royalty info enabled default + ownable', { + royaltyInfo: royaltyInfoOptions.enabledDefault, + access: 'ownable' +}); + +testERC1155('royalty info enabled default + roles', { + royaltyInfo: royaltyInfoOptions.enabledDefault, + access: 'roles' +}); + +testERC1155('royalty info enabled custom + ownable', { + royaltyInfo: royaltyInfoOptions.enabledCustom, + access: 'ownable' +}); + +testERC1155('royalty info enabled custom + roles', { + royaltyInfo: royaltyInfoOptions.enabledCustom, + access: 'roles' +}); + testERC1155('full non-upgradeable', { - mintable: true, + ...allFeaturesON, access: 'roles', - burnable: true, - pausable: true, upgradeable: false, }); testERC1155('full upgradeable', { - mintable: true, + ...allFeaturesON, 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 basic', { name: CUSTOM_NAME, baseUri: BASE_URI }); testAPIEquivalence('API full upgradeable', { - name: 'CustomToken', - baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', - mintable: true, + ...allFeaturesON, + name: CUSTOM_NAME, + baseUri: BASE_URI, access: 'roles', - burnable: true, - pausable: true, upgradeable: true, }); @@ -98,6 +129,8 @@ test('API isAccessControlRequired', async t => { 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({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); + t.is(erc1155.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); + t.is(erc1155.isAccessControlRequired({ updatableUri: false }), false); t.is(erc1155.isAccessControlRequired({}), true); // updatableUri is true by default }); \ No newline at end of file diff --git a/packages/core-cairo/src/erc1155.test.ts.md b/packages/core-cairo/src/erc1155.test.ts.md index 02bd3e393..1d01fbe55 100644 --- a/packages/core-cairo/src/erc1155.test.ts.md +++ b/packages/core-cairo/src/erc1155.test.ts.md @@ -848,6 +848,558 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## royalty info disabled + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::ERC1155HooksEmptyImpl;␊ + 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;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::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 UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.ownable.assert_only_owner();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled default + ownable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::ERC1155HooksEmptyImpl;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled default + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + 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::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::ERC1155HooksEmptyImpl;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + uri_setter: ContractAddress,␊ + upgrader: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ + ␊ + 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);␊ + self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled custom + ownable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::ERC1155HooksEmptyImpl;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.ownable.initializer(owner);␊ + self.erc2981.initializer(default_royalty_receiver, 15125);␊ + }␊ + ␊ + impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig {␊ + const FEE_DENOMINATOR: u128 = 100000;␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled custom + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + 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::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc1155::ERC1155Component;␊ + use openzeppelin::token::erc1155::ERC1155HooksEmptyImpl;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ + ␊ + impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc1155: ERC1155Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC1155Event: ERC1155Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + uri_setter: ContractAddress,␊ + upgrader: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ + ) {␊ + self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ + self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 15125);␊ + ␊ + 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);␊ + self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ + }␊ + ␊ + impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig {␊ + const FEE_DENOMINATOR: u128 = 100000;␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + ## full non-upgradeable > Snapshot 1 @@ -865,6 +1417,8 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ use starknet::ContractAddress;␊ use starknet::get_caller_address;␊ @@ -874,6 +1428,7 @@ Generated by [AVA](https://avajs.dev). component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -883,10 +1438,17 @@ Generated by [AVA](https://avajs.dev). impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ #[abi(embed_v0)]␊ impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -898,6 +1460,8 @@ Generated by [AVA](https://avajs.dev). pausable: PausableComponent::Storage,␊ #[substorage(v0)]␊ accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ }␊ ␊ #[event]␊ @@ -911,6 +1475,8 @@ Generated by [AVA](https://avajs.dev). PausableEvent: PausableComponent::Event,␊ #[flat]␊ AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ }␊ ␊ #[constructor]␊ @@ -920,14 +1486,18 @@ Generated by [AVA](https://avajs.dev). pauser: ContractAddress,␊ minter: ContractAddress,␊ uri_setter: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ ) {␊ self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ ␊ 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(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ }␊ ␊ impl ERC1155HooksImpl of ERC1155Component::ERC1155HooksTrait {␊ @@ -1058,6 +1628,8 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ use openzeppelin::token::erc1155::ERC1155Component;␊ use openzeppelin::upgrades::UpgradeableComponent;␊ use openzeppelin::upgrades::interface::IUpgradeable;␊ @@ -1071,6 +1643,7 @@ Generated by [AVA](https://avajs.dev). component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);␊ component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ ␊ #[abi(embed_v0)]␊ impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl;␊ @@ -1080,11 +1653,18 @@ Generated by [AVA](https://avajs.dev). impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ #[abi(embed_v0)]␊ impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ ␊ impl ERC1155InternalImpl = ERC1155Component::InternalImpl;␊ impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ ␊ #[storage]␊ struct Storage {␊ @@ -1098,6 +1678,8 @@ Generated by [AVA](https://avajs.dev). accesscontrol: AccessControlComponent::Storage,␊ #[substorage(v0)]␊ upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ }␊ ␊ #[event]␊ @@ -1113,6 +1695,8 @@ Generated by [AVA](https://avajs.dev). AccessControlEvent: AccessControlComponent::Event,␊ #[flat]␊ UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ }␊ ␊ #[constructor]␊ @@ -1123,15 +1707,19 @@ Generated by [AVA](https://avajs.dev). minter: ContractAddress,␊ uri_setter: ContractAddress,␊ upgrader: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ ) {␊ self.erc1155.initializer("https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/");␊ self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ ␊ 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);␊ + self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ }␊ ␊ impl ERC1155HooksImpl of ERC1155Component::ERC1155HooksTrait {␊ diff --git a/packages/core-cairo/src/erc1155.test.ts.snap b/packages/core-cairo/src/erc1155.test.ts.snap index c97319dfb515842cbb4b2736bdddde9e6b4b9be5..7b320e027bac40f047d7e999fdf1f950b740a344 100644 GIT binary patch literal 3353 zcmV+!4d(JeRzV>pI-~_Pb68 zbNu3izyILYk8g?3_kMWmlkflOgUwBJc5?9i;WNu1)FTg@9YVQfT7+rnu-RJM+(dP! z=VETPZGzrdyo-)qLSGWsCALLT-JzUegL}wy7^-87Ip}v=8{3aIw$|3}f9-M1UQ@y~ z&4?1dTI)F-bl7h>uL(uptRaMu?-Asd+Io^y++UF{btX# z2bl+m#L(3`9Y#Fw@mi3^h|62AxR`e}l&(h=dE8<6f@mlzvx>+ip*%_25Z@j=&KIX( zA;}U*C4xkvp;FT7kOW04OJqe!V{w7J`(NX>RUy4L>FAeR4_*a-w0f=`H{sBFYf%9l z+RFftK%6A{GKTVuV@|$$Jfe)KMsgYH_9PvyxQtAlo0Jnqv0YegIYDlf<=UmBWh*#T zT7*EPl(6j{XTHJFS@^eT=tb%Lc9gV|KqWqLr_O0iB&K==DZwCAJKk9N<4GU~1VzgG zxY16Pm^~R%86-wz zl*dmV1XA_{0K=i7wjCAuH;t7nqYbxWD0A6mz^`*pA=Xv|Q8M{B6%TdDMN&Xnni2TWg*4{;=cW zC!}_`-FKfnf3#0)-kW;o>HeeN)PMi1ws|j_eUf6Mj-V79)Hq6*wEu!o!Z0T~XV^-5 zf|w6n!gPz1o-*gWm4=#QCf{2kv(h6-^o!a*O^Q6i^)~j1?lY?x-8hAY_WPXF7{h%T z+DtpK=MlzrhuVETXlF%2B%=&nsYLuB<%r-J-IRUok@HavIUn7QOoh%<^$GudzZO|B zB(P$#R?A0Rv0BJSV71KIYEd&sCZ?xp=L2JNWRZhhmUwlU*dUtLlqBRDNjCPpr`YR) z1vOM$o+2cXl2ed~m9l_wHc#U$ktS&%B{5H(VDgPCMe6nWBLob{(b?0}L=lo;hi2{2 zqDf93_>s{mzX(%fTWYmQ<4#?S9VKscL%cTiF7~<_N?xH;ur;L-6+c(h@^b7uFv`np zl=nhv@pHr+n|N8{{Pwqsan5ef)Hn}j4|IOo)Xy5NR^wDZJ$}~EkV$O5v%7=#L>q~i zTh=T0QmMo+jtJ zY#s%b1t(*+PDYZ%cL~$9Z&KZ?s&=pUrSeMhyQCye>3xY@CEIHFDN2*0Lh{i|4fN}l zRmVLtR)I-|xp_%ydsUf5RR)jgdLEPdS}24Wyr(g}r|i^L4DJFwsicr1^Ds%!Atv^1 zuH#P6q8S%!kj`h8FkX3%A68BGk)kBa;;<=$Ln%CdnG__|FBqm=7p89|YiU(gFo%~a zi^`Y`1F**sBAXf2L=`3ULDe?O4pz3I;FXRAo!ZfEwx{Y+2l=jWN`+(TGb`(?e)w<2 zS^ejSu#yB$UddOIrsGoOiiR+snfXXl^<>g*hTx%zy_n(BRy!1F0tY5f3`8^`SV@}5 zW=~ALZrf)xKg9XtyNZc-`(rTiz{DHM<0nZj2-gj4+l1-3goYr56qV5sgpeW#DJq*G z2q8sa2(DyEkuW=pVZw&t_@Xc4aj|H9FV3h+6rWHR6}5|dI(A*=T#}A%I!wp5U5R8>BS{acd9w;*4iom=QSH3- z^!T*-vT=~A)%#+eIe;)JCI%*gWNf;*jk(d)1DQ&ZBbUKYxMrHKFC|r6MNdO#E~Z}! zrG2F)3F=<(`^xEepplH0y-3a8WP0A8xtg2P(sBSvAr#GPAOTU0(^xC&y4d#$2!tOg z1j2vs0D%za^oZdzi}y7xhELN@qNka$4@*lRl!zM0C`pnOt1^)mCkC=%0{0Wc%sXE~UWWG>HB6)-FdVOW$oI9D-1(JUuLBTuZs3Q0mY6uoMK zQ~?Pwl%;7C(_uspBAiH(8CjflR3SSUeW@*<)}Q}6Lk)^>6jaV>m2=( zc2?9J8}S4edpXqMy7(LEREoJJAlep5v<2!Z!f6U%BwZpbXl&Qjr*o@eTof<`i(v|S z7UlU&!JWS-Ou;)~3Lw;=>`Vdp^<{Lu!BvJJg0i`|;Gseg!Aklz!l0b_YIPK8kafKC zeh}7da0Rv}!DqBEfXTA8mqR*ZyPJ8pSIh zR*{rKI;RMZIf7%37QQ_qI_5~~q1ANHgfov;f&+mwkKoLs@Ut9j44iocXC9$xo_W*@ z-llLU(n1bJQVfhx6L6vuoT!vcEH|1@8=)|1YyiKOQovLhp6TcT__h_}+u#_s0SD(Z zr;lykM;0|5)UkpnrViZd156Y|&p`AHM9-`)UjxxI5IqCYGY~zqMCT{3>O%S3Uno{O z2~g<7)E)Xfo~v{2`}vPzw*PCk_x+U7Y&V=<&k4=&okw46YuZ7GYTcoxbun({#%WzV zfX@Iv!yJ8vBKVZ9sKO_HlHn8IZ=4Go+-}o!KZNf?G23z2d94V zPJNODZr)u~6nYV$<>du#LQwli6mJA8k*BHQ+{9>o&5EHY$##IOCV{PpK8xR6FcPH| zW-b=HMWFzR1+p0Pyww?v&x!{Mg;ycNGOVof-Z*^)0ohS;diGrB0b}!5)A+gmw}%& z8u~%w==iXCRBIidYRKQ-`2y{s?Je;spVJK?9uVR&+?~EZ+?_6p@c5hJO#cxgJXR*c z14sb~(tse1rQj7HNTV#Q1_WsU+W|ou5=tw~d_a%}1Zk8oNF%0ShLr|k8pFagfUSZM zkpXrPA~GvZi<`b}BkItHQisTLSa<)da9I7jbKtN}YUgLeDJUt$fV-OaO&KdpdwIn0 znZ^5>mRta@W$qa-`L3v|@Ki~X;6`a|+l1-3h|Q8ZZ50i>5~EziRz~lVq_}q^(v-XD z7e=$4ZL3X?DR0tT z<;;&^D+e_udh$>LYdTlB0CQHZ?M;eV9`=zkT|6^2!Yzp(Na&g(%QZ+9kT^qmuQo9q zMs(lpU`~qemdF;8JO(mTC8)PTn9S*oyn14`SB0dx7@Keua{;<1>KyV2=0@NmM!%%v zrPLf7@jExBsHnsBpl5a}OT^6uH7eq*CgIqV280oHRnQ^gbeJ{?aN)(?Es*3ui@^tLF%()~T-E^3a zZF_JxODO38N(zdU|}?eAzfi)#`mQRU1H9bcNA`mlp*x3ud$eD+SH# z@}iZ~??5BPS0hcP=lz+hdE!fvhTM6S14Z*1NWc|oa77wikp@?!jRhF2+stQ+U^7)6%bNUh9!3hskqttg^&se jseq7*Ns)(;imU1aLMpBf7=%$f`1KT zz%}#l&mW5j00000000B+UCnPCw-t9Av`rMiNs&_y259H9M%R*MKmkn00ah!y-omm~ zeE@C(XTY81XeXNGOh}GoIq)Hu0`0wjKyt~or(XMK^;&SgmYk1WX|-RodV7%fgM1`^ zkL0}fdyn+bPS^A8Q}NGVsC1=A!!8l7jhN3Kguw|XJxaPBbp<1V==)Oq^mDIAB>CHi z|N7w8A8x7FZ+>^{kH7uzj|)AFi!^@vV@ApE4>jwv&Qy z)&_nL?Oh!Cr<9=|*APM|6cqUZW$zNH7!%v3LSXElGc^#5U&U`!UwlTHd{R--Ws>_M zptf{seZZ%lz?DJd7lnDnt5-M z4?W7=GuqhUe$dobUpyb>}XZMcCWHvT%W_aFD)a@O8F+G-u`4}N$g$TQm7+q?*# zy?(sSTH?Ijd%penFYWJNv^MS~yHD0^G7*esqaG&(v+%n*3tUPlq8vrPU>ZS|N9<(M?NWw0$9Ii*s_JLt7Xp5rT4Q z`OLepqH#7fL<-8Vj83F)v>FLK=D0JvJ*PrD~R3Qm=XweR>+T`?wpBSC` zgD|(YwNX1Poz&IXF?w#<>al6{iRfdLJwm5oYicVhecv$KE3xmuD6g|oK8UHsKOydW zR1}T#+dmn`Ilnzq<2>3u(D6aXI@~=v+C8uiUcK1G$f2HmwDkx*Rb!-TZ@JsJmn)@~ zaZ-K%&j+oYF_rZ)s_GQ{g81L4uJ2*oiaxgEk7Lc}3L!hY&sxVXj;z+sUgu@hS#UCD z>ttj_!hmv&f6Ps@ra8Ud*UBp?pOTt5WAax^ zn46bmj@Oh~)n)LQuIDkCkA+H@!F#%*_f*{4s=-~LCzUl)Wgcb)dekAICoR$&xUAq} z4amW zgy?2QGgCzkeNc0Zx`S05D0rn;g3g?1tL>=<%tv9MoKodjhTJVWs~`Q>a902Q5k!)} z$*XxJX*w=dsc1;^nVF9?RZk{AW=I~I*ozr0?PjMUP2j+kiGhqJ1d*hPZ1&95>vu!W z%2S*_ygi6aMo=3SwYG?>TN>LpRK}acrl%l#Bf{;=KhTx4% zDN<%7L?`oFbW@Sec423#0zQsArGNPC5nv@^b{2>l01g4lQxU) zK20l{64fi#MZ=up!XiPy{WIFL9G_dn^BRe)W+Lf+vus!4+~>-kd)YcZdj9I5^Udx~ zu2=7?dFBYhteF&;1d=P$&0Qkxz7@$dnj9MjLvhTsTwg}1w2K0x!+@}_l+wO7i$r~| z`2EJ|cc_tqmPMjwF`1sXXRhYXw6q*SQVB)p8c0A^<1}JL{eXm`f)1SS{TTN3CM;NyhMHh_~C+CIVK|; zm?^FV0$@hs*K#l;*7aH(Q|qFGLgM&DSY2uVgaR6S~zw?OcUH_A>*)rU zdO6IOmimtN8r9qq5N(Sj+9LH-;WPy>vMCWabY<5yWOM6b+7vJai(v`|E|cX+9%xgR2Zl1a)(9!9#^4f*a}ED1&n5(ds1Apy+s){h+Mb#2-&C z!x$IZoXi_gBe#^Dw2(`Y z3Oq(}NG>RdJNjb3@Z) z+m?V5o9S^M;M?YR*l6nf-AXZS(>fwF31hdK9NV=I_)JLBEs$M{(%454XV<6SfLkjU zWtbI@IZog4T3{;OVFN8psLn6-OC-fDs5n8 zvcw@I1|cy>X|G|RZL~y4jYkU%+m(r`S8n|rqJIdI?^iKEco`v=$=rts<7z}0;rzvN zoxgwpECgWd5TJtq>}u;50S|>>dB=1rvL!= C1-x_s diff --git a/packages/core-cairo/src/erc1155.ts b/packages/core-cairo/src/erc1155.ts index bfb421e6a..2aded8abd 100644 --- a/packages/core-cairo/src/erc1155.ts +++ b/packages/core-cairo/src/erc1155.ts @@ -11,6 +11,7 @@ import { printContract } from './print'; import { addSRC5Component } from './common-components'; import { externalTrait } from './external-trait'; import { toByteArray } from './utils/convert-strings'; +import { RoyaltyInfoOptions, setRoyaltyInfo, defaults as royaltyInfoDefaults } from './set-royalty-info'; export const defaults: Required = { name: 'MyToken', @@ -19,6 +20,7 @@ export const defaults: Required = { pausable: false, mintable: false, updatableUri: true, + royaltyInfo: royaltyInfoDefaults, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info @@ -35,6 +37,7 @@ export interface ERC1155Options extends CommonContractOptions { pausable?: boolean; mintable?: boolean; updatableUri?: boolean; + royaltyInfo?: RoyaltyInfoOptions; } function withDefaults(opts: ERC1155Options): Required { @@ -45,11 +48,12 @@ function withDefaults(opts: ERC1155Options): Required { pausable: opts.pausable ?? defaults.pausable, mintable: opts.mintable ?? defaults.mintable, updatableUri: opts.updatableUri ?? defaults.updatableUri, + royaltyInfo: opts.royaltyInfo ?? defaults.royaltyInfo, }; } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.updatableUri !== false || opts.upgradeable === true; + return opts.mintable === true || opts.pausable === true || opts.updatableUri !== false || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; } export function buildERC1155(opts: ERC1155Options): Contract { @@ -76,11 +80,12 @@ export function buildERC1155(opts: ERC1155Options): Contract { addSetBaseUri(c, allOpts.access); } - addHooks(c, allOpts); - setAccessControl(c, allOpts.access); setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); + setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); + + addHooks(c, allOpts); return c; } diff --git a/packages/core-cairo/src/erc20.ts b/packages/core-cairo/src/erc20.ts index 3e13b6119..6e4a349ed 100644 --- a/packages/core-cairo/src/erc20.ts +++ b/packages/core-cairo/src/erc20.ts @@ -1,4 +1,4 @@ -import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; +import { Contract, ContractBuilder } from './contract'; import { Access, requireAccessControl, setAccessControl } from './set-access-control'; import { addPausable } from './add-pausable'; import { defineFunctions } from './utils/define-functions'; @@ -10,7 +10,7 @@ import { defineComponents } from './utils/define-components'; import { contractDefaults as commonDefaults } from './common-options'; import { printContract } from './print'; import { externalTrait } from './external-trait'; -import { toByteArray, toFelt252 } from './utils/convert-strings'; +import { toByteArray, toFelt252, toUint } from './utils/convert-strings'; import { addVotesComponent } from './common-components'; @@ -193,7 +193,7 @@ function addPremint(c: ContractBuilder, amount: string) { }); } - const premintAbsolute = getInitialSupply(amount, 18); + const premintAbsolute = toUint(getInitialSupply(amount, 18), 'premint', 'u256'); c.addStandaloneImport('starknet::ContractAddress'); c.addConstructorArgument({ name:'recipient', type:'ContractAddress' }); diff --git a/packages/core-cairo/src/erc721.test.ts b/packages/core-cairo/src/erc721.test.ts index d59400092..6608368fa 100644 --- a/packages/core-cairo/src/erc721.test.ts +++ b/packages/core-cairo/src/erc721.test.ts @@ -2,25 +2,35 @@ import test from 'ava'; import { buildERC721, ERC721Options } from './erc721'; import { printContract } from './print'; +import { royaltyInfoOptions } from './set-royalty-info'; import { erc721, OptionsError } from '.'; +const NAME = 'MyToken'; +const CUSTOM_NAME = 'CustomToken'; +const SYMBOL = 'MTK'; +const CUSTOM_SYMBOL = 'CTK'; +const APP_NAME = 'MY_DAPP_NAME'; +const APP_VERSION = 'MY_DAPP_VERSION'; +const BASE_URI = 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/'; + const allFeaturesON: Partial = { mintable: true, burnable: true, pausable: true, enumerable: true, + royaltyInfo: royaltyInfoOptions.enabledDefault, votes: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: APP_NAME, + appVersion: APP_VERSION, upgradeable: true } as const; function testERC721(title: string, opts: Partial) { test(title, t => { const c = buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: NAME, + symbol: SYMBOL, ...opts, }); t.snapshot(printContract(c)); @@ -33,8 +43,8 @@ function testERC721(title: string, opts: Partial) { function testAPIEquivalence(title: string, opts?: ERC721Options) { test(title, t => { t.is(erc721.print(opts), printContract(buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: NAME, + symbol: SYMBOL, ...opts, }))); }); @@ -47,7 +57,7 @@ testERC721('basic non-upgradeable', { testERC721('basic', {}); testERC721('base uri', { - baseUri: 'https://gateway.pinata.cloud/ipfs/QmcP9hxrnC1T5ATPmq2saFeAM1ypFX9BnAswCdHB9JCjLA/', + baseUri: BASE_URI, }); testERC721('burnable', { @@ -76,6 +86,30 @@ testERC721('mintable + roles', { access: 'roles', }); +testERC721('royalty info disabled', { + royaltyInfo: royaltyInfoOptions.disabled +}); + +testERC721('royalty info enabled default + ownable', { + royaltyInfo: royaltyInfoOptions.enabledDefault, + access: 'ownable' +}); + +testERC721('royalty info enabled default + roles', { + royaltyInfo: royaltyInfoOptions.enabledDefault, + access: 'roles' +}); + +testERC721('royalty info enabled custom + ownable', { + royaltyInfo: royaltyInfoOptions.enabledCustom, + access: 'ownable' +}); + +testERC721('royalty info enabled custom + roles', { + royaltyInfo: royaltyInfoOptions.enabledCustom, + access: 'roles' +}); + testERC721('full non-upgradeable', { ...allFeaturesON, upgradeable: false, @@ -83,19 +117,19 @@ testERC721('full non-upgradeable', { testERC721('erc721 votes', { votes: true, - appName: 'MY_DAPP_NAME', + appName: APP_NAME, }); testERC721('erc721 votes, version', { votes: true, - appName: 'MY_DAPP_NAME', - appVersion: 'MY_DAPP_VERSION', + appName: APP_NAME, + appVersion: APP_VERSION, }); test('erc721 votes, no name', async t => { let error = t.throws(() => buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: NAME, + symbol: SYMBOL, votes: true, })); t.is((error as OptionsError).messages.appName, 'Application Name is required when Votes are enabled'); @@ -103,10 +137,10 @@ test('erc721 votes, no name', async t => { test('erc721 votes, no version', async t => { let error = t.throws(() => buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: NAME, + symbol: SYMBOL, votes: true, - appName: 'MY_DAPP_NAME', + appName: APP_NAME, appVersion: '' })); t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); @@ -114,10 +148,10 @@ test('erc721 votes, no version', async t => { test('erc721 votes, empty version', async t => { let error = t.throws(() => buildERC721({ - name: 'MyToken', - symbol: 'MTK', + name: NAME, + symbol: SYMBOL, votes: true, - appName: 'MY_DAPP_NAME', + appName: APP_NAME, appVersion: '', // avoids default value of v1 })); t.is((error as OptionsError).messages.appVersion, 'Application Version is required when Votes are enabled'); @@ -125,7 +159,7 @@ test('erc721 votes, empty version', async t => { testERC721('erc721 votes, non-upgradeable', { votes: true, - appName: 'MY_DAPP_NAME', + appName: APP_NAME, upgradeable: false, }); @@ -133,12 +167,12 @@ testERC721('full upgradeable', allFeaturesON); testAPIEquivalence('API default'); -testAPIEquivalence('API basic', { name: 'CustomToken', symbol: 'CTK' }); +testAPIEquivalence('API basic', { name: CUSTOM_NAME, symbol: CUSTOM_SYMBOL }); testAPIEquivalence('API full upgradeable', { ...allFeaturesON, - name: 'CustomToken', - symbol: 'CTK' + name: CUSTOM_NAME, + symbol: CUSTOM_SYMBOL }); test('API assert defaults', async t => { @@ -149,6 +183,9 @@ test('API isAccessControlRequired', async t => { t.is(erc721.isAccessControlRequired({ mintable: true }), true); t.is(erc721.isAccessControlRequired({ pausable: true }), true); t.is(erc721.isAccessControlRequired({ upgradeable: true }), true); + t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledDefault }), true); + t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.enabledCustom }), true); + t.is(erc721.isAccessControlRequired({ royaltyInfo: royaltyInfoOptions.disabled }), false); t.is(erc721.isAccessControlRequired({ burnable: true }), false); t.is(erc721.isAccessControlRequired({ enumerable: true }), false); }); \ No newline at end of file diff --git a/packages/core-cairo/src/erc721.test.ts.md b/packages/core-cairo/src/erc721.test.ts.md index c781aa915..f6bbdf5ee 100644 --- a/packages/core-cairo/src/erc721.test.ts.md +++ b/packages/core-cairo/src/erc721.test.ts.md @@ -814,6 +814,477 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## royalty info disabled + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::ERC721HooksEmptyImpl;␊ + 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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled default + ownable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::ERC721HooksEmptyImpl;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.ownable.initializer(owner);␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled default + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + 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::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::ERC721HooksEmptyImpl;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + use super::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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + upgrader: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ + ) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ + ␊ + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ + self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled custom + ownable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + #[starknet::contract]␊ + mod MyToken {␊ + use openzeppelin::access::ownable::OwnableComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::ERC721HooksEmptyImpl;␊ + 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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + ownable: OwnableComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + OwnableEvent: OwnableComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.ownable.initializer(owner);␊ + self.erc2981.initializer(default_royalty_receiver, 15125);␊ + }␊ + ␊ + impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig {␊ + const FEE_DENOMINATOR: u128 = 100000;␊ + }␊ + ␊ + #[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);␊ + }␊ + }␊ + }␊ + ` + +## royalty info enabled custom + roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.19.0␊ + ␊ + 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::token::common::erc2981::ERC2981Component;␊ + use openzeppelin::token::erc721::ERC721Component;␊ + use openzeppelin::token::erc721::ERC721HooksEmptyImpl;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + use starknet::ContractAddress;␊ + use super::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);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlImpl = AccessControlComponent::AccessControlImpl;␊ + #[abi(embed_v0)]␊ + impl AccessControlCamelImpl = AccessControlComponent::AccessControlCamelImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminAccessControlImpl = ERC2981Component::ERC2981AdminAccessControlImpl;␊ + ␊ + impl ERC721InternalImpl = ERC721Component::InternalImpl;␊ + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + erc721: ERC721Component::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + accesscontrol: AccessControlComponent::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + ERC721Event: ERC721Component::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + AccessControlEvent: AccessControlComponent::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(␊ + ref self: ContractState,␊ + default_admin: ContractAddress,␊ + upgrader: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + royalty_admin: ContractAddress,␊ + ) {␊ + self.erc721.initializer("MyToken", "MTK", "");␊ + self.accesscontrol.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 15125);␊ + ␊ + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, default_admin);␊ + self.accesscontrol._grant_role(UPGRADER_ROLE, upgrader);␊ + self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin);␊ + }␊ + ␊ + impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig {␊ + const FEE_DENOMINATOR: u128 = 100000;␊ + }␊ + ␊ + #[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 @@ -828,6 +1299,8 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::governance::votes::VotesComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent;␊ use openzeppelin::utils::cryptography::nonces::NoncesComponent;␊ @@ -840,6 +1313,7 @@ Generated by [AVA](https://avajs.dev). component!(path: PausableComponent, storage: pausable, event: PausableEvent);␊ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);␊ component!(path: VotesComponent, storage: votes, event: VotesEvent);␊ ␊ @@ -852,6 +1326,12 @@ Generated by [AVA](https://avajs.dev). #[abi(embed_v0)]␊ impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl;␊ #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + #[abi(embed_v0)]␊ impl NoncesImpl = NoncesComponent::NoncesImpl;␊ #[abi(embed_v0)]␊ impl VotesImpl = VotesComponent::VotesImpl;␊ @@ -860,6 +1340,7 @@ Generated by [AVA](https://avajs.dev). impl PausableInternalImpl = PausableComponent::InternalImpl;␊ impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ impl VotesInternalImpl = VotesComponent::InternalImpl;␊ ␊ #[storage]␊ @@ -875,6 +1356,8 @@ Generated by [AVA](https://avajs.dev). #[substorage(v0)]␊ erc721_enumerable: ERC721EnumerableComponent::Storage,␊ #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + #[substorage(v0)]␊ nonces: NoncesComponent::Storage,␊ #[substorage(v0)]␊ votes: VotesComponent::Storage,␊ @@ -894,16 +1377,23 @@ Generated by [AVA](https://avajs.dev). #[flat]␊ ERC721EnumerableEvent: ERC721EnumerableComponent::Event,␊ #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + #[flat]␊ NoncesEvent: NoncesComponent::Event,␊ #[flat]␊ VotesEvent: VotesComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ self.erc721.initializer("MyToken", "MTK", "");␊ self.ownable.initializer(owner);␊ self.erc721_enumerable.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ }␊ ␊ impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait {␊ @@ -1301,6 +1791,8 @@ Generated by [AVA](https://avajs.dev). use openzeppelin::governance::votes::VotesComponent;␊ use openzeppelin::introspection::src5::SRC5Component;␊ use openzeppelin::security::pausable::PausableComponent;␊ + use openzeppelin::token::common::erc2981::DefaultConfig;␊ + use openzeppelin::token::common::erc2981::ERC2981Component;␊ use openzeppelin::token::erc721::ERC721Component;␊ use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent;␊ use openzeppelin::upgrades::UpgradeableComponent;␊ @@ -1317,6 +1809,7 @@ Generated by [AVA](https://avajs.dev). component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);␊ component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent);␊ component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event);␊ component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);␊ component!(path: VotesComponent, storage: votes, event: VotesEvent);␊ ␊ @@ -1329,6 +1822,12 @@ Generated by [AVA](https://avajs.dev). #[abi(embed_v0)]␊ impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl;␊ #[abi(embed_v0)]␊ + impl ERC2981Impl = ERC2981Component::ERC2981Impl;␊ + #[abi(embed_v0)]␊ + impl ERC2981InfoImpl = ERC2981Component::ERC2981InfoImpl;␊ + #[abi(embed_v0)]␊ + impl ERC2981AdminOwnableImpl = ERC2981Component::ERC2981AdminOwnableImpl;␊ + #[abi(embed_v0)]␊ impl NoncesImpl = NoncesComponent::NoncesImpl;␊ #[abi(embed_v0)]␊ impl VotesImpl = VotesComponent::VotesImpl;␊ @@ -1338,6 +1837,7 @@ Generated by [AVA](https://avajs.dev). impl OwnableInternalImpl = OwnableComponent::InternalImpl;␊ impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl;␊ impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + impl ERC2981InternalImpl = ERC2981Component::InternalImpl;␊ impl VotesInternalImpl = VotesComponent::InternalImpl;␊ ␊ #[storage]␊ @@ -1355,6 +1855,8 @@ Generated by [AVA](https://avajs.dev). #[substorage(v0)]␊ upgradeable: UpgradeableComponent::Storage,␊ #[substorage(v0)]␊ + erc2981: ERC2981Component::Storage,␊ + #[substorage(v0)]␊ nonces: NoncesComponent::Storage,␊ #[substorage(v0)]␊ votes: VotesComponent::Storage,␊ @@ -1376,16 +1878,23 @@ Generated by [AVA](https://avajs.dev). #[flat]␊ UpgradeableEvent: UpgradeableComponent::Event,␊ #[flat]␊ + ERC2981Event: ERC2981Component::Event,␊ + #[flat]␊ NoncesEvent: NoncesComponent::Event,␊ #[flat]␊ VotesEvent: VotesComponent::Event,␊ }␊ ␊ #[constructor]␊ - fn constructor(ref self: ContractState, owner: ContractAddress) {␊ + fn constructor(␊ + ref self: ContractState,␊ + owner: ContractAddress,␊ + default_royalty_receiver: ContractAddress,␊ + ) {␊ self.erc721.initializer("MyToken", "MTK", "");␊ self.ownable.initializer(owner);␊ self.erc721_enumerable.initializer();␊ + self.erc2981.initializer(default_royalty_receiver, 500);␊ }␊ ␊ impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait {␊ diff --git a/packages/core-cairo/src/erc721.test.ts.snap b/packages/core-cairo/src/erc721.test.ts.snap index 34aee36dbaacd8b40882c9b63ec6c49a8a8736dc..af7502f5e79eb1c07788638d1d897432ded25273 100644 GIT binary patch literal 3491 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 = { name: 'MyToken', @@ -22,6 +23,7 @@ export const defaults: Required = { mintable: false, enumerable: false, votes: false, + royaltyInfo: royaltyInfoDefaults, appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled appVersion: 'v1', access: commonDefaults.access, @@ -42,6 +44,7 @@ export interface ERC721Options extends CommonContractOptions { mintable?: boolean; enumerable?: boolean; votes?: boolean; + royaltyInfo?: RoyaltyInfoOptions; appName?: string; appVersion?: string; } @@ -55,6 +58,7 @@ function withDefaults(opts: ERC721Options): Required { pausable: opts.pausable ?? defaults.pausable, mintable: opts.mintable ?? defaults.mintable, enumerable: opts.enumerable ?? defaults.enumerable, + royaltyInfo: opts.royaltyInfo ?? defaults.royaltyInfo, votes: opts.votes ?? defaults.votes, appName: opts.appName ?? defaults.appName, appVersion: opts.appVersion ?? defaults.appVersion @@ -62,7 +66,7 @@ function withDefaults(opts: ERC721Options): Required { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable === true || opts.pausable === true || opts.upgradeable === true; + return opts.mintable === true || opts.pausable === true || opts.upgradeable === true || opts.royaltyInfo?.enabled === true; } export function buildERC721(opts: ERC721Options): Contract { @@ -92,6 +96,8 @@ export function buildERC721(opts: ERC721Options): Contract { setAccessControl(c, allOpts.access); setUpgradeable(c, allOpts.upgradeable, allOpts.access); setInfo(c, allOpts.info); + setRoyaltyInfo(c, allOpts.royaltyInfo, allOpts.access); + addHooks(c, allOpts); return c; diff --git a/packages/core-cairo/src/generate/erc1155.ts b/packages/core-cairo/src/generate/erc1155.ts index bb5b55b34..07170fcbd 100644 --- a/packages/core-cairo/src/generate/erc1155.ts +++ b/packages/core-cairo/src/generate/erc1155.ts @@ -2,6 +2,7 @@ import type { ERC1155Options } from '../erc1155'; import { accessOptions } from '../set-access-control'; import { infoOptions } from '../set-info'; import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; import { generateAlternatives } from './alternatives'; const booleans = [true, false]; @@ -13,8 +14,9 @@ const blueprint = { pausable: booleans, mintable: booleans, updatableUri: booleans, - access: accessOptions, upgradeable: upgradeableOptions, + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], + access: accessOptions, info: infoOptions, }; diff --git a/packages/core-cairo/src/generate/erc721.ts b/packages/core-cairo/src/generate/erc721.ts index 99f97a120..e78a94f15 100644 --- a/packages/core-cairo/src/generate/erc721.ts +++ b/packages/core-cairo/src/generate/erc721.ts @@ -2,6 +2,7 @@ import type { ERC721Options } from '../erc721'; import { accessOptions } from '../set-access-control'; import { infoOptions } from '../set-info'; import { upgradeableOptions } from '../set-upgradeable'; +import { royaltyInfoOptions } from '../set-royalty-info'; import { generateAlternatives } from './alternatives'; const booleans = [true, false]; @@ -17,6 +18,7 @@ const blueprint = { appVersion: ['v1'], pausable: booleans, mintable: booleans, + royaltyInfo: [royaltyInfoOptions.disabled, royaltyInfoOptions.enabledDefault, royaltyInfoOptions.enabledCustom], access: accessOptions, upgradeable: upgradeableOptions, info: infoOptions, diff --git a/packages/core-cairo/src/index.ts b/packages/core-cairo/src/index.ts index 448e90c47..7551f94aa 100644 --- a/packages/core-cairo/src/index.ts +++ b/packages/core-cairo/src/index.ts @@ -10,10 +10,12 @@ export type { Access } from './set-access-control'; export type { Account } from './account'; export type { Upgradeable } from './set-upgradeable'; export type { Info } from './set-info'; +export type { RoyaltyInfoOptions } from './set-royalty-info'; export { premintPattern } from './erc20'; export { defaults as infoDefaults } from './set-info'; +export { defaults as royaltyInfoDefaults } from './set-royalty-info'; export type { OptionsErrorMessages } from './error'; export { OptionsError } from './error'; diff --git a/packages/core-cairo/src/print.ts b/packages/core-cairo/src/print.ts index 827919a89..153521e00 100644 --- a/packages/core-cairo/src/print.ts +++ b/packages/core-cairo/src/print.ts @@ -37,26 +37,29 @@ function withSemicolons(lines: string[]): string[] { return lines.map(line => line.endsWith(';') ? line : line + ';'); } -function printSuperVariables(contract: Contract) { +function printSuperVariables(contract: Contract): string[] { return withSemicolons(contract.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`)); } -function printImports(contract: Contract) { +function printImports(contract: Contract): string[] { const lines: string[] = []; sortImports(contract).forEach(i => lines.push(`use ${i}`)); return withSemicolons(lines); } -function sortImports(contract: Contract) { +function sortImports(contract: Contract): string[] { const componentImports = contract.components.flatMap(c => `${c.path}::${c.name}`); const allImports = componentImports.concat(contract.standaloneImports); - if (contract.superVariables.length > 0) { - allImports.push(`super::{${contract.superVariables.map(v => v.name).join(', ')}}`); + const superVars = contract.superVariables; + if (superVars.length === 1) { + allImports.push(`super::${superVars[0]!.name}`); + } else if (superVars.length > 1) { + allImports.push(`super::{${superVars.map(v => v.name).join(', ')}}`); } return allImports.sort(); } -function printComponentDeclarations(contract: Contract) { +function printComponentDeclarations(contract: Contract): string[] { const lines = []; for (const component of contract.components) { lines.push(`component!(path: ${component.name}, storage: ${component.substorage.name}, event: ${component.event.name});`); @@ -64,7 +67,7 @@ function printComponentDeclarations(contract: Contract) { return lines; } -function printImpls(contract: Contract) { +function printImpls(contract: Contract): Lines[] { const externalImpls = contract.components.flatMap(c => c.impls); const internalImpls = contract.components.flatMap(c => c.internalImpl ? [c.internalImpl] : []); @@ -74,7 +77,7 @@ function printImpls(contract: Contract) { ); } -function printImpl(impl: Impl, internal = false) { +function printImpl(impl: Impl, internal = false): string[] { const lines = []; if (!internal) { lines.push('#[abi(embed_v0)]'); @@ -83,7 +86,7 @@ function printImpl(impl: Impl, internal = false) { return lines; } -function printStorage(contract: Contract) { +function printStorage(contract: Contract): (string | string[])[] { const lines = []; // storage is required regardless of whether there are components lines.push('#[storage]'); @@ -98,7 +101,7 @@ function printStorage(contract: Contract) { return lines; } -function printEvents(contract: Contract) { +function printEvents(contract: Contract): (string | string[])[] { const lines = []; if (contract.components.length > 0) { lines.push('#[event]'); @@ -115,7 +118,7 @@ function printEvents(contract: Contract) { return lines; } -function printImplementedTraits(contract: Contract) { +function printImplementedTraits(contract: Contract): Lines[] { const impls = []; // sort first by priority, then number of tags, then name @@ -133,15 +136,22 @@ function printImplementedTraits(contract: Contract) { const implLines = []; implLines.push(...trait.tags.map(t => `#[${t}]`)); implLines.push(`impl ${trait.name} of ${trait.of} {`); + + const superVars = withSemicolons( + trait.superVariables.map(v => `const ${v.name}: ${v.type} = ${v.value}`) + ); + implLines.push(superVars); + const fns = trait.functions.map(fn => printFunction(fn)); implLines.push(spaceBetween(...fns)); + implLines.push('}'); impls.push(implLines); } return spaceBetween(...impls); } -function printFunction(fn: ContractFunction) { +function printFunction(fn: ContractFunction): Lines[] { const head = `fn ${fn.name}`; const args = fn.args.map(a => printArgument(a)); @@ -191,7 +201,7 @@ function printConstructor(contract: Contract): Lines[] { } } -function hasInitializer(parent: Component) { +function hasInitializer(parent: Component): boolean { return parent.initializer !== undefined && parent.substorage?.name !== undefined; } @@ -220,6 +230,8 @@ export function printValue(value: Value): string { } else { throw new Error(`Number not representable (${value})`); } + } else if (typeof value === 'bigint') { + return `${value}` } else { return `"${value}"`; } @@ -227,7 +239,14 @@ export function printValue(value: Value): string { // generic for functions and constructors // kindedName = 'fn foo' -function printFunction2(kindedName: string, args: string[], tag: string | undefined, returns: string | undefined, returnLine: string | undefined, code: Lines[]): Lines[] { +function printFunction2( + kindedName: string, + args: string[], + tag: string | undefined, + returns: string | undefined, + returnLine: string | undefined, + code: Lines[] +): Lines[] { const fn = []; if (tag !== undefined) { diff --git a/packages/core-cairo/src/set-access-control.ts b/packages/core-cairo/src/set-access-control.ts index bdaca0f3c..328ad3377 100644 --- a/packages/core-cairo/src/set-access-control.ts +++ b/packages/core-cairo/src/set-access-control.ts @@ -3,13 +3,14 @@ import { defineComponents } from './utils/define-components'; import { addSRC5Component } from './common-components'; export const accessOptions = [false, 'ownable', 'roles'] as const; +export const DEFAULT_ACCESS_CONTROL = 'ownable'; export type Access = typeof accessOptions[number]; /** * Sets access control for the contract by adding inheritance. */ - export function setAccessControl(c: ContractBuilder, access: Access) { + export function setAccessControl(c: ContractBuilder, access: Access): void { switch (access) { case 'ownable': { c.addComponent(components.OwnableComponent, [{ lit: 'owner' }], true); @@ -53,11 +54,17 @@ export type Access = typeof accessOptions[number]; /** * Enables access control for the contract and restricts the given function with access control. */ -export function requireAccessControl(c: ContractBuilder, trait: BaseImplementedTrait, fn: BaseFunction, access: Access, roleIdPrefix: string, roleOwner: string | undefined) { +export function requireAccessControl( + c: ContractBuilder, + trait: BaseImplementedTrait, + fn: BaseFunction, + access: Access, + roleIdPrefix: string, + roleOwner: string | undefined +): void { if (access === false) { - access = 'ownable'; + access = DEFAULT_ACCESS_CONTROL; } - setAccessControl(c, access); switch (access) { diff --git a/packages/core-cairo/src/set-info.ts b/packages/core-cairo/src/set-info.ts index 09d72552a..421a15029 100644 --- a/packages/core-cairo/src/set-info.ts +++ b/packages/core-cairo/src/set-info.ts @@ -8,7 +8,7 @@ export type Info = { license?: string; } -export function setInfo(c: ContractBuilder, info: Info) { +export function setInfo(c: ContractBuilder, info: Info): void { const { license } = info; if (license) { diff --git a/packages/core-cairo/src/set-royalty-info.ts b/packages/core-cairo/src/set-royalty-info.ts new file mode 100644 index 000000000..0a7ef470f --- /dev/null +++ b/packages/core-cairo/src/set-royalty-info.ts @@ -0,0 +1,130 @@ +import type { BaseImplementedTrait, ContractBuilder } from './contract'; +import { defineComponents } from './utils/define-components'; +import { OptionsError } from "./error"; +import { toUint } from './utils/convert-strings'; +import { Access, setAccessControl, DEFAULT_ACCESS_CONTROL } from './set-access-control'; + +const DEFAULT_FEE_DENOMINATOR = BigInt(10_000); + +export const defaults: RoyaltyInfoOptions = { + enabled: false, + defaultRoyaltyFraction: '0', + feeDenominator: DEFAULT_FEE_DENOMINATOR.toString() +}; + +export const royaltyInfoOptions = { + disabled: defaults, + enabledDefault: { + enabled: true, + defaultRoyaltyFraction: '500', + feeDenominator: DEFAULT_FEE_DENOMINATOR.toString(), + }, + enabledCustom: { + enabled: true, + defaultRoyaltyFraction: '15125', + feeDenominator: '100000', + } +} + +export type RoyaltyInfoOptions = { + enabled: boolean, + defaultRoyaltyFraction: string, + feeDenominator: string, +}; + +export function setRoyaltyInfo(c: ContractBuilder, options: RoyaltyInfoOptions, access: Access): void { + if (!options.enabled) { + return; + } + if (access === false) { + access = DEFAULT_ACCESS_CONTROL; + } + setAccessControl(c, access); + + const { defaultRoyaltyFraction, feeDenominator } = getRoyaltyParameters(options); + const initParams = [ + { lit: 'default_royalty_receiver' }, + defaultRoyaltyFraction + ]; + + c.addComponent(components.ERC2981Component, initParams, true); + c.addStandaloneImport('starknet::ContractAddress'); + c.addConstructorArgument({ name: 'default_royalty_receiver', type: 'ContractAddress'}); + + switch (access) { + case 'ownable': + c.addImplToComponent(components.ERC2981Component, { + name: 'ERC2981AdminOwnableImpl', + value: `ERC2981Component::ERC2981AdminOwnableImpl`, + }); + break; + case 'roles': + c.addImplToComponent(components.ERC2981Component, { + name: 'ERC2981AdminAccessControlImpl', + value: `ERC2981Component::ERC2981AdminAccessControlImpl`, + }); + c.addConstructorArgument({ name: 'royalty_admin', type: 'ContractAddress'}); + c.addConstructorCode('self.accesscontrol._grant_role(ERC2981Component::ROYALTY_ADMIN_ROLE, royalty_admin)'); + break; + } + + if (feeDenominator === DEFAULT_FEE_DENOMINATOR) { + c.addStandaloneImport('openzeppelin::token::common::erc2981::DefaultConfig'); + } else { + const trait: BaseImplementedTrait = { + name: 'ERC2981ImmutableConfig', + of: 'ERC2981Component::ImmutableConfig', + tags: [], + }; + c.addImplementedTrait(trait); + c.addSuperVariableToTrait(trait, { + name: 'FEE_DENOMINATOR', + type: 'u128', + value: feeDenominator.toString() + }); + } +} + +function getRoyaltyParameters(opts: Required): { defaultRoyaltyFraction: bigint, feeDenominator: bigint } { + const feeDenominator = toUint(opts.feeDenominator, 'feeDenominator', 'u128'); + if (feeDenominator === BigInt(0)) { + throw new OptionsError({ + feeDenominator: 'Must be greater than 0' + }); + } + const defaultRoyaltyFraction = toUint(opts.defaultRoyaltyFraction, 'defaultRoyaltyFraction', 'u128'); + if (defaultRoyaltyFraction > feeDenominator) { + throw new OptionsError({ + defaultRoyaltyFraction: 'Cannot be greater than fee denominator' + }); + } + return { defaultRoyaltyFraction, feeDenominator }; +} + +const components = defineComponents({ + ERC2981Component: { + path: 'openzeppelin::token::common::erc2981', + substorage: { + name: 'erc2981', + type: 'ERC2981Component::Storage', + }, + event: { + name: 'ERC2981Event', + type: 'ERC2981Component::Event', + }, + impls: [ + { + name: 'ERC2981Impl', + value: 'ERC2981Component::ERC2981Impl', + }, + { + name: 'ERC2981InfoImpl', + value: 'ERC2981Component::ERC2981InfoImpl', + } + ], + internalImpl: { + name: 'ERC2981InternalImpl', + value: 'ERC2981Component::InternalImpl', + }, + }, +}); diff --git a/packages/core-cairo/src/set-upgradeable.ts b/packages/core-cairo/src/set-upgradeable.ts index 3a94b872b..a419439d8 100644 --- a/packages/core-cairo/src/set-upgradeable.ts +++ b/packages/core-cairo/src/set-upgradeable.ts @@ -33,14 +33,14 @@ function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseI return t; } -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { requireAccessControl(c, trait, functions.upgrade, access, 'UPGRADER', 'upgrader'); } } -export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account) { +export function setAccountUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, type: Account): void { const trait = setUpgradeableBase(c, upgradeable); if (trait !== undefined) { switch (type) { diff --git a/packages/core-cairo/src/utils/convert-strings.ts b/packages/core-cairo/src/utils/convert-strings.ts index fe8369bc2..75cc1758a 100644 --- a/packages/core-cairo/src/utils/convert-strings.ts +++ b/packages/core-cairo/src/utils/convert-strings.ts @@ -45,4 +45,45 @@ export function toFelt252(str: string, field: string): string { } else { return result; } -} \ No newline at end of file +} + +function maxValueOfUint(bits: number): bigint { + if (bits <= 0) { + throw new Error(`Number of bits must be positive (actual '${bits}').`) + } + if (bits % 8 !== 0) { + throw new Error(`The number of bits must be a multiple of 8 (actual '${bits}').`) + } + const bytes = bits / 8; + return BigInt('0x' + 'ff'.repeat(bytes)) +} + +const UINT_MAX_VALUES = { + 'u8': maxValueOfUint(8), + 'u16': maxValueOfUint(16), + 'u32': maxValueOfUint(32), + 'u64': maxValueOfUint(64), + 'u128': maxValueOfUint(128), + 'u256': maxValueOfUint(256) +} as const; + +export type UintType = keyof typeof UINT_MAX_VALUES; + +/** + * Validates a string value to be a valid uint and converts it to bigint + */ +export function toUint(str: string, field: string, type: UintType): bigint { + const isValidNumber = /^\d+$/.test(str); + if (!isValidNumber) { + throw new OptionsError({ + [field]: 'Not a valid number' + }); + } + const numValue = BigInt(str); + if (numValue > UINT_MAX_VALUES[type]) { + throw new OptionsError({ + [field]: `Value is greater than ${type} max value` + }); + } + return numValue; +} diff --git a/packages/ui/src/cairo/App.svelte b/packages/ui/src/cairo/App.svelte index ba1956cce..f80714492 100644 --- a/packages/ui/src/cairo/App.svelte +++ b/packages/ui/src/cairo/App.svelte @@ -160,7 +160,7 @@
- +
diff --git a/packages/ui/src/cairo/ERC1155Controls.svelte b/packages/ui/src/cairo/ERC1155Controls.svelte index 13c528ea1..a292405ee 100644 --- a/packages/ui/src/cairo/ERC1155Controls.svelte +++ b/packages/ui/src/cairo/ERC1155Controls.svelte @@ -1,11 +1,12 @@ @@ -70,6 +73,8 @@
+ + diff --git a/packages/ui/src/cairo/ERC721Controls.svelte b/packages/ui/src/cairo/ERC721Controls.svelte index 1d78ffe78..62f786016 100644 --- a/packages/ui/src/cairo/ERC721Controls.svelte +++ b/packages/ui/src/cairo/ERC721Controls.svelte @@ -6,6 +6,7 @@ import AccessControlSection from './AccessControlSection.svelte'; import UpgradeabilityField from './UpgradeabilityField.svelte'; + import RoyaltyInfoSection from './RoyaltyInfoSection.svelte'; import InfoSection from './InfoSection.svelte'; import { error } from '../error-tooltip'; @@ -110,6 +111,8 @@ + + \ No newline at end of file diff --git a/packages/ui/src/cairo/RoyaltyInfoSection.svelte b/packages/ui/src/cairo/RoyaltyInfoSection.svelte new file mode 100644 index 000000000..1b225bc08 --- /dev/null +++ b/packages/ui/src/cairo/RoyaltyInfoSection.svelte @@ -0,0 +1,42 @@ + + +
+

+ + +

+ + + + +
diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index c707141b3..0998a203e 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -33,7 +33,7 @@ function removeComponentName(libraryPathSegments: Array) { const lastItem = libraryPathSegments[libraryPathSegments.length - 1]; if (lastItem !== undefined && componentMappings[lastItem] !== undefined) { // Replace component name with the name of its .cairo file - libraryPathSegments.splice(-1, 1, componentMappings[lastItem]); + libraryPathSegments.splice(-1, 1, componentMappings[lastItem] as string); } else { libraryPathSegments.pop(); } diff --git a/packages/ui/src/main.ts b/packages/ui/src/main.ts index e2a9cba37..3cb12a50a 100644 --- a/packages/ui/src/main.ts +++ b/packages/ui/src/main.ts @@ -8,7 +8,7 @@ import UnsupportedVersion from './UnsupportedVersion.svelte'; import semver from 'semver'; import { compatibleContractsSemver as compatibleSolidityContractsSemver } from '@openzeppelin/wizard'; import { compatibleContractsSemver as compatibleCairoContractsSemver } from '@openzeppelin/wizard-cairo'; -import { InitialOptions } from './initial-options'; +import type { InitialOptions } from './initial-options'; function postResize() { const { height } = document.documentElement.getBoundingClientRect();