diff --git a/packages/core-cairo/CHANGELOG.md b/packages/core-cairo/CHANGELOG.md index 9f6cbdce6..5e12569e5 100644 --- a/packages/core-cairo/CHANGELOG.md +++ b/packages/core-cairo/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add Account and EthAccount. ([#387](https://github.com/OpenZeppelin/contracts-wizard/pull/387)) + - **Breaking changes**: - Use OpenZeppelin Contracts for Cairo v0.15.0. ([#378](https://github.com/OpenZeppelin/contracts-wizard/pull/378)) - Use OpenZeppelin Contracts for Cairo v0.16.0. ([#384](https://github.com/OpenZeppelin/contracts-wizard/pull/384)) diff --git a/packages/core-cairo/README.md b/packages/core-cairo/README.md index ad3bd62e7..4ad6626b9 100644 --- a/packages/core-cairo/README.md +++ b/packages/core-cairo/README.md @@ -16,6 +16,7 @@ The following contract types are supported: - `erc20` - `erc721` - `erc1155` +- `account` - `custom` Each contract type has functions/constants as defined below. @@ -33,6 +34,9 @@ function print(opts?: ERC721Options): string function print(opts?: ERC1155Options): string ``` ```js +function print(opts?: AccountOptions): string +``` +```js function print(opts?: CustomOptions): string ``` Returns a string representation of a contract generated using the provided options. If `opts` is not provided, uses [`defaults`](#defaults). @@ -48,6 +52,9 @@ const defaults: Required const defaults: Required ``` ```js +const defaults: Required +``` +```js const defaults: Required ``` The default options that are used for [`print`](#print). @@ -67,6 +74,8 @@ function isAccessControlRequired(opts: Partial): boolean ``` Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. +> Note that account contracts handle permissions differently from the other supported contracts. +Thus, the `account` contract type does not include `isAccessControlRequired`. ### Contract specific functions diff --git a/packages/core-cairo/src/account.test.ts b/packages/core-cairo/src/account.test.ts new file mode 100644 index 000000000..e6771d00e --- /dev/null +++ b/packages/core-cairo/src/account.test.ts @@ -0,0 +1,196 @@ +import test from 'ava'; + +import { buildAccount, AccountOptions } from './account'; +import { printContract } from './print'; + +import { account } from '.'; + +function testAccount(title: string, opts: Partial) { + test(title, t => { + const c = buildAccount({ + name: 'MyAccount', + type: 'stark', + ...opts, + }); + t.snapshot(printContract(c)); + }); +} + +function testEthAccount(title: string, opts: Partial) { + test(title, t => { + const c = buildAccount({ + name: 'MyAccount', + type: 'eth', + ...opts, + }); + t.snapshot(printContract(c)); + }); +} + +/** + * Tests external API for equivalence with internal API + */ +function testAPIEquivalence(title: string, opts?: AccountOptions) { + test(title, t => { + t.is(account.print(opts), printContract(buildAccount({ + name: 'MyAccount', + type: 'stark', + declare: true, + deploy: true, + pubkey: true, + ...opts, + }))); + }); +} + +testAccount('default full account, mixin + upgradeable', {}); + +testAccount('default full account, mixin + non-upgradeable', { + upgradeable: false +}); + +testAccount('explicit full account, mixin + upgradeable', { + name: 'MyAccount', + type: 'stark', + declare: true, + deploy: true, + pubkey: true, + upgradeable: true +}); + +testAccount('explicit full account, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'stark', + declare: true, + deploy: true, + pubkey: true, + upgradeable: false +}); + +testAccount('basic account, upgradeable', { + declare: false, + deploy: false, + pubkey: false +}); + +testAccount('basic account, non-upgradeable', { + declare: false, + deploy: false, + pubkey: false, + upgradeable: false +}); + +testAccount('account declarer', { + deploy: false, + pubkey: false +}); + +testAccount('account deployable', { + declare: false, + pubkey: false +}); + +testAccount('account public key', { + declare: false, + deploy: false, +}); + +testAccount('account declarer, deployable', { + pubkey: false +}); + +testAccount('account declarer, public key', { + deploy: false +}); + +testAccount('account deployable, public key', { + declare: false +}); + +testEthAccount('default full ethAccount, mixin + upgradeable', {}); + +testEthAccount('default full ethAccount, mixin + non-upgradeable', { + upgradeable: false +}); + +testEthAccount('explicit full ethAccount, mixin + upgradeable', { + name: 'MyAccount', + type: 'eth', + declare: true, + deploy: true, + pubkey: true, + upgradeable: true +}); + +testEthAccount('explicit full ethAccount, mixin + non-upgradeable', { + name: 'MyAccount', + type: 'eth', + declare: true, + deploy: true, + pubkey: true, + upgradeable: false +}); + +testEthAccount('basic ethAccount, upgradeable', { + declare: false, + deploy: false, + pubkey: false +}); + +testEthAccount('basic ethAccount, non-upgradeable', { + declare: false, + deploy: false, + pubkey: false, + upgradeable: false +}); + +testEthAccount('ethAccount declarer', { + deploy: false, + pubkey: false +}); + +testEthAccount('ethAccount deployable', { + declare: false, + pubkey: false +}); + +testEthAccount('ethAccount public key', { + declare: false, + deploy: false, +}); + +testEthAccount('ethAccount declarer, deployable', { + pubkey: false +}); + +testEthAccount('ethAccount declarer, public key', { + deploy: false +}); + +testEthAccount('ethAccount deployable, public key', { + declare: false +}); + +testAPIEquivalence('account API default'); + +testAPIEquivalence('account API basic', { + name: 'CustomAccount', + type: 'stark', + declare: false, + deploy: false, + pubkey: false, + upgradeable: false, +}); + +testAPIEquivalence('account API full upgradeable', { + name: 'CustomAccount', + type: 'stark', + declare: true, + deploy: true, + pubkey: true, + upgradeable: true, +}); + +test('account API assert defaults', async t => { + t.is(account.print(account.defaults), account.print()); +}); diff --git a/packages/core-cairo/src/account.test.ts.md b/packages/core-cairo/src/account.test.ts.md new file mode 100644 index 000000000..72a52e2c9 --- /dev/null +++ b/packages/core-cairo/src/account.test.ts.md @@ -0,0 +1,1491 @@ +# Snapshot report for `src/account.test.ts` + +The actual snapshot is saved in `account.test.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## default full account, mixin + upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl AccountMixinImpl = AccountComponent::AccountMixinImpl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## default full account, mixin + non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl AccountMixinImpl = AccountComponent::AccountMixinImpl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + }␊ + ` + +## explicit full account, mixin + upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl AccountMixinImpl = AccountComponent::AccountMixinImpl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## explicit full account, mixin + non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl AccountMixinImpl = AccountComponent::AccountMixinImpl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + }␊ + ` + +## basic account, upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## basic account, non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + }␊ + ` + +## account declarer + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = AccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## account deployable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = AccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## account public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = AccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## account declarer, deployable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = AccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = AccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## account declarer, public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = AccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = AccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## account deployable, public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::AccountComponent;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: AccountComponent, storage: account, event: AccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = AccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = AccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = AccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl AccountInternalImpl = AccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + account: AccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + AccountEvent: AccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: felt252) {␊ + self.account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## default full ethAccount, mixin + upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl EthAccountMixinImpl = EthAccountComponent::EthAccountMixinImpl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## default full ethAccount, mixin + non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl EthAccountMixinImpl = EthAccountComponent::EthAccountMixinImpl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + }␊ + ` + +## explicit full ethAccount, mixin + upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl EthAccountMixinImpl = EthAccountComponent::EthAccountMixinImpl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## explicit full ethAccount, mixin + non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl EthAccountMixinImpl = EthAccountComponent::EthAccountMixinImpl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + }␊ + ` + +## basic ethAccount, upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## basic ethAccount, non-upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + }␊ + ` + +## ethAccount declarer + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = EthAccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## ethAccount deployable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = EthAccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## ethAccount public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = EthAccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = EthAccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## ethAccount declarer, deployable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = EthAccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = EthAccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## ethAccount declarer, public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeclarerImpl = EthAccountComponent::DeclarerImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = EthAccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = EthAccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` + +## ethAccount deployable, public key + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Cairo ^0.16.0␊ + ␊ + #[starknet::contract(account)]␊ + mod MyAccount {␊ + use openzeppelin::account::eth_account::EthAccountComponent;␊ + use openzeppelin::account::interface::EthPublicKey;␊ + use openzeppelin::introspection::src5::SRC5Component;␊ + use openzeppelin::upgrades::UpgradeableComponent;␊ + use openzeppelin::upgrades::interface::IUpgradeable;␊ + use starknet::ClassHash;␊ + ␊ + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent);␊ + component!(path: SRC5Component, storage: src5, event: SRC5Event);␊ + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);␊ + ␊ + #[abi(embed_v0)]␊ + impl SRC6Impl = EthAccountComponent::SRC6Impl;␊ + #[abi(embed_v0)]␊ + impl SRC6CamelOnlyImpl = EthAccountComponent::SRC6CamelOnlyImpl;␊ + #[abi(embed_v0)]␊ + impl DeployableImpl = EthAccountComponent::DeployableImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyImpl = EthAccountComponent::PublicKeyImpl;␊ + #[abi(embed_v0)]␊ + impl PublicKeyCamelImpl = EthAccountComponent::PublicKeyCamelImpl;␊ + #[abi(embed_v0)]␊ + impl SRC5Impl = SRC5Component::SRC5Impl;␊ + ␊ + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl;␊ + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;␊ + ␊ + #[storage]␊ + struct Storage {␊ + #[substorage(v0)]␊ + eth_account: EthAccountComponent::Storage,␊ + #[substorage(v0)]␊ + src5: SRC5Component::Storage,␊ + #[substorage(v0)]␊ + upgradeable: UpgradeableComponent::Storage,␊ + }␊ + ␊ + #[event]␊ + #[derive(Drop, starknet::Event)]␊ + enum Event {␊ + #[flat]␊ + EthAccountEvent: EthAccountComponent::Event,␊ + #[flat]␊ + SRC5Event: SRC5Component::Event,␊ + #[flat]␊ + UpgradeableEvent: UpgradeableComponent::Event,␊ + }␊ + ␊ + #[constructor]␊ + fn constructor(ref self: ContractState, public_key: EthPublicKey) {␊ + self.eth_account.initializer(public_key);␊ + }␊ + ␊ + #[abi(embed_v0)]␊ + impl UpgradeableImpl of IUpgradeable {␊ + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {␊ + self.eth_account.assert_only_self();␊ + self.upgradeable.upgrade(new_class_hash);␊ + }␊ + }␊ + }␊ + ` diff --git a/packages/core-cairo/src/account.test.ts.snap b/packages/core-cairo/src/account.test.ts.snap new file mode 100644 index 000000000..9b606fbef Binary files /dev/null and b/packages/core-cairo/src/account.test.ts.snap differ diff --git a/packages/core-cairo/src/account.ts b/packages/core-cairo/src/account.ts new file mode 100644 index 000000000..b5768bcd5 --- /dev/null +++ b/packages/core-cairo/src/account.ts @@ -0,0 +1,185 @@ +import { Contract, ContractBuilder } from './contract'; +import { CommonOptions, withCommonDefaults } from './common-options'; +import { defaults as commonDefaults } from './common-options'; +import { setAccountUpgradeable } from './set-upgradeable'; +import { setInfo } from './set-info'; +import { defineComponents } from './utils/define-components'; +import { printContract } from './print'; +import { addSRC5Component } from './common-components'; + + +export const accountTypes = ['stark', 'eth'] as const; +export type Account = typeof accountTypes[number]; + +export const defaults: Required = { + name: 'MyAccount', + type: 'stark', + declare: true, + deploy: true, + pubkey: true, + upgradeable: commonDefaults.upgradeable, + info: commonDefaults.info +} as const; + +export function printAccount(opts: AccountOptions = defaults): string { + return printContract(buildAccount(opts)); +} + +export interface AccountOptions extends CommonOptions { + name: string; + type: Account; + declare?: boolean; + deploy?: boolean; + pubkey?: boolean; +} + +function withDefaults(opts: AccountOptions): Required { + return { + ...opts, + ...withCommonDefaults(opts), + declare: opts.declare ?? defaults.declare, + deploy: opts.deploy ?? defaults.deploy, + pubkey: opts.pubkey ?? defaults.pubkey + } +} + +export function buildAccount(opts: AccountOptions): Contract { + const isAccount = true; + const c = new ContractBuilder(opts.name, isAccount); + + const allOpts = withDefaults(opts); + + switch (allOpts.type) { + case 'stark': + c.addConstructorArgument({ name: 'public_key', type: 'felt252' }); + c.addComponent(components.AccountComponent, [{ lit: 'public_key' }], true); + break; + case 'eth': + c.addStandaloneImport('openzeppelin::account::interface::EthPublicKey;'); + c.addConstructorArgument({ name: 'public_key', type: 'EthPublicKey' }); + c.addComponent(components.EthAccountComponent, [{ lit: 'public_key' }], true); + break; + } + + if (allOpts.declare && allOpts.deploy && allOpts.pubkey) { + addAccountMixin(c, allOpts.type); + } else { + addSRC6(c, allOpts.type); + + if (allOpts.declare) { + addDeclarer(c, allOpts.type); + } + + if (allOpts.deploy) { + addDeployer(c, allOpts.type); + } + + if (allOpts.pubkey) { + addPublicKey(c, allOpts.type); + } + } + + setAccountUpgradeable(c, allOpts.upgradeable, allOpts.type); + setInfo(c, allOpts.info); + + return c; +} + +function addSRC6(c: ContractBuilder, accountType: Account) { + const [baseComponent, componentType] = getBaseCompAndCompType(accountType); + + c.addImplToComponent(componentType, { + name: 'SRC6Impl', + value: `${baseComponent}::SRC6Impl`, + }); + c.addImplToComponent(componentType, { + name: 'SRC6CamelOnlyImpl', + value: `${baseComponent}::SRC6CamelOnlyImpl`, + }); + + addSRC5Component(c); +} + +function addDeclarer(c: ContractBuilder, accountType: Account) { + const [baseComponent, componentType] = getBaseCompAndCompType(accountType); + + c.addImplToComponent(componentType, { + name: 'DeclarerImpl', + value: `${baseComponent}::DeclarerImpl`, + }); +} + +function addDeployer(c: ContractBuilder, accountType: Account) { + const [baseComponent, componentType] = getBaseCompAndCompType(accountType); + + c.addImplToComponent(componentType, { + name: 'DeployableImpl', + value: `${baseComponent}::DeployableImpl`, + }); +} + +function addPublicKey(c: ContractBuilder, accountType: Account) { + const [baseComponent, componentType] = getBaseCompAndCompType(accountType); + + c.addImplToComponent(componentType, { + name: 'PublicKeyImpl', + value: `${baseComponent}::PublicKeyImpl`, + }); + c.addImplToComponent(componentType, { + name: 'PublicKeyCamelImpl', + value: `${baseComponent}::PublicKeyCamelImpl`, + }); +} + +function addAccountMixin(c: ContractBuilder, accountType: Account) { + const accountMixinImpl = accountType === 'stark' ? 'AccountMixinImpl' : 'EthAccountMixinImpl'; + const [baseComponent, componentType] = getBaseCompAndCompType(accountType); + + c.addImplToComponent(componentType, { + name: `${accountMixinImpl}`, + value: `${baseComponent}::${accountMixinImpl}`, + }); + + c.addInterfaceFlag('ISRC5'); + addSRC5Component(c); +} + +function getBaseCompAndCompType(accountType: Account): [string, typeof componentType] { + const [baseComponent, componentType] = accountType === 'stark' ? ['AccountComponent', components.AccountComponent] : ['EthAccountComponent', components.EthAccountComponent]; + return [baseComponent, componentType]; +} + +const components = defineComponents( { + AccountComponent: { + path: 'openzeppelin::account', + substorage: { + name: 'account', + type: 'AccountComponent::Storage', + }, + event: { + name: 'AccountEvent', + type: 'AccountComponent::Event', + }, + impls: [], + internalImpl: { + name: 'AccountInternalImpl', + value: 'AccountComponent::InternalImpl', + }, + }, + EthAccountComponent: { + path: 'openzeppelin::account::eth_account', + substorage: { + name: 'eth_account', + type: 'EthAccountComponent::Storage', + }, + event: { + name: 'EthAccountEvent', + type: 'EthAccountComponent::Event', + }, + impls: [], + internalImpl: { + name: 'EthAccountInternalImpl', + value: 'EthAccountComponent::InternalImpl', + }, + }, +}); diff --git a/packages/core-cairo/src/api.ts b/packages/core-cairo/src/api.ts index 95fdf1a9f..f8b419626 100644 --- a/packages/core-cairo/src/api.ts +++ b/packages/core-cairo/src/api.ts @@ -1,15 +1,28 @@ -import type { CommonOptions } from './common-options'; +import type { CommonOptions, CommonContractOptions } from './common-options'; import { printERC20, defaults as erc20defaults, isAccessControlRequired as erc20IsAccessControlRequired, ERC20Options } from './erc20'; import { printERC721, defaults as erc721defaults, isAccessControlRequired as erc721IsAccessControlRequired, ERC721Options } from './erc721'; import { printERC1155, defaults as erc1155defaults, isAccessControlRequired as erc1155IsAccessControlRequired, ERC1155Options } from './erc1155'; +import { printAccount, defaults as accountDefaults, AccountOptions } from './account'; import { printCustom, defaults as customDefaults, isAccessControlRequired as customIsAccessControlRequired, CustomOptions } from './custom'; -export interface WizardContractAPI { +export interface WizardAccountAPI{ /** * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. */ - print: (opts?: Options) => string, - + print: (opts?: Options) => string; + + /** + * The default options that are used for `print`. + */ + defaults: Required; +} + +export interface WizardContractAPI { + /** + * Returns a string representation of a contract generated using the provided options. If opts is not provided, uses `defaults`. + */ + print: (opts?: Options) => string; + /** * The default options that are used for `print`. */ @@ -17,14 +30,15 @@ export interface WizardContractAPI { /** * Whether any of the provided options require access control to be enabled. If this returns `true`, then calling `print` with the - * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. + * same options would cause the `access` option to default to `'ownable'` if it was `undefined` or `false`. */ - isAccessControlRequired: (opts: Partial) => boolean, + isAccessControlRequired: (opts: Partial) => boolean; } export type ERC20 = WizardContractAPI; export type ERC721 = WizardContractAPI; export type ERC1155 = WizardContractAPI; +export type Account = WizardAccountAPI; export type Custom = WizardContractAPI; export const erc20: ERC20 = { @@ -42,6 +56,10 @@ export const erc1155: ERC1155 = { defaults: erc1155defaults, isAccessControlRequired: erc1155IsAccessControlRequired } +export const account: Account = { + print: printAccount, + defaults: accountDefaults, +} export const custom: Custom = { print: printCustom, defaults: customDefaults, diff --git a/packages/core-cairo/src/build-generic.ts b/packages/core-cairo/src/build-generic.ts index b15eadbe8..ef2e485b2 100644 --- a/packages/core-cairo/src/build-generic.ts +++ b/packages/core-cairo/src/build-generic.ts @@ -2,12 +2,14 @@ import { ERC20Options, buildERC20 } from './erc20'; import { ERC721Options, buildERC721 } from './erc721'; import { ERC1155Options, buildERC1155 } from './erc1155'; import { CustomOptions, buildCustom } from './custom'; +import { AccountOptions, buildAccount } from './account'; export interface KindedOptions { ERC20: { kind: 'ERC20' } & ERC20Options; ERC721: { kind: 'ERC721' } & ERC721Options; ERC1155: { kind: 'ERC1155' } & ERC1155Options; - Custom: { kind: 'Custom' } & CustomOptions; + Account: { kind: 'Account' } & AccountOptions; + Custom: { kind: 'Custom' } & CustomOptions; } export type GenericOptions = KindedOptions[keyof KindedOptions]; @@ -23,6 +25,9 @@ export function buildGeneric(opts: GenericOptions) { case 'ERC1155': return buildERC1155(opts); + case 'Account': + return buildAccount(opts); + case 'Custom': return buildCustom(opts); diff --git a/packages/core-cairo/src/common-options.ts b/packages/core-cairo/src/common-options.ts index 183087728..067ea317a 100644 --- a/packages/core-cairo/src/common-options.ts +++ b/packages/core-cairo/src/common-options.ts @@ -5,25 +5,38 @@ import { defaults as infoDefaults } from "./set-info"; import type { Upgradeable } from "./set-upgradeable"; export const defaults: Required = { - access: false, upgradeable: true, info: infoDefaults, } as const; +export const contractDefaults: Required = { + ...defaults, + access: false, +} as const; + export interface CommonOptions { - access?: Access; upgradeable?: Upgradeable; info?: Info; } +export interface CommonContractOptions extends CommonOptions { + access?: Access; +} + export function withCommonDefaults(opts: CommonOptions): Required { return { - access: opts.access ?? defaults.access, upgradeable: opts.upgradeable ?? defaults.upgradeable, info: opts.info ?? defaults.info, }; } +export function withCommonContractDefaults(opts: CommonContractOptions): Required { + return { + ...withCommonDefaults(opts), + access: opts.access ?? contractDefaults.access, + }; +} + export function getSelfArg(scope: 'external' | 'view' = 'external'): Argument { if (scope === 'view') { return { name: 'self', type: '@ContractState' }; diff --git a/packages/core-cairo/src/contract.ts b/packages/core-cairo/src/contract.ts index 0875221ea..81833f74c 100644 --- a/packages/core-cairo/src/contract.ts +++ b/packages/core-cairo/src/contract.ts @@ -3,11 +3,12 @@ import { toIdentifier } from './utils/convert-strings'; export interface Contract { license: string; name: string; + account: boolean; standaloneImports: string[]; components: Component[]; constructorCode: string[]; constructorArgs: Argument[]; - upgradeable: boolean; + upgradeable: boolean; implementedTraits: ImplementedTrait[]; superVariables: Variable[]; } @@ -84,6 +85,7 @@ export interface Argument { export class ContractBuilder implements Contract { readonly name: string; + readonly account: boolean; license: string = 'MIT'; upgradeable = false; @@ -96,8 +98,9 @@ export class ContractBuilder implements Contract { private standaloneImportsSet: Set = new Set(); private interfaceFlagsSet: Set = new Set(); - constructor(name: string) { + constructor(name: string, account: boolean = false) { this.name = toIdentifier(name, true); + this.account = account; } get components(): Component[] { diff --git a/packages/core-cairo/src/custom.ts b/packages/core-cairo/src/custom.ts index 7aabdc4d9..7f361415f 100644 --- a/packages/core-cairo/src/custom.ts +++ b/packages/core-cairo/src/custom.ts @@ -1,10 +1,10 @@ import { Contract, ContractBuilder } from './contract'; import { setAccessControl } from './set-access-control'; import { addPausable } from './add-pausable'; -import { CommonOptions, withCommonDefaults } from './common-options'; +import { CommonContractOptions, withCommonContractDefaults } from './common-options'; import { setUpgradeable } from './set-upgradeable'; import { setInfo } from './set-info'; -import { defaults as commonDefaults } from './common-options'; +import { contractDefaults as commonDefaults } from './common-options'; import { printContract } from './print'; export const defaults: Required = { @@ -19,7 +19,7 @@ export function printCustom(opts: CustomOptions = defaults): string { return printContract(buildCustom(opts)); } -export interface CustomOptions extends CommonOptions { +export interface CustomOptions extends CommonContractOptions { name: string; pausable?: boolean; } @@ -27,7 +27,7 @@ export interface CustomOptions extends CommonOptions { function withDefaults(opts: CustomOptions): Required { return { ...opts, - ...withCommonDefaults(opts), + ...withCommonContractDefaults(opts), pausable: opts.pausable ?? defaults.pausable, }; } diff --git a/packages/core-cairo/src/erc1155.ts b/packages/core-cairo/src/erc1155.ts index db32cd638..8ccacbbce 100644 --- a/packages/core-cairo/src/erc1155.ts +++ b/packages/core-cairo/src/erc1155.ts @@ -2,11 +2,11 @@ import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; import { Access, requireAccessControl, setAccessControl } from './set-access-control'; import { addPausable } from './add-pausable'; import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, getSelfArg } from './common-options'; +import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; import { setUpgradeable } from './set-upgradeable'; import { setInfo } from './set-info'; import { defineComponents } from './utils/define-components'; -import { defaults as commonDefaults } from './common-options'; +import { contractDefaults as commonDefaults } from './common-options'; import { printContract } from './print'; import { addSRC5Component } from './common-components'; import { externalTrait } from './external-trait'; @@ -28,7 +28,7 @@ export function printERC1155(opts: ERC1155Options = defaults): string { return printContract(buildERC1155(opts)); } -export interface ERC1155Options extends CommonOptions { +export interface ERC1155Options extends CommonContractOptions { name: string; baseUri: string; burnable?: boolean; @@ -40,7 +40,7 @@ export interface ERC1155Options extends CommonOptions { function withDefaults(opts: ERC1155Options): Required { return { ...opts, - ...withCommonDefaults(opts), + ...withCommonContractDefaults(opts), burnable: opts.burnable ?? defaults.burnable, pausable: opts.pausable ?? defaults.pausable, mintable: opts.mintable ?? defaults.mintable, diff --git a/packages/core-cairo/src/erc20.ts b/packages/core-cairo/src/erc20.ts index 075f99e03..690db04d9 100644 --- a/packages/core-cairo/src/erc20.ts +++ b/packages/core-cairo/src/erc20.ts @@ -2,12 +2,12 @@ import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; import { Access, requireAccessControl, setAccessControl } from './set-access-control'; import { addPausable } from './add-pausable'; import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, getSelfArg } from './common-options'; +import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; import { setUpgradeable } from './set-upgradeable'; import { setInfo } from './set-info'; import { OptionsError } from './error'; import { defineComponents } from './utils/define-components'; -import { defaults as commonDefaults } from './common-options'; +import { contractDefaults as commonDefaults } from './common-options'; import { printContract } from './print'; import { externalTrait } from './external-trait'; import { toByteArray, toFelt252 } from './utils/convert-strings'; @@ -31,7 +31,7 @@ export function printERC20(opts: ERC20Options = defaults): string { return printContract(buildERC20(opts)); } -export interface ERC20Options extends CommonOptions { +export interface ERC20Options extends CommonContractOptions { name: string; symbol: string; burnable?: boolean; @@ -46,7 +46,7 @@ export interface ERC20Options extends CommonOptions { function withDefaults(opts: ERC20Options): Required { return { ...opts, - ...withCommonDefaults(opts), + ...withCommonContractDefaults(opts), burnable: opts.burnable ?? defaults.burnable, pausable: opts.pausable ?? defaults.pausable, premint: opts.premint || defaults.premint, diff --git a/packages/core-cairo/src/erc721.ts b/packages/core-cairo/src/erc721.ts index 3e762d7fa..2b430277b 100644 --- a/packages/core-cairo/src/erc721.ts +++ b/packages/core-cairo/src/erc721.ts @@ -2,11 +2,11 @@ import { BaseImplementedTrait, Contract, ContractBuilder } from './contract'; import { Access, requireAccessControl, setAccessControl } from './set-access-control'; import { addPausable } from './add-pausable'; import { defineFunctions } from './utils/define-functions'; -import { CommonOptions, withCommonDefaults, getSelfArg } from './common-options'; +import { CommonContractOptions, withCommonContractDefaults, getSelfArg } from './common-options'; import { setUpgradeable } from './set-upgradeable'; import { setInfo } from './set-info'; import { defineComponents } from './utils/define-components'; -import { defaults as commonDefaults } from './common-options'; +import { contractDefaults as commonDefaults } from './common-options'; import { printContract } from './print'; import { addSRC5Component } from './common-components'; import { externalTrait } from './external-trait'; @@ -28,7 +28,7 @@ export function printERC721(opts: ERC721Options = defaults): string { return printContract(buildERC721(opts)); } -export interface ERC721Options extends CommonOptions { +export interface ERC721Options extends CommonContractOptions { name: string; symbol: string; baseUri?: string; @@ -40,7 +40,7 @@ export interface ERC721Options extends CommonOptions { function withDefaults(opts: ERC721Options): Required { return { ...opts, - ...withCommonDefaults(opts), + ...withCommonContractDefaults(opts), baseUri: opts.baseUri ?? defaults.baseUri, burnable: opts.burnable ?? defaults.burnable, pausable: opts.pausable ?? defaults.pausable, diff --git a/packages/core-cairo/src/generate/account.ts b/packages/core-cairo/src/generate/account.ts new file mode 100644 index 000000000..aeca5c763 --- /dev/null +++ b/packages/core-cairo/src/generate/account.ts @@ -0,0 +1,20 @@ +import { accountTypes, type AccountOptions } from '../account'; +import { infoOptions } from '../set-info'; +import { upgradeableOptions } from '../set-upgradeable'; +import { generateAlternatives } from './alternatives'; + +const booleans = [true, false]; + +const blueprint = { + name: ['MyAccount'], + type: accountTypes, + declare: booleans, + deploy: booleans, + pubkey: booleans, + upgradeable: upgradeableOptions, + info: infoOptions, +}; + +export function* generateAccountOptions(): Generator> { + yield* generateAlternatives(blueprint); +} diff --git a/packages/core-cairo/src/generate/sources.ts b/packages/core-cairo/src/generate/sources.ts index 60b98fdfb..9d99d8986 100644 --- a/packages/core-cairo/src/generate/sources.ts +++ b/packages/core-cairo/src/generate/sources.ts @@ -5,6 +5,7 @@ import crypto from 'crypto'; import { generateERC20Options } from './erc20'; import { generateERC721Options } from './erc721'; import { generateERC1155Options } from './erc1155'; +import { generateAccountOptions } from './account'; import { generateCustomOptions } from './custom'; import { buildGeneric, GenericOptions, KindedOptions } from '../build-generic'; import { printContract } from '../print'; @@ -35,6 +36,12 @@ export function* generateOptions(kind?: Kind): Generator { } } + if (!kind || kind === 'Account') { + for (const kindOpts of generateAccountOptions()) { + yield { kind: 'Account', ...kindOpts }; + } + } + if (!kind || kind === 'Custom') { for (const kindOpts of generateCustomOptions()) { yield { kind: 'Custom', ...kindOpts }; diff --git a/packages/core-cairo/src/index.ts b/packages/core-cairo/src/index.ts index 7e360866a..448e90c47 100644 --- a/packages/core-cairo/src/index.ts +++ b/packages/core-cairo/src/index.ts @@ -7,6 +7,7 @@ export { ContractBuilder } from './contract'; export { printContract } from './print'; export type { Access } from './set-access-control'; +export type { Account } from './account'; export type { Upgradeable } from './set-upgradeable'; export type { Info } from './set-info'; @@ -22,4 +23,4 @@ export { sanitizeKind } from './kind'; export { contractsVersion, contractsVersionTag, compatibleContractsSemver } from './utils/version'; -export { erc20, erc721, erc1155, custom } from './api'; +export { erc20, erc721, erc1155, account, custom } from './api'; diff --git a/packages/core-cairo/src/kind.ts b/packages/core-cairo/src/kind.ts index 30544f33d..9dcd2710a 100644 --- a/packages/core-cairo/src/kind.ts +++ b/packages/core-cairo/src/kind.ts @@ -17,6 +17,7 @@ function isKind(value: Kind | T): value is Kind { case 'ERC20': case 'ERC721': case 'ERC1155': + case 'Account': case 'Custom': return true; diff --git a/packages/core-cairo/src/print.ts b/packages/core-cairo/src/print.ts index 5d780e5cc..827919a89 100644 --- a/packages/core-cairo/src/print.ts +++ b/packages/core-cairo/src/print.ts @@ -7,6 +7,7 @@ import { getSelfArg } from './common-options'; import { compatibleContractsSemver } from './utils/version'; export function printContract(contract: Contract): string { + const contractAttribute = contract.account ? '#[starknet::contract(account)]' : '#[starknet::contract]' return formatLines( ...spaceBetween( [ @@ -15,7 +16,7 @@ export function printContract(contract: Contract): string { ], printSuperVariables(contract), [ - `#[starknet::contract]`, + `${contractAttribute}`, `mod ${contract.name} {`, spaceBetween( printImports(contract), diff --git a/packages/core-cairo/src/set-access-control.ts b/packages/core-cairo/src/set-access-control.ts index e3a7130d1..3c98afd61 100644 --- a/packages/core-cairo/src/set-access-control.ts +++ b/packages/core-cairo/src/set-access-control.ts @@ -57,7 +57,7 @@ export function requireAccessControl(c: ContractBuilder, trait: BaseImplementedT if (access === false) { access = 'ownable'; } - + setAccessControl(c, access); switch (access) { @@ -73,11 +73,11 @@ export function requireAccessControl(c: ContractBuilder, trait: BaseImplementedT c.addConstructorArgument({ name: roleOwner, type: 'ContractAddress'}); if (addedSuper) { c.addConstructorCode(`self.accesscontrol._grant_role(${roleId}, ${roleOwner})`); - } + } } c.addFunctionCodeBefore(trait, fn, `self.accesscontrol.assert_only_role(${roleId})`); - + break; } } diff --git a/packages/core-cairo/src/set-upgradeable.ts b/packages/core-cairo/src/set-upgradeable.ts index 1f2c70ed7..3a94b872b 100644 --- a/packages/core-cairo/src/set-upgradeable.ts +++ b/packages/core-cairo/src/set-upgradeable.ts @@ -3,12 +3,13 @@ import type { BaseImplementedTrait, ContractBuilder } from './contract'; import { Access, requireAccessControl } from './set-access-control'; import { defineComponents } from './utils/define-components'; import { defineFunctions } from './utils/define-functions'; +import type { Account } from './account'; export const upgradeableOptions = [false, true] as const; export type Upgradeable = typeof upgradeableOptions[number]; -export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { +function setUpgradeableBase(c: ContractBuilder, upgradeable: Upgradeable): BaseImplementedTrait | undefined { if (upgradeable === false) { return; } @@ -28,7 +29,29 @@ export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, acc ], }; c.addImplementedTrait(t); - requireAccessControl(c, t, functions.upgrade, access, 'UPGRADER', 'upgrader'); + + return t; +} + +export function setUpgradeable(c: ContractBuilder, upgradeable: Upgradeable, access: Access) { + 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) { + const trait = setUpgradeableBase(c, upgradeable); + if (trait !== undefined) { + switch (type) { + case 'stark': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.account.assert_only_self()'); + break; + case 'eth': + c.addFunctionCodeBefore(trait, functions.upgrade, 'self.eth_account.assert_only_self()'); + break; + } + } } const components = defineComponents( { diff --git a/packages/core-cairo/src/test.ts b/packages/core-cairo/src/test.ts index f3181056a..e86519798 100644 --- a/packages/core-cairo/src/test.ts +++ b/packages/core-cairo/src/test.ts @@ -26,6 +26,10 @@ test.serial('erc1155 result generated', async t => { await testGenerate(t, 'ERC1155'); }); +test.serial('account result generated', async t => { + await testGenerate(t, 'Account'); +}); + test.serial('custom result generated', async t => { await testGenerate(t, 'Custom'); }); @@ -46,6 +50,8 @@ function isAccessControlRequired(opts: GenericOptions) { return erc721.isAccessControlRequired(opts); case 'ERC1155': return erc1155.isAccessControlRequired(opts); + case 'Account': + throw new Error("Not applicable for accounts"); case 'Custom': return custom.isAccessControlRequired(opts); default: @@ -57,7 +63,7 @@ test('is access control required', async t => { for (const contract of generateSources('all')) { const regexOwnable = /(use openzeppelin::access::ownable::OwnableComponent)/gm; - if (!contract.options.access) { + if (contract.options.kind !== 'Account' && !contract.options.access) { if (isAccessControlRequired(contract.options)) { t.regex(contract.source, regexOwnable, JSON.stringify(contract.options)); } else { diff --git a/packages/ui/src/cairo/AccountControls.svelte b/packages/ui/src/cairo/AccountControls.svelte new file mode 100644 index 000000000..faf3d8295 --- /dev/null +++ b/packages/ui/src/cairo/AccountControls.svelte @@ -0,0 +1,81 @@ + + + +
+

Settings

+ + +
+ +
+

Account Type

+
+ + + +
+
+ +
+

Features

+ +
+ + + + + + +
+
+ + diff --git a/packages/ui/src/cairo/App.svelte b/packages/ui/src/cairo/App.svelte index 352c7b47c..ba1956cce 100644 --- a/packages/ui/src/cairo/App.svelte +++ b/packages/ui/src/cairo/App.svelte @@ -7,6 +7,7 @@ import ERC721Controls from './ERC721Controls.svelte'; import ERC1155Controls from './ERC1155Controls.svelte'; import CustomControls from './CustomControls.svelte'; + import AccountControls from './AccountControls.svelte'; import CopyIcon from '../icons/CopyIcon.svelte'; import CheckIcon from '../icons/CheckIcon.svelte'; import DownloadIcon from '../icons/DownloadIcon.svelte'; @@ -52,6 +53,8 @@ case 'ERC721': opts.symbol = initialOpts.symbol ?? opts.symbol; break; + case 'Account': + break; case 'ERC1155': case 'Custom': } @@ -111,6 +114,9 @@ + @@ -156,6 +162,9 @@
+
+ +
diff --git a/packages/ui/src/cairo/inject-hyperlinks.ts b/packages/ui/src/cairo/inject-hyperlinks.ts index 89e504bbb..c707141b3 100644 --- a/packages/ui/src/cairo/inject-hyperlinks.ts +++ b/packages/ui/src/cairo/inject-hyperlinks.ts @@ -24,11 +24,16 @@ export function injectHyperlinks(code: string) { return result; } +const componentMappings: { [key: string]: string } = { + 'AccountComponent': 'account', + 'UpgradeableComponent': 'upgradeable', +} as const; + function removeComponentName(libraryPathSegments: Array) { const lastItem = libraryPathSegments[libraryPathSegments.length - 1]; - if (lastItem === 'UpgradeableComponent') { - // Replace component name with 'upgradeable' - libraryPathSegments.splice(-1, 1, 'upgradeable'); + if (lastItem !== undefined && componentMappings[lastItem] !== undefined) { + // Replace component name with the name of its .cairo file + libraryPathSegments.splice(-1, 1, componentMappings[lastItem]); } else { libraryPathSegments.pop(); }