diff --git a/CHANGELOG.md b/CHANGELOG.md index 42b3f7fc0..c1b367890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - before_update and after_update hooks to ERC20Component (#951) - INSUFFICIENT_BALANCE and INSUFFICIENT_ALLOWANCE errors to ERC20Component (#951) - ERC20Votes component (#951) +- Preset interfaces (#964) - UDC docs (#954) - Util functions to precompute addresses (#954) @@ -21,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow testing utilities to be importable (#963) - Utilities documentation (#963) - Parameter name in `tests::utils::drop_events` (`count` -> `n_events`) (#963) +- Presets to include upgradeable functionality (#964) +- ERC20, ERC721, and ERC1155 presets include Ownable functionality (#964) ## 0.11.0 (2024-03-29) diff --git a/README.md b/README.md index c84130c3e..bcd26f9e6 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ mod MyToken { component!(path: ERC20Component, storage: erc20, event: ERC20Event); - // ERC20 Mixin + // ERC20 Mixin #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; diff --git a/docs/modules/ROOT/pages/api/account.adoc b/docs/modules/ROOT/pages/api/account.adoc index 300ba8b1d..9f95d93ba 100644 --- a/docs/modules/ROOT/pages/api/account.adoc +++ b/docs/modules/ROOT/pages/api/account.adoc @@ -504,27 +504,27 @@ Emitted when a `public_key` is removed. == Presets [.contract] -[[Account]] -=== `++Account++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/account.cairo[{github-icon},role=heading-link] +[[AccountUpgradeable]] +=== `++AccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/account.cairo[{github-icon},role=heading-link] ```javascript -use openzeppelin::presets::Account; +use openzeppelin::presets::AccountUpgradeable; ``` -Basic account contract leveraging xref:#AccountComponent[AccountComponent]. +Upgradeable account contract leveraging xref:#AccountComponent[AccountComponent]. include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{Account-class-hash} +{AccountUpgradeable-class-hash} -- [.contract-index] .Constructor -- -* xref:#Account-constructor[`++constructor(self, public_key)++`] +* xref:#AccountUpgradeable-constructor[`++constructor(self, public_key)++`] -- [.contract-index] @@ -535,15 +535,35 @@ include::../utils/_class_hashes.adoc[] * xref:#AccountComponent-Embeddable-Mixin-Impl[`++AccountMixinImpl++`] -- -[#Account-constructor-section] +[.contract-index] +.External Functions +-- +* xref:#AccountUpgradeable-upgrade[`++upgrade(self, new_class_hash)++`] +-- + +[#AccountUpgradeable-constructor-section] ==== Constructor [.contract-item] -[[Account-constructor]] +[[AccountUpgradeable-constructor]] ==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, public_key: felt252)++` [.item-kind]#constructor# Sets the account `public_key` and registers the interfaces the contract supports. +[#AccountUpgradeable-external-functions] +==== External functions + +[.contract-item] +[[AccountUpgradeable-upgrade]] +==== `[.contract-item-name]#++upgrade++#++(ref self: ContractState, new_class_hash: ClassHash)++` [.item-kind]#external# + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. + [.contract] [[EthAccountUpgradeable]] === `++EthAccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/eth_account.cairo[{github-icon},role=heading-link] @@ -552,7 +572,7 @@ Sets the account `public_key` and registers the interfaces the contract supports use openzeppelin::presets::EthAccountUpgradeable; ``` -Account contract leveraging xref:#EthAccountComponent[EthAccountComponent]. +Upgradeable account contract leveraging xref:#EthAccountComponent[EthAccountComponent]. NOTE: The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. @@ -601,3 +621,8 @@ Sets the account `public_key` and registers the interfaces the contract supports ==== `[.contract-item-name]#++upgrade++#++(ref self: ContractState, new_class_hash: ClassHash)++` [.item-kind]#external# Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. diff --git a/docs/modules/ROOT/pages/api/erc1155.adoc b/docs/modules/ROOT/pages/api/erc1155.adoc index a04c407d1..ce7e5096e 100644 --- a/docs/modules/ROOT/pages/api/erc1155.adoc +++ b/docs/modules/ROOT/pages/api/erc1155.adoc @@ -623,27 +623,27 @@ Registers the `IERC1155Receiver` interface ID as supported through introspection == Presets [.contract] -[[ERC1155]] -=== `++ERC1155++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc1155.cairo[{github-icon},role=heading-link] +[[ERC1155Upgradeable]] +=== `++ERC1155Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc1155.cairo[{github-icon},role=heading-link] ```javascript use openzeppelin::presets::ERC1155; ``` -Basic ERC1155 contract leveraging xref:#ERC1155Component[ERC1155Component]. +Upgradeable ERC1155 contract leveraging xref:#ERC1155Component[ERC1155Component]. include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{ERC1155-class-hash} +{ERC1155Upgradeable-class-hash} -- [.contract-index] .Constructor -- -* xref:#ERC1155-constructor[`++constructor(self, base_uri, recipient, token_ids, values)++`] +* xref:#ERC1155Upgradeable-constructor[`++constructor(self, base_uri, recipient, token_ids, values, owner)++`] -- [.contract-index] @@ -652,20 +652,45 @@ include::../utils/_class_hashes.adoc[] .ERC1155Component * xref:#ERC1155Component-Embeddable-Mixin-Impl[`++ERC1155MixinImpl++`] + +.OwnableMixinImpl + +* xref:/api/access.adoc#OwnableComponent-Mixin-Impl[`++OwnableMixinImpl++`] -- -[#ERC1155-constructor-section] +[.contract-index] +.External Functions +-- +* xref:#ERC1155Upgradeable-upgrade[`++upgrade(self, new_class_hash)++`] +-- + +[#ERC1155Upgradeable-constructor-section] ==== Constructor [.contract-item] -[[ERC1155-constructor]] -==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, values: Span)++` [.item-kind]#constructor# +[[ERC1155Upgradeable-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, values: Span, owner: ContractAddress)++` [.item-kind]#constructor# Sets the `base_uri` for all tokens and registers the supported interfaces. Mints the `values` for `token_ids` tokens to `recipient`. +Assigns `owner` as the contract owner with permissions to upgrade. Requirements: - `to` is either an account contract (supporting ISRC6) or supports the `IERC1155Receiver` interface. - `token_ids` and `values` must have the same length. + +[#ERC1155Upgradeable-external-functions] +==== External Functions + +[.contract-item] +[[ERC1155Upgradeable-upgrade]] +==== `[.contract-item-name]#++upgrade++#++(ref self: ContractState, new_class_hash: ClassHash)++` [.item-kind]#external# + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc index bc3db7b98..b5e217d6d 100644 --- a/docs/modules/ROOT/pages/api/erc20.adoc +++ b/docs/modules/ROOT/pages/api/erc20.adoc @@ -653,27 +653,27 @@ Emitted when `delegate` votes are updated from `previous_votes` to `new_votes`. == Presets [.contract] -[[ERC20]] -=== `++ERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc20.cairo[{github-icon},role=heading-link] +[[ERC20Upgradeable]] +=== `++ERC20Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc20.cairo[{github-icon},role=heading-link] ```javascript -use openzeppelin::presets::ERC20; +use openzeppelin::presets::ERC20Upgradeable; ``` -Basic ERC20 contract leveraging xref:#ERC20Component[ERC20Component] with a fixed-supply mechanism for token distribution. +Upgradeable ERC20 contract leveraging xref:#ERC20Component[ERC20Component] with a fixed-supply mechanism for token distribution. include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{ERC20-class-hash} +{ERC20Upgradeable-class-hash} -- [.contract-index] .Constructor -- -* xref:#ERC20-constructor[`++constructor(self, name, symbol, fixed_supply, recipient)++`] +* xref:#ERC20Upgradeable-constructor[`++constructor(self, name, symbol, fixed_supply, recipient, owner)++`] -- [.contract-index] @@ -682,13 +682,38 @@ include::../utils/_class_hashes.adoc[] .ERC20MixinImpl * xref:#ERC20Component-Embeddable-Mixin-Impl[`++ERC20MixinImpl++`] + +.OwnableMixinImpl + +* xref:/api/access.adoc#OwnableComponent-Mixin-Impl[`++OwnableMixinImpl++`] -- -[#ERC20-constructor-section] +[.contract-index] +.External Functions +-- +* xref:#ERC20Upgradeable-upgrade[`++upgrade(self, new_class_hash)++`] +-- + +[#ERC20Upgradeable-constructor-section] ==== Constructor [.contract-item] -[[ERC20-constructor]] -==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress)++` [.item-kind]#constructor# +[[ERC20Upgradeable-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, fixed_supply: u256, recipient: ContractAddress, owner: ContractAddress)++` [.item-kind]#constructor# Sets the `name` and `symbol` and mints `fixed_supply` tokens to `recipient`. +Assigns `owner` as the contract owner with permissions to upgrade. + +[#ERC20Upgradeable-external-functions] +==== External functions + +[.contract-item] +[[ERC20Upgradeable-upgrade]] +==== `[.contract-item-name]#++upgrade++#++(ref self: ContractState, new_class_hash: ClassHash)++` [.item-kind]#external# + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 473875af5..62919191c 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -689,27 +689,27 @@ Registers the `IERC721Receiver` interface ID as supported through introspection. == Presets [.contract] -[[ERC721]] -=== `++ERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc721.cairo[{github-icon},role=heading-link] +[[ERC721Upgradeable]] +=== `++ERC721Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.11.0/src/presets/erc721.cairo[{github-icon},role=heading-link] ```javascript -use openzeppelin::presets::ERC721; +use openzeppelin::presets::ERC721Upgradeable; ``` -Basic ERC721 contract leveraging xref:#ERC721Component[ERC721Component]. +Upgradeable ERC721 contract leveraging xref:#ERC721Component[ERC721Component]. include::../utils/_class_hashes.adoc[] [.contract-index] .{presets-page} -- -{ERC721-class-hash} +{ERC721Upgradeable-class-hash} -- [.contract-index] .Constructor -- -* xref:#ERC721-constructor[`++constructor(self, name, symbol, recipient, token_ids, base_uri)++`] +* xref:#ERC721Upgradeable-constructor[`++constructor(self, name, symbol, recipient, token_ids, base_uri, owner)++`] -- [.contract-index] @@ -718,15 +718,39 @@ include::../utils/_class_hashes.adoc[] .ERC721MixinImpl * xref:#ERC721Component-Embeddable-Mixin-Impl[`++ERC721MixinImpl++`] + +.OwnableMixinImpl + +* xref:/api/access.adoc#OwnableComponent-Mixin-Impl[`++OwnableMixinImpl++`] -- -[#ERC721-constructor-section] +[.contract-index] +.External Functions +-- +* xref:#ERC721Upgradeable-upgrade[`++upgrade(self, new_class_hash)++`] +-- + +[#ERC721Upgradeable-constructor-section] ==== Constructor [.contract-item] -[[ERC721Component-constructor]] -==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, recipient: ContractAddress, token_ids: Span, base_uri: ByteArray)++` [.item-kind]#constructor# +[[ERC721Upgradeable-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: ByteArray, symbol: ByteArray, recipient: ContractAddress, token_ids: Span, base_uri: ByteArray, owner: ContractAddress)++` [.item-kind]#constructor# Sets the `name` and `symbol`. - Mints `token_ids` tokens to `recipient` and sets the `base_uri`. +Assigns `owner` as the contract owner with permissions to upgrade. + +[#ERC721Upgradeable-external-functions] +==== External functions + +[.contract-item] +[[ERC721Upgradeable-upgrade]] +==== `[.contract-item-name]#++upgrade++#++(ref self: ContractState, new_class_hash: ClassHash)++` [.item-kind]#external# + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. diff --git a/docs/modules/ROOT/pages/api/utilities.adoc b/docs/modules/ROOT/pages/api/utilities.adoc index 4696e6350..ab66d81de 100644 --- a/docs/modules/ROOT/pages/api/utilities.adoc +++ b/docs/modules/ROOT/pages/api/utilities.adoc @@ -422,7 +422,7 @@ The `contract_address_salt` is always set to zero, and `deploy_from_zero` is set Usage example: ```javascript -use openzeppelin::presets::Account; +use openzeppelin::presets::AccountUpgradeable; use openzeppelin::tests::utils; use starknet::ContractAddress; @@ -430,7 +430,7 @@ const PUBKEY: felt252 = 'PUBKEY'; fn deploy_test_contract() -> ContractAddress { let calldata = array![PUBKEY]; - utils::deploy(Account::TEST_CLASS_HASH, calldata) + utils::deploy(AccountUpgradeable::TEST_CLASS_HASH, calldata) } ``` diff --git a/docs/modules/ROOT/pages/presets.adoc b/docs/modules/ROOT/pages/presets.adoc index 0d59b9e6c..9681e3a87 100644 --- a/docs/modules/ROOT/pages/presets.adoc +++ b/docs/modules/ROOT/pages/presets.adoc @@ -1,7 +1,7 @@ -:account: xref:/api/account.adoc#Account[Account] -:erc20: xref:/api/erc20.adoc#ERC20[ERC20] -:erc721: xref:/api/erc721.adoc#ERC721[ERC721] -:erc1155: xref:/api/erc1155.adoc#ERC1155[ERC1155] +:account-upgradeable: xref:/api/account.adoc#AccountUpgradeable[AccountUpgradeable] +:erc20-upgradeable: xref:/api/erc20.adoc#ERC20Upgradeable[ERC20Upgradeable] +:erc721-upgradeable: xref:/api/erc721.adoc#ERC721Upgradeable[ERC721Upgradeable] +:erc1155-upgradeable: xref:/api/erc1155.adoc#ERC1155Upgradeable[ERC1155Upgradeable] :eth-account-upgradeable: xref:/api/account.adoc#EthAccountUpgradeable[EthAccountUpgradeable] :udc: xref:/api/udc.adoc#UniversalDeployer[UniversalDeployer] :sierra-class-hashes: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/class-hash[Sierra class hashes] @@ -28,17 +28,17 @@ NOTE: Class hashes were computed using {class-hash-cairo-version}. |=== | Name | Sierra Class Hash -| `{account}` -| `{Account-class-hash}` +| `{account-upgradeable}` +| `{AccountUpgradeable-class-hash}` -| `{erc20}` -| `{ERC20-class-hash}` +| `{erc20-upgradeable}` +| `{ERC20Upgradeable-class-hash}` -| `{erc721}` -| `{ERC721-class-hash}` +| `{erc721-upgradeable}` +| `{ERC721Upgradeable-class-hash}` -| `{erc1155}` -| `{ERC1155-class-hash}` +| `{erc1155-upgradeable}` +| `{ERC1155Upgradeable-class-hash}` | `{eth-account-upgradeable}` | `{EthAccountUpgradeable-class-hash}` @@ -75,27 +75,49 @@ Copy the target preset contract from the {presets_dir} and paste it in the new p // src/lib.cairo #[starknet::contract] -mod ERC20 { +mod ERC20Upgradeable { + use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // ERC20 Mixin #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; - impl InternalImpl = ERC20Component::InternalImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; #[storage] struct Storage { #[substorage(v0)] - erc20: ERC20Component::Storage + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] - ERC20Event: ERC20Component::Event + OwnableEvent: OwnableComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event } #[constructor] @@ -104,11 +126,21 @@ mod ERC20 { name: ByteArray, symbol: ByteArray, fixed_supply: u256, - recipient: ContractAddress + recipient: ContractAddress, + owner: ContractAddress ) { + self.ownable.initializer(owner); self.erc20.initializer(name, symbol); self.erc20._mint(recipient, fixed_supply); } + + #[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); + } + } } ---- @@ -123,6 +155,6 @@ Finally, declare the preset. [,bash] ---- -starkli declare target/dev/my_project_ERC20.contract_class.json \ +starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ --network="sepolia" ---- diff --git a/docs/modules/ROOT/pages/utils/_class_hashes.adoc b/docs/modules/ROOT/pages/utils/_class_hashes.adoc index 9f391eeda..48d6f83c9 100644 --- a/docs/modules/ROOT/pages/utils/_class_hashes.adoc +++ b/docs/modules/ROOT/pages/utils/_class_hashes.adoc @@ -2,10 +2,10 @@ :class-hash-cairo-version: https://crates.io/crates/cairo-lang-compiler/2.6.3[cairo 2.6.3] // Class Hashes -:Account-class-hash: 0x0450f568a8cb6ea1bcce446355e8a1c2e5852a6b8dc3536f495cdceb62e8a7e2 -:ERC1155-class-hash: 0x0165bfb888913ab8adda83e5d859fd1c283460d51f8f694da00c121b65a6fcc8 -:ERC20-class-hash: 0x06aff5bf77ad88f5ccadc80360e3dcdd12e1a8009160458296b8aac9de2240ff -:ERC721-class-hash: 0x04cd23b9cf1f3206bef278fe4a4cf6c34e88a7ae20d85884e3d6a1bf8df06693 +:AccountUpgradeable-class-hash: 0x0450f568a8cb6ea1bcce446355e8a1c2e5852a6b8dc3536f495cdceb62e8a7e2 +:ERC1155Upgradeable-class-hash: 0x0165bfb888913ab8adda83e5d859fd1c283460d51f8f694da00c121b65a6fcc8 +:ERC20Upgradeable-class-hash: 0x06aff5bf77ad88f5ccadc80360e3dcdd12e1a8009160458296b8aac9de2240ff +:ERC721Upgradeable-class-hash: 0x04cd23b9cf1f3206bef278fe4a4cf6c34e88a7ae20d85884e3d6a1bf8df06693 :EthAccountUpgradeable-class-hash: 0x078ca36b65e3790d9a1b8474221a52b4c600efe5c55e5d1e972a61f69365a151 :UniversalDeployer-class-hash: 0x00c46a81caa79e379148caaceb3db2e7c6d59c89a47a53b51d59494d65233e87 diff --git a/scripts/get_hashes_page.py b/scripts/get_hashes_page.py index 716de5a80..9ce7105f8 100644 --- a/scripts/get_hashes_page.py +++ b/scripts/get_hashes_page.py @@ -2,10 +2,10 @@ import json KNOWN_ORDER = [ - "ERC20", - "ERC721", - "ERC1155", - "Account", + "ERC20Upgradeable", + "ERC721Upgradeable", + "ERC1155Upgradeable", + "AccountUpgradeable", "EthAccountUpgradeable", "UniversalDeployer" ] diff --git a/src/presets.cairo b/src/presets.cairo index 932bc9b35..3c75cdf06 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -3,11 +3,12 @@ mod erc1155; mod erc20; mod erc721; mod eth_account; +mod interfaces; mod universal_deployer; -use account::Account; -use erc1155::ERC1155; -use erc20::ERC20; -use erc721::ERC721; +use account::AccountUpgradeable; +use erc1155::ERC1155Upgradeable; +use erc20::ERC20Upgradeable; +use erc721::ERC721Upgradeable; use eth_account::EthAccountUpgradeable; use universal_deployer::UniversalDeployer; diff --git a/src/presets/account.cairo b/src/presets/account.cairo index b3490b1b0..e84413dc3 100644 --- a/src/presets/account.cairo +++ b/src/presets/account.cairo @@ -3,26 +3,35 @@ /// # Account Preset /// -/// OpenZeppelin's basic account which can change its public key and declare, deploy, or call contracts. +/// OpenZeppelin's upgradeable account which can change its public key and declare, deploy, or call contracts. #[starknet::contract(account)] -mod Account { +mod AccountUpgradeable { 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); // Account Mixin #[abi(embed_v0)] impl AccountMixinImpl = AccountComponent::AccountMixinImpl; impl AccountInternalImpl = AccountComponent::InternalImpl; + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] struct Storage { #[substorage(v0)] account: AccountComponent::Storage, #[substorage(v0)] - src5: SRC5Component::Storage + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] @@ -31,11 +40,21 @@ mod Account { #[flat] AccountEvent: AccountComponent::Event, #[flat] - SRC5Event: SRC5Component::Event + 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); + } + } } diff --git a/src/presets/erc1155.cairo b/src/presets/erc1155.cairo index 5f2c19c41..467f064f0 100644 --- a/src/presets/erc1155.cairo +++ b/src/presets/erc1155.cairo @@ -1,44 +1,66 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.11.0 (presets/erc1155.cairo) -/// # ERC1155 Preset +/// # ERC1155Upgradeable Preset /// -/// The ERC1155 contract offers a batch-mint mechanism that +/// The upgradeable ERC1155 contract offers a batch-mint mechanism that /// can only be executed once upon contract construction. /// /// For more complex or custom contracts, use Wizard for Cairo /// https://wizard.openzeppelin.com/cairo #[starknet::contract] -mod ERC1155 { +mod ERC1155Upgradeable { + use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc1155::ERC1155Component; - use starknet::ContractAddress; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; // ERC1155 Mixin #[abi(embed_v0)] impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, #[substorage(v0)] erc1155: ERC1155Component::Storage, #[substorage(v0)] - src5: SRC5Component::Storage + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, #[flat] ERC1155Event: ERC1155Component::Event, #[flat] - SRC5Event: SRC5Component::Event + SRC5Event: SRC5Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event } + /// Assigns `owner` as the contract owner. /// Sets the `base_uri` for all tokens. /// Mints the `values` for `token_ids` tokens to `recipient`. /// @@ -53,11 +75,23 @@ mod ERC1155 { base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, - values: Span + values: Span, + owner: ContractAddress ) { + self.ownable.initializer(owner); self.erc1155.initializer(base_uri); self .erc1155 .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + /// Upgrades the contract class hash to `new_class_hash`. + /// This may only be called by the contract owner. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } } diff --git a/src/presets/erc20.cairo b/src/presets/erc20.cairo index 0d5398e41..68a99e3b2 100644 --- a/src/presets/erc20.cairo +++ b/src/presets/erc20.cairo @@ -3,34 +3,59 @@ /// # ERC20 Preset /// -/// The ERC20 contract offers basic functionality and provides a +/// The upgradeable ERC20 contract offers basic functionality and provides a /// fixed-supply mechanism for token distribution. The fixed supply is /// set in the constructor. +/// +/// For more complex or custom contracts, use Wizard for Cairo +/// https://wizard.openzeppelin.com/cairo #[starknet::contract] -mod ERC20 { +mod ERC20Upgradeable { + use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; - use starknet::ContractAddress; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; // ERC20 Mixin #[abi(embed_v0)] impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] struct Storage { #[substorage(v0)] - erc20: ERC20Component::Storage + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { #[flat] - ERC20Event: ERC20Component::Event + OwnableEvent: OwnableComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event } + /// Assigns `owner` as the contract owner. /// Sets the token `name` and `symbol`. /// Mints `fixed_supply` tokens to `recipient`. #[constructor] @@ -39,9 +64,21 @@ mod ERC20 { name: ByteArray, symbol: ByteArray, fixed_supply: u256, - recipient: ContractAddress + recipient: ContractAddress, + owner: ContractAddress ) { + self.ownable.initializer(owner); self.erc20.initializer(name, symbol); self.erc20._mint(recipient, fixed_supply); } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + /// Upgrades the contract class hash to `new_class_hash`. + /// This may only be called by the contract owner. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } } diff --git a/src/presets/erc721.cairo b/src/presets/erc721.cairo index 64aec5dd5..1d96b2878 100644 --- a/src/presets/erc721.cairo +++ b/src/presets/erc721.cairo @@ -3,39 +3,64 @@ /// # ERC721 Preset /// -/// The ERC721 contract offers a batch-mint mechanism that +/// The upgradeable ERC721 contract offers a batch-mint mechanism that /// can only be executed once upon contract construction. +/// +/// For more complex or custom contracts, use Wizard for Cairo +/// https://wizard.openzeppelin.com/cairo #[starknet::contract] -mod ERC721 { +mod ERC721Upgradeable { + use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::ERC721Component; - use starknet::ContractAddress; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC721Component, storage: erc721, event: ERC721Event); component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; // ERC721 Mixin #[abi(embed_v0)] impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; impl ERC721InternalImpl = ERC721Component::InternalImpl; + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, #[substorage(v0)] erc721: ERC721Component::Storage, #[substorage(v0)] - src5: SRC5Component::Storage + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, #[flat] ERC721Event: ERC721Component::Event, #[flat] - SRC5Event: SRC5Component::Event + SRC5Event: SRC5Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event } + /// Assigns `owner` as the contract owner. /// Sets the token `name` and `symbol`. /// Mints the `token_ids` tokens to `recipient` and sets /// the base URI. @@ -47,11 +72,23 @@ mod ERC721 { base_uri: ByteArray, recipient: ContractAddress, token_ids: Span, + owner: ContractAddress ) { + self.ownable.initializer(owner); self.erc721.initializer(name, symbol, base_uri); self._mint_assets(recipient, token_ids); } + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + /// Upgrades the contract class hash to `new_class_hash`. + /// This may only be called by the contract owner. + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + #[generate_trait] impl InternalImpl of InternalTrait { /// Mints `token_ids` to `recipient`. diff --git a/src/presets/eth_account.cairo b/src/presets/eth_account.cairo index 412002c82..f54133ce1 100644 --- a/src/presets/eth_account.cairo +++ b/src/presets/eth_account.cairo @@ -3,7 +3,7 @@ /// # EthAccount Preset /// -/// OpenZeppelin's account which can change its public key and declare, +/// OpenZeppelin's upgradeable account which can change its public key and declare, /// deploy, or call contracts, using Ethereum signing keys. #[starknet::contract(account)] mod EthAccountUpgradeable { diff --git a/src/presets/interfaces.cairo b/src/presets/interfaces.cairo new file mode 100644 index 000000000..21f04d8a6 --- /dev/null +++ b/src/presets/interfaces.cairo @@ -0,0 +1,16 @@ +mod account; +mod erc1155; +mod erc20; +mod erc721; +mod eth_account; + +use account::AccountUpgradeableABI; +use account::{AccountUpgradeableABIDispatcher, AccountUpgradeableABIDispatcherTrait}; +use erc1155::ERC1155UpgradeableABI; +use erc1155::{ERC1155UpgradeableABIDispatcher, ERC1155UpgradeableABIDispatcherTrait}; +use erc20::ERC20UpgradeableABI; +use erc20::{ERC20UpgradeableABIDispatcher, ERC20UpgradeableABIDispatcherTrait}; +use erc721::ERC721UpgradeableABI; +use erc721::{ERC721UpgradeableABIDispatcher, ERC721UpgradeableABIDispatcherTrait}; +use eth_account::EthAccountUpgradeableABI; +use eth_account::{EthAccountUpgradeableABIDispatcher, EthAccountUpgradeableABIDispatcherTrait}; diff --git a/src/presets/interfaces/account.cairo b/src/presets/interfaces/account.cairo new file mode 100644 index 000000000..3bda0eb19 --- /dev/null +++ b/src/presets/interfaces/account.cairo @@ -0,0 +1,35 @@ +use starknet::account::Call; +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait AccountUpgradeableABI { + // ISRC6 + fn __execute__(self: @TState, calls: Array) -> Array>; + fn __validate__(self: @TState, calls: Array) -> felt252; + fn is_valid_signature(self: @TState, hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(self: @TState, class_hash: felt252) -> felt252; + + // IDeployable + fn __validate_deploy__( + self: @TState, class_hash: felt252, contract_address_salt: felt252, public_key: felt252 + ) -> felt252; + + // IPublicKey + fn get_public_key(self: @TState) -> felt252; + fn set_public_key(ref self: TState, new_public_key: felt252); + + // ISRC6CamelOnly + fn isValidSignature(self: @TState, hash: felt252, signature: Array) -> felt252; + + // IPublicKeyCamel + fn getPublicKey(self: @TState) -> felt252; + fn setPublicKey(ref self: TState, newPublicKey: felt252); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/src/presets/interfaces/erc1155.cairo b/src/presets/interfaces/erc1155.cairo new file mode 100644 index 000000000..7bb758122 --- /dev/null +++ b/src/presets/interfaces/erc1155.cairo @@ -0,0 +1,72 @@ +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait ERC1155UpgradeableABI { + // IERC1155 + fn balance_of(self: @TState, account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + self: @TState, accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IERC1155MetadataURI + fn uri(self: @TState, token_id: u256) -> ByteArray; + + // IERC1155Camel + fn balanceOf(self: @TState, account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + self: @TState, accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/src/presets/interfaces/erc20.cairo b/src/presets/interfaces/erc20.cairo new file mode 100644 index 000000000..0d212091a --- /dev/null +++ b/src/presets/interfaces/erc20.cairo @@ -0,0 +1,38 @@ +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait ERC20UpgradeableABI { + // IERC20 + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + + // IERC20CamelOnly + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/src/presets/interfaces/erc721.cairo b/src/presets/interfaces/erc721.cairo new file mode 100644 index 000000000..eb4c49ea6 --- /dev/null +++ b/src/presets/interfaces/erc721.cairo @@ -0,0 +1,60 @@ +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait ERC721UpgradeableABI { + // IERC721 + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn owner_of(self: @TState, token_id: u256) -> ContractAddress; + fn safe_transfer_from( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(ref self: TState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TState, operator: ContractAddress, approved: bool); + fn get_approved(self: @TState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IERC721Metadata + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; + + // IERC721CamelOnly + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn ownerOf(self: @TState, tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + ref self: TState, + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(ref self: TState, operator: ContractAddress, approved: bool); + fn getApproved(self: @TState, tokenId: u256) -> ContractAddress; + fn isApprovedForAll(self: @TState, owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721MetadataCamelOnly + fn tokenURI(self: @TState, tokenId: u256) -> ByteArray; + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/src/presets/interfaces/eth_account.cairo b/src/presets/interfaces/eth_account.cairo new file mode 100644 index 000000000..e9f5f95da --- /dev/null +++ b/src/presets/interfaces/eth_account.cairo @@ -0,0 +1,37 @@ +use openzeppelin::account::interface::EthPublicKey; +use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; +use starknet::account::Call; +use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +trait EthAccountUpgradeableABI { + // ISRC6 + fn __execute__(self: @TState, calls: Array) -> Array>; + fn __validate__(self: @TState, calls: Array) -> felt252; + fn is_valid_signature(self: @TState, hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(self: @TState, class_hash: felt252) -> felt252; + + // IEthDeployable + fn __validate_deploy__( + self: @TState, class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey + ) -> felt252; + + // IEthPublicKey + fn get_public_key(self: @TState) -> EthPublicKey; + fn set_public_key(ref self: TState, new_public_key: EthPublicKey); + + // ISRC6CamelOnly + fn isValidSignature(self: @TState, hash: felt252, signature: Array) -> felt252; + + // IEthPublicKeyCamel + fn getPublicKey(self: @TState) -> EthPublicKey; + fn setPublicKey(ref self: TState, newPublicKey: EthPublicKey); + + // IUpgradeable + fn upgrade(ref self: TState, new_class_hash: ClassHash); +} diff --git a/src/tests/access/test_ownable.cairo b/src/tests/access/test_ownable.cairo index 7d7abb968..6844bca89 100644 --- a/src/tests/access/test_ownable.cairo +++ b/src/tests/access/test_ownable.cairo @@ -40,7 +40,7 @@ fn test_initializer_owner() { state.initializer(OWNER()); - assert_event_ownership_transferred(ZERO(), OWNER()); + assert_event_ownership_transferred(ZERO(), ZERO(), OWNER()); let new_owner = state.Ownable_owner.read(); assert_eq!(new_owner, OWNER()); @@ -81,7 +81,7 @@ fn test__transfer_ownership() { let mut state = setup(); state._transfer_ownership(OTHER()); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); let current_owner = state.Ownable_owner.read(); assert_eq!(current_owner, OTHER()); @@ -97,7 +97,7 @@ fn test_transfer_ownership() { testing::set_caller_address(OWNER()); state.transfer_ownership(OTHER()); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); } @@ -130,7 +130,7 @@ fn test_transferOwnership() { testing::set_caller_address(OWNER()); state.transferOwnership(OTHER()); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); } @@ -167,7 +167,7 @@ fn test_renounce_ownership() { testing::set_caller_address(OWNER()); state.renounce_ownership(); - assert_event_ownership_transferred(OWNER(), ZERO()); + assert_event_ownership_transferred(ZERO(), OWNER(), ZERO()); assert!(state.owner().is_zero()); } @@ -192,7 +192,7 @@ fn test_renounceOwnership() { testing::set_caller_address(OWNER()); state.renounceOwnership(); - assert_event_ownership_transferred(OWNER(), ZERO()); + assert_event_ownership_transferred(ZERO(), OWNER(), ZERO()); assert!(state.owner().is_zero()); } @@ -215,13 +215,21 @@ fn test_renounceOwnership_from_nonowner() { // Helpers // -fn assert_event_ownership_transferred(previous_owner: ContractAddress, new_owner: ContractAddress) { - let event = utils::pop_log::(ZERO()).unwrap(); +fn assert_only_event_ownership_transferred( + contract: ContractAddress, previous_owner: ContractAddress, new_owner: ContractAddress +) { + assert_event_ownership_transferred(contract, previous_owner, new_owner); + utils::assert_no_events_left(contract); +} + +fn assert_event_ownership_transferred( + contract: ContractAddress, previous_owner: ContractAddress, new_owner: ContractAddress +) { + let event = utils::pop_log::(contract).unwrap(); let expected = OwnableComponent::Event::OwnershipTransferred( OwnershipTransferred { previous_owner, new_owner } ); assert!(event == expected); - utils::assert_no_events_left(ZERO()); let mut indexed_keys = array![]; indexed_keys.append_serde(selector!("OwnershipTransferred")); diff --git a/src/tests/access/test_ownable_twostep.cairo b/src/tests/access/test_ownable_twostep.cairo index 2ce7af905..68b65ca3d 100644 --- a/src/tests/access/test_ownable_twostep.cairo +++ b/src/tests/access/test_ownable_twostep.cairo @@ -40,7 +40,7 @@ fn test_initializer_owner_pending_owner() { assert!(state.Ownable_pending_owner.read().is_zero()); state.initializer(OWNER()); - assert_event_ownership_transferred(ZERO(), OWNER()); + assert_event_ownership_transferred(ZERO(), ZERO(), OWNER()); assert_eq!(state.Ownable_owner.read(), OWNER()); assert!(state.Ownable_pending_owner.read().is_zero()); @@ -57,7 +57,7 @@ fn test__accept_ownership() { state._accept_ownership(); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); assert!(state.pending_owner().is_zero()); } @@ -179,7 +179,7 @@ fn test_accept_ownership() { state.accept_ownership(); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); assert!(state.pending_owner().is_zero()); } @@ -201,7 +201,7 @@ fn test_acceptOwnership() { state.acceptOwnership(); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); assert!(state.pendingOwner().is_zero()); } @@ -225,7 +225,7 @@ fn test_renounce_ownership() { testing::set_caller_address(OWNER()); state.renounce_ownership(); - assert_event_ownership_transferred(OWNER(), ZERO()); + assert_event_ownership_transferred(ZERO(), OWNER(), ZERO()); assert!(state.owner().is_zero()); } @@ -251,7 +251,7 @@ fn test_renounceOwnership() { testing::set_caller_address(OWNER()); state.renounceOwnership(); - assert_event_ownership_transferred(OWNER(), ZERO()); + assert_event_ownership_transferred(ZERO(), OWNER(), ZERO()); assert!(state.owner().is_zero()); } @@ -284,7 +284,7 @@ fn test_full_two_step_transfer() { testing::set_caller_address(OTHER()); state.accept_ownership(); - assert_event_ownership_transferred(OWNER(), OTHER()); + assert_event_ownership_transferred(ZERO(), OWNER(), OTHER()); assert_eq!(state.owner(), OTHER()); assert!(state.pending_owner().is_zero()); } @@ -301,13 +301,13 @@ fn test_pending_accept_after_owner_renounce() { state.renounce_ownership(); - assert_event_ownership_transferred(OWNER(), ZERO()); + assert_event_ownership_transferred(ZERO(), OWNER(), ZERO()); assert!(state.owner().is_zero()); testing::set_caller_address(OTHER()); state.accept_ownership(); - assert_event_ownership_transferred(ZERO(), OTHER()); + assert_event_ownership_transferred(ZERO(), ZERO(), OTHER()); assert_eq!(state.owner(), OTHER()); assert!(state.pending_owner().is_zero()); } diff --git a/src/tests/presets/test_account.cairo b/src/tests/presets/test_account.cairo index a15075f48..596e8bc70 100644 --- a/src/tests/presets/test_account.cairo +++ b/src/tests/presets/test_account.cairo @@ -1,43 +1,53 @@ use openzeppelin::account::AccountComponent::{OwnerAdded, OwnerRemoved}; use openzeppelin::account::interface::ISRC6_ID; -use openzeppelin::account::interface::{AccountABIDispatcherTrait, AccountABIDispatcher}; use openzeppelin::introspection::interface::ISRC5_ID; -use openzeppelin::presets::Account; +use openzeppelin::presets::AccountUpgradeable; +use openzeppelin::presets::interfaces::{ + AccountUpgradeableABIDispatcher, AccountUpgradeableABIDispatcherTrait +}; use openzeppelin::tests::account::test_account::{ assert_only_event_owner_added, assert_event_owner_removed }; use openzeppelin::tests::account::test_account::{ deploy_erc20, SIGNED_TX_DATA, SignedTransactionData }; +use openzeppelin::tests::mocks::account_mocks::SnakeAccountMock; +use openzeppelin::tests::upgrades::test_upgradeable::assert_only_event_upgraded; use openzeppelin::tests::utils::constants::{ PUBKEY, NEW_PUBKEY, SALT, ZERO, CALLER, RECIPIENT, OTHER, QUERY_OFFSET, QUERY_VERSION, - MIN_TRANSACTION_VERSION + MIN_TRANSACTION_VERSION, CLASS_HASH_ZERO }; use openzeppelin::tests::utils; use openzeppelin::token::erc20::interface::{IERC20DispatcherTrait, IERC20Dispatcher}; use openzeppelin::utils::selectors; use openzeppelin::utils::serde::SerializedAppend; -use starknet::ContractAddress; use starknet::account::Call; use starknet::testing; +use starknet::{ContractAddress, ClassHash}; fn CLASS_HASH() -> felt252 { - Account::TEST_CLASS_HASH + AccountUpgradeable::TEST_CLASS_HASH +} + +fn V2_CLASS_HASH() -> ClassHash { + SnakeAccountMock::TEST_CLASS_HASH.try_into().unwrap() } // // Setup // -fn setup_dispatcher() -> AccountABIDispatcher { +fn setup_dispatcher() -> AccountUpgradeableABIDispatcher { let calldata = array![PUBKEY]; let target = utils::deploy(CLASS_HASH(), calldata); utils::drop_event(target); - AccountABIDispatcher { contract_address: target } + AccountUpgradeableABIDispatcher { contract_address: target } } -fn setup_dispatcher_with_data(data: Option<@SignedTransactionData>) -> AccountABIDispatcher { +fn setup_dispatcher_with_data( + data: Option<@SignedTransactionData> +) -> AccountUpgradeableABIDispatcher { testing::set_version(MIN_TRANSACTION_VERSION); let mut calldata = array![]; @@ -51,7 +61,7 @@ fn setup_dispatcher_with_data(data: Option<@SignedTransactionData>) -> AccountAB calldata.append(PUBKEY); } let address = utils::deploy(CLASS_HASH(), calldata); - AccountABIDispatcher { contract_address: address } + AccountUpgradeableABIDispatcher { contract_address: address } } // @@ -60,18 +70,18 @@ fn setup_dispatcher_with_data(data: Option<@SignedTransactionData>) -> AccountAB #[test] fn test_constructor() { - let mut state = Account::contract_state_for_testing(); - Account::constructor(ref state, PUBKEY); + let mut state = AccountUpgradeable::contract_state_for_testing(); + AccountUpgradeable::constructor(ref state, PUBKEY); assert_only_event_owner_added(ZERO(), PUBKEY); - let public_key = Account::AccountMixinImpl::get_public_key(@state); + let public_key = AccountUpgradeable::AccountMixinImpl::get_public_key(@state); assert_eq!(public_key, PUBKEY); - let supports_isrc5 = Account::AccountMixinImpl::supports_interface(@state, ISRC5_ID); + let supports_isrc5 = AccountUpgradeable::AccountMixinImpl::supports_interface(@state, ISRC5_ID); assert!(supports_isrc5); - let supports_isrc6 = Account::AccountMixinImpl::supports_interface(@state, ISRC6_ID); + let supports_isrc6 = AccountUpgradeable::AccountMixinImpl::supports_interface(@state, ISRC6_ID); assert!(supports_isrc6); } @@ -125,7 +135,7 @@ fn test_setPublicKey_different_account() { // is_valid_signature & isValidSignature // -fn is_valid_sig_dispatcher() -> (AccountABIDispatcher, felt252, Array) { +fn is_valid_sig_dispatcher() -> (AccountUpgradeableABIDispatcher, felt252, Array) { let dispatcher = setup_dispatcher(); let data = SIGNED_TX_DATA(); @@ -433,3 +443,75 @@ fn test_account_called_from_contract() { account.__execute__(calls); } + +// +// upgrade +// + +#[test] +#[should_panic(expected: ('Account: unauthorized', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_access_control() { + let v1 = setup_dispatcher(); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_with_class_hash_zero() { + let v1 = setup_dispatcher(); + + set_contract_and_caller(v1.contract_address); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +fn test_upgraded_event() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + set_contract_and_caller(v1.contract_address); + v1.upgrade(v2_class_hash); + + assert_only_event_upgraded(v1.contract_address, v2_class_hash); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_v2_missing_camel_selector() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + set_contract_and_caller(v1.contract_address); + v1.upgrade(v2_class_hash); + + let dispatcher = AccountUpgradeableABIDispatcher { contract_address: v1.contract_address }; + dispatcher.getPublicKey(); +} + +#[test] +fn test_state_persists_after_upgrade() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + set_contract_and_caller(v1.contract_address); + let dispatcher = AccountUpgradeableABIDispatcher { contract_address: v1.contract_address }; + + dispatcher.set_public_key(PUBKEY); + + let camel_public_key = dispatcher.getPublicKey(); + assert_eq!(camel_public_key, PUBKEY); + + v1.upgrade(v2_class_hash); + let snake_public_key = dispatcher.get_public_key(); + + assert_eq!(snake_public_key, camel_public_key); +} + +// +// Helpers +// + +fn set_contract_and_caller(address: ContractAddress) { + testing::set_contract_address(address); + testing::set_caller_address(address); +} diff --git a/src/tests/presets/test_erc1155.cairo b/src/tests/presets/test_erc1155.cairo index 1b2b5a587..c22111bd7 100644 --- a/src/tests/presets/test_erc1155.cairo +++ b/src/tests/presets/test_erc1155.cairo @@ -1,5 +1,10 @@ use openzeppelin::introspection; -use openzeppelin::presets::ERC1155; +use openzeppelin::presets::ERC1155Upgradeable; +use openzeppelin::presets::interfaces::{ + ERC1155UpgradeableABIDispatcher, ERC1155UpgradeableABIDispatcherTrait +}; +use openzeppelin::tests::access::test_ownable::assert_event_ownership_transferred; +use openzeppelin::tests::mocks::erc1155_mocks::SnakeERC1155Mock; use openzeppelin::tests::token::test_erc1155::{ assert_only_event_transfer_single, assert_only_event_transfer_batch, assert_only_event_approval_for_all @@ -8,21 +13,30 @@ use openzeppelin::tests::token::test_erc1155::{ setup_account, setup_receiver, setup_camel_receiver, setup_account_with_salt, setup_src5 }; use openzeppelin::tests::token::test_erc1155::{get_ids_and_values, get_ids_and_split_values}; +use openzeppelin::tests::upgrades::test_upgradeable::assert_only_event_upgraded; use openzeppelin::tests::utils::constants::{ - EMPTY_DATA, ZERO, OWNER, OPERATOR, OTHER, TOKEN_ID, TOKEN_ID_2, TOKEN_VALUE, TOKEN_VALUE_2 + EMPTY_DATA, ZERO, OWNER, RECIPIENT, CLASS_HASH_ZERO, OPERATOR, OTHER, TOKEN_ID, TOKEN_ID_2, + TOKEN_VALUE, TOKEN_VALUE_2 }; use openzeppelin::tests::utils; -use openzeppelin::token::erc1155::interface::{ERC1155ABIDispatcher, ERC1155ABIDispatcherTrait}; +use openzeppelin::token::erc1155::interface::{ + IERC1155CamelDispatcher, IERC1155CamelDispatcherTrait +}; +use openzeppelin::token::erc1155::interface::{IERC1155Dispatcher, IERC1155DispatcherTrait}; use openzeppelin::token::erc1155; use openzeppelin::utils::serde::SerializedAppend; -use starknet::ContractAddress; use starknet::testing; +use starknet::{ContractAddress, ClassHash}; + +fn V2_CLASS_HASH() -> ClassHash { + SnakeERC1155Mock::TEST_CLASS_HASH.try_into().unwrap() +} // // Setup // -fn setup_dispatcher_with_event() -> (ERC1155ABIDispatcher, ContractAddress) { +fn setup_dispatcher_with_event() -> (ERC1155UpgradeableABIDispatcher, ContractAddress) { let uri: ByteArray = "URI"; let mut calldata = array![]; let mut token_ids = array![TOKEN_ID, TOKEN_ID_2]; @@ -35,14 +49,16 @@ fn setup_dispatcher_with_event() -> (ERC1155ABIDispatcher, ContractAddress) { calldata.append_serde(owner); calldata.append_serde(token_ids); calldata.append_serde(values); + calldata.append_serde(owner); - let address = utils::deploy(ERC1155::TEST_CLASS_HASH, calldata); - (ERC1155ABIDispatcher { contract_address: address }, owner) + let address = utils::deploy(ERC1155Upgradeable::TEST_CLASS_HASH, calldata); + (ERC1155UpgradeableABIDispatcher { contract_address: address }, owner) } -fn setup_dispatcher() -> (ERC1155ABIDispatcher, ContractAddress) { +fn setup_dispatcher() -> (ERC1155UpgradeableABIDispatcher, ContractAddress) { let (dispatcher, owner) = setup_dispatcher_with_event(); - utils::drop_event(dispatcher.contract_address); + utils::drop_event(dispatcher.contract_address); // `TransferOwnership` + utils::drop_event(dispatcher.contract_address); // `Transfer` (dispatcher, owner) } @@ -659,12 +675,206 @@ fn test_setApprovalForAll_owner_equal_operator_false() { dispatcher.setApprovalForAll(OWNER(), false); } +// +// transfer_ownership & transferOwnership +// + +#[test] +fn test_transfer_ownership() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.transfer_ownership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, owner, OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.transfer_ownership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_zero() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_nonowner() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +fn test_transferOwnership() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.transferOwnership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, owner, OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_to_zero() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.transferOwnership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_zero() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.transferOwnership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_nonowner() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transferOwnership(OTHER()); +} + +// +// renounce_ownership & renounceOwnership +// + +#[test] +fn test_renounce_ownership() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.renounce_ownership(); + + assert_event_ownership_transferred(dispatcher.contract_address, owner, ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_zero_address() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.renounce_ownership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_nonowner() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounce_ownership(); +} + +#[test] +fn test_renounceOwnership() { + let (dispatcher, owner) = setup_dispatcher(); + testing::set_contract_address(owner); + dispatcher.renounceOwnership(); + + assert_event_ownership_transferred(dispatcher.contract_address, owner, ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_zero_address() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.renounceOwnership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_nonowner() { + let (dispatcher, _) = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounceOwnership(); +} + +// +// upgrade +// + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_unauthorized() { + let (v1, _) = setup_dispatcher(); + testing::set_contract_address(OTHER()); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_with_class_hash_zero() { + let (v1, owner) = setup_dispatcher(); + + testing::set_contract_address(owner); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +fn test_upgraded_event() { + let (v1, owner) = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(owner); + v1.upgrade(v2_class_hash); + + assert_only_event_upgraded(v1.contract_address, v2_class_hash); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_v2_missing_camel_selector() { + let (v1, owner) = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(owner); + v1.upgrade(v2_class_hash); + + let dispatcher = IERC1155CamelDispatcher { contract_address: v1.contract_address }; + dispatcher.balanceOf(owner, TOKEN_ID); +} + +#[test] +fn test_state_persists_after_upgrade() { + let (v1, owner) = setup_dispatcher(); + let recipient = setup_receiver(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(owner); + v1.safeTransferFrom(owner, recipient, TOKEN_ID, TOKEN_VALUE, array![].span()); + + // Check RECIPIENT balance v1 + let camel_balance = v1.balanceOf(recipient, TOKEN_ID); + assert_eq!(camel_balance, TOKEN_VALUE); + + v1.upgrade(v2_class_hash); + + // Check RECIPIENT balance v2 + let v2 = IERC1155Dispatcher { contract_address: v1.contract_address }; + let snake_balance = v2.balance_of(recipient, TOKEN_ID); + assert_eq!(snake_balance, camel_balance); +} + // // Helpers // fn assert_state_before_transfer_single( - dispatcher: ERC1155ABIDispatcher, + dispatcher: ERC1155UpgradeableABIDispatcher, sender: ContractAddress, recipient: ContractAddress, token_id: u256 @@ -674,7 +884,7 @@ fn assert_state_before_transfer_single( } fn assert_state_after_transfer_single( - dispatcher: ERC1155ABIDispatcher, + dispatcher: ERC1155UpgradeableABIDispatcher, sender: ContractAddress, recipient: ContractAddress, token_id: u256 @@ -684,7 +894,7 @@ fn assert_state_after_transfer_single( } fn assert_state_before_transfer_batch( - dispatcher: ERC1155ABIDispatcher, + dispatcher: ERC1155UpgradeableABIDispatcher, sender: ContractAddress, recipient: ContractAddress, token_ids: Span, @@ -705,7 +915,7 @@ fn assert_state_before_transfer_batch( } fn assert_state_after_transfer_batch( - dispatcher: ERC1155ABIDispatcher, + dispatcher: ERC1155UpgradeableABIDispatcher, sender: ContractAddress, recipient: ContractAddress, token_ids: Span, diff --git a/src/tests/presets/test_erc20.cairo b/src/tests/presets/test_erc20.cairo index 6ef49688c..e0a780fa4 100644 --- a/src/tests/presets/test_erc20.cairo +++ b/src/tests/presets/test_erc20.cairo @@ -1,35 +1,50 @@ use integer::BoundedInt; -use openzeppelin::presets::ERC20; +use openzeppelin::access::ownable::OwnableComponent::OwnershipTransferred; +use openzeppelin::presets::ERC20Upgradeable; +use openzeppelin::presets::interfaces::{ + ERC20UpgradeableABIDispatcher, ERC20UpgradeableABIDispatcherTrait +}; +use openzeppelin::tests::access::test_ownable::assert_event_ownership_transferred; +use openzeppelin::tests::mocks::erc20_mocks::SnakeERC20Mock; +use openzeppelin::tests::token::test_erc20::{ + assert_event_approval, assert_only_event_approval, assert_only_event_transfer +}; +use openzeppelin::tests::upgrades::test_upgradeable::assert_only_event_upgraded; use openzeppelin::tests::utils::constants::{ - ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE + ZERO, OWNER, SPENDER, RECIPIENT, OTHER, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE, CLASS_HASH_ZERO }; use openzeppelin::tests::utils; -use openzeppelin::token::erc20::ERC20Component::{Approval, Transfer}; -use openzeppelin::token::erc20::ERC20Component; -use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait}; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use openzeppelin::utils::serde::SerializedAppend; -use starknet::ContractAddress; +use starknet::ClassHash; use starknet::testing; +fn V2_CLASS_HASH() -> ClassHash { + SnakeERC20Mock::TEST_CLASS_HASH.try_into().unwrap() +} + // // Setup // -fn setup_dispatcher_with_event() -> ERC20ABIDispatcher { +fn setup_dispatcher_with_event() -> ERC20UpgradeableABIDispatcher { let mut calldata = array![]; calldata.append_serde(NAME()); calldata.append_serde(SYMBOL()); calldata.append_serde(SUPPLY); calldata.append_serde(OWNER()); + calldata.append_serde(OWNER()); - let address = utils::deploy(ERC20::TEST_CLASS_HASH, calldata); - ERC20ABIDispatcher { contract_address: address } + let address = utils::deploy(ERC20Upgradeable::TEST_CLASS_HASH, calldata); + ERC20UpgradeableABIDispatcher { contract_address: address } } -fn setup_dispatcher() -> ERC20ABIDispatcher { +fn setup_dispatcher() -> ERC20UpgradeableABIDispatcher { let dispatcher = setup_dispatcher_with_event(); - utils::drop_event(dispatcher.contract_address); + utils::drop_event(dispatcher.contract_address); // Ownable `OwnershipTransferred` + utils::drop_event(dispatcher.contract_address); // ERC20 `Transfer` dispatcher } @@ -41,6 +56,9 @@ fn setup_dispatcher() -> ERC20ABIDispatcher { fn test_constructor() { let mut dispatcher = setup_dispatcher_with_event(); + assert_eq!(dispatcher.owner(), OWNER()); + assert_event_ownership_transferred(dispatcher.contract_address, ZERO(), OWNER()); + assert_eq!(dispatcher.name(), NAME()); assert_eq!(dispatcher.symbol(), SYMBOL()); assert_eq!(dispatcher.decimals(), DECIMALS); @@ -289,49 +307,190 @@ fn test_transferFrom_from_zero_address() { } // -// Helpers +// transfer_ownership & transferOwnership // -fn assert_event_approval( - contract: ContractAddress, owner: ContractAddress, spender: ContractAddress, value: u256 -) { - let event = utils::pop_log::(contract).unwrap(); - let expected = ERC20Component::Event::Approval(Approval { owner, spender, value }); - assert!(event == expected); - - // Check indexed keys - let mut indexed_keys = array![]; - indexed_keys.append_serde(selector!("Approval")); - indexed_keys.append_serde(owner); - indexed_keys.append_serde(spender); - utils::assert_indexed_keys(event, indexed_keys.span()) -} - -fn assert_only_event_approval( - contract: ContractAddress, owner: ContractAddress, spender: ContractAddress, value: u256 -) { - assert_event_approval(contract, owner, spender, value); - utils::assert_no_events_left(contract); -} - -fn assert_event_transfer( - contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 -) { - let event = utils::pop_log::(contract).unwrap(); - let expected = ERC20Component::Event::Transfer(Transfer { from, to, value }); - assert!(event == expected); - - // Check indexed keys - let mut indexed_keys = array![]; - indexed_keys.append_serde(selector!("Transfer")); - indexed_keys.append_serde(from); - indexed_keys.append_serde(to); - utils::assert_indexed_keys(event, indexed_keys.span()); -} - -fn assert_only_event_transfer( - contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 -) { - assert_event_transfer(contract, from, to, value); - utils::assert_no_events_left(contract); +#[test] +fn test_transfer_ownership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transfer_ownership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transfer_ownership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_zero() { + let mut dispatcher = setup_dispatcher(); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +fn test_transferOwnership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transferOwnership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transferOwnership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_zero() { + let mut dispatcher = setup_dispatcher(); + dispatcher.transferOwnership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transferOwnership(OTHER()); +} + +// +// renounce_ownership & renounceOwnership +// + +#[test] +fn test_renounce_ownership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.renounce_ownership(); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.renounce_ownership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounce_ownership(); +} + +#[test] +fn test_renounceOwnership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.renounceOwnership(); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.renounceOwnership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounceOwnership(); +} + +// +// upgrade +// + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_unauthorized() { + let v1 = setup_dispatcher(); + testing::set_contract_address(OTHER()); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_with_class_hash_zero() { + let v1 = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +fn test_upgraded_event() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.upgrade(v2_class_hash); + + assert_only_event_upgraded(v1.contract_address, v2_class_hash); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_v2_missing_camel_selector() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.upgrade(v2_class_hash); + + let dispatcher = IERC20CamelDispatcher { contract_address: v1.contract_address }; + dispatcher.totalSupply(); +} + +#[test] +fn test_state_persists_after_upgrade() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.transfer(RECIPIENT(), VALUE); + + // Check RECIPIENT balance v1 + let camel_balance = v1.balanceOf(RECIPIENT()); + assert_eq!(camel_balance, VALUE); + + v1.upgrade(v2_class_hash); + + // Check RECIPIENT balance v2 + let v2 = IERC20Dispatcher { contract_address: v1.contract_address }; + let snake_balance = v2.balance_of(RECIPIENT()); + assert_eq!(snake_balance, camel_balance); } diff --git a/src/tests/presets/test_erc721.cairo b/src/tests/presets/test_erc721.cairo index d956ba05e..2edef2fcf 100644 --- a/src/tests/presets/test_erc721.cairo +++ b/src/tests/presets/test_erc721.cairo @@ -1,25 +1,36 @@ -use openzeppelin::account::AccountComponent; use openzeppelin::introspection::interface::ISRC5_ID; -use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; -use openzeppelin::presets::ERC721::InternalImpl; -use openzeppelin::presets::ERC721; +use openzeppelin::presets::ERC721Upgradeable::InternalImpl; +use openzeppelin::presets::ERC721Upgradeable; +use openzeppelin::presets::interfaces::{ + ERC721UpgradeableABIDispatcher, ERC721UpgradeableABIDispatcherTrait +}; +use openzeppelin::tests::access::test_ownable::assert_event_ownership_transferred; use openzeppelin::tests::mocks::account_mocks::{DualCaseAccountMock, CamelAccountMock}; +use openzeppelin::tests::mocks::erc721_mocks::SnakeERC721Mock; use openzeppelin::tests::mocks::erc721_receiver_mocks::{ CamelERC721ReceiverMock, SnakeERC721ReceiverMock }; use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; +use openzeppelin::tests::token::test_erc721::{ + assert_event_transfer, assert_only_event_transfer, assert_event_approval, + assert_event_approval_for_all +}; +use openzeppelin::tests::upgrades::test_upgradeable::assert_only_event_upgraded; use openzeppelin::tests::utils::constants::{ - ZERO, DATA, OWNER, SPENDER, RECIPIENT, OTHER, OPERATOR, PUBKEY, NAME, SYMBOL, BASE_URI + ZERO, DATA, OWNER, SPENDER, RECIPIENT, OTHER, OPERATOR, CLASS_HASH_ZERO, PUBKEY, NAME, SYMBOL, + BASE_URI }; use openzeppelin::tests::utils; -use openzeppelin::token::erc721::ERC721Component::{Approval, ApprovalForAll, Transfer}; +use openzeppelin::token::erc721::ERC721Component::ERC721Impl; use openzeppelin::token::erc721::ERC721Component; -use openzeppelin::token::erc721::interface::ERC721ABI; -use openzeppelin::token::erc721::interface::{ERC721ABIDispatcher, ERC721ABIDispatcherTrait}; +use openzeppelin::token::erc721::interface::{ + IERC721CamelOnlyDispatcher, IERC721CamelOnlyDispatcherTrait +}; +use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait}; use openzeppelin::token::erc721::interface::{IERC721_ID, IERC721_METADATA_ID}; use openzeppelin::utils::serde::SerializedAppend; -use starknet::ContractAddress; use starknet::testing; +use starknet::{ContractAddress, ClassHash}; // Token IDs @@ -30,11 +41,15 @@ const NONEXISTENT: u256 = 9898; const TOKENS_LEN: u256 = 3; +fn V2_CLASS_HASH() -> ClassHash { + SnakeERC721Mock::TEST_CLASS_HASH.try_into().unwrap() +} + // // Setup // -fn setup_dispatcher_with_event() -> ERC721ABIDispatcher { +fn setup_dispatcher_with_event() -> ERC721UpgradeableABIDispatcher { let mut calldata = array![]; let mut token_ids = array![TOKEN_1, TOKEN_2, TOKEN_3]; @@ -46,14 +61,16 @@ fn setup_dispatcher_with_event() -> ERC721ABIDispatcher { calldata.append_serde(BASE_URI()); calldata.append_serde(OWNER()); calldata.append_serde(token_ids); + calldata.append_serde(OWNER()); - let address = utils::deploy(ERC721::TEST_CLASS_HASH, calldata); - ERC721ABIDispatcher { contract_address: address } + let address = utils::deploy(ERC721Upgradeable::TEST_CLASS_HASH, calldata); + ERC721UpgradeableABIDispatcher { contract_address: address } } -fn setup_dispatcher() -> ERC721ABIDispatcher { +fn setup_dispatcher() -> ERC721UpgradeableABIDispatcher { let dispatcher = setup_dispatcher_with_event(); - utils::drop_events(dispatcher.contract_address, TOKENS_LEN.try_into().unwrap()); + // `OwnershipTransferred` + `Transfer`s + utils::drop_events(dispatcher.contract_address, TOKENS_LEN.try_into().unwrap() + 1); dispatcher } @@ -81,7 +98,7 @@ fn setup_camel_account() -> ContractAddress { #[test] fn test__mint_assets() { - let mut state = ERC721::contract_state_for_testing(); + let mut state = ERC721Upgradeable::contract_state_for_testing(); let mut token_ids = array![TOKEN_1, TOKEN_2, TOKEN_3].span(); state._mint_assets(OWNER(), token_ids); @@ -134,6 +151,7 @@ fn test_constructor_events() { let dispatcher = setup_dispatcher_with_event(); let mut tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; + assert_event_ownership_transferred(dispatcher.contract_address, ZERO(), OWNER()); loop { let token = tokens.pop_front().unwrap(); if tokens.len() == 0 { @@ -950,12 +968,205 @@ fn test_safeTransferFrom_unauthorized() { dispatcher.safeTransferFrom(OWNER(), RECIPIENT(), TOKEN_1, DATA(true)); } +// +// transfer_ownership & transferOwnership +// + +#[test] +fn test_transfer_ownership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transfer_ownership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transfer_ownership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transfer_ownership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transfer_ownership(OTHER()); +} + +#[test] +fn test_transferOwnership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transferOwnership(OTHER()); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), OTHER()); + assert_eq!(dispatcher.owner(), OTHER()); +} + +#[test] +#[should_panic(expected: ('New owner is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transferOwnership(ZERO()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.transferOwnership(OTHER()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_transferOwnership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.transferOwnership(OTHER()); +} + +// +// renounce_ownership & renounceOwnership +// + +#[test] +fn test_renounce_ownership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.renounce_ownership(); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.renounce_ownership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounce_ownership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounce_ownership(); +} + +#[test] +fn test_renounceOwnership() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.renounceOwnership(); + + assert_event_ownership_transferred(dispatcher.contract_address, OWNER(), ZERO()); + assert!(dispatcher.owner().is_zero()); +} + +#[test] +#[should_panic(expected: ('Caller is the zero address', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(ZERO()); + dispatcher.renounceOwnership(); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED'))] +fn test_renounceOwnership_from_nonowner() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OTHER()); + dispatcher.renounceOwnership(); +} + +// +// upgrade +// + +#[test] +#[should_panic(expected: ('Caller is not the owner', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_unauthorized() { + let v1 = setup_dispatcher(); + testing::set_contract_address(OTHER()); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_with_class_hash_zero() { + let v1 = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + v1.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +fn test_upgraded_event() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.upgrade(v2_class_hash); + + assert_only_event_upgraded(v1.contract_address, v2_class_hash); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_v2_missing_camel_selector() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.upgrade(v2_class_hash); + + let dispatcher = IERC721CamelOnlyDispatcher { contract_address: v1.contract_address }; + dispatcher.ownerOf(TOKEN_1); +} + +#[test] +fn test_state_persists_after_upgrade() { + let v1 = setup_dispatcher(); + let v2_class_hash = V2_CLASS_HASH(); + + testing::set_contract_address(OWNER()); + v1.transferFrom(OWNER(), RECIPIENT(), TOKEN_1); + + // Check RECIPIENT balance v1 + let camel_balance = v1.balanceOf(RECIPIENT()); + assert_eq!(camel_balance, 1); + + v1.upgrade(v2_class_hash); + + // Check RECIPIENT balance v2 + let v2 = IERC721Dispatcher { contract_address: v1.contract_address }; + let snake_balance = v2.balance_of(RECIPIENT()); + assert_eq!(snake_balance, camel_balance); +} + // // Helpers // fn assert_state_before_transfer( - dispatcher: ERC721ABIDispatcher, + dispatcher: ERC721UpgradeableABIDispatcher, owner: ContractAddress, recipient: ContractAddress, token_id: u256 @@ -966,7 +1177,7 @@ fn assert_state_before_transfer( } fn assert_state_after_transfer( - dispatcher: ERC721ABIDispatcher, + dispatcher: ERC721UpgradeableABIDispatcher, owner: ContractAddress, recipient: ContractAddress, token_id: u256 @@ -981,66 +1192,11 @@ fn assert_state_after_transfer( } fn assert_state_transfer_to_self( - dispatcher: ERC721ABIDispatcher, target: ContractAddress, token_id: u256, token_balance: u256 + dispatcher: ERC721UpgradeableABIDispatcher, + target: ContractAddress, + token_id: u256, + token_balance: u256 ) { assert_eq!(dispatcher.owner_of(token_id), target); assert_eq!(dispatcher.balance_of(target), token_balance); } - -fn assert_event_approval_for_all( - contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool -) { - let event = utils::pop_log::(contract).unwrap(); - let expected = ERC721Component::Event::ApprovalForAll( - ApprovalForAll { owner, operator, approved, } - ); - assert!(event == expected); - utils::assert_no_events_left(contract); - - // Check indexed keys - let mut indexed_keys = array![]; - indexed_keys.append_serde(selector!("ApprovalForAll")); - indexed_keys.append_serde(owner); - indexed_keys.append_serde(operator); - utils::assert_indexed_keys(event, indexed_keys.span()); -} - -fn assert_event_approval( - contract: ContractAddress, owner: ContractAddress, approved: ContractAddress, token_id: u256 -) { - let event = utils::pop_log::(contract).unwrap(); - let expected = ERC721Component::Event::Approval(Approval { owner, approved, token_id }); - assert!(event == expected); - utils::assert_no_events_left(contract); - - // Check indexed keys - let mut indexed_keys = array![]; - indexed_keys.append_serde(selector!("Approval")); - indexed_keys.append_serde(owner); - indexed_keys.append_serde(approved); - indexed_keys.append_serde(token_id); - utils::assert_indexed_keys(event, indexed_keys.span()); -} - -fn assert_event_transfer( - contract: ContractAddress, from: ContractAddress, to: ContractAddress, token_id: u256 -) { - let event = utils::pop_log::(contract).unwrap(); - let expected = ERC721Component::Event::Transfer(Transfer { from, to, token_id }); - assert!(event == expected); - - // Check indexed keys - let mut indexed_keys = array![]; - indexed_keys.append_serde(selector!("Transfer")); - indexed_keys.append_serde(from); - indexed_keys.append_serde(to); - indexed_keys.append_serde(token_id); - utils::assert_indexed_keys(event, indexed_keys.span()); -} - -fn assert_only_event_transfer( - contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 -) { - assert_event_transfer(contract, from, to, value); - utils::assert_no_events_left(contract); -} diff --git a/src/tests/presets/test_eth_account.cairo b/src/tests/presets/test_eth_account.cairo index e4d71a23f..736af2114 100644 --- a/src/tests/presets/test_eth_account.cairo +++ b/src/tests/presets/test_eth_account.cairo @@ -1,13 +1,12 @@ -use core::serde::Serde; -use core::traits::TryInto; -use openzeppelin::account::EthAccountComponent::{OwnerAdded, OwnerRemoved}; use openzeppelin::account::interface::ISRC6_ID; -use openzeppelin::account::interface::{EthAccountABIDispatcherTrait, EthAccountABIDispatcher}; use openzeppelin::account::utils::secp256k1::{ DebugSecp256k1Point, Secp256k1PointSerde, Secp256k1PointPartialEq }; use openzeppelin::introspection::interface::ISRC5_ID; use openzeppelin::presets::EthAccountUpgradeable; +use openzeppelin::presets::interfaces::{ + EthAccountUpgradeableABIDispatcher, EthAccountUpgradeableABIDispatcherTrait +}; use openzeppelin::tests::account::test_eth_account::{ assert_only_event_owner_added, assert_event_owner_removed }; @@ -23,7 +22,6 @@ use openzeppelin::tests::utils::constants::{ }; use openzeppelin::tests::utils; use openzeppelin::token::erc20::interface::IERC20DispatcherTrait; -use openzeppelin::upgrades::interface::{IUpgradeableDispatcherTrait, IUpgradeableDispatcher}; use openzeppelin::utils::selectors; use openzeppelin::utils::serde::SerializedAppend; use starknet::account::Call; @@ -43,17 +41,19 @@ fn V2_CLASS_HASH() -> ClassHash { // Setup // -fn setup_dispatcher() -> EthAccountABIDispatcher { +fn setup_dispatcher() -> EthAccountUpgradeableABIDispatcher { let mut calldata = array![]; calldata.append_serde(ETH_PUBKEY()); let target = utils::deploy(CLASS_HASH(), calldata); utils::drop_event(target); - EthAccountABIDispatcher { contract_address: target } + EthAccountUpgradeableABIDispatcher { contract_address: target } } -fn setup_dispatcher_with_data(data: Option<@SignedTransactionData>) -> EthAccountABIDispatcher { +fn setup_dispatcher_with_data( + data: Option<@SignedTransactionData> +) -> EthAccountUpgradeableABIDispatcher { testing::set_version(MIN_TRANSACTION_VERSION); let mut calldata = array![]; @@ -70,17 +70,7 @@ fn setup_dispatcher_with_data(data: Option<@SignedTransactionData>) -> EthAccoun calldata.append_serde(ETH_PUBKEY()); } let address = utils::deploy(CLASS_HASH(), calldata); - EthAccountABIDispatcher { contract_address: address } -} - -fn setup_upgradeable() -> IUpgradeableDispatcher { - let mut calldata = array![]; - calldata.append_serde(ETH_PUBKEY()); - - let target = utils::deploy(CLASS_HASH(), calldata); - utils::drop_event(target); - - IUpgradeableDispatcher { contract_address: target } + EthAccountUpgradeableABIDispatcher { contract_address: address } } // @@ -159,7 +149,7 @@ fn test_setPublicKey_different_account() { // is_valid_signature & isValidSignature // -fn is_valid_sig_dispatcher() -> (EthAccountABIDispatcher, felt252, Array) { +fn is_valid_sig_dispatcher() -> (EthAccountUpgradeableABIDispatcher, felt252, Array) { let dispatcher = setup_dispatcher(); let data = SIGNED_TX_DATA(); @@ -448,14 +438,14 @@ fn test_account_called_from_contract() { #[test] #[should_panic(expected: ('EthAccount: unauthorized', 'ENTRYPOINT_FAILED',))] fn test_upgrade_access_control() { - let v1 = setup_upgradeable(); + let v1 = setup_dispatcher(); v1.upgrade(CLASS_HASH_ZERO()); } #[test] #[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] fn test_upgrade_with_class_hash_zero() { - let v1 = setup_upgradeable(); + let v1 = setup_dispatcher(); set_contract_and_caller(v1.contract_address); v1.upgrade(CLASS_HASH_ZERO()); @@ -463,35 +453,35 @@ fn test_upgrade_with_class_hash_zero() { #[test] fn test_upgraded_event() { - let v1 = setup_upgradeable(); + let v1 = setup_dispatcher(); let v2_class_hash = V2_CLASS_HASH(); set_contract_and_caller(v1.contract_address); v1.upgrade(v2_class_hash); - assert_only_event_upgraded(v2_class_hash, v1.contract_address); + assert_only_event_upgraded(v1.contract_address, v2_class_hash); } #[test] #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] fn test_v2_missing_camel_selector() { - let v1 = setup_upgradeable(); + let v1 = setup_dispatcher(); let v2_class_hash = V2_CLASS_HASH(); set_contract_and_caller(v1.contract_address); v1.upgrade(v2_class_hash); - let dispatcher = EthAccountABIDispatcher { contract_address: v1.contract_address }; + let dispatcher = EthAccountUpgradeableABIDispatcher { contract_address: v1.contract_address }; dispatcher.getPublicKey(); } #[test] fn test_state_persists_after_upgrade() { - let v1 = setup_upgradeable(); + let v1 = setup_dispatcher(); let v2_class_hash = V2_CLASS_HASH(); set_contract_and_caller(v1.contract_address); - let dispatcher = EthAccountABIDispatcher { contract_address: v1.contract_address }; + let dispatcher = EthAccountUpgradeableABIDispatcher { contract_address: v1.contract_address }; let (point, _) = get_points(); diff --git a/src/tests/token/test_erc721.cairo b/src/tests/token/test_erc721.cairo index 2eae699dc..4b3590f53 100644 --- a/src/tests/token/test_erc721.cairo +++ b/src/tests/token/test_erc721.cairo @@ -205,7 +205,7 @@ fn test_approve_from_owner() { testing::set_caller_address(OWNER()); state.approve(SPENDER(), TOKEN_ID); - assert_only_event_approval(OWNER(), SPENDER(), TOKEN_ID); + assert_only_event_approval(ZERO(), OWNER(), SPENDER(), TOKEN_ID); let approved = state.get_approved(TOKEN_ID); assert_eq!(approved, SPENDER()); @@ -221,7 +221,7 @@ fn test_approve_from_operator() { testing::set_caller_address(OPERATOR()); state.approve(SPENDER(), TOKEN_ID); - assert_only_event_approval(OWNER(), SPENDER(), TOKEN_ID); + assert_only_event_approval(ZERO(), OWNER(), SPENDER(), TOKEN_ID); let approved = state.get_approved(TOKEN_ID); assert_eq!(approved, SPENDER()); @@ -256,7 +256,7 @@ fn test_approve_nonexistent() { fn test__approve() { let mut state = setup(); state._approve(SPENDER(), TOKEN_ID); - assert_only_event_approval(OWNER(), SPENDER(), TOKEN_ID); + assert_only_event_approval(ZERO(), OWNER(), SPENDER(), TOKEN_ID); let approved = state.get_approved(TOKEN_ID); assert_eq!(approved, SPENDER()); @@ -289,13 +289,13 @@ fn test_set_approval_for_all() { assert!(not_approved_for_all); state.set_approval_for_all(OPERATOR(), true); - assert_only_event_approval_for_all(OWNER(), OPERATOR(), true); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), true); let is_approved_for_all = state.is_approved_for_all(OWNER(), OPERATOR()); assert!(is_approved_for_all); state.set_approval_for_all(OPERATOR(), false); - assert_only_event_approval_for_all(OWNER(), OPERATOR(), false); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), false); let not_approved_for_all = !state.is_approved_for_all(OWNER(), OPERATOR()); assert!(not_approved_for_all); @@ -325,13 +325,13 @@ fn test__set_approval_for_all() { assert!(not_approved_for_all); state._set_approval_for_all(OWNER(), OPERATOR(), true); - assert_only_event_approval_for_all(OWNER(), OPERATOR(), true); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), true); let is_approved_for_all = state.is_approved_for_all(OWNER(), OPERATOR()); assert!(is_approved_for_all); state._set_approval_for_all(OWNER(), OPERATOR(), false); - assert_only_event_approval_for_all(OWNER(), OPERATOR(), false); + assert_only_event_approval_for_all(ZERO(), OWNER(), OPERATOR(), false); let not_approved_for_all = !state.is_approved_for_all(OWNER(), OPERATOR()); assert!(not_approved_for_all); @@ -372,7 +372,7 @@ fn test_transfer_from_owner() { testing::set_caller_address(owner); state.transfer_from(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -394,7 +394,7 @@ fn test_transferFrom_owner() { testing::set_caller_address(owner); state.transferFrom(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -439,7 +439,7 @@ fn test_transfer_from_to_owner() { testing::set_caller_address(OWNER()); state.transfer_from(OWNER(), OWNER(), TOKEN_ID); - assert_only_event_transfer(OWNER(), OWNER(), TOKEN_ID); + assert_only_event_transfer(ZERO(), OWNER(), OWNER(), TOKEN_ID); assert_eq!(state.owner_of(TOKEN_ID), OWNER()); assert_eq!(state.balance_of(OWNER()), 1); @@ -454,7 +454,7 @@ fn test_transferFrom_to_owner() { testing::set_caller_address(OWNER()); state.transferFrom(OWNER(), OWNER(), TOKEN_ID); - assert_only_event_transfer(OWNER(), OWNER(), TOKEN_ID); + assert_only_event_transfer(ZERO(), OWNER(), OWNER(), TOKEN_ID); assert_eq!(state.owner_of(TOKEN_ID), OWNER()); assert_eq!(state.balance_of(OWNER()), 1); @@ -474,7 +474,7 @@ fn test_transfer_from_approved() { testing::set_caller_address(OPERATOR()); state.transfer_from(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -493,7 +493,7 @@ fn test_transferFrom_approved() { testing::set_caller_address(OPERATOR()); state.transferFrom(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -513,7 +513,7 @@ fn test_transfer_from_approved_for_all() { testing::set_caller_address(OPERATOR()); state.transfer_from(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -533,7 +533,7 @@ fn test_transferFrom_approved_for_all() { testing::set_caller_address(OPERATOR()); state.transferFrom(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -569,7 +569,7 @@ fn test_safe_transfer_from_to_account() { testing::set_caller_address(owner); state.safe_transfer_from(owner, account, token_id, DATA(true)); - assert_only_event_transfer(owner, account, token_id); + assert_only_event_transfer(ZERO(), owner, account, token_id); assert_state_after_transfer(owner, account, token_id); } @@ -585,7 +585,7 @@ fn test_safeTransferFrom_to_account() { testing::set_caller_address(owner); state.safeTransferFrom(owner, account, token_id, DATA(true)); - assert_only_event_transfer(owner, account, token_id); + assert_only_event_transfer(ZERO(), owner, account, token_id); assert_state_after_transfer(owner, account, token_id); } @@ -601,7 +601,7 @@ fn test_safe_transfer_from_to_account_camel() { testing::set_caller_address(owner); state.safe_transfer_from(owner, account, token_id, DATA(true)); - assert_only_event_transfer(owner, account, token_id); + assert_only_event_transfer(ZERO(), owner, account, token_id); assert_state_after_transfer(owner, account, token_id); } @@ -617,7 +617,7 @@ fn test_safeTransferFrom_to_account_camel() { testing::set_caller_address(owner); state.safeTransferFrom(owner, account, token_id, DATA(true)); - assert_only_event_transfer(owner, account, token_id); + assert_only_event_transfer(ZERO(), owner, account, token_id); assert_state_after_transfer(owner, account, token_id); } @@ -633,7 +633,7 @@ fn test_safe_transfer_from_to_receiver() { testing::set_caller_address(owner); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -649,7 +649,7 @@ fn test_safeTransferFrom_to_receiver() { testing::set_caller_address(owner); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -665,7 +665,7 @@ fn test_safe_transfer_from_to_receiver_camel() { testing::set_caller_address(owner); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -681,7 +681,7 @@ fn test_safeTransferFrom_to_receiver_camel() { testing::set_caller_address(owner); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -802,7 +802,7 @@ fn test_safe_transfer_from_to_owner() { testing::set_caller_address(owner); state.safe_transfer_from(owner, owner, token_id, DATA(true)); - assert_only_event_transfer(owner, owner, token_id); + assert_only_event_transfer(ZERO(), owner, owner, token_id); assert_eq!(state.owner_of(token_id), owner); assert_eq!(state.balance_of(owner), 1); @@ -822,7 +822,7 @@ fn test_safeTransferFrom_to_owner() { testing::set_caller_address(owner); state.safeTransferFrom(owner, owner, token_id, DATA(true)); - assert_only_event_transfer(owner, owner, token_id); + assert_only_event_transfer(ZERO(), owner, owner, token_id); assert_eq!(state.owner_of(token_id), owner); assert_eq!(state.balance_of(owner), 1); @@ -842,7 +842,7 @@ fn test_safe_transfer_from_to_owner_camel() { testing::set_caller_address(owner); state.safe_transfer_from(owner, owner, token_id, DATA(true)); - assert_only_event_transfer(owner, owner, token_id); + assert_only_event_transfer(ZERO(), owner, owner, token_id); assert_eq!(state.owner_of(token_id), owner); assert_eq!(state.balance_of(owner), 1); @@ -862,7 +862,7 @@ fn test_safeTransferFrom_to_owner_camel() { testing::set_caller_address(owner); state.safeTransferFrom(owner, owner, token_id, DATA(true)); - assert_only_event_transfer(owner, owner, token_id); + assert_only_event_transfer(ZERO(), owner, owner, token_id); assert_eq!(state.owner_of(token_id), owner); assert_eq!(state.balance_of(owner), 1); @@ -883,7 +883,7 @@ fn test_safe_transfer_from_approved() { testing::set_caller_address(OPERATOR()); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -903,7 +903,7 @@ fn test_safeTransferFrom_approved() { testing::set_caller_address(OPERATOR()); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -923,7 +923,7 @@ fn test_safe_transfer_from_approved_camel() { testing::set_caller_address(OPERATOR()); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -943,7 +943,7 @@ fn test_safeTransferFrom_approved_camel() { testing::set_caller_address(OPERATOR()); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -963,7 +963,7 @@ fn test_safe_transfer_from_approved_for_all() { testing::set_caller_address(OPERATOR()); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -983,7 +983,7 @@ fn test_safeTransferFrom_approved_for_all() { testing::set_caller_address(OPERATOR()); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -1003,7 +1003,7 @@ fn test_safe_transfer_from_approved_for_all_camel() { testing::set_caller_address(OPERATOR()); state.safe_transfer_from(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -1023,7 +1023,7 @@ fn test_safeTransferFrom_approved_for_all_camel() { testing::set_caller_address(OPERATOR()); state.safeTransferFrom(owner, receiver, token_id, DATA(true)); - assert_only_event_transfer(owner, receiver, token_id); + assert_only_event_transfer(ZERO(), owner, receiver, token_id); assert_state_after_transfer(owner, receiver, token_id); } @@ -1058,7 +1058,7 @@ fn test__transfer() { assert_state_before_transfer(owner, recipient, token_id); state._transfer(owner, recipient, token_id); - assert_only_event_transfer(owner, recipient, token_id); + assert_only_event_transfer(ZERO(), owner, recipient, token_id); assert_state_after_transfer(owner, recipient, token_id); } @@ -1096,7 +1096,7 @@ fn test__mint() { assert_state_before_mint(recipient); state._mint(recipient, TOKEN_ID); - assert_only_event_transfer(ZERO(), recipient, token_id); + assert_only_event_transfer(ZERO(), ZERO(), recipient, token_id); assert_state_after_mint(recipient, token_id); } @@ -1127,7 +1127,7 @@ fn test__safe_mint_to_receiver() { assert_state_before_mint(recipient); state._safe_mint(recipient, token_id, DATA(true)); - assert_only_event_transfer(ZERO(), recipient, token_id); + assert_only_event_transfer(ZERO(), ZERO(), recipient, token_id); assert_state_after_mint(recipient, token_id); } @@ -1140,7 +1140,7 @@ fn test__safe_mint_to_receiver_camel() { assert_state_before_mint(recipient); state._safe_mint(recipient, token_id, DATA(true)); - assert_only_event_transfer(ZERO(), recipient, token_id); + assert_only_event_transfer(ZERO(), ZERO(), recipient, token_id); assert_state_after_mint(recipient, token_id); } @@ -1153,7 +1153,7 @@ fn test__safe_mint_to_account() { assert_state_before_mint(account); state._safe_mint(account, token_id, DATA(true)); - assert_only_event_transfer(ZERO(), account, token_id); + assert_only_event_transfer(ZERO(), ZERO(), account, token_id); assert_state_after_mint(account, token_id); } @@ -1166,7 +1166,7 @@ fn test__safe_mint_to_account_camel() { assert_state_before_mint(account); state._safe_mint(account, token_id, DATA(true)); - assert_only_event_transfer(ZERO(), account, token_id); + assert_only_event_transfer(ZERO(), ZERO(), account, token_id); assert_state_after_mint(account, token_id); } @@ -1237,7 +1237,7 @@ fn test__burn() { assert_eq!(state.get_approved(TOKEN_ID), OTHER()); state._burn(TOKEN_ID); - assert_only_event_transfer(OWNER(), ZERO(), TOKEN_ID); + assert_only_event_transfer(ZERO(), OWNER(), ZERO(), TOKEN_ID); assert_eq!(state.ERC721_owners.read(TOKEN_ID), ZERO()); assert_eq!(state.balance_of(OWNER()), 0); @@ -1317,15 +1317,14 @@ fn assert_state_after_mint(recipient: ContractAddress, token_id: u256) { assert!(state.get_approved(token_id).is_zero()); } -fn assert_only_event_approval_for_all( - owner: ContractAddress, operator: ContractAddress, approved: bool +fn assert_event_approval_for_all( + contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool ) { - let event = utils::pop_log::(ZERO()).unwrap(); + let event = utils::pop_log::(contract).unwrap(); let expected = ERC721Component::Event::ApprovalForAll( ApprovalForAll { owner, operator, approved } ); assert!(event == expected); - utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; @@ -1335,11 +1334,19 @@ fn assert_only_event_approval_for_all( utils::assert_indexed_keys(event, indexed_keys.span()); } -fn assert_only_event_approval(owner: ContractAddress, approved: ContractAddress, token_id: u256) { - let event = utils::pop_log::(ZERO()).unwrap(); +fn assert_only_event_approval_for_all( + contract: ContractAddress, owner: ContractAddress, operator: ContractAddress, approved: bool +) { + assert_event_approval_for_all(contract, owner, operator, approved); + utils::assert_no_events_left(contract); +} + +fn assert_event_approval( + contract: ContractAddress, owner: ContractAddress, approved: ContractAddress, token_id: u256 +) { + let event = utils::pop_log::(contract).unwrap(); let expected = ERC721Component::Event::Approval(Approval { owner, approved, token_id }); assert!(event == expected); - utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; @@ -1350,11 +1357,19 @@ fn assert_only_event_approval(owner: ContractAddress, approved: ContractAddress, utils::assert_indexed_keys(event, indexed_keys.span()); } -fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, token_id: u256) { - let event = testing::pop_log::(ZERO()).unwrap(); +fn assert_only_event_approval( + contract: ContractAddress, owner: ContractAddress, approved: ContractAddress, token_id: u256 +) { + assert_event_approval(contract, owner, approved, token_id); + utils::assert_no_events_left(contract); +} + +fn assert_event_transfer( + contract: ContractAddress, from: ContractAddress, to: ContractAddress, token_id: u256 +) { + let event = testing::pop_log::(contract).unwrap(); let expected = ERC721Component::Event::Transfer(Transfer { from, to, token_id }); assert!(event == expected); - utils::assert_no_events_left(ZERO()); // Check indexed keys let mut indexed_keys = array![]; @@ -1364,3 +1379,10 @@ fn assert_only_event_transfer(from: ContractAddress, to: ContractAddress, token_ indexed_keys.append_serde(token_id); utils::assert_indexed_keys(event, indexed_keys.span()); } + +fn assert_only_event_transfer( + contract: ContractAddress, from: ContractAddress, to: ContractAddress, token_id: u256 +) { + assert_event_transfer(contract, from, to, token_id); + utils::assert_no_events_left(contract); +} diff --git a/src/tests/upgrades/test_upgradeable.cairo b/src/tests/upgrades/test_upgradeable.cairo index fdad7c8e3..4f3a1b8f7 100644 --- a/src/tests/upgrades/test_upgradeable.cairo +++ b/src/tests/upgrades/test_upgradeable.cairo @@ -43,7 +43,7 @@ fn test_upgraded_event() { let v1 = deploy_v1(); v1.upgrade(V2_CLASS_HASH()); - assert_only_event_upgraded(V2_CLASS_HASH(), v1.contract_address); + assert_only_event_upgraded(v1.contract_address, V2_CLASS_HASH()); } #[test] @@ -87,13 +87,13 @@ fn test_remove_selector_fails_in_v2() { // Helpers // -fn assert_event_upgraded(class_hash: ClassHash, contract: ContractAddress) { +fn assert_event_upgraded(contract: ContractAddress, class_hash: ClassHash) { let event = utils::pop_log::(contract).unwrap(); let expected = UpgradeableComponent::Event::Upgraded(Upgraded { class_hash }); assert!(event == expected); } -fn assert_only_event_upgraded(class_hash: ClassHash, contract: ContractAddress) { - assert_event_upgraded(class_hash, contract); +fn assert_only_event_upgraded(contract: ContractAddress, class_hash: ClassHash) { + assert_event_upgraded(contract, class_hash); utils::assert_no_events_left(ZERO()); } diff --git a/src/upgrades/upgradeable.cairo b/src/upgrades/upgradeable.cairo index 1afcaa1c6..c7ea59ea8 100644 --- a/src/upgrades/upgradeable.cairo +++ b/src/upgrades/upgradeable.cairo @@ -36,7 +36,7 @@ mod UpgradeableComponent { /// /// Requirements: /// - /// - `new_class_hash` is not the zero address. + /// - `new_class_hash` is not zero. /// /// Emits an `Upgraded` event. fn _upgrade(ref self: ComponentState, new_class_hash: ClassHash) {