From 65894fbee925b967ab996a69ddf80e3dd00bbdf0 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 2 Jul 2024 10:46:27 -0400 Subject: [PATCH 01/12] Remove unnecessary `mut`, minor fixes (#1032) * clean up code * change array syntax * fix fmt --- docs/modules/ROOT/pages/udc.adoc | 2 +- src/access/ownable/dual_ownable.cairo | 2 +- src/account/utils.cairo | 2 +- src/presets/universal_deployer.cairo | 3 +-- src/tests/security/test_reentrancyguard.cairo | 2 +- src/tests/token/erc1155/common.cairo | 4 ++-- .../token/erc1155/test_dual1155_receiver.cairo | 14 +++++--------- src/tests/token/erc721/test_dual721_receiver.cairo | 14 +++++--------- src/token/erc20/dual20.cairo | 2 +- 9 files changed, 18 insertions(+), 27 deletions(-) diff --git a/docs/modules/ROOT/pages/udc.adoc b/docs/modules/ROOT/pages/udc.adoc index d0af0dd0c..fd30ce729 100644 --- a/docs/modules/ROOT/pages/udc.adoc +++ b/docs/modules/ROOT/pages/udc.adoc @@ -56,7 +56,7 @@ fn deploy() -> ContractAddress { >(); let salt = 1234567879; let from_zero = false; - let mut calldata = array![]; + let calldata = array![]; // The UDC returns the deployed contract address dispatcher.deploy_contract(class_hash, salt, from_zero, calldata.span()) diff --git a/src/access/ownable/dual_ownable.cairo b/src/access/ownable/dual_ownable.cairo index d4ae86aa2..7c3052c81 100644 --- a/src/access/ownable/dual_ownable.cairo +++ b/src/access/ownable/dual_ownable.cairo @@ -42,7 +42,7 @@ impl DualCaseOwnableImpl of DualCaseOwnableTrait { } fn renounce_ownership(self: @DualCaseOwnable) { - let mut args = array![]; + let args = array![]; try_selector_with_fallback( *self.contract_address, diff --git a/src/account/utils.cairo b/src/account/utils.cairo index 8dfe7b49d..a1209c732 100644 --- a/src/account/utils.cairo +++ b/src/account/utils.cairo @@ -15,7 +15,7 @@ pub const QUERY_OFFSET: u256 = 0x100000000000000000000000000000000; pub const QUERY_VERSION: u256 = 0x100000000000000000000000000000001; pub fn execute_calls(mut calls: Array) -> Array> { - let mut res = ArrayTrait::new(); + let mut res = array![]; loop { match calls.pop_front() { Option::Some(call) => { diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index 2d4f84070..d4a928ca9 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -45,8 +45,7 @@ pub(crate) mod UniversalDeployer { let deployer: ContractAddress = get_caller_address(); let mut _salt: felt252 = salt; if !from_zero { - let mut hash_state = PoseidonTrait::new(); - _salt = hash_state.update_with(deployer).update_with(salt).finalize(); + _salt = PoseidonTrait::new().update_with(deployer).update_with(salt).finalize() } let (address, _) = starknet::syscalls::deploy_syscall( diff --git a/src/tests/security/test_reentrancyguard.cairo b/src/tests/security/test_reentrancyguard.cairo index 327f72fa4..b243b4aa4 100644 --- a/src/tests/security/test_reentrancyguard.cairo +++ b/src/tests/security/test_reentrancyguard.cairo @@ -76,7 +76,7 @@ fn test_remote_callback() { let contract = deploy_mock(); // Deploy attacker - let calldata = ArrayTrait::new(); + let calldata = array![]; let attacker_addr = utils::deploy(Attacker::TEST_CLASS_HASH, calldata); contract.count_and_call(attacker_addr); diff --git a/src/tests/token/erc1155/common.cairo b/src/tests/token/erc1155/common.cairo index 3230c45b0..ba7ca499a 100644 --- a/src/tests/token/erc1155/common.cairo +++ b/src/tests/token/erc1155/common.cairo @@ -22,12 +22,12 @@ pub(crate) fn setup_camel_receiver() -> ContractAddress { } pub(crate) fn setup_account() -> ContractAddress { - let mut calldata = array![PUBKEY]; + let calldata = array![PUBKEY]; utils::deploy(SnakeAccountMock::TEST_CLASS_HASH, calldata) } pub(crate) fn setup_account_with_salt(salt: felt252) -> ContractAddress { - let mut calldata = array![PUBKEY]; + let calldata = array![PUBKEY]; utils::deploy_with_salt(SnakeAccountMock::TEST_CLASS_HASH, calldata, salt) } diff --git a/src/tests/token/erc1155/test_dual1155_receiver.cairo b/src/tests/token/erc1155/test_dual1155_receiver.cairo index da0b87bba..d62ab277b 100644 --- a/src/tests/token/erc1155/test_dual1155_receiver.cairo +++ b/src/tests/token/erc1155/test_dual1155_receiver.cairo @@ -21,7 +21,7 @@ use openzeppelin::token::erc1155::interface::{ // fn setup_snake() -> (DualCaseERC1155Receiver, IERC1155ReceiverDispatcher) { - let mut calldata = ArrayTrait::new(); + let calldata = array![]; let target = utils::deploy(SnakeERC1155ReceiverMock::TEST_CLASS_HASH, calldata); ( DualCaseERC1155Receiver { contract_address: target }, @@ -30,7 +30,7 @@ fn setup_snake() -> (DualCaseERC1155Receiver, IERC1155ReceiverDispatcher) { } fn setup_camel() -> (DualCaseERC1155Receiver, IERC1155ReceiverCamelDispatcher) { - let mut calldata = ArrayTrait::new(); + let calldata = array![]; let target = utils::deploy(CamelERC1155ReceiverMock::TEST_CLASS_HASH, calldata); ( DualCaseERC1155Receiver { contract_address: target }, @@ -39,18 +39,14 @@ fn setup_camel() -> (DualCaseERC1155Receiver, IERC1155ReceiverCamelDispatcher) { } fn setup_non_erc1155_receiver() -> DualCaseERC1155Receiver { - let calldata = ArrayTrait::new(); + let calldata = array![]; let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); DualCaseERC1155Receiver { contract_address: target } } fn setup_erc1155_receiver_panic() -> (DualCaseERC1155Receiver, DualCaseERC1155Receiver) { - let snake_target = utils::deploy( - SnakeERC1155ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() - ); - let camel_target = utils::deploy( - CamelERC1155ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() - ); + let snake_target = utils::deploy(SnakeERC1155ReceiverPanicMock::TEST_CLASS_HASH, array![]); + let camel_target = utils::deploy(CamelERC1155ReceiverPanicMock::TEST_CLASS_HASH, array![]); ( DualCaseERC1155Receiver { contract_address: snake_target }, DualCaseERC1155Receiver { contract_address: camel_target } diff --git a/src/tests/token/erc721/test_dual721_receiver.cairo b/src/tests/token/erc721/test_dual721_receiver.cairo index b201b14d3..d6073197d 100644 --- a/src/tests/token/erc721/test_dual721_receiver.cairo +++ b/src/tests/token/erc721/test_dual721_receiver.cairo @@ -23,7 +23,7 @@ use openzeppelin::token::erc721::interface::{ // fn setup_snake() -> (DualCaseERC721Receiver, IERC721ReceiverDispatcher) { - let mut calldata = ArrayTrait::new(); + let mut calldata = array![]; let target = utils::deploy(SnakeERC721ReceiverMock::TEST_CLASS_HASH, calldata); ( DualCaseERC721Receiver { contract_address: target }, @@ -32,7 +32,7 @@ fn setup_snake() -> (DualCaseERC721Receiver, IERC721ReceiverDispatcher) { } fn setup_camel() -> (DualCaseERC721Receiver, IERC721ReceiverCamelDispatcher) { - let mut calldata = ArrayTrait::new(); + let mut calldata = array![]; let target = utils::deploy(CamelERC721ReceiverMock::TEST_CLASS_HASH, calldata); ( DualCaseERC721Receiver { contract_address: target }, @@ -41,18 +41,14 @@ fn setup_camel() -> (DualCaseERC721Receiver, IERC721ReceiverCamelDispatcher) { } fn setup_non_erc721_receiver() -> DualCaseERC721Receiver { - let calldata = ArrayTrait::new(); + let calldata = array![]; let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); DualCaseERC721Receiver { contract_address: target } } fn setup_erc721_receiver_panic() -> (DualCaseERC721Receiver, DualCaseERC721Receiver) { - let snake_target = utils::deploy( - SnakeERC721ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() - ); - let camel_target = utils::deploy( - CamelERC721ReceiverPanicMock::TEST_CLASS_HASH, ArrayTrait::new() - ); + let snake_target = utils::deploy(SnakeERC721ReceiverPanicMock::TEST_CLASS_HASH, array![]); + let camel_target = utils::deploy(CamelERC721ReceiverPanicMock::TEST_CLASS_HASH, array![]); ( DualCaseERC721Receiver { contract_address: snake_target }, DualCaseERC721Receiver { contract_address: camel_target } diff --git a/src/token/erc20/dual20.cairo b/src/token/erc20/dual20.cairo index 54b42697e..6c0f50628 100644 --- a/src/token/erc20/dual20.cairo +++ b/src/token/erc20/dual20.cairo @@ -48,7 +48,7 @@ impl DualCaseERC20Impl of DualCaseERC20Trait { } fn total_supply(self: @DualCaseERC20) -> u256 { - let mut args = array![]; + let args = array![]; try_selector_with_fallback( *self.contract_address, selectors::total_supply, selectors::totalSupply, args.span() ) From 4f4e2e48bd86d99c3ee5ee405140bf1681529a2e Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 5 Jul 2024 12:50:12 +0200 Subject: [PATCH 02/12] Improve SNIP12 examples (#1036) * docs: improve examples * refactor: remove empty line * Update docs/modules/ROOT/pages/guides/snip12.adoc Co-authored-by: Andrew Fleming --------- Co-authored-by: Andrew Fleming --- docs/modules/ROOT/pages/guides/snip12.adoc | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/guides/snip12.adoc b/docs/modules/ROOT/pages/guides/snip12.adoc index 1ad4c134e..fb919e065 100644 --- a/docs/modules/ROOT/pages/guides/snip12.adoc +++ b/docs/modules/ROOT/pages/guides/snip12.adoc @@ -163,9 +163,25 @@ impl SNIP12MetadataImpl of SNIP12Metadata { } ---- -NOTE: These params could be set in the contract constructor, but then two storage reads would be executed every time -a message hash needs to be generated, and this is unnecessary overhead. When Starknet implements immutable storage -set in constructor, that approach will be more efficient. +In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in +some cases we may need to read from storage to get the domain separator values. This can be accomplished even when +the trait is not bounded to the ContractState, like this: + +[,cairo] +---- +use openzeppelin::utils::snip12::SNIP12Metadata; + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + let state = unsafe_new_contract_state(); + + // Some logic to get the name from storage + state.erc20.name().at(0).unwrap().into() + } + + fn version() -> felt252 { 'v1' } +} +---- === 5. Generate the hash. From 6869190bbc3eef90f49651a42e0b44dcc4bec842 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Wed, 10 Jul 2024 12:05:28 +0200 Subject: [PATCH 03/12] Release v0.15.0-rc.0 (#1039) * feat: update CHANGELOG * Bump version to 0.15.0-rc.0 * docs: add missing header * feat: apply review suggestions --------- Co-authored-by: ericnordelo --- CHANGELOG.md | 10 ++++++++++ README.md | 8 ++++---- Scarb.lock | 2 +- Scarb.toml | 2 +- docs/antora.yml | 2 +- docs/modules/ROOT/pages/api/access.adoc | 6 +++--- docs/modules/ROOT/pages/api/account.adoc | 10 +++++----- docs/modules/ROOT/pages/api/erc1155.adoc | 12 ++++++------ docs/modules/ROOT/pages/api/erc20.adoc | 11 ++++++----- docs/modules/ROOT/pages/api/erc721.adoc | 12 ++++++------ docs/modules/ROOT/pages/api/governance.adoc | 2 +- docs/modules/ROOT/pages/api/introspection.adoc | 4 ++-- docs/modules/ROOT/pages/api/security.adoc | 6 +++--- docs/modules/ROOT/pages/api/upgrades.adoc | 4 ++-- docs/modules/ROOT/pages/api/utilities.adoc | 2 +- docs/modules/ROOT/pages/index.adoc | 2 +- docs/modules/ROOT/pages/presets.adoc | 2 +- docs/modules/ROOT/pages/upgrades.adoc | 2 +- docs/modules/ROOT/pages/utils/_class_hashes.adoc | 14 +++++++------- docs/modules/ROOT/pages/wizard.adoc | 2 +- src/access/accesscontrol/dual_accesscontrol.cairo | 2 +- src/access/accesscontrol/interface.cairo | 2 +- src/access/ownable/dual_ownable.cairo | 2 +- src/access/ownable/interface.cairo | 2 +- src/access/ownable/ownable.cairo | 2 +- src/account/account.cairo | 2 +- src/account/dual_account.cairo | 2 +- src/account/dual_eth_account.cairo | 2 +- src/account/eth_account.cairo | 2 +- src/account/interface.cairo | 2 +- src/account/utils.cairo | 2 +- src/account/utils/secp256k1.cairo | 2 +- src/account/utils/signature.cairo | 2 +- src/introspection/interface.cairo | 2 +- src/introspection/src5.cairo | 2 +- src/presets/account.cairo | 2 +- src/presets/erc1155.cairo | 2 +- src/presets/erc20.cairo | 2 +- src/presets/erc721.cairo | 2 +- src/presets/eth_account.cairo | 2 +- src/presets/universal_deployer.cairo | 2 +- src/security/initializable.cairo | 2 +- src/security/pausable.cairo | 2 +- src/security/reentrancyguard.cairo | 2 +- src/token/erc1155/dual1155.cairo | 2 +- src/token/erc1155/dual1155_receiver.cairo | 2 +- src/token/erc1155/erc1155.cairo | 2 +- src/token/erc1155/erc1155_receiver.cairo | 2 +- src/token/erc1155/interface.cairo | 2 +- src/token/erc20/dual20.cairo | 2 +- src/token/erc20/erc20.cairo | 4 ++-- src/token/erc20/extensions/erc20_votes.cairo | 2 +- src/token/erc20/interface.cairo | 2 +- src/token/erc721/dual721.cairo | 2 +- src/token/erc721/dual721_receiver.cairo | 2 +- src/token/erc721/erc721.cairo | 2 +- src/token/erc721/erc721_receiver.cairo | 2 +- src/token/erc721/interface.cairo | 2 +- src/upgrades/interface.cairo | 2 +- src/upgrades/upgradeable.cairo | 2 +- src/utils.cairo | 2 +- src/utils/cryptography/interface.cairo | 2 +- src/utils/cryptography/nonces.cairo | 2 +- src/utils/cryptography/snip12.cairo | 2 +- src/utils/deployments/interface.cairo | 2 +- src/utils/math.cairo | 2 +- src/utils/selectors.cairo | 2 +- src/utils/serde.cairo | 2 +- src/utils/structs/checkpoint.cairo | 2 +- src/utils/structs/storage_array.cairo | 2 +- src/utils/unwrap_and_cast.cairo | 2 +- 71 files changed, 115 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 562be9606..ff13ab1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.15.0-rc.0 (2024-07-8) + +### Changed + +- `Trace`, `Checkpoint`, and `StorageArray` structs made public. + +### Changed (Breaking) + +- Removed `num_checkpoints` and `checkpoints` from `ERC20VotesABI`. + ## 0.14.0 (2024-06-14) ### Changed (Breaking) diff --git a/README.md b/README.md index b5750a91e..beee9ab5a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Edit `scarb.toml` and add: ```toml [dependencies] -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.14.0" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.15.0-rc.0" } ``` Build the project to download it: @@ -105,7 +105,7 @@ mod MyToken { ### Unsupported -[`DualCase` dispatchers](https://docs.openzeppelin.com/contracts-cairo/0.14.0/interfaces#dualcase_dispatchers) rely on Sierra's ability to catch a revert to resume execution. Currently, Starknet live chains (testnets and mainnet) don't implement that behavior. Starknet's testing framework does support it. +[`DualCase` dispatchers](https://docs.openzeppelin.com/contracts-cairo/0.15.0-rc.0/interfaces#dualcase_dispatchers) rely on Sierra's ability to catch a revert to resume execution. Currently, Starknet live chains (testnets and mainnet) don't implement that behavior. Starknet's testing framework does support it. ## Learn @@ -155,8 +155,8 @@ git clone git@github.com:OpenZeppelin/cairo-contracts.git $ cd cairo-contracts $ scarb build -Compiling lib(openzeppelin) openzeppelin v0.14.0 (~/cairo-contracts/Scarb.toml) -Compiling starknet-contract(openzeppelin) openzeppelin v0.14.0 (~/cairo-contracts/Scarb.toml) +Compiling lib(openzeppelin) openzeppelin v0.15.0-rc.0 (~/cairo-contracts/Scarb.toml) +Compiling starknet-contract(openzeppelin) openzeppelin v0.15.0-rc.0 (~/cairo-contracts/Scarb.toml) Finished release target(s) in 16 seconds ``` diff --git a/Scarb.lock b/Scarb.lock index 8e8b73b05..ceb36ef86 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -3,4 +3,4 @@ version = 1 [[package]] name = "openzeppelin" -version = "0.14.0" +version = "0.15.0-rc.0" diff --git a/Scarb.toml b/Scarb.toml index a99d0ba14..b697f2cc3 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,6 +1,6 @@ [package] name = "openzeppelin" -version = "0.14.0" +version = "0.15.0-rc.0" edition = "2023_11" cairo-version = "2.6.4" scarb-version = "2.6.5" diff --git a/docs/antora.yml b/docs/antora.yml index 1e484920d..625262280 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,6 +1,6 @@ name: contracts-cairo title: Contracts for Cairo -version: 0.14.0 +version: 0.15.0-rc.0 nav: - modules/ROOT/nav.adoc asciidoc: diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc index 5338692ce..1b362e728 100644 --- a/docs/modules/ROOT/pages/api/access.adoc +++ b/docs/modules/ROOT/pages/api/access.adoc @@ -20,7 +20,7 @@ assigned each to multiple accounts. [.contract] [[OwnableComponent]] -=== `++OwnableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/access/ownable/ownable.cairo[{github-icon},role=heading-link] +=== `++OwnableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/access/ownable/ownable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::access::ownable::OwnableComponent; @@ -267,7 +267,7 @@ Emitted when the ownership is transferred. [.contract] [[IAccessControl]] -=== `++IAccessControl++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/access/accesscontrol/interface.cairo[{github-icon},role=heading-link] +=== `++IAccessControl++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/access/accesscontrol/interface.cairo[{github-icon},role=heading-link] :grant_role: xref:#IAccessControl-grant_role[grant_role] :revoke_role: xref:#IAccessControl-revoke_role[revoke_role] @@ -400,7 +400,7 @@ Emitted when `account` is revoked `role`. [.contract] [[AccessControlComponent]] -=== `++AccessControlComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/access/accesscontrol/accesscontrol.cairo[{github-icon},role=heading-link] +=== `++AccessControlComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/access/accesscontrol/accesscontrol.cairo[{github-icon},role=heading-link] :assert_only_role: xref:#AccessControlComponent-assert_only_role :grant_role: xref:#AccessControlComponent-grant_role[grant_role] diff --git a/docs/modules/ROOT/pages/api/account.adoc b/docs/modules/ROOT/pages/api/account.adoc index 04cd5e9f4..c5ece578d 100644 --- a/docs/modules/ROOT/pages/api/account.adoc +++ b/docs/modules/ROOT/pages/api/account.adoc @@ -12,7 +12,7 @@ include::../utils/_common.adoc[] [.contract] [[ISRC6]] -=== `++ISRC6++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/account/interface.cairo[{github-icon},role=heading-link] +=== `++ISRC6++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/account/interface.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::account::interface::ISRC6; @@ -65,7 +65,7 @@ Returns the short string `'VALID'` if valid, otherwise it reverts. [.contract] [[AccountComponent]] -=== `++AccountComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/account/account.cairo[{github-icon},role=heading-link] +=== `++AccountComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/account/account.cairo[{github-icon},role=heading-link] :OwnerAdded: xref:AccountComponent-OwnerAdded[OwnerAdded] :OwnerRemoved: xref:AccountComponent-OwnerRemoved[OwnerRemoved] @@ -315,7 +315,7 @@ Emitted when a `public_key` is removed. [.contract] [[EthAccountComponent]] -=== `++EthAccountComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/account/eth_account.cairo[{github-icon},role=heading-link] +=== `++EthAccountComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/account/eth_account.cairo[{github-icon},role=heading-link] :OwnerAdded: xref:EthAccountComponent-OwnerAdded[OwnerAdded] :OwnerRemoved: xref:EthAccountComponent-OwnerRemoved[OwnerRemoved] @@ -570,7 +570,7 @@ Emitted when a `public_key` is removed. [.contract] [[AccountUpgradeable]] -=== `++AccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets/account.cairo[{github-icon},role=heading-link] +=== `++AccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets/account.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::presets::AccountUpgradeable; @@ -631,7 +631,7 @@ Requirements: [.contract] [[EthAccountUpgradeable]] -=== `++EthAccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets/eth_account.cairo[{github-icon},role=heading-link] +=== `++EthAccountUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets/eth_account.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::presets::EthAccountUpgradeable; diff --git a/docs/modules/ROOT/pages/api/erc1155.adoc b/docs/modules/ROOT/pages/api/erc1155.adoc index 029cf84e3..20846ec46 100644 --- a/docs/modules/ROOT/pages/api/erc1155.adoc +++ b/docs/modules/ROOT/pages/api/erc1155.adoc @@ -16,7 +16,7 @@ TIP: For an overview of ERC1155, read our xref:erc1155.adoc[ERC1155 guide]. [.contract] [[IERC1155]] -=== `++IERC1155++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] +=== `++IERC1155++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -126,7 +126,7 @@ Emitted when the token URI is updated to `value` for the `id` token. [.contract] [[IERC1155MetadataURI]] -=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] +=== `++IERC1155MetadataURI++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -156,7 +156,7 @@ Returns the Uniform Resource Identifier (URI) for the `token_id` token. [.contract] [[ERC1155Component]] -=== `++ERC1155Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc1155/erc1155.cairo[{github-icon},role=heading-link] +=== `++ERC1155Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc1155/erc1155.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -543,7 +543,7 @@ See <>. [.contract] [[IERC1155Receiver]] -=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] +=== `++IERC1155Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc1155/interface.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -583,7 +583,7 @@ via <> by [.contract] [[ERC1155ReceiverComponent]] -=== `++ERC1155ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc1155/erc1155_receiver.cairo[{github-icon},role=heading-link] +=== `++ERC1155ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc1155/erc1155_receiver.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -660,7 +660,7 @@ Registers the `IERC1155Receiver` interface ID as supported through introspection [.contract] [[ERC1155Upgradeable]] -=== `++ERC1155Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets/erc1155.cairo[{github-icon},role=heading-link] +=== `++ERC1155Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets/erc1155.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::presets::ERC1155; diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc index 48e2f7349..05fe286fd 100644 --- a/docs/modules/ROOT/pages/api/erc20.adoc +++ b/docs/modules/ROOT/pages/api/erc20.adoc @@ -16,7 +16,7 @@ TIP: For an overview of ERC20, read our {erc20-guide}. [.contract] [[IERC20]] -=== `++IERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc20/interface.cairo[{github-icon},role=heading-link] +=== `++IERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc20/interface.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -114,7 +114,7 @@ Emitted when the allowance of a `spender` for an `owner` is set. [.contract] [[IERC20Metadata]] -=== `++IERC20Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc20/interface.cairo#L19[{github-icon},role=heading-link] +=== `++IERC20Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc20/interface.cairo#L19[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -162,7 +162,7 @@ NOTE: This information is only used for _display_ purposes: it in no way affects [.contract] [[ERC20Component]] -=== `++ERC20Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc20/erc20.cairo[{github-icon},role=heading-link] +=== `++ERC20Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc20/erc20.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -460,7 +460,7 @@ See <>. [.contract] [[ERC20VotesComponent]] -=== `++ERC20VotesComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc20/extensions/erc20_votes.cairo[{github-icon},role=heading-link] +=== `++ERC20VotesComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc20/extensions/erc20_votes.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::token::extensions::ERC20VotesComponent; @@ -506,6 +506,7 @@ By default, token balance does not account for voting power. This makes transfer * xref:#ERC20VotesComponent-_delegate[`++_delegate(self, account, delegatee)++`] * xref:#ERC20VotesComponent-move_delegate_votes[`++move_delegate_votes(self, from, to, amount)++`] * xref:#ERC20VotesComponent-transfer_voting_units[`++transfer_voting_units(self, from, to, amount)++`] +* xref:#ERC20VotesComponent-num_checkpoints[`++num_checkpoints(self, account)++`] * xref:#ERC20VotesComponent-checkpoints[`++checkpoints(self, account, pos)++`] * xref:#ERC20VotesComponent-get_voting_units[`++get_voting_units(self, account)++`] -- @@ -654,7 +655,7 @@ Emitted when `delegate` votes are updated from `previous_votes` to `new_votes`. [.contract] [[ERC20Upgradeable]] -=== `++ERC20Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets/erc20.cairo[{github-icon},role=heading-link] +=== `++ERC20Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets/erc20.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::presets::ERC20Upgradeable; diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index ecf4b7a97..09ab75d29 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -16,7 +16,7 @@ TIP: For an overview of ERC721, read our xref:erc721.adoc[ERC721 guide]. [.contract] [[IERC721]] -=== `++IERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc721/interface.cairo#L13-L31[{github-icon},role=heading-link] +=== `++IERC721++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/interface.cairo#L13-L31[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -135,7 +135,7 @@ Emitted when `token_id` token is transferred from `from` to `to`. [.contract] [[IERC721Metadata]] -=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc721/interface.cairo#L54-L59[{github-icon},role=heading-link] +=== `++IERC721Metadata++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/interface.cairo#L54-L59[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -181,7 +181,7 @@ If the URI is not set for `token_id`, the return value will be an empty `ByteArr [.contract] [[ERC721Component]] -=== `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] +=== `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -693,7 +693,7 @@ See <>. [.contract] [[IERC721Receiver]] -=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -724,7 +724,7 @@ Whenever an IERC721 `token_id` token is transferred to this non-account contract [.contract] [[ERC721ReceiverComponent]] -=== `++ERC721ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/token/erc721/erc721_receiver.cairo[{github-icon},role=heading-link] +=== `++ERC721ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/erc721_receiver.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo @@ -790,7 +790,7 @@ Registers the `IERC721Receiver` interface ID as supported through introspection. [.contract] [[ERC721Upgradeable]] -=== `++ERC721Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets/erc721.cairo[{github-icon},role=heading-link] +=== `++ERC721Upgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets/erc721.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::presets::ERC721Upgradeable; diff --git a/docs/modules/ROOT/pages/api/governance.adoc b/docs/modules/ROOT/pages/api/governance.adoc index 1d2451eb2..997815023 100644 --- a/docs/modules/ROOT/pages/api/governance.adoc +++ b/docs/modules/ROOT/pages/api/governance.adoc @@ -8,7 +8,7 @@ Reference of interfaces and utilities related to Governance. [.contract] [[IVotes]] -=== `++IVotes++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/governance/utils/interfaces/votes.cairo[{github-icon},role=heading-link] +=== `++IVotes++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/governance/utils/interfaces/votes.cairo[{github-icon},role=heading-link] [.hljs-theme-dark] ```cairo diff --git a/docs/modules/ROOT/pages/api/introspection.adoc b/docs/modules/ROOT/pages/api/introspection.adoc index c1e35dcba..77fb3b2b9 100644 --- a/docs/modules/ROOT/pages/api/introspection.adoc +++ b/docs/modules/ROOT/pages/api/introspection.adoc @@ -10,7 +10,7 @@ Reference of interfaces and utilities related to https://en.wikipedia.org/wiki/T [.contract] [[ISRC5]] -=== `++ISRC5++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/introspection/interface.cairo#L7[{github-icon},role=heading-link] +=== `++ISRC5++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/introspection/interface.cairo#L7[{github-icon},role=heading-link] ```cairo use openzeppelin::introspection::interface::ISRC5; @@ -44,7 +44,7 @@ on how to compute this ID. [.contract] [[SRC5Component]] -=== `++SRC5Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/introspection/src5.cairo[{github-icon},role=heading-link] +=== `++SRC5Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/introspection/src5.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::introspection::src5::SRC5Component; diff --git a/docs/modules/ROOT/pages/api/security.adoc b/docs/modules/ROOT/pages/api/security.adoc index 02b137467..3a6558408 100644 --- a/docs/modules/ROOT/pages/api/security.adoc +++ b/docs/modules/ROOT/pages/api/security.adoc @@ -8,7 +8,7 @@ Reference of components, interfaces and utilities found in the library's `securi [.contract] [[InitializableComponent]] -=== `++InitializableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/security/initializable.cairo[{github-icon},role=heading-link] +=== `++InitializableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/security/initializable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::security::InitializableComponent; @@ -58,7 +58,7 @@ Requirements: [.contract] [[PausableComponent]] -=== `++PausableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/security/pausable.cairo[{github-icon},role=heading-link] +=== `++PausableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/security/pausable.cairo[{github-icon},role=heading-link] :Paused: xref:PausableComponent-Paused[Paused] :Unpaused: xref:PausableComponent-Unpaused[Unpaused] @@ -163,7 +163,7 @@ Emitted when the contract is unpaused by `account`. [.contract] [[ReentrancyGuardComponent]] -=== `++ReentrancyGuardComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/security/reentrancyguard.cairo[{github-icon},role=heading-link] +=== `++ReentrancyGuardComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/security/reentrancyguard.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::security::ReentrancyGuardComponent; diff --git a/docs/modules/ROOT/pages/api/upgrades.adoc b/docs/modules/ROOT/pages/api/upgrades.adoc index c7673fbf2..08060dd87 100644 --- a/docs/modules/ROOT/pages/api/upgrades.adoc +++ b/docs/modules/ROOT/pages/api/upgrades.adoc @@ -9,7 +9,7 @@ Reference of interfaces and utilities related to upgradeability. [.contract] [[IUpgradeable]] -=== `++IUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/upgrades/interface.cairo#L3[{github-icon},role=heading-link] +=== `++IUpgradeable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/upgrades/interface.cairo#L3[{github-icon},role=heading-link] :Upgraded: xref:UpgradeableComponent-Upgraded[Upgraded] @@ -38,7 +38,7 @@ NOTE: This function is usually protected by an xref:access.adoc[Access Control] [.contract] [[UpgradeableComponent]] -=== `++UpgradeableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/upgrades/upgradeable.cairo[{github-icon},role=heading-link] +=== `++UpgradeableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/upgrades/upgradeable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::upgrades::upgradeable::UpgradeableComponent; diff --git a/docs/modules/ROOT/pages/api/utilities.adoc b/docs/modules/ROOT/pages/api/utilities.adoc index 3feaa991f..d103d311d 100644 --- a/docs/modules/ROOT/pages/api/utilities.adoc +++ b/docs/modules/ROOT/pages/api/utilities.adoc @@ -261,7 +261,7 @@ NOTE: `T` is a generic value matching different numeric implementations. use openzeppelin::utils::selectors; ``` -:selectors: https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/utils/selectors.cairo[selectors.cairo] +:selectors: https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/utils/selectors.cairo[selectors.cairo] Module containing constants matching multiple selectors used through the library. To see the full list of selectors, see {selectors}. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 77a1f138e..6703db744 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -57,7 +57,7 @@ Install the library by declaring it as a dependency in the project's `Scarb.toml [,text] ---- [dependencies] -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.14.0" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.15.0-rc.0" } ---- WARNING: Make sure the tag matches the target release. diff --git a/docs/modules/ROOT/pages/presets.adoc b/docs/modules/ROOT/pages/presets.adoc index f0a3f8ce3..3f56743f1 100644 --- a/docs/modules/ROOT/pages/presets.adoc +++ b/docs/modules/ROOT/pages/presets.adoc @@ -53,7 +53,7 @@ TIP: {starkli} class-hash command can be used to compute the class hash from a S :setup_project: xref:index.adoc#set_up_your_project[setting up a project] :install_lib: xref:index.adoc#install the_library[installing the Contracts for Cairo library] -:presets_dir: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/presets[presets directory] +:presets_dir: link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/presets[presets directory] These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. Simply deploy the preset class hash and add the appropriate constructor arguments. diff --git a/docs/modules/ROOT/pages/upgrades.adoc b/docs/modules/ROOT/pages/upgrades.adoc index dff92c758..3f58469e0 100644 --- a/docs/modules/ROOT/pages/upgrades.adoc +++ b/docs/modules/ROOT/pages/upgrades.adoc @@ -1,7 +1,7 @@ :contract_classes: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-classes/[Contract Classes] :class_hash: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/class-hash/[class hash] :replace_class_syscall: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/#replace_class[replace_class] -:upgradeable: https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.14.0/src/upgrades/upgradeable.cairo[Upgradeable] +:upgradeable: https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/upgrades/upgradeable.cairo[Upgradeable] :ownable: xref:access.adoc#ownership_and_ownable[Ownable] :i_upgradeable: xref:api/upgrades.adoc#IUpgradeable[IUpgradeable] :library_calls: https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/#library_call[library calls] diff --git a/docs/modules/ROOT/pages/utils/_class_hashes.adoc b/docs/modules/ROOT/pages/utils/_class_hashes.adoc index 1ca009026..8c722bd41 100644 --- a/docs/modules/ROOT/pages/utils/_class_hashes.adoc +++ b/docs/modules/ROOT/pages/utils/_class_hashes.adoc @@ -1,13 +1,13 @@ // Version -:class-hash-cairo-version: https://crates.io/crates/cairo-lang-compiler/2.6.3[cairo 2.6.3] +:class-hash-cairo-version: https://crates.io/crates/cairo-lang-compiler/2.6.4[cairo 2.6.4] // Class Hashes -:ERC20Upgradeable-class-hash: 0x040b9e69e14ddc34a98ec8133c80807c144b818bc6cbf5a119d8f62535258142 -:ERC721Upgradeable-class-hash: 0x00f56255bf8db96498c71884407f3b623f20a5d507cbb7c41deeeb65a1bf8725 -:ERC1155Upgradeable-class-hash: 0x017baa69866decbb1ac5e6d5ef2e69b7d5dd7113111a8cc51a48f01854df571f -:AccountUpgradeable-class-hash: 0x00e2eb8f5672af4e6a4e8a8f1b44989685e668489b0a25437733756c5a34a1d6 -:EthAccountUpgradeable-class-hash: 0x0169e64bc8a60422da86e6cc821c498d2f8cba60888399ce4be86690aaa51e4a -:UniversalDeployer-class-hash: 0x0536e18e3e9820b48e0477e83e0a4d6d923ccddc06deb61585dfd7402dd40733 +:ERC20Upgradeable-class-hash: 0x02718db92cfa9342bd19306fb1d10cb5c61385293b4bdaac23c36577dd7ed882 +:ERC721Upgradeable-class-hash: 0x07e6fe2c473ea7018e3db87712497deae535694870d8265203212ddbdca1642d +:ERC1155Upgradeable-class-hash: 0x06cb57fea4ed34a68db38e3b2ca46b3ad39b123952b31e10e79c5ea3a1eb0a20 +:AccountUpgradeable-class-hash: 0x04343194a4a6082192502e132d9e7834b5d9bfc7a0c1dd990e95b66f85a66d46 +:EthAccountUpgradeable-class-hash: 0x02203673e728fa07de1c2ea60405399ffefaf875f1b7ae54e747659e1e216d94 +:UniversalDeployer-class-hash: 0x03a901e3f8d717544d07df0043dcdd6537c1ae4c046674599793783f73511155 // Presets page :presets-page: xref:presets.adoc[Sierra class hash] diff --git a/docs/modules/ROOT/pages/wizard.adoc b/docs/modules/ROOT/pages/wizard.adoc index c98a6b489..b18c0ddc4 100644 --- a/docs/modules/ROOT/pages/wizard.adoc +++ b/docs/modules/ROOT/pages/wizard.adoc @@ -10,5 +10,5 @@ NOTE: We strongly recommend checking the xref:components.adoc[Components] sectio ++++ - + ++++ diff --git a/src/access/accesscontrol/dual_accesscontrol.cairo b/src/access/accesscontrol/dual_accesscontrol.cairo index b9cca51f9..82b4f9256 100644 --- a/src/access/accesscontrol/dual_accesscontrol.cairo +++ b/src/access/accesscontrol/dual_accesscontrol.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (access/accesscontrol/dual_accesscontrol.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/accesscontrol/dual_accesscontrol.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/access/accesscontrol/interface.cairo b/src/access/accesscontrol/interface.cairo index 8487b007f..9af3f87f7 100644 --- a/src/access/accesscontrol/interface.cairo +++ b/src/access/accesscontrol/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (access/accesscontrol/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/accesscontrol/interface.cairo) use starknet::ContractAddress; diff --git a/src/access/ownable/dual_ownable.cairo b/src/access/ownable/dual_ownable.cairo index 7c3052c81..1695e3e2d 100644 --- a/src/access/ownable/dual_ownable.cairo +++ b/src/access/ownable/dual_ownable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (access/ownable/dual_ownable.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/ownable/dual_ownable.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/access/ownable/interface.cairo b/src/access/ownable/interface.cairo index 591eb4fa7..2f6b11578 100644 --- a/src/access/ownable/interface.cairo +++ b/src/access/ownable/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (access/ownable/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/ownable/interface.cairo) use starknet::ContractAddress; diff --git a/src/access/ownable/ownable.cairo b/src/access/ownable/ownable.cairo index c60b214e1..8443a23ff 100644 --- a/src/access/ownable/ownable.cairo +++ b/src/access/ownable/ownable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (access/ownable/ownable.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/ownable/ownable.cairo) /// # Ownable Component /// diff --git a/src/account/account.cairo b/src/account/account.cairo index c13192540..1dbf6ad56 100644 --- a/src/account/account.cairo +++ b/src/account/account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/account.cairo) /// # Account Component /// diff --git a/src/account/dual_account.cairo b/src/account/dual_account.cairo index 3c0d33c14..dd9e4be34 100644 --- a/src/account/dual_account.cairo +++ b/src/account/dual_account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/dual_account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/dual_account.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/account/dual_eth_account.cairo b/src/account/dual_eth_account.cairo index eee086574..0f2bef2ff 100644 --- a/src/account/dual_eth_account.cairo +++ b/src/account/dual_eth_account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/dual_eth_account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/dual_eth_account.cairo) use openzeppelin::account::interface::EthPublicKey; use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; diff --git a/src/account/eth_account.cairo b/src/account/eth_account.cairo index cb8082932..65dbb4b7b 100644 --- a/src/account/eth_account.cairo +++ b/src/account/eth_account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/eth_account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/eth_account.cairo) /// # EthAccount Component /// diff --git a/src/account/interface.cairo b/src/account/interface.cairo index 957a0e5e9..b6cd9e320 100644 --- a/src/account/interface.cairo +++ b/src/account/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/interface.cairo) use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use starknet::ContractAddress; diff --git a/src/account/utils.cairo b/src/account/utils.cairo index a1209c732..6fed1e68e 100644 --- a/src/account/utils.cairo +++ b/src/account/utils.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/utils.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/utils.cairo) pub mod secp256k1; pub mod signature; diff --git a/src/account/utils/secp256k1.cairo b/src/account/utils/secp256k1.cairo index 084ee6ddc..61c93d0b4 100644 --- a/src/account/utils/secp256k1.cairo +++ b/src/account/utils/secp256k1.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/utils/secp256k1.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/utils/secp256k1.cairo) use core::fmt::{Debug, Formatter, Error}; use starknet::SyscallResultTrait; diff --git a/src/account/utils/signature.cairo b/src/account/utils/signature.cairo index 13caa8391..f247df83f 100644 --- a/src/account/utils/signature.cairo +++ b/src/account/utils/signature.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (account/utils/signature.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/utils/signature.cairo) use core::ecdsa::check_ecdsa_signature; use openzeppelin::account::interface::EthPublicKey; diff --git a/src/introspection/interface.cairo b/src/introspection/interface.cairo index bb0d6c9cf..0de388130 100644 --- a/src/introspection/interface.cairo +++ b/src/introspection/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (introspection/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (introspection/interface.cairo) pub const ISRC5_ID: felt252 = 0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055; diff --git a/src/introspection/src5.cairo b/src/introspection/src5.cairo index 4ac0d4b7e..f550feb9c 100644 --- a/src/introspection/src5.cairo +++ b/src/introspection/src5.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (introspection/src5.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (introspection/src5.cairo) /// # SRC5 Component /// diff --git a/src/presets/account.cairo b/src/presets/account.cairo index e9805bf3e..05b5bc2da 100644 --- a/src/presets/account.cairo +++ b/src/presets/account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/account.cairo) /// # Account Preset /// diff --git a/src/presets/erc1155.cairo b/src/presets/erc1155.cairo index 2552e7323..b24bdf100 100644 --- a/src/presets/erc1155.cairo +++ b/src/presets/erc1155.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/erc1155.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/erc1155.cairo) /// # ERC1155Upgradeable Preset /// diff --git a/src/presets/erc20.cairo b/src/presets/erc20.cairo index a12f43d2b..94506ba3e 100644 --- a/src/presets/erc20.cairo +++ b/src/presets/erc20.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/erc20.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/erc20.cairo) /// # ERC20 Preset /// diff --git a/src/presets/erc721.cairo b/src/presets/erc721.cairo index 91a469a6f..1359736f7 100644 --- a/src/presets/erc721.cairo +++ b/src/presets/erc721.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/erc721.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/erc721.cairo) /// # ERC721 Preset /// diff --git a/src/presets/eth_account.cairo b/src/presets/eth_account.cairo index f641904be..2c1e8ea3c 100644 --- a/src/presets/eth_account.cairo +++ b/src/presets/eth_account.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/eth_account.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/eth_account.cairo) /// # EthAccount Preset /// diff --git a/src/presets/universal_deployer.cairo b/src/presets/universal_deployer.cairo index d4a928ca9..2777a6107 100644 --- a/src/presets/universal_deployer.cairo +++ b/src/presets/universal_deployer.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (presets/universal_deployer.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (presets/universal_deployer.cairo) /// # UniversalDeployerContract Preset /// diff --git a/src/security/initializable.cairo b/src/security/initializable.cairo index b265bbc8a..11e7bdabb 100644 --- a/src/security/initializable.cairo +++ b/src/security/initializable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (security/initializable.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (security/initializable.cairo) /// # Initializable Component /// diff --git a/src/security/pausable.cairo b/src/security/pausable.cairo index d10a201a5..0be0d3a43 100644 --- a/src/security/pausable.cairo +++ b/src/security/pausable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (security/pausable.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (security/pausable.cairo) /// # Pausable Component /// diff --git a/src/security/reentrancyguard.cairo b/src/security/reentrancyguard.cairo index de43d6c0d..31ff47d32 100644 --- a/src/security/reentrancyguard.cairo +++ b/src/security/reentrancyguard.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (security/reentrancyguard.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (security/reentrancyguard.cairo) /// # ReentrancyGuard Component /// diff --git a/src/token/erc1155/dual1155.cairo b/src/token/erc1155/dual1155.cairo index b0aac8f8b..a6644df67 100644 --- a/src/token/erc1155/dual1155.cairo +++ b/src/token/erc1155/dual1155.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc1155/dual1155.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc1155/dual1155.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/token/erc1155/dual1155_receiver.cairo b/src/token/erc1155/dual1155_receiver.cairo index 24d35834f..e53df5d3c 100644 --- a/src/token/erc1155/dual1155_receiver.cairo +++ b/src/token/erc1155/dual1155_receiver.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc1155/dual1155_receiver.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc1155/dual1155_receiver.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/token/erc1155/erc1155.cairo b/src/token/erc1155/erc1155.cairo index 493207c40..dbbf3b777 100644 --- a/src/token/erc1155/erc1155.cairo +++ b/src/token/erc1155/erc1155.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc1155/erc1155.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc1155/erc1155.cairo) use starknet::ContractAddress; diff --git a/src/token/erc1155/erc1155_receiver.cairo b/src/token/erc1155/erc1155_receiver.cairo index 48e7e7361..f9ac00b80 100644 --- a/src/token/erc1155/erc1155_receiver.cairo +++ b/src/token/erc1155/erc1155_receiver.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc1155/erc1155_receiver.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc1155/erc1155_receiver.cairo) /// # ERC1155Receiver Component /// diff --git a/src/token/erc1155/interface.cairo b/src/token/erc1155/interface.cairo index db7be1e27..6fb5bd109 100644 --- a/src/token/erc1155/interface.cairo +++ b/src/token/erc1155/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc1155/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc1155/interface.cairo) use starknet::ContractAddress; diff --git a/src/token/erc20/dual20.cairo b/src/token/erc20/dual20.cairo index 6c0f50628..9165574d0 100644 --- a/src/token/erc20/dual20.cairo +++ b/src/token/erc20/dual20.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc20/dual20.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc20/dual20.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/token/erc20/erc20.cairo b/src/token/erc20/erc20.cairo index 4cd568375..b295ea9fe 100644 --- a/src/token/erc20/erc20.cairo +++ b/src/token/erc20/erc20.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc20/erc20.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc20/erc20.cairo) use starknet::ContractAddress; @@ -9,7 +9,7 @@ use starknet::ContractAddress; /// non-standard implementations that can be used to create an ERC20 contract. This /// component is agnostic regarding how tokens are created, which means that developers /// must create their own token distribution mechanism. -/// See [the documentation](https://docs.openzeppelin.com/contracts-cairo/0.14.0/guides/erc20-supply) +/// See [the documentation](https://docs.openzeppelin.com/contracts-cairo/0.15.0-rc.0/guides/erc20-supply) /// for examples. #[starknet::component] pub mod ERC20Component { diff --git a/src/token/erc20/extensions/erc20_votes.cairo b/src/token/erc20/extensions/erc20_votes.cairo index 069fd626c..b507fe5a3 100644 --- a/src/token/erc20/extensions/erc20_votes.cairo +++ b/src/token/erc20/extensions/erc20_votes.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc20/extensions/erc20_votes.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc20/extensions/erc20_votes.cairo) use core::hash::{Hash, HashStateTrait, HashStateExTrait}; use core::poseidon::PoseidonTrait; diff --git a/src/token/erc20/interface.cairo b/src/token/erc20/interface.cairo index 44c61694f..74ed4c817 100644 --- a/src/token/erc20/interface.cairo +++ b/src/token/erc20/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc20/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc20/interface.cairo) use openzeppelin::utils::structs::checkpoint::Checkpoint; use starknet::ContractAddress; diff --git a/src/token/erc721/dual721.cairo b/src/token/erc721/dual721.cairo index 4c710f588..7b57c787c 100644 --- a/src/token/erc721/dual721.cairo +++ b/src/token/erc721/dual721.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/dual721.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc721/dual721.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/token/erc721/dual721_receiver.cairo b/src/token/erc721/dual721_receiver.cairo index a41f9699b..bc6e74242 100644 --- a/src/token/erc721/dual721_receiver.cairo +++ b/src/token/erc721/dual721_receiver.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/dual721_receiver.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc721/dual721_receiver.cairo) use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 6f838205a..0ad907cda 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/erc721.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc721/erc721.cairo) use starknet::ContractAddress; diff --git a/src/token/erc721/erc721_receiver.cairo b/src/token/erc721/erc721_receiver.cairo index bbc8db4ff..693f4d6fa 100644 --- a/src/token/erc721/erc721_receiver.cairo +++ b/src/token/erc721/erc721_receiver.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/erc721_receiver.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc721/erc721_receiver.cairo) /// # ERC721Receiver Component /// diff --git a/src/token/erc721/interface.cairo b/src/token/erc721/interface.cairo index c821677d5..26f4cecac 100644 --- a/src/token/erc721/interface.cairo +++ b/src/token/erc721/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (token/erc721/interface.cairo) use starknet::ContractAddress; diff --git a/src/upgrades/interface.cairo b/src/upgrades/interface.cairo index 43233f751..cc011515e 100644 --- a/src/upgrades/interface.cairo +++ b/src/upgrades/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (upgrades/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (upgrades/interface.cairo) use starknet::ClassHash; diff --git a/src/upgrades/upgradeable.cairo b/src/upgrades/upgradeable.cairo index 443182d29..e8d436998 100644 --- a/src/upgrades/upgradeable.cairo +++ b/src/upgrades/upgradeable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (upgrades/upgradeable.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (upgrades/upgradeable.cairo) /// # Upgradeable Component /// diff --git a/src/utils.cairo b/src/utils.cairo index 666ec68dc..530d909b3 100644 --- a/src/utils.cairo +++ b/src/utils.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils.cairo) pub mod cryptography; pub mod deployments; diff --git a/src/utils/cryptography/interface.cairo b/src/utils/cryptography/interface.cairo index 54d22f4cb..a80966140 100644 --- a/src/utils/cryptography/interface.cairo +++ b/src/utils/cryptography/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/cryptography/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/cryptography/interface.cairo) use starknet::ContractAddress; diff --git a/src/utils/cryptography/nonces.cairo b/src/utils/cryptography/nonces.cairo index 6358cefe6..cf85c3970 100644 --- a/src/utils/cryptography/nonces.cairo +++ b/src/utils/cryptography/nonces.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/cryptography/nonces.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/cryptography/nonces.cairo) #[starknet::component] pub mod NoncesComponent { diff --git a/src/utils/cryptography/snip12.cairo b/src/utils/cryptography/snip12.cairo index 9aa7dc7fe..0f09a51e2 100644 --- a/src/utils/cryptography/snip12.cairo +++ b/src/utils/cryptography/snip12.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/cryptography/snip12.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/cryptography/snip12.cairo) use core::hash::{Hash, HashStateTrait, HashStateExTrait}; use core::poseidon::PoseidonTrait; diff --git a/src/utils/deployments/interface.cairo b/src/utils/deployments/interface.cairo index c80f10e4f..10231a5a9 100644 --- a/src/utils/deployments/interface.cairo +++ b/src/utils/deployments/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/universal_deployer/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/universal_deployer/interface.cairo) use starknet::ClassHash; use starknet::ContractAddress; diff --git a/src/utils/math.cairo b/src/utils/math.cairo index cc9960aef..7d80a075c 100644 --- a/src/utils/math.cairo +++ b/src/utils/math.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/math.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/math.cairo) use core::traits::Into; diff --git a/src/utils/selectors.cairo b/src/utils/selectors.cairo index 61e9758e0..2fafd30e8 100644 --- a/src/utils/selectors.cairo +++ b/src/utils/selectors.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/selectors.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/selectors.cairo) // // AccessControl diff --git a/src/utils/serde.cairo b/src/utils/serde.cairo index e08fe03d8..dfe6efcf3 100644 --- a/src/utils/serde.cairo +++ b/src/utils/serde.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/serde.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/serde.cairo) pub trait SerializedAppend { fn append_serde(ref self: Array, value: T); diff --git a/src/utils/structs/checkpoint.cairo b/src/utils/structs/checkpoint.cairo index 0a55edf9a..de07f5d84 100644 --- a/src/utils/structs/checkpoint.cairo +++ b/src/utils/structs/checkpoint.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/structs/checkpoint.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/structs/checkpoint.cairo) use core::integer::u32_sqrt; use openzeppelin::utils::math; diff --git a/src/utils/structs/storage_array.cairo b/src/utils/structs/storage_array.cairo index 7b890cf37..5a35dfd5e 100644 --- a/src/utils/structs/storage_array.cairo +++ b/src/utils/structs/storage_array.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/structs/storage_array.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/structs/storage_array.cairo) use core::hash::{HashStateExTrait, HashStateTrait}; use core::poseidon::PoseidonTrait; diff --git a/src/utils/unwrap_and_cast.cairo b/src/utils/unwrap_and_cast.cairo index 775385cb1..0ab73c8f1 100644 --- a/src/utils/unwrap_and_cast.cairo +++ b/src/utils/unwrap_and_cast.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.14.0 (utils/unwrap_and_cast.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/unwrap_and_cast.cairo) use starknet::SyscallResult; use starknet::SyscallResultTrait; From 232c673ed5a5ceda6457e8b9d60932d908b7b127 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Thu, 11 Jul 2024 14:10:04 +0200 Subject: [PATCH 04/12] Bump cairo to 2.7.0-rc.1 (#1025) * feat: migrate modules * feat: delete cairo_project.toml * feat: update tests * feat: apply review updates * fix: remove extra line * feat: add CHANGELOG entry * fix: CHANGELOG --- CHANGELOG.md | 8 +- Scarb.toml | 9 ++- src/access/accesscontrol/accesscontrol.cairo | 9 ++- src/account/account.cairo | 4 +- src/account/dual_eth_account.cairo | 1 - src/account/eth_account.cairo | 6 +- src/account/interface.cairo | 1 - src/account/utils/secp256k1.cairo | 14 +--- src/cairo_project.toml | 2 - src/governance/utils/interfaces/votes.cairo | 7 +- src/introspection/src5.cairo | 3 +- src/presets/account.cairo | 3 +- src/presets/eth_account.cairo | 1 - src/presets/interfaces/eth_account.cairo | 1 - src/security/reentrancyguard.cairo | 3 +- src/tests/access/test_ownable.cairo | 1 - src/tests/access/test_ownable_twostep.cairo | 1 - .../ethereum/test_dual_eth_account.cairo | 20 +++-- .../account/ethereum/test_eth_account.cairo | 4 +- src/tests/account/test_secp256k1.cairo | 50 +----------- src/tests/mocks/erc721_receiver_mocks.cairo | 2 +- src/tests/mocks/eth_account_mocks.cairo | 5 -- src/tests/presets/test_eth_account.cairo | 14 ++-- src/tests/security/test_reentrancyguard.cairo | 1 - src/tests/token/erc1155/test_dual1155.cairo | 8 +- src/tests/token/erc1155/test_erc1155.cairo | 1 - src/tests/token/erc20/test_erc20_votes.cairo | 5 +- src/tests/token/erc721/test_erc721.cairo | 1 - src/token/erc1155/erc1155.cairo | 51 ++++++++----- src/token/erc20/erc20.cairo | 15 ++-- src/token/erc20/extensions/erc20_votes.cairo | 26 ++++--- src/token/erc721/erc721.cairo | 76 +++++++++++-------- src/upgrades/upgradeable.cairo | 2 +- src/utils/cryptography/nonces.cairo | 10 ++- src/utils/deployments.cairo | 10 ++- src/utils/structs/checkpoint.cairo | 10 +-- 36 files changed, 178 insertions(+), 207 deletions(-) delete mode 100644 src/cairo_project.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index ff13ab1ea..76f22a2b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Bump scarb to v2.7.0-rc.1 (#1025) + ## 0.15.0-rc.0 (2024-07-8) ### Changed @@ -101,10 +105,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ERC1155Component and ERC1155ReceiverComponent mixins (#941) - ERC721ReceiverComponent documentation (#945) -### Changed - -- Bump scarb to v2.6.3 (#946) - ### Fixed - ERC721ReceiverComponent mixin embeddable implementation name (#945) diff --git a/Scarb.toml b/Scarb.toml index b697f2cc3..1b79be6b4 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -2,8 +2,8 @@ name = "openzeppelin" version = "0.15.0-rc.0" edition = "2023_11" -cairo-version = "2.6.4" -scarb-version = "2.6.5" +cairo-version = "2.7.0-rc.1" +scarb-version = "2.7.0-rc.1" authors = ["OpenZeppelin Community "] description = "OpenZeppelin Contracts written in Cairo for StarkNet, a decentralized ZK Rollup" documentation = "https://docs.openzeppelin.com/contracts-cairo" @@ -13,7 +13,10 @@ license-file = "LICENSE" keywords = ["openzeppelin", "starknet", "cairo", "contracts", "security", "standards"] [dependencies] -starknet = "2.6.4" +starknet = "2.7.0-rc.1" + +[dev-dependencies] +cairo_test = "2.7.0-rc.1" [lib] diff --git a/src/access/accesscontrol/accesscontrol.cairo b/src/access/accesscontrol/accesscontrol.cairo index 3f320d988..891b3a270 100644 --- a/src/access/accesscontrol/accesscontrol.cairo +++ b/src/access/accesscontrol/accesscontrol.cairo @@ -13,11 +13,12 @@ pub mod AccessControlComponent { use openzeppelin::introspection::src5::SRC5Component; use starknet::ContractAddress; use starknet::get_caller_address; + use starknet::storage::Map; #[storage] struct Storage { - AccessControl_role_admin: LegacyMap, - AccessControl_role_member: LegacyMap<(felt252, ContractAddress), bool>, + AccessControl_role_admin: Map, + AccessControl_role_member: Map<(felt252, ContractAddress), bool>, } #[event] @@ -96,7 +97,7 @@ pub mod AccessControlComponent { fn grant_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - let admin = AccessControl::get_role_admin(@self, role); + let admin = Self::get_role_admin(@self, role); self.assert_only_role(admin); self._grant_role(role, account); } @@ -111,7 +112,7 @@ pub mod AccessControlComponent { fn revoke_role( ref self: ComponentState, role: felt252, account: ContractAddress ) { - let admin = AccessControl::get_role_admin(@self, role); + let admin = Self::get_role_admin(@self, role); self.assert_only_role(admin); self._revoke_role(role, account); } diff --git a/src/account/account.cairo b/src/account/account.cairo index 1dbf6ad56..b63d756d4 100644 --- a/src/account/account.cairo +++ b/src/account/account.cairo @@ -327,8 +327,8 @@ pub mod AccountComponent { /// Validates that `new_owner` accepted the ownership of the contract. /// - /// WARNING: This function assumes that `current_owner` is the current owner of the contract, and - /// does not validate this assumption. + /// WARNING: This function assumes that `current_owner` is the current owner of the + /// contract, and does not validate this assumption. /// /// Requirements: /// diff --git a/src/account/dual_eth_account.cairo b/src/account/dual_eth_account.cairo index 0f2bef2ff..22cd39f9e 100644 --- a/src/account/dual_eth_account.cairo +++ b/src/account/dual_eth_account.cairo @@ -2,7 +2,6 @@ // OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/dual_eth_account.cairo) use openzeppelin::account::interface::EthPublicKey; -use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use openzeppelin::utils::UnwrapAndCast; use openzeppelin::utils::selectors; use openzeppelin::utils::serde::SerializedAppend; diff --git a/src/account/eth_account.cairo b/src/account/eth_account.cairo index 65dbb4b7b..61eb038af 100644 --- a/src/account/eth_account.cairo +++ b/src/account/eth_account.cairo @@ -12,7 +12,7 @@ pub mod EthAccountComponent { use core::starknet::secp256_trait::Secp256PointTrait; use openzeppelin::account::interface::EthPublicKey; use openzeppelin::account::interface; - use openzeppelin::account::utils::secp256k1::{Secp256k1PointSerde, Secp256k1PointStorePacking}; + use openzeppelin::account::utils::secp256k1::Secp256k1PointStorePacking; use openzeppelin::account::utils::{MIN_TRANSACTION_VERSION, QUERY_VERSION, QUERY_OFFSET}; use openzeppelin::account::utils::{execute_calls, is_valid_eth_signature}; use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; @@ -333,8 +333,8 @@ pub mod EthAccountComponent { /// Validates that `new_owner` accepted the ownership of the contract. /// - /// WARNING: This function assumes that `current_owner` is the current owner of the contract, and - /// does not validate this assumption. + /// WARNING: This function assumes that `current_owner` is the current owner of the + /// contract, and does not validate this assumption. /// /// Requirements: /// diff --git a/src/account/interface.cairo b/src/account/interface.cairo index b6cd9e320..3b7e8f69d 100644 --- a/src/account/interface.cairo +++ b/src/account/interface.cairo @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (account/interface.cairo) -use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use starknet::ContractAddress; use starknet::account::Call; diff --git a/src/account/utils/secp256k1.cairo b/src/account/utils/secp256k1.cairo index 61c93d0b4..fe4f00695 100644 --- a/src/account/utils/secp256k1.cairo +++ b/src/account/utils/secp256k1.cairo @@ -11,7 +11,8 @@ use starknet::storage_access::StorePacking; /// /// The packing is done as follows: /// - First felt contains x.low (x being the x-coordinate of the point). -/// - Second felt contains x.high and the parity bit, at the least significant bits (2 * x.high + parity). +/// - Second felt contains x.high and the parity bit, at the least significant bits (2 * x.high + +/// parity). pub impl Secp256k1PointStorePacking of StorePacking { fn pack(value: Secp256k1Point) -> (felt252, felt252) { let (x, y) = value.get_coordinates().unwrap_syscall(); @@ -38,17 +39,6 @@ pub impl Secp256k1PointStorePacking of StorePacking { - fn serialize(self: @Secp256k1Point, ref output: Array) { - let point = (*self).get_coordinates().unwrap_syscall(); - point.serialize(ref output) - } - fn deserialize(ref serialized: Span) -> Option { - let (x, y) = Serde::<(u256, u256)>::deserialize(ref serialized)?; - Secp256Trait::secp256_ec_new_syscall(x, y).unwrap_syscall() - } -} - pub(crate) impl Secp256k1PointPartialEq of PartialEq { #[inline(always)] fn eq(lhs: @Secp256k1Point, rhs: @Secp256k1Point) -> bool { diff --git a/src/cairo_project.toml b/src/cairo_project.toml deleted file mode 100644 index badca57cd..000000000 --- a/src/cairo_project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[crate_roots] -openzeppelin = "." diff --git a/src/governance/utils/interfaces/votes.cairo b/src/governance/utils/interfaces/votes.cairo index 44188130a..d55c481c2 100644 --- a/src/governance/utils/interfaces/votes.cairo +++ b/src/governance/utils/interfaces/votes.cairo @@ -11,9 +11,10 @@ pub trait IVotes { /// Returns the total supply of votes available at a specific moment in the past. /// - /// NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. - /// Votes that have not been delegated are still part of total supply, even though they would not participate in a - /// vote. + /// NOTE: This value is the sum of all available votes, which is not necessarily the sum of all + /// delegated votes. + /// Votes that have not been delegated are still part of total supply, even though they would + /// not participate in a vote. fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; /// Returns the delegate that `account` has chosen. diff --git a/src/introspection/src5.cairo b/src/introspection/src5.cairo index f550feb9c..530fb73b2 100644 --- a/src/introspection/src5.cairo +++ b/src/introspection/src5.cairo @@ -7,10 +7,11 @@ #[starknet::component] pub mod SRC5Component { use openzeppelin::introspection::interface; + use starknet::storage::Map; #[storage] struct Storage { - SRC5_supported_interfaces: LegacyMap + SRC5_supported_interfaces: Map } pub mod Errors { diff --git a/src/presets/account.cairo b/src/presets/account.cairo index 05b5bc2da..93cb2b5ef 100644 --- a/src/presets/account.cairo +++ b/src/presets/account.cairo @@ -3,7 +3,8 @@ /// # Account Preset /// -/// OpenZeppelin's upgradeable 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)] pub(crate) mod AccountUpgradeable { use openzeppelin::account::AccountComponent; diff --git a/src/presets/eth_account.cairo b/src/presets/eth_account.cairo index 2c1e8ea3c..97bdbc8b4 100644 --- a/src/presets/eth_account.cairo +++ b/src/presets/eth_account.cairo @@ -9,7 +9,6 @@ pub(crate) mod EthAccountUpgradeable { use openzeppelin::account::EthAccountComponent; use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::upgrades::UpgradeableComponent; use openzeppelin::upgrades::interface::IUpgradeable; diff --git a/src/presets/interfaces/eth_account.cairo b/src/presets/interfaces/eth_account.cairo index 9ee9ad22c..ab9af2f69 100644 --- a/src/presets/interfaces/eth_account.cairo +++ b/src/presets/interfaces/eth_account.cairo @@ -1,5 +1,4 @@ use openzeppelin::account::interface::EthPublicKey; -use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use starknet::account::Call; use starknet::{ContractAddress, ClassHash}; diff --git a/src/security/reentrancyguard.cairo b/src/security/reentrancyguard.cairo index 31ff47d32..994e71762 100644 --- a/src/security/reentrancyguard.cairo +++ b/src/security/reentrancyguard.cairo @@ -22,7 +22,8 @@ pub mod ReentrancyGuardComponent { pub impl InternalImpl< TContractState, +HasComponent > of InternalTrait { - /// Prevents a contract's function from calling itself or another protected function, directly or indirectly. + /// Prevents a contract's function from calling itself or another protected function, + /// directly or indirectly. fn start(ref self: ComponentState) { assert(!self.ReentrancyGuard_entered.read(), Errors::REENTRANT_CALL); self.ReentrancyGuard_entered.write(true); diff --git a/src/tests/access/test_ownable.cairo b/src/tests/access/test_ownable.cairo index ea4b97c74..354c96fc4 100644 --- a/src/tests/access/test_ownable.cairo +++ b/src/tests/access/test_ownable.cairo @@ -5,7 +5,6 @@ use openzeppelin::access::ownable::interface::{IOwnable, IOwnableCamelOnly}; use openzeppelin::tests::mocks::ownable_mocks::DualCaseOwnableMock; use openzeppelin::tests::utils::constants::{ZERO, OTHER, OWNER}; use openzeppelin::tests::utils; -use starknet::storage::StorageMemberAccessTrait; use starknet::testing; use super::common::assert_only_event_ownership_transferred; diff --git a/src/tests/access/test_ownable_twostep.cairo b/src/tests/access/test_ownable_twostep.cairo index 0b300ddc4..86a3120a6 100644 --- a/src/tests/access/test_ownable_twostep.cairo +++ b/src/tests/access/test_ownable_twostep.cairo @@ -8,7 +8,6 @@ use openzeppelin::tests::utils::constants::{ZERO, OWNER, OTHER, NEW_OWNER}; use openzeppelin::tests::utils; use openzeppelin::utils::serde::SerializedAppend; use starknet::ContractAddress; -use starknet::storage::StorageMemberAccessTrait; use starknet::testing; use super::common::assert_only_event_ownership_transferred; diff --git a/src/tests/account/ethereum/test_dual_eth_account.cairo b/src/tests/account/ethereum/test_dual_eth_account.cairo index b627724d5..6307f1781 100644 --- a/src/tests/account/ethereum/test_dual_eth_account.cairo +++ b/src/tests/account/ethereum/test_dual_eth_account.cairo @@ -1,8 +1,6 @@ use openzeppelin::account::dual_eth_account::{DualCaseEthAccountABI, DualCaseEthAccount}; use openzeppelin::account::interface::{EthAccountABIDispatcherTrait, EthAccountABIDispatcher}; -use openzeppelin::account::utils::secp256k1::{ - DebugSecp256k1Point, Secp256k1PointPartialEq, Secp256k1PointSerde -}; +use openzeppelin::account::utils::secp256k1::{DebugSecp256k1Point, Secp256k1PointPartialEq}; use openzeppelin::account::utils::signature::EthSignature; use openzeppelin::introspection::interface::ISRC5_ID; use openzeppelin::tests::mocks::eth_account_mocks::{ @@ -230,7 +228,7 @@ fn test_dual_isValidSignature_exists_and_panics() { fn get_accept_ownership_signature_snake() -> Span { let mut output = array![]; - // 0x03e8d3aa715dc5fc3b93c7572df7d6f227a6aad93a77873db3308b30897eee53 = + // 0x061bf2fc78c9b412089e2833cc7a503be80949195280623eb73aa50a8321bb30 = // PoseidonTrait::new() // .update_with('StarkNet Message') // .update_with('accept_ownership') @@ -243,10 +241,10 @@ fn get_accept_ownership_signature_snake() -> Span { // - public_key: // r: 0x829307f82a1883c2414503ba85fc85037f22c6fc6f80910801f6b01a4131da1e // s: 0x2a23f7bddf3715d11767b1247eccc68c89e11b926e2615268db6ad1af8d8da96 - // - msg_hash: 0x03e8d3aa715dc5fc3b93c7572df7d6f227a6aad93a77873db3308b30897eee53 + // - msg_hash: 0x061bf2fc78c9b412089e2833cc7a503be80949195280623eb73aa50a8321bb30 EthSignature { - r: 0x7e1ff13cbdf03e92125a69cb1e4ad94f2178720d156df3827c8d3172484fbfd8, - s: 0x0def4eb71f21bc623c0ca896cb3356cee12504da7b19021d3253d433366e0a3e, + r: 0x09128734fb9b787e4c512798dd4c1ead152f390d02f5859a5274298a050e5f7c, + s: 0x2b4ce3c0c5b7f3d8e3954254131593f0b3c1ace654d36ec9a90338cfc3d7cabb, } .serialize(ref output); @@ -256,7 +254,7 @@ fn get_accept_ownership_signature_snake() -> Span { fn get_accept_ownership_signature_camel() -> Span { let mut output = array![]; - // 0x048d4c831924c90963645d7473e0954d2ac37c1f20e201ed7c1778942df5d58d = + // 0x071fd050d0433b0301af96d4878de600248060d300f4fc715e57e2cc5739eda7 = // PoseidonTrait::new() // .update_with('StarkNet Message') // .update_with('accept_ownership') @@ -269,10 +267,10 @@ fn get_accept_ownership_signature_camel() -> Span { // - public_key: // r: 0x829307f82a1883c2414503ba85fc85037f22c6fc6f80910801f6b01a4131da1e // s: 0x2a23f7bddf3715d11767b1247eccc68c89e11b926e2615268db6ad1af8d8da96 - // - msg_hash: 0x048d4c831924c90963645d7473e0954d2ac37c1f20e201ed7c1778942df5d58d + // - msg_hash: 0x071fd050d0433b0301af96d4878de600248060d300f4fc715e57e2cc5739eda7 EthSignature { - r: 0x7a0fa1e6bfc6a0b86cdbb9877551a108d42d3de50cb7a516e63fe5a26e5a9c52, - s: 0x3cc64ca8bf6963ae01125f0d932b8780ca0ed1612fb74a84d4f76593e6687b74, + r: 0xcc988a5f4ac1a98a8418b06ce4bf4eeac3fb37aef73fd7a349ea3e8389f030ef, + s: 0x56391a25f7f4c196307d910f72eb4ae62d7976fcdb70ddad9de7bf3e0fcf944a, } .serialize(ref output); diff --git a/src/tests/account/ethereum/test_eth_account.cairo b/src/tests/account/ethereum/test_eth_account.cairo index 8c1a8e8d6..37b77ad87 100644 --- a/src/tests/account/ethereum/test_eth_account.cairo +++ b/src/tests/account/ethereum/test_eth_account.cairo @@ -4,9 +4,7 @@ use openzeppelin::account::EthAccountComponent; use openzeppelin::account::interface::EthPublicKey; use openzeppelin::account::interface::{EthAccountABIDispatcherTrait, EthAccountABIDispatcher}; use openzeppelin::account::interface::{ISRC6, ISRC6_ID}; -use openzeppelin::account::utils::secp256k1::{ - DebugSecp256k1Point, Secp256k1PointPartialEq, Secp256k1PointSerde -}; +use openzeppelin::account::utils::secp256k1::{DebugSecp256k1Point, Secp256k1PointPartialEq}; use openzeppelin::account::utils::signature::EthSignature; use openzeppelin::introspection::interface::{ISRC5, ISRC5_ID}; use openzeppelin::tests::mocks::eth_account_mocks::DualCaseEthAccountMock; diff --git a/src/tests/account/test_secp256k1.cairo b/src/tests/account/test_secp256k1.cairo index 5466eee55..8c7b8117b 100644 --- a/src/tests/account/test_secp256k1.cairo +++ b/src/tests/account/test_secp256k1.cairo @@ -1,6 +1,5 @@ use openzeppelin::account::utils::secp256k1::{ - DebugSecp256k1Point, Secp256k1PointSerde, Secp256k1PointPartialEq, - Secp256k1PointStorePacking as StorePacking + DebugSecp256k1Point, Secp256k1PointPartialEq, Secp256k1PointStorePacking as StorePacking }; use starknet::SyscallResultTrait; use starknet::secp256_trait::{Secp256Trait, Secp256PointTrait}; @@ -65,53 +64,6 @@ fn test_unpack_big_secp256k1_points() { assert_eq!(y, expected_y); } -#[test] -fn test_secp256k1_serialization() { - let (big_point_1, big_point_2) = get_points(); - - let mut serialized_point = array![]; - let mut expected_serialization = array![]; - - // Check point 1 - - big_point_1.serialize(ref serialized_point); - big_point_1.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); - - assert!(serialized_point == expected_serialization); - - // Check point 2 - - big_point_2.serialize(ref serialized_point); - big_point_2.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); - - assert!(serialized_point == expected_serialization); -} - -#[test] -fn test_secp256k1_deserialization() { - let (big_point_1, big_point_2) = get_points(); - - // Check point 1 - - let mut expected_serialization = array![]; - - big_point_1.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); - let mut expected_serialization = expected_serialization.span(); - let deserialized_point = Secp256k1PointSerde::deserialize(ref expected_serialization).unwrap(); - - assert_eq!(big_point_1, deserialized_point); - - // Check point 2 - - let mut expected_serialization = array![]; - - big_point_2.get_coordinates().unwrap_syscall().serialize(ref expected_serialization); - let mut expected_serialization = expected_serialization.span(); - let deserialized_point = Secp256k1PointSerde::deserialize(ref expected_serialization).unwrap(); - - assert_eq!(big_point_2, deserialized_point); -} - #[test] fn test_partial_eq() { let (big_point_1, big_point_2) = get_points(); diff --git a/src/tests/mocks/erc721_receiver_mocks.cairo b/src/tests/mocks/erc721_receiver_mocks.cairo index f496d0a9c..55ca3aebf 100644 --- a/src/tests/mocks/erc721_receiver_mocks.cairo +++ b/src/tests/mocks/erc721_receiver_mocks.cairo @@ -65,7 +65,7 @@ pub(crate) mod DualCaseERC721ReceiverMock { tokenId: u256, data: Span ) -> felt252 { - ExternalTrait::on_erc721_received(self, operator, from, tokenId, data) + Self::on_erc721_received(self, operator, from, tokenId, data) } } } diff --git a/src/tests/mocks/eth_account_mocks.cairo b/src/tests/mocks/eth_account_mocks.cairo index 687e5d68f..24161d3ba 100644 --- a/src/tests/mocks/eth_account_mocks.cairo +++ b/src/tests/mocks/eth_account_mocks.cairo @@ -2,7 +2,6 @@ pub(crate) mod DualCaseEthAccountMock { use openzeppelin::account::EthAccountComponent; use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use openzeppelin::introspection::src5::SRC5Component; component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); @@ -47,7 +46,6 @@ pub(crate) mod DualCaseEthAccountMock { pub(crate) mod SnakeEthAccountMock { use openzeppelin::account::EthAccountComponent; use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use openzeppelin::introspection::src5::SRC5Component; component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); @@ -88,7 +86,6 @@ pub(crate) mod SnakeEthAccountMock { pub(crate) mod CamelEthAccountMock { use openzeppelin::account::EthAccountComponent; use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use openzeppelin::introspection::src5::SRC5Component; use starknet::account::Call; @@ -151,7 +148,6 @@ pub(crate) mod CamelEthAccountMock { #[starknet::contract] pub(crate) mod SnakeEthAccountPanicMock { use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use starknet::SyscallResultTrait; use starknet::secp256_trait::Secp256Trait; @@ -193,7 +189,6 @@ pub(crate) mod SnakeEthAccountPanicMock { #[starknet::contract] pub(crate) mod CamelEthAccountPanicMock { use openzeppelin::account::interface::EthPublicKey; - use openzeppelin::account::utils::secp256k1::Secp256k1PointSerde; use starknet::SyscallResultTrait; use starknet::secp256_trait::Secp256Trait; diff --git a/src/tests/presets/test_eth_account.cairo b/src/tests/presets/test_eth_account.cairo index b8510958a..4b4633ca7 100644 --- a/src/tests/presets/test_eth_account.cairo +++ b/src/tests/presets/test_eth_account.cairo @@ -1,8 +1,6 @@ use core::num::traits::Zero; use openzeppelin::account::interface::ISRC6_ID; -use openzeppelin::account::utils::secp256k1::{ - DebugSecp256k1Point, Secp256k1PointSerde, Secp256k1PointPartialEq -}; +use openzeppelin::account::utils::secp256k1::{DebugSecp256k1Point, Secp256k1PointPartialEq}; use openzeppelin::account::utils::signature::EthSignature; use openzeppelin::introspection::interface::ISRC5_ID; use openzeppelin::presets::EthAccountUpgradeable; @@ -104,7 +102,7 @@ fn test_constructor() { // #[test] -fn test_public_key_setter_and_getter_2() { +fn test_public_key_setter_and_getter() { let dispatcher = setup_dispatcher(); let new_public_key = NEW_ETH_PUBKEY(); @@ -505,7 +503,7 @@ fn set_contract_and_caller(address: ContractAddress) { fn get_accept_ownership_signature() -> Span { let mut output = array![]; - // 0x054308383e1c733aa36ccf3cc62e3107b6bcb10bafcab39912108c6b52655b4c = + // 0x077ab2f889d044a0a23f30c86151d7b5c6d2adc91fb603b16bb32fd35d3ac07d = // PoseidonTrait::new() // .update_with('StarkNet Message') // .update_with('accept_ownership') @@ -518,10 +516,10 @@ fn get_accept_ownership_signature() -> Span { // - public_key: // r: 0x829307f82a1883c2414503ba85fc85037f22c6fc6f80910801f6b01a4131da1e // s: 0x2a23f7bddf3715d11767b1247eccc68c89e11b926e2615268db6ad1af8d8da96 - // - msg_hash: 0x054308383e1c733aa36ccf3cc62e3107b6bcb10bafcab39912108c6b52655b4c + // - msg_hash: 0x077ab2f889d044a0a23f30c86151d7b5c6d2adc91fb603b16bb32fd35d3ac07d EthSignature { - r: 0xc4de7637e4206e64ddae9261782dad6d3c99eaaede20ff3fb183f751b94ee9ff, - s: 0x77c24e1ad34f5ba0627048f6a11c1e7ee1ddd3b5d518ee4c71ebd5725390f860, + r: 0x518caf844e08000c67ef7f9f56105ae52b9f7532baac1561bbfb4a8fb691ba80, + s: 0x559ce65943263032ff0b5906466cd67fd15f7965c8befc162a276abcd63f7c2c, } .serialize(ref output); diff --git a/src/tests/security/test_reentrancyguard.cairo b/src/tests/security/test_reentrancyguard.cairo index b243b4aa4..02add1645 100644 --- a/src/tests/security/test_reentrancyguard.cairo +++ b/src/tests/security/test_reentrancyguard.cairo @@ -4,7 +4,6 @@ use openzeppelin::tests::mocks::reentrancy_mocks::{ Attacker, ReentrancyMock, IReentrancyMockDispatcher, IReentrancyMockDispatcherTrait }; use openzeppelin::tests::utils; -use starknet::storage::StorageMemberAccessTrait; type ComponentState = ReentrancyGuardComponent::ComponentState; diff --git a/src/tests/token/erc1155/test_dual1155.cairo b/src/tests/token/erc1155/test_dual1155.cairo index 16bfe4bfa..f4c598046 100644 --- a/src/tests/token/erc1155/test_dual1155.cairo +++ b/src/tests/token/erc1155/test_dual1155.cairo @@ -74,10 +74,14 @@ fn setup_erc1155_panic() -> (DualCaseERC1155, DualCaseERC1155) { // #[test] -fn test_dual_uri() { +fn test_snake_target_uri() { let (snake_dispatcher, _, _) = setup_snake(); - let (camel_dispatcher, _, _) = setup_camel(); assert_eq!(snake_dispatcher.uri(TOKEN_ID), "URI"); +} + +#[test] +fn test_camel_target_uri() { + let (camel_dispatcher, _, _) = setup_camel(); assert_eq!(camel_dispatcher.uri(TOKEN_ID), "URI"); } diff --git a/src/tests/token/erc1155/test_erc1155.cairo b/src/tests/token/erc1155/test_erc1155.cairo index 978bc1634..57d2fb3f3 100644 --- a/src/tests/token/erc1155/test_erc1155.cairo +++ b/src/tests/token/erc1155/test_erc1155.cairo @@ -1,5 +1,4 @@ use core::num::traits::Zero; -use core::starknet::storage::StorageMemberAccessTrait; use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc1155_mocks::DualCaseERC1155Mock; diff --git a/src/tests/token/erc20/test_erc20_votes.cairo b/src/tests/token/erc20/test_erc20_votes.cairo index e241e891c..907a2c1c2 100644 --- a/src/tests/token/erc20/test_erc20_votes.cairo +++ b/src/tests/token/erc20/test_erc20_votes.cairo @@ -17,7 +17,6 @@ use openzeppelin::utils::serde::SerializedAppend; use openzeppelin::utils::structs::checkpoint::{Checkpoint, Trace, TraceTrait}; use starknet::ContractAddress; use starknet::contract_address_const; -use starknet::storage::{StorageMapMemberAccessTrait, StorageMemberAccessTrait}; use starknet::testing; use super::common::{assert_event_approval, assert_only_event_approval, assert_only_event_transfer}; @@ -298,7 +297,7 @@ fn test_delegate_by_sig_invalid_signature() { #[test] fn test_num_checkpoints() { - let state = setup(); + let state = @setup(); let mut trace = state.ERC20Votes_delegate_checkpoints.read(OWNER()); trace.push('ts1', 0x111); @@ -316,7 +315,7 @@ fn test_num_checkpoints() { #[test] fn test_checkpoints() { - let state = setup(); + let state = @setup(); let mut trace = state.ERC20Votes_delegate_checkpoints.read(OWNER()); trace.push('ts1', 0x111); diff --git a/src/tests/token/erc721/test_erc721.cairo b/src/tests/token/erc721/test_erc721.cairo index 7dd5ae1bf..93cab1e1c 100644 --- a/src/tests/token/erc721/test_erc721.cairo +++ b/src/tests/token/erc721/test_erc721.cairo @@ -23,7 +23,6 @@ use openzeppelin::token::erc721::interface::IERC721; use openzeppelin::token::erc721; use starknet::ContractAddress; use starknet::contract_address_const; -use starknet::storage::StorageMapMemberAccessTrait; use starknet::testing; use super::common::{ diff --git a/src/token/erc1155/erc1155.cairo b/src/token/erc1155/erc1155.cairo index dbbf3b777..f852af2a0 100644 --- a/src/token/erc1155/erc1155.cairo +++ b/src/token/erc1155/erc1155.cairo @@ -21,11 +21,12 @@ pub mod ERC1155Component { use openzeppelin::token::erc1155::interface; use starknet::ContractAddress; use starknet::get_caller_address; + use starknet::storage::Map; #[storage] struct Storage { - ERC1155_balances: LegacyMap<(u256, ContractAddress), u256>, - ERC1155_operator_approvals: LegacyMap<(ContractAddress, ContractAddress), bool>, + ERC1155_balances: Map<(u256, ContractAddress), u256>, + ERC1155_operator_approvals: Map<(ContractAddress, ContractAddress), bool>, ERC1155_uri: ByteArray, } @@ -75,10 +76,11 @@ pub mod ERC1155Component { pub approved: bool } - /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + /// Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic + /// URI. /// - /// If an `URI` event was emitted for `id`, the standard guarantees that `value` will equal the value - /// returned by `IERC1155MetadataURI::uri`. + /// If an `URI` event was emitted for `id`, the standard guarantees that `value` will equal the + /// value returned by `IERC1155MetadataURI::uri`. /// https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions #[derive(Drop, PartialEq, starknet::Event)] pub struct URI { @@ -158,19 +160,20 @@ pub mod ERC1155Component { break; } batch_balances - .append(ERC1155::balance_of(self, *accounts.at(index), *token_ids.at(index))); + .append(Self::balance_of(self, *accounts.at(index), *token_ids.at(index))); index += 1; }; batch_balances.span() } - /// Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an account or `IERC1155Receiver`. + /// Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an + /// account or `IERC1155Receiver`. /// /// `data` is additional data, it has no specified format and it is passed to `to`. /// - /// WARNING: This function can potentially allow a reentrancy attack when transferring tokens - /// to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. + /// WARNING: This function can potentially allow a reentrancy attack when transferring + /// tokens to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. /// Ensure to follow the checks-effects-interactions pattern and consider employing /// reentrancy guards when interacting with untrusted contracts. /// @@ -179,7 +182,8 @@ pub mod ERC1155Component { /// - Caller is either approved or the `token_id` owner. /// - `from` is not the zero address. /// - `to` is not the zero address. - /// - If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_received` + /// - If `to` refers to a non-account contract, it must implement + /// `IERC1155Receiver::on_ERC1155_received` /// and return the required magic value. /// /// Emits a `TransferSingle` event. @@ -193,13 +197,14 @@ pub mod ERC1155Component { ) { let token_ids = array![token_id].span(); let values = array![value].span(); - ERC1155::safe_batch_transfer_from(ref self, from, to, token_ids, values, data) + Self::safe_batch_transfer_from(ref self, from, to, token_ids, values, data) } /// Batched version of `safe_transfer_from`. /// - /// WARNING: This function can potentially allow a reentrancy attack when transferring tokens - /// to an untrusted contract, when invoking `on_ERC1155_batch_received` on the receiver. + /// WARNING: This function can potentially allow a reentrancy attack when transferring + /// tokens to an untrusted contract, when invoking `on_ERC1155_batch_received` on the + /// receiver. /// Ensure to follow the checks-effects-interactions pattern and consider employing /// reentrancy guards when interacting with untrusted contracts. /// @@ -209,10 +214,12 @@ pub mod ERC1155Component { /// - `from` is not the zero address. /// - `to` is not the zero address. /// - `token_ids` and `values` must have the same length. - /// - If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` + /// - If `to` refers to a non-account contract, it must implement + /// `IERC1155Receiver::on_ERC1155_batch_received` /// and return the acceptance magic value. /// - /// Emits either a `TransferSingle` or a `TransferBatch` event, depending on the length of the array arguments. + /// Emits either a `TransferSingle` or a `TransferBatch` event, depending on the length of + /// the array arguments. fn safe_batch_transfer_from( ref self: ComponentState, from: starknet::ContractAddress, @@ -226,7 +233,7 @@ pub mod ERC1155Component { let operator = get_caller_address(); if from != operator { - assert(ERC1155::is_approved_for_all(@self, from, operator), Errors::UNAUTHORIZED); + assert(Self::is_approved_for_all(@self, from, operator), Errors::UNAUTHORIZED); } self.update_with_acceptance_check(from, to, token_ids, values, data); @@ -483,7 +490,8 @@ pub mod ERC1155Component { /// Requirements: /// /// - `to` cannot be the zero address. - /// - If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_received` + /// - If `to` refers to a smart contract, it must implement + /// `IERC1155Receiver::on_ERC1155_received` /// and return the acceptance magic value. /// /// Emits a `TransferSingle` event. @@ -507,7 +515,8 @@ pub mod ERC1155Component { /// /// - `to` cannot be the zero address. /// - `token_ids` and `values` must have the same length. - /// - If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` + /// - If `to` refers to a smart contract, it must implement + /// `IERC1155Receiver::on_ERC1155_batch_received` /// and return the acceptance magic value. /// /// Emits a `TransferBatch` event. @@ -571,7 +580,8 @@ pub mod ERC1155Component { /// - `to` is either an account contract or supports the `IERC1155Receiver` interface. /// - `token_ids` and `values` must have the same length. /// - /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` otherwise. + /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` + /// otherwise. fn update_with_acceptance_check( ref self: ComponentState, from: ContractAddress, @@ -596,7 +606,8 @@ pub mod ERC1155Component { /// /// - `token_ids` and `values` must have the same length. /// - /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` otherwise. + /// Emits a `TransferSingle` event if the arrays contain one element, and `TransferBatch` + /// otherwise. /// /// NOTE: This function can be extended using the `ERC1155HooksTrait`, to add /// functionality before and/or after the transfer, mint, or burn. diff --git a/src/token/erc20/erc20.cairo b/src/token/erc20/erc20.cairo index b295ea9fe..6d8e0f3cf 100644 --- a/src/token/erc20/erc20.cairo +++ b/src/token/erc20/erc20.cairo @@ -9,7 +9,8 @@ use starknet::ContractAddress; /// non-standard implementations that can be used to create an ERC20 contract. This /// component is agnostic regarding how tokens are created, which means that developers /// must create their own token distribution mechanism. -/// See [the documentation](https://docs.openzeppelin.com/contracts-cairo/0.15.0-rc.0/guides/erc20-supply) +/// See [the documentation] +/// (https://docs.openzeppelin.com/contracts-cairo/0.15.0-rc.0/guides/erc20-supply) /// for examples. #[starknet::component] pub mod ERC20Component { @@ -18,14 +19,15 @@ pub mod ERC20Component { use openzeppelin::token::erc20::interface; use starknet::ContractAddress; use starknet::get_caller_address; + use starknet::storage::Map; #[storage] struct Storage { ERC20_name: ByteArray, ERC20_symbol: ByteArray, ERC20_total_supply: u256, - ERC20_balances: LegacyMap, - ERC20_allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + ERC20_balances: Map, + ERC20_allowances: Map<(ContractAddress, ContractAddress), u256>, } #[event] @@ -294,7 +296,8 @@ pub mod ERC20Component { TContractState, +HasComponent, impl Hooks: ERC20HooksTrait > of InternalTrait { /// Initializes the contract by setting the token name and symbol. - /// To prevent reinitialization, this should only be used inside of a contract's constructor. + /// To prevent reinitialization, this should only be used inside of a contract's + /// constructor. fn initializer( ref self: ComponentState, name: ByteArray, symbol: ByteArray ) { @@ -330,8 +333,8 @@ pub mod ERC20Component { } - /// Transfers an `amount` of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is - /// the zero address. + /// Transfers an `amount` of tokens from `from` to `to`, or alternatively mints (or burns) + /// if `from` (or `to`) is the zero address. /// /// NOTE: This function can be extended using the `ERC20HooksTrait`, to add /// functionality before and/or after the transfer, mint, or burn. diff --git a/src/token/erc20/extensions/erc20_votes.cairo b/src/token/erc20/extensions/erc20_votes.cairo index b507fe5a3..0a9d2f50b 100644 --- a/src/token/erc20/extensions/erc20_votes.cairo +++ b/src/token/erc20/extensions/erc20_votes.cairo @@ -8,11 +8,12 @@ use starknet::ContractAddress; /// # ERC20Votes Component /// -/// The ERC20Votes component tracks voting units from ERC20 balances, which are a measure of voting power that can be -/// transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of -/// "representative" that will pool delegated voting units from different accounts and can then use it to vote in -/// decisions. In fact, voting units MUST be delegated in order to count as actual votes, and an account has to -/// delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. +/// The ERC20Votes component tracks voting units from ERC20 balances, which are a measure of voting +/// power that can be transferred, and provides a system of vote delegation, where an account can +/// delegate its voting units to a sort of "representative" that will pool delegated voting units +/// from different accounts and can then use it to vote in decisions. In fact, voting units MUST be +/// delegated in order to count as actual votes, and an account has to delegate those votes to +/// itself if it wishes to participate in decisions and does not have a trusted representative. #[starknet::component] pub mod ERC20VotesComponent { use core::num::traits::Zero; @@ -24,12 +25,13 @@ pub mod ERC20VotesComponent { use openzeppelin::utils::nonces::NoncesComponent; use openzeppelin::utils::structs::checkpoint::{Checkpoint, Trace, TraceTrait}; use starknet::ContractAddress; + use starknet::storage::Map; use super::{Delegation, OffchainMessageHash, SNIP12Metadata}; #[storage] struct Storage { - ERC20Votes_delegatee: LegacyMap, - ERC20Votes_delegate_checkpoints: LegacyMap, + ERC20Votes_delegatee: Map, + ERC20Votes_delegate_checkpoints: Map, ERC20Votes_total_checkpoints: Trace } @@ -101,9 +103,10 @@ pub mod ERC20VotesComponent { /// /// - `timepoint` must be in the past. /// - /// NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. - /// Votes that have not been delegated are still part of total supply, even though they would not participate in a - /// vote. + /// NOTE: This value is the sum of all available votes, which is not necessarily the sum of + /// all delegated votes. + /// Votes that have not been delegated are still part of total supply, even though they + /// would not participate in a vote. fn get_past_total_supply(self: @ComponentState, timepoint: u64) -> u256 { let current_timepoint = starknet::get_block_timestamp(); assert(timepoint < current_timepoint, Errors::FUTURE_LOOKUP); @@ -127,7 +130,8 @@ pub mod ERC20VotesComponent { self._delegate(sender, delegatee); } - /// Delegates votes from the sender to `delegatee` through a SNIP12 message signature validation. + /// Delegates votes from the sender to `delegatee` through a SNIP12 message signature + /// validation. /// /// Requirements: /// diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 0ad907cda..476ac76ee 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -21,15 +21,16 @@ pub mod ERC721Component { use openzeppelin::token::erc721::interface; use starknet::ContractAddress; use starknet::get_caller_address; + use starknet::storage::Map; #[storage] struct Storage { ERC721_name: ByteArray, ERC721_symbol: ByteArray, - ERC721_owners: LegacyMap, - ERC721_balances: LegacyMap, - ERC721_token_approvals: LegacyMap, - ERC721_operator_approvals: LegacyMap<(ContractAddress, ContractAddress), bool>, + ERC721_owners: Map, + ERC721_balances: Map, + ERC721_token_approvals: Map, + ERC721_operator_approvals: Map<(ContractAddress, ContractAddress), bool>, ERC721_base_uri: ByteArray } @@ -133,11 +134,13 @@ pub mod ERC721Component { self._require_owned(token_id) } - /// Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. + /// Transfers ownership of `token_id` from `from` if `to` is either an account or + /// `IERC721Receiver`. /// /// `data` is additional data, it has no specified format and it is sent in call to `to`. /// - /// WARNING: This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + /// WARNING: This method makes an external call to the recipient contract, which can lead to + /// reentrancy vulnerabilities. /// /// Requirements: /// @@ -155,7 +158,7 @@ pub mod ERC721Component { token_id: u256, data: Span ) { - ERC721::transfer_from(ref self, from, to, token_id); + Self::transfer_from(ref self, from, to, token_id); assert( _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED ); @@ -163,7 +166,8 @@ pub mod ERC721Component { /// Transfers ownership of `token_id` from `from` to `to`. /// - /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 + /// protocol. /// /// Requirements: /// @@ -181,8 +185,9 @@ pub mod ERC721Component { ) { assert(!to.is_zero(), Errors::INVALID_RECEIVER); - // Setting an "auth" arguments enables the `_is_authorized` check which verifies that the token exists - // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + // Setting an "auth" arguments enables the `_is_authorized` check which verifies that + // the token exists (from != 0). Therefore, it is not needed to verify that the return + // value is not 0 here. let previous_owner = self.update(to, token_id, get_caller_address()); assert(from == previous_owner, Errors::INVALID_SENDER); @@ -499,7 +504,8 @@ pub mod ERC721Component { /// /// Internal function without access restriction. /// - /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 + /// protocol. /// /// Requirements: /// @@ -525,7 +531,8 @@ pub mod ERC721Component { /// Mints `token_id` and transfers it to `to`. /// Internal function without access restriction. /// - /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + /// WARNING: This method may lead to the loss of tokens if `to` is not aware of the ERC721 + /// protocol. /// /// Requirements: /// @@ -541,11 +548,13 @@ pub mod ERC721Component { assert(previous_owner.is_zero(), Errors::ALREADY_MINTED); } - /// Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. + /// Transfers ownership of `token_id` from `from` if `to` is either an account or + /// `IERC721Receiver`. /// /// `data` is additional data, it has no specified format and it is sent in call to `to`. /// - /// WARNING: This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + /// WARNING: This method makes an external call to the recipient contract, which can lead to + /// reentrancy vulnerabilities. /// /// Requirements: /// @@ -572,7 +581,8 @@ pub mod ERC721Component { /// /// `data` is additional data, it has no specified format and it is sent in call to `to`. /// - /// WARNING: This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + /// WARNING: This method makes an external call to the recipient contract, which can lead to + /// reentrancy vulnerabilities. /// /// Requirements: /// @@ -608,11 +618,13 @@ pub mod ERC721Component { assert(!previous_owner.is_zero(), Errors::INVALID_TOKEN_ID); } - /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner - /// (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) + /// if the current owner (or `to`) is the zero address. Returns the owner of the `token_id` + /// before the update. /// - /// The `auth` argument is optional. If the value passed is non-zero, then this function will check that - /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). + /// The `auth` argument is optional. If the value passed is non-zero, then this function + /// will check that `auth` is either the owner of the token, or approved to operate on the + /// token (by the owner). /// /// Emits a `Transfer` event. /// @@ -671,8 +683,9 @@ pub mod ERC721Component { /// Approve `to` to operate on `token_id` /// - /// The `auth` argument is optional. If the value passed is non-zero, then this function will check that `auth` is - /// either the owner of the token, or approved to operate on all tokens held by this owner. + /// The `auth` argument is optional. If the value passed is non-zero, then this function + /// will check that `auth` is either the owner of the token, or approved to operate on all + /// tokens held by this owner. /// /// Emits an `Approval` event. fn _approve( @@ -684,10 +697,11 @@ pub mod ERC721Component { self._approve_with_optional_event(to, token_id, auth, true); } - /// Variant of `_approve` with an optional flag to enable or disable the `Approval` event. The event is not - /// emitted in the context of transfers. + /// Variant of `_approve` with an optional flag to enable or disable the `Approval` event. + /// The event is not emitted in the context of transfers. /// - /// WARNING: If `auth` is zero and `emit_event` is false, this function will not check that the token exists. + /// WARNING: If `auth` is zero and `emit_event` is false, this function will not check that + /// the token exists. /// /// Requirements: /// @@ -744,7 +758,8 @@ pub mod ERC721Component { /// Base URI for computing `token_uri`. /// - /// If set, the resulting URI for each token will be the concatenation of the base URI and the token ID. + /// If set, the resulting URI for each token will be the concatenation of the base URI and + /// the token ID. /// Returns an empty `ByteArray` if not set. fn _base_uri(self: @ComponentState) -> ByteArray { self.ERC721_base_uri.read() @@ -753,8 +768,8 @@ pub mod ERC721Component { /// Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in /// particular (ignoring whether it is owned by `owner`). /// - /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this - /// assumption. + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does + /// not verify this assumption. fn _is_authorized( self: @ComponentState, owner: ContractAddress, @@ -769,7 +784,8 @@ pub mod ERC721Component { || spender == ERC721::get_approved(self, token_id)) } - /// Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + /// Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the + /// actual owner. /// /// Requirements: /// @@ -777,8 +793,8 @@ pub mod ERC721Component { /// - `spender` cannot be the zero address. /// - `spender` must be the owner of `token_id` or be approved to operate on it. /// - /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this - /// assumption. + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does + /// not verify this assumption. fn _check_authorized( self: @ComponentState, owner: ContractAddress, diff --git a/src/upgrades/upgradeable.cairo b/src/upgrades/upgradeable.cairo index e8d436998..c78d25476 100644 --- a/src/upgrades/upgradeable.cairo +++ b/src/upgrades/upgradeable.cairo @@ -25,7 +25,7 @@ pub mod UpgradeableComponent { pub class_hash: ClassHash } - mod Errors { + pub mod Errors { pub const INVALID_CLASS: felt252 = 'Class hash cannot be zero'; } diff --git a/src/utils/cryptography/nonces.cairo b/src/utils/cryptography/nonces.cairo index cf85c3970..40089d76a 100644 --- a/src/utils/cryptography/nonces.cairo +++ b/src/utils/cryptography/nonces.cairo @@ -5,13 +5,14 @@ pub mod NoncesComponent { use openzeppelin::utils::interfaces::INonces; use starknet::ContractAddress; + use starknet::storage::Map; #[storage] struct Storage { - Nonces_nonces: LegacyMap + Nonces_nonces: Map } - mod Errors { + pub mod Errors { pub const INVALID_NONCE: felt252 = 'Nonces: invalid nonce'; } @@ -31,8 +32,9 @@ pub mod NoncesComponent { > of InternalTrait { /// Consumes a nonce, returns the current value, and increments nonce. fn use_nonce(ref self: ComponentState, owner: ContractAddress) -> felt252 { - // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be - // decremented or reset. This guarantees that the nonce never overflows. + // For each account, the nonce has an initial value of 0, can only be incremented by + // one, and cannot be decremented or reset. This guarantees that the nonce never + // overflows. let nonce = self.Nonces_nonces.read(owner); self.Nonces_nonces.write(owner, nonce + 1); nonce diff --git a/src/utils/deployments.cairo b/src/utils/deployments.cairo index 16a1f0519..0ca55135c 100644 --- a/src/utils/deployments.cairo +++ b/src/utils/deployments.cairo @@ -15,8 +15,11 @@ const L2_ADDRESS_UPPER_BOUND: felt252 = const CONTRACT_ADDRESS_PREFIX: felt252 = 'STARKNET_CONTRACT_ADDRESS'; /// Returns the contract address from a `deploy_syscall`. -/// `deployer_address` should be the zero address if the deployment is origin-independent (deployed from zero). -/// For more information, see https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-address/ +/// `deployer_address` should be the zero address if the deployment is origin-independent (deployed +/// from zero). +/// For more information, see +/// +/// https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-address/ pub fn calculate_contract_address_from_deploy_syscall( salt: felt252, class_hash: ClassHash, @@ -65,7 +68,8 @@ pub struct DeployerInfo { } /// Returns the calculated contract address for contracts deployed through the UDC. -/// Origin-independent deployments (deployed from zero) should pass `Option::None` as `deployer_info`. +/// Origin-independent deployments (deployed from zero) should pass `Option::None` as +/// `deployer_info`. pub fn calculate_contract_address_from_udc( salt: felt252, class_hash: ClassHash, diff --git a/src/utils/structs/checkpoint.cairo b/src/utils/structs/checkpoint.cairo index de07f5d84..c26938dd8 100644 --- a/src/utils/structs/checkpoint.cairo +++ b/src/utils/structs/checkpoint.cairo @@ -112,8 +112,8 @@ pub impl TraceImpl of TraceTrait { #[generate_trait] impl CheckpointImpl of CheckpointTrait { - /// Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, - /// or by updating the last one. + /// Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a + /// new checkpoint, or by updating the last one. fn _insert(ref self: StorageArray, key: u64, value: u256) -> (u256, u256) { let pos = self.len(); @@ -138,9 +138,9 @@ impl CheckpointImpl of CheckpointTrait { } } - /// Returns the index of the last (most recent) checkpoint with the key lower than or equal to the search key, - /// or `high` if there is none. `low` and `high` define a section where to do the search, with - /// inclusive `low` and exclusive `high`. + /// Returns the index of the last (most recent) checkpoint with the key lower than or equal to + /// the search key, or `high` if there is none. `low` and `high` define a section where to do + /// the search, with inclusive `low` and exclusive `high`. fn _upper_binary_lookup(self: @StorageArray, key: u64, low: u32, high: u32) -> u32 { let mut _low = low; let mut _high = high; From 5f03fba338b4cb321776947fb98b4584a32adece Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Mon, 15 Jul 2024 12:56:37 -0400 Subject: [PATCH 05/12] Bump Scarb to 2.7.0-rc.2 (#1052) * bump scarb to 2.7.0-rc.2 * add changelog entry * update changelog entry * bump scarb in installation page --- CHANGELOG.md | 1 + Scarb.toml | 8 ++++---- docs/modules/ROOT/pages/index.adoc | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f22a2b1..41c14cb56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump scarb to v2.7.0-rc.1 (#1025) +- Bump scarb to v2.7.0-rc.2 (#1052) ## 0.15.0-rc.0 (2024-07-8) diff --git a/Scarb.toml b/Scarb.toml index 1b79be6b4..21be19db4 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -2,8 +2,8 @@ name = "openzeppelin" version = "0.15.0-rc.0" edition = "2023_11" -cairo-version = "2.7.0-rc.1" -scarb-version = "2.7.0-rc.1" +cairo-version = "2.7.0-rc.2" +scarb-version = "2.7.0-rc.2" authors = ["OpenZeppelin Community "] description = "OpenZeppelin Contracts written in Cairo for StarkNet, a decentralized ZK Rollup" documentation = "https://docs.openzeppelin.com/contracts-cairo" @@ -13,10 +13,10 @@ license-file = "LICENSE" keywords = ["openzeppelin", "starknet", "cairo", "contracts", "security", "standards"] [dependencies] -starknet = "2.7.0-rc.1" +starknet = "2.7.0-rc.2" [dev-dependencies] -cairo_test = "2.7.0-rc.1" +cairo_test = "2.7.0-rc.2" [lib] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 6703db744..1bfd1a442 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -20,9 +20,9 @@ before proceeding, and run the following command to check that the installation ---- $ scarb --version -scarb 2.6.5 (d49f54394 2024-06-11) -cairo: 2.6.4 (https://crates.io/crates/cairo-lang-compiler/2.6.4) -sierra: 1.5.0 +scarb 2.7.0-rc.2 (55754b5d3 2024-07-12) +cairo: 2.7.0-rc.2 (https://crates.io/crates/cairo-lang-compiler/2.7.0-rc.2) +sierra: 1.6.0 ---- === Set up your project From 557ed27c3b89b234631c061c30435816bb277b32 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Mon, 22 Jul 2024 14:30:30 -0400 Subject: [PATCH 06/12] Add timelock component (#996) * start timelock comp draft * move timelock to own dir, finish drafting component * tmp: add utility impls * add timelock mock * add test mod for timelock * fix commnet * add constructor to timelock mock * set min_delay in initializer * start tests * fix fmt * fix schedule assertion * add schedule tests * fix fmt * remove unused import * fix errs, _before_call * start execute tests * fix fmt * fix after_call * add execute tests * add abi interface * add reentrancy mock for timelock * add tests for cancel and update_delay * fix hash_op test * add execute with predecessor test * add timelock utils, add operation_state debug impl * fix fmt * improve imports * improve _execute * add basic mock for tests * add tmp call struct * add batch fns to interface * add batch fns * refactor tests to use dedicated mock, add batch tests * fix fmt * remove use clause * add timelock mixin * fix interface name * improve event assertions * fix execute and schedule events * fix fmt * add safe token transfer tests * fix fmt * tidy up code * add descriptions to events * clean up code * inline CallPartialEq fns * start fn descriptions * remove comments * remove comment * fix fmt * add changelog entries * improve spacing * add line break to hash test * clean up tests * clean up tests * fix constants in attacker impl * add initializer helper, register access control support * add _before_call and _after_call tests * fix reentrant batch mock call * add _schedule and _execute tests * add timelock description * fix formatting * fix comments * fix comment * fmt * tidy up tests * remove batch helper fn * remove event from mocks * Apply suggestions from code review Co-authored-by: Eric Nordelo * update spdx * remove token receiver support * add additional cancel tests * Apply suggestions from code review Co-authored-by: Eric Nordelo * initializer: remove mut, use while loop * fix fmt * add assert_only_self fn * fix getter comments re: pending/waiting * add assert_only_role * add specific op errors * make event names consistent * fix test * remove serialization from HashCallImpl * remove unused components from mock * clean up code * update to 2.7.0-rc.1 * fix fmt * import Call from corelib * update spdx * fix fmt * move OperationState, derive debug * fix fmt * fix hash impls * add for loops * fix PartialEq, add tests * fix fmt * simplify mixin fns * switch Poseidon to Pedersen * make admin a req in initializer * update tests with admin * fix fmt * fix comment * undo changes * add no admin initializer test --------- Co-authored-by: Eric Nordelo --- CHANGELOG.md | 5 + src/governance.cairo | 1 + src/governance/timelock.cairo | 9 + src/governance/timelock/interface.cairo | 69 + .../timelock/timelock_controller.cairo | 659 ++++++ src/governance/timelock/utils.cairo | 1 + .../timelock/utils/call_impls.cairo | 41 + src/tests.cairo | 2 + src/tests/governance.cairo | 2 + src/tests/governance/test_timelock.cairo | 1799 +++++++++++++++++ src/tests/governance/test_utils.cairo | 146 ++ src/tests/mocks.cairo | 1 + src/tests/mocks/timelock_mocks.cairo | 151 ++ 13 files changed, 2886 insertions(+) create mode 100644 src/governance/timelock.cairo create mode 100644 src/governance/timelock/interface.cairo create mode 100644 src/governance/timelock/timelock_controller.cairo create mode 100644 src/governance/timelock/utils.cairo create mode 100644 src/governance/timelock/utils/call_impls.cairo create mode 100644 src/tests/governance.cairo create mode 100644 src/tests/governance/test_timelock.cairo create mode 100644 src/tests/governance/test_utils.cairo create mode 100644 src/tests/mocks/timelock_mocks.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c14cb56..983569924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- TimelockController component (#996) +- HashCall implementation (#996) + ### Changed - Bump scarb to v2.7.0-rc.1 (#1025) diff --git a/src/governance.cairo b/src/governance.cairo index b5614dd82..01d740d38 100644 --- a/src/governance.cairo +++ b/src/governance.cairo @@ -1 +1,2 @@ +pub mod timelock; pub mod utils; diff --git a/src/governance/timelock.cairo b/src/governance/timelock.cairo new file mode 100644 index 000000000..0abcf6e3b --- /dev/null +++ b/src/governance/timelock.cairo @@ -0,0 +1,9 @@ +pub mod interface; +pub mod timelock_controller; +pub mod utils; + +pub use timelock_controller::OperationState; +pub use timelock_controller::TimelockControllerComponent::{ + PROPOSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE +}; +pub use timelock_controller::TimelockControllerComponent; diff --git a/src/governance/timelock/interface.cairo b/src/governance/timelock/interface.cairo new file mode 100644 index 000000000..0c89d0c20 --- /dev/null +++ b/src/governance/timelock/interface.cairo @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (governance/timelock/interface.cairo) + +use openzeppelin::governance::timelock::OperationState; +use starknet::ContractAddress; +use starknet::account::Call; + +#[starknet::interface] +pub trait ITimelock { + fn is_operation(self: @TState, id: felt252) -> bool; + fn is_operation_pending(self: @TState, id: felt252) -> bool; + fn is_operation_ready(self: @TState, id: felt252) -> bool; + fn is_operation_done(self: @TState, id: felt252) -> bool; + fn get_timestamp(self: @TState, id: felt252) -> u64; + fn get_operation_state(self: @TState, id: felt252) -> OperationState; + fn get_min_delay(self: @TState) -> u64; + fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; + fn hash_operation_batch( + self: @TState, calls: Span, predecessor: felt252, salt: felt252 + ) -> felt252; + fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); + fn schedule_batch( + ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 + ); + fn cancel(ref self: TState, id: felt252); + fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); + fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); + fn update_delay(ref self: TState, new_delay: u64); +} + +#[starknet::interface] +pub trait TimelockABI { + fn is_operation(self: @TState, id: felt252) -> bool; + fn is_operation_pending(self: @TState, id: felt252) -> bool; + fn is_operation_ready(self: @TState, id: felt252) -> bool; + fn is_operation_done(self: @TState, id: felt252) -> bool; + fn get_timestamp(self: @TState, id: felt252) -> u64; + fn get_operation_state(self: @TState, id: felt252) -> OperationState; + fn get_min_delay(self: @TState) -> u64; + fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; + fn hash_operation_batch( + self: @TState, calls: Span, predecessor: felt252, salt: felt252 + ) -> felt252; + fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); + fn schedule_batch( + ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 + ); + fn cancel(ref self: TState, id: felt252); + fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); + fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); + fn update_delay(ref self: TState, new_delay: u64); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IAccessControl + fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(self: @TState, role: felt252) -> felt252; + fn grant_role(ref self: TState, role: felt252, account: ContractAddress); + fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); + fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(self: @TState, role: felt252) -> felt252; + fn grantRole(ref self: TState, role: felt252, account: ContractAddress); + fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); + fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); +} diff --git a/src/governance/timelock/timelock_controller.cairo b/src/governance/timelock/timelock_controller.cairo new file mode 100644 index 000000000..494692025 --- /dev/null +++ b/src/governance/timelock/timelock_controller.cairo @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (governance/timelock/timelock_controller.cairo) + +/// # TimelockController Component +/// +/// Component that acts as a timelocked controller. When set as the owner of an `Ownable` smart +/// contract, it enforces a timelock on all `only_owner` maintenance operations. This gives time for +/// users of the controlled contract to exit before a potentially dangerous maintenance operation is +/// applied. +/// +/// By default, this component is self administered, meaning administration tasks have to go through +/// the timelock process. The proposer role is in charge of proposing operations. A common use case +/// is to position the timelock controller as the owner of a smart contract, with a multi-sig +/// or a DAO as the sole proposer. +#[starknet::component] +pub mod TimelockControllerComponent { + use core::hash::{HashStateTrait, HashStateExTrait}; + use core::num::traits::Zero; + use core::pedersen::PedersenTrait; + use openzeppelin::access::accesscontrol::AccessControlComponent::InternalTrait as AccessControlInternalTrait; + use openzeppelin::access::accesscontrol::AccessControlComponent::{ + AccessControlImpl, AccessControlCamelImpl + }; + use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; + use openzeppelin::governance::timelock::interface::{ITimelock, TimelockABI}; + use openzeppelin::governance::timelock::utils::call_impls::{ + HashCallImpl, HashCallsImpl, CallPartialEq + }; + use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; + use openzeppelin::introspection::src5::SRC5Component; + use starknet::ContractAddress; + use starknet::SyscallResultTrait; + use starknet::account::Call; + use starknet::storage::Map; + use super::OperationState; + + // Constants + pub const PROPOSER_ROLE: felt252 = selector!("PROPOSER_ROLE"); + pub const EXECUTOR_ROLE: felt252 = selector!("EXECUTOR_ROLE"); + pub const CANCELLER_ROLE: felt252 = selector!("CANCELLER_ROLE"); + const DONE_TIMESTAMP: u64 = 1; + + #[storage] + struct Storage { + TimelockController_timestamps: Map, + TimelockController_min_delay: u64 + } + + #[event] + #[derive(Drop, PartialEq, starknet::Event)] + pub enum Event { + CallScheduled: CallScheduled, + CallExecuted: CallExecuted, + CallSalt: CallSalt, + CallCancelled: CallCancelled, + MinDelayChanged: MinDelayChanged + } + + /// Emitted when `call` is scheduled as part of operation `id`. + #[derive(Drop, PartialEq, starknet::Event)] + pub struct CallScheduled { + #[key] + pub id: felt252, + #[key] + pub index: felt252, + pub call: Call, + pub predecessor: felt252, + pub delay: u64 + } + + /// Emitted when `call` is performed as part of operation `id`. + #[derive(Drop, PartialEq, starknet::Event)] + pub struct CallExecuted { + #[key] + pub id: felt252, + #[key] + pub index: felt252, + pub call: Call + } + + /// Emitted when a new proposal is scheduled with non-zero salt. + #[derive(Drop, PartialEq, starknet::Event)] + pub struct CallSalt { + #[key] + pub id: felt252, + pub salt: felt252 + } + + /// Emitted when operation `id` is cancelled. + #[derive(Drop, PartialEq, starknet::Event)] + pub struct CallCancelled { + #[key] + pub id: felt252 + } + + /// Emitted when the minimum delay for future operations is modified. + #[derive(Drop, PartialEq, starknet::Event)] + pub struct MinDelayChanged { + pub old_duration: u64, + pub new_duration: u64 + } + + pub mod Errors { + pub const INVALID_OPERATION_LEN: felt252 = 'Timelock: invalid operation len'; + pub const INSUFFICIENT_DELAY: felt252 = 'Timelock: insufficient delay'; + pub const EXPECTED_UNSET_OPERATION: felt252 = 'Timelock: expected Unset op'; + pub const EXPECTED_PENDING_OPERATION: felt252 = 'Timelock: expected Pending op'; + pub const EXPECTED_READY_OPERATION: felt252 = 'Timelock: expected Ready op'; + pub const UNEXECUTED_PREDECESSOR: felt252 = 'Timelock: awaiting predecessor'; + pub const UNAUTHORIZED_CALLER: felt252 = 'Timelock: unauthorized caller'; + } + + #[embeddable_as(TimelockImpl)] + impl Timelock< + TContractState, + +HasComponent, + +SRC5Component::HasComponent, + +AccessControlComponent::HasComponent, + +Drop + > of ITimelock> { + /// Returns whether `id` corresponds to a registered operation. + /// This includes the OperationStates: Waiting, Ready, and Done. + fn is_operation(self: @ComponentState, id: felt252) -> bool { + Self::get_operation_state(self, id) != OperationState::Unset + } + + /// Returns whether the `id` OperationState is pending or not. + /// Note that a pending operation may be either Waiting or Ready. + fn is_operation_pending(self: @ComponentState, id: felt252) -> bool { + let state = Self::get_operation_state(self, id); + state == OperationState::Waiting || state == OperationState::Ready + } + + /// Returns whether the `id` OperationState is Ready or not. + fn is_operation_ready(self: @ComponentState, id: felt252) -> bool { + Self::get_operation_state(self, id) == OperationState::Ready + } + + /// Returns whether the `id` OperationState is Done or not. + fn is_operation_done(self: @ComponentState, id: felt252) -> bool { + Self::get_operation_state(self, id) == OperationState::Done + } + + /// Returns the timestamp at which `id` becomes Ready. + /// + /// NOTE: `0` means the OperationState is Unset and `1` means the OperationState + /// is Done. + fn get_timestamp(self: @ComponentState, id: felt252) -> u64 { + self.TimelockController_timestamps.read(id) + } + + /// Returns the OperationState for `id`. + fn get_operation_state( + self: @ComponentState, id: felt252 + ) -> OperationState { + let timestamp = Self::get_timestamp(self, id); + if (timestamp == 0) { + return OperationState::Unset; + } else if (timestamp == DONE_TIMESTAMP) { + return OperationState::Done; + } else if (timestamp > starknet::get_block_timestamp()) { + return OperationState::Waiting; + } else { + return OperationState::Ready; + } + } + + /// Returns the minimum delay in seconds for an operation to become valid. + /// This value can be changed by executing an operation that calls `update_delay`. + fn get_min_delay(self: @ComponentState) -> u64 { + self.TimelockController_min_delay.read() + } + + /// Returns the identifier of an operation containing a single transaction. + fn hash_operation( + self: @ComponentState, call: Call, predecessor: felt252, salt: felt252 + ) -> felt252 { + PedersenTrait::new(0) + .update_with(call) + .update_with(predecessor) + .update_with(salt) + .finalize() + } + + /// Returns the identifier of an operation containing a batch of transactions. + fn hash_operation_batch( + self: @ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252 + ) -> felt252 { + PedersenTrait::new(0) + .update_with(calls) + .update_with(predecessor) + .update_with(salt) + .finalize() + } + + /// Schedule an operation containing a single transaction. + /// + /// Requirements: + /// + /// - the caller must have the `PROPOSER_ROLE` role. + /// + /// Emits `CallScheduled` event. + /// If `salt` is not zero, emits `CallSalt` event. + fn schedule( + ref self: ComponentState, + call: Call, + predecessor: felt252, + salt: felt252, + delay: u64 + ) { + self.assert_only_role(PROPOSER_ROLE); + + let id = Self::hash_operation(@self, call, predecessor, salt); + self._schedule(id, delay); + self.emit(CallScheduled { id, index: 0, call, predecessor, delay }); + + if salt != 0 { + self.emit(CallSalt { id, salt }); + } + } + + /// Schedule an operation containing a batch of transactions. + /// + /// Requirements: + /// + /// - the caller must have the `PROPOSER_ROLE` role. + /// + /// Emits one `CallScheduled` event for each transaction in the batch. + /// If `salt` is not zero, emits `CallSalt` event. + fn schedule_batch( + ref self: ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252, + delay: u64 + ) { + self.assert_only_role(PROPOSER_ROLE); + + let id = Self::hash_operation_batch(@self, calls, predecessor, salt); + self._schedule(id, delay); + + let mut index = 0; + for call in calls { + self.emit(CallScheduled { id, index, call: *call, predecessor, delay }); + index += 1; + }; + + if salt != 0 { + self.emit(CallSalt { id, salt }); + } + } + + /// Cancel an operation. + /// + /// Requirements: + /// + /// - The caller must have the `CANCELLER_ROLE` role. + /// - `id` must be an operation. + /// + /// Emits a `CallCancelled` event. + fn cancel(ref self: ComponentState, id: felt252) { + self.assert_only_role(CANCELLER_ROLE); + assert(Self::is_operation_pending(@self, id), Errors::EXPECTED_PENDING_OPERATION); + + self.TimelockController_timestamps.write(id, 0); + self.emit(CallCancelled { id }); + } + + /// Execute a (Ready) operation containing a single Call. + /// + /// Requirements: + /// + /// - Caller must have `EXECUTOR_ROLE`. + /// - `id` must be in Ready OperationState. + /// - `predecessor` must either be `0` or in Done OperationState. + /// + /// NOTE: This function can reenter, but it doesn't pose a risk because `_after_call` + /// checks that the proposal is pending, thus any modifications to the operation during + /// reentrancy should be caught. + /// + /// Emits a `CallExecuted` event. + fn execute( + ref self: ComponentState, + call: Call, + predecessor: felt252, + salt: felt252 + ) { + self.assert_only_role_or_open_role(EXECUTOR_ROLE); + + let id = Self::hash_operation(@self, call, predecessor, salt); + self._before_call(id, predecessor); + self._execute(call); + self.emit(CallExecuted { id, index: 0, call }); + self._after_call(id); + } + + /// Execute a (Ready) operation containing a batch of Calls. + /// + /// Requirements: + /// + /// - Caller must have `EXECUTOR_ROLE`. + /// - `id` must be in Ready OperationState. + /// - `predecessor` must either be `0` or in Done OperationState. + /// + /// NOTE: This function can reenter, but it doesn't pose a risk because `_after_call` + /// checks that the proposal is pending, thus any modifications to the operation during + /// reentrancy should be caught. + /// + /// Emits a `CallExecuted` event for each Call. + fn execute_batch( + ref self: ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252 + ) { + self.assert_only_role_or_open_role(EXECUTOR_ROLE); + + let id = Self::hash_operation_batch(@self, calls, predecessor, salt); + self._before_call(id, predecessor); + + let mut index = 0; + for call in calls { + self._execute(*call); + self.emit(CallExecuted { id, index, call: *call }); + index += 1; + }; + + self._after_call(id); + } + + /// Changes the minimum timelock duration for future operations. + /// + /// Requirements: + /// + /// - The caller must be the timelock itself. This can only be achieved by scheduling + /// and later executing an operation where the timelock is the target and the data + /// is the serialized call to this function. + /// + /// Emits a `MinDelayChanged` event. + fn update_delay(ref self: ComponentState, new_delay: u64) { + self.assert_only_self(); + + let min_delay = self.TimelockController_min_delay.read(); + self.emit(MinDelayChanged { old_duration: min_delay, new_duration: new_delay }); + + self.TimelockController_min_delay.write(new_delay); + } + } + + #[embeddable_as(TimelockMixinImpl)] + impl TimelockMixin< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + impl AccessControl: AccessControlComponent::HasComponent, + +Drop + > of TimelockABI> { + fn is_operation(self: @ComponentState, id: felt252) -> bool { + Timelock::is_operation(self, id) + } + + fn is_operation_pending(self: @ComponentState, id: felt252) -> bool { + Timelock::is_operation_pending(self, id) + } + + fn is_operation_ready(self: @ComponentState, id: felt252) -> bool { + Timelock::is_operation_ready(self, id) + } + + fn is_operation_done(self: @ComponentState, id: felt252) -> bool { + Timelock::is_operation_done(self, id) + } + + fn get_timestamp(self: @ComponentState, id: felt252) -> u64 { + Timelock::get_timestamp(self, id) + } + + fn get_operation_state( + self: @ComponentState, id: felt252 + ) -> OperationState { + Timelock::get_operation_state(self, id) + } + + fn get_min_delay(self: @ComponentState) -> u64 { + Timelock::get_min_delay(self) + } + + fn hash_operation( + self: @ComponentState, call: Call, predecessor: felt252, salt: felt252 + ) -> felt252 { + Timelock::hash_operation(self, call, predecessor, salt) + } + + fn hash_operation_batch( + self: @ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252 + ) -> felt252 { + Timelock::hash_operation_batch(self, calls, predecessor, salt) + } + + fn schedule( + ref self: ComponentState, + call: Call, + predecessor: felt252, + salt: felt252, + delay: u64 + ) { + Timelock::schedule(ref self, call, predecessor, salt, delay); + } + + fn schedule_batch( + ref self: ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252, + delay: u64 + ) { + Timelock::schedule_batch(ref self, calls, predecessor, salt, delay); + } + + fn cancel(ref self: ComponentState, id: felt252) { + Timelock::cancel(ref self, id); + } + + fn execute( + ref self: ComponentState, + call: Call, + predecessor: felt252, + salt: felt252 + ) { + Timelock::execute(ref self, call, predecessor, salt); + } + + fn execute_batch( + ref self: ComponentState, + calls: Span, + predecessor: felt252, + salt: felt252 + ) { + Timelock::execute_batch(ref self, calls, predecessor, salt); + } + + fn update_delay(ref self: ComponentState, new_delay: u64) { + Timelock::update_delay(ref self, new_delay); + } + + // ISRC5 + fn supports_interface( + self: @ComponentState, interface_id: felt252 + ) -> bool { + let src5 = get_dep_component!(self, SRC5); + src5.supports_interface(interface_id) + } + + // IAccessControl + fn has_role( + self: @ComponentState, role: felt252, account: ContractAddress + ) -> bool { + let access_control = get_dep_component!(self, AccessControl); + access_control.has_role(role, account) + } + + fn get_role_admin(self: @ComponentState, role: felt252) -> felt252 { + let access_control = get_dep_component!(self, AccessControl); + access_control.get_role_admin(role) + } + + fn grant_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + let mut access_control = get_dep_component_mut!(ref self, AccessControl); + access_control.grant_role(role, account); + } + + fn revoke_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + let mut access_control = get_dep_component_mut!(ref self, AccessControl); + access_control.revoke_role(role, account); + } + fn renounce_role( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + let mut access_control = get_dep_component_mut!(ref self, AccessControl); + access_control.renounce_role(role, account); + } + + // IAccessControlCamel + fn hasRole( + self: @ComponentState, role: felt252, account: ContractAddress + ) -> bool { + Self::has_role(self, role, account) + } + + fn getRoleAdmin(self: @ComponentState, role: felt252) -> felt252 { + Self::getRoleAdmin(self, role) + } + + fn grantRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + Self::grant_role(ref self, role, account); + } + + fn revokeRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + Self::revoke_role(ref self, role, account); + } + + fn renounceRole( + ref self: ComponentState, role: felt252, account: ContractAddress + ) { + Self::renounce_role(ref self, role, account); + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, + +HasComponent, + impl SRC5: SRC5Component::HasComponent, + impl AccessControl: AccessControlComponent::HasComponent, + +Drop + > of InternalTrait { + /// Initializes the contract by registering support for SRC5 and AccessControl. + /// + /// This function also configures the contract with the following parameters: + /// + /// - `min_delay`: initial minimum delay in seconds for operations. + /// - `proposers`: accounts to be granted proposer and canceller roles. + /// - `executors`: accounts to be granted executor role. + /// - `admin`: optional account to be granted admin role; disable with zero address. + /// + /// WARNING: The optional admin can aid with initial configuration of roles after deployment + /// without being subject to delay, but this role should be subsequently renounced in favor + /// of administration through timelocked proposals. + /// + /// Emits two `RoleGranted` events for each account in `proposers` with `PROPOSER_ROLE` + /// admin `CANCELLER_ROLE` roles. + /// + /// Emits a `RoleGranted` event for each account in `executors` with `EXECUTOR_ROLE` role. + /// + /// May emit a `RoleGranted` event for `admin` with `DEFAULT_ADMIN_ROLE` role (if `admin` is + /// not zero). + /// + /// Emits `MinDelayChanged` event. + fn initializer( + ref self: ComponentState, + min_delay: u64, + proposers: Span, + executors: Span, + admin: ContractAddress + ) { + // Register access control ID and self as default admin + let mut access_component = get_dep_component_mut!(ref self, AccessControl); + access_component.initializer(); + access_component._grant_role(DEFAULT_ADMIN_ROLE, starknet::get_contract_address()); + + // Optional admin + if admin != Zero::zero() { + access_component._grant_role(DEFAULT_ADMIN_ROLE, admin) + }; + + // Register proposers and cancellers + for proposer in proposers { + access_component._grant_role(PROPOSER_ROLE, *proposer); + access_component._grant_role(CANCELLER_ROLE, *proposer); + }; + + // Register executors + for executor in executors { + access_component._grant_role(EXECUTOR_ROLE, *executor); + }; + + // Set minimum delay + self.TimelockController_min_delay.write(min_delay); + self.emit(MinDelayChanged { old_duration: 0, new_duration: min_delay }); + } + + /// Validates that the caller has the given `role`. + /// Otherwise it reverts. + fn assert_only_role(self: @ComponentState, role: felt252) { + let access_component = get_dep_component!(self, AccessControl); + access_component.assert_only_role(role); + } + + /// Validates that the caller has the given `role`. + /// If `role` is granted to the zero address, then this is considered an open role which + /// allows anyone to be the caller. + fn assert_only_role_or_open_role(self: @ComponentState, role: felt252) { + let access_component = get_dep_component!(self, AccessControl); + let is_role_open = access_component.has_role(role, Zero::zero()); + if !is_role_open { + access_component.assert_only_role(role); + } + } + + /// Validates that the caller is the timelock contract itself. + /// Otherwise it reverts. + fn assert_only_self(self: @ComponentState) { + let this = starknet::get_contract_address(); + let caller = starknet::get_caller_address(); + assert(caller == this, Errors::UNAUTHORIZED_CALLER); + } + + /// Private function that checks before execution of an operation's calls. + /// + /// Requirements: + /// + /// - `id` must be in the Ready OperationState. + /// - `predecessor` must either be zero or be in the Done OperationState. + fn _before_call(self: @ComponentState, id: felt252, predecessor: felt252) { + assert(Timelock::is_operation_ready(self, id), Errors::EXPECTED_READY_OPERATION); + assert( + predecessor == 0 || Timelock::is_operation_done(self, predecessor), + Errors::UNEXECUTED_PREDECESSOR + ); + } + + /// Private functions that checks after execution of an operation's calls. + /// + /// Requirements: + /// + /// - `id` must be in the Ready OperationState. + fn _after_call(ref self: ComponentState, id: felt252) { + assert(Timelock::is_operation_ready(@self, id), Errors::EXPECTED_READY_OPERATION); + self.TimelockController_timestamps.write(id, DONE_TIMESTAMP); + } + + /// Private function that schedules an operation that is to become valid after a given + /// `delay`. + fn _schedule(ref self: ComponentState, id: felt252, delay: u64) { + assert(!Timelock::is_operation(@self, id), Errors::EXPECTED_UNSET_OPERATION); + assert(Timelock::get_min_delay(@self) <= delay, Errors::INSUFFICIENT_DELAY); + self.TimelockController_timestamps.write(id, starknet::get_block_timestamp() + delay); + } + + /// Private function that executes an operation's calls. + fn _execute(ref self: ComponentState, call: Call) { + let Call { to, selector, calldata } = call; + starknet::syscalls::call_contract_syscall(to, selector, calldata).unwrap_syscall(); + } + } +} + +#[derive(Drop, Serde, PartialEq, Debug)] +pub enum OperationState { + Unset, + Waiting, + Ready, + Done +} diff --git a/src/governance/timelock/utils.cairo b/src/governance/timelock/utils.cairo new file mode 100644 index 000000000..0b626634e --- /dev/null +++ b/src/governance/timelock/utils.cairo @@ -0,0 +1 @@ +pub mod call_impls; diff --git a/src/governance/timelock/utils/call_impls.cairo b/src/governance/timelock/utils/call_impls.cairo new file mode 100644 index 000000000..428259e57 --- /dev/null +++ b/src/governance/timelock/utils/call_impls.cairo @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (governance/timelock/utils/call_impls.cairo) + +use core::hash::{HashStateTrait, HashStateExTrait, Hash}; +use starknet::account::Call; + +pub(crate) impl HashCallImpl, +Drop> of Hash { + fn update_state(mut state: S, value: Call) -> S { + let Call { to, selector, calldata } = value; + state = state.update_with(to).update_with(selector).update_with(calldata.len()); + for elem in calldata { + state = state.update_with(*elem); + }; + + state + } +} + +pub(crate) impl HashCallsImpl, +Drop> of Hash, S> { + fn update_state(mut state: S, value: Span) -> S { + state = state.update_with(value.len()); + for call in value { + state = state.update_with(*call); + }; + + state + } +} + +pub(crate) impl CallPartialEq of PartialEq { + #[inline(always)] + fn eq(lhs: @Call, rhs: @Call) -> bool { + let Call { to: l_to, selector: l_selector, calldata: l_calldata } = lhs; + let Call { to: r_to, selector: r_selector, calldata: r_calldata } = rhs; + l_to == r_to && l_selector == r_selector && l_calldata == r_calldata + } + #[inline(always)] + fn ne(lhs: @Call, rhs: @Call) -> bool { + !(lhs == rhs) + } +} diff --git a/src/tests.cairo b/src/tests.cairo index 1f53e79de..f2263ef47 100644 --- a/src/tests.cairo +++ b/src/tests.cairo @@ -5,6 +5,8 @@ mod account; #[cfg(test)] mod cryptography; #[cfg(test)] +mod governance; +#[cfg(test)] mod introspection; #[cfg(test)] mod mocks; diff --git a/src/tests/governance.cairo b/src/tests/governance.cairo new file mode 100644 index 000000000..3aa8297b3 --- /dev/null +++ b/src/tests/governance.cairo @@ -0,0 +1,2 @@ +mod test_timelock; +mod test_utils; diff --git a/src/tests/governance/test_timelock.cairo b/src/tests/governance/test_timelock.cairo new file mode 100644 index 000000000..c654c2082 --- /dev/null +++ b/src/tests/governance/test_timelock.cairo @@ -0,0 +1,1799 @@ +use core::hash::{HashStateTrait, HashStateExTrait}; +use core::num::traits::Zero; +use core::pedersen::PedersenTrait; +use openzeppelin::access::accesscontrol::AccessControlComponent::{ + AccessControlImpl, InternalImpl as AccessControlInternalImpl +}; +use openzeppelin::access::accesscontrol::DEFAULT_ADMIN_ROLE; +use openzeppelin::access::accesscontrol::interface::IACCESSCONTROL_ID; +use openzeppelin::access::accesscontrol::interface::IAccessControl; +use openzeppelin::governance::timelock::OperationState; +use openzeppelin::governance::timelock::TimelockControllerComponent::{ + CallScheduled, CallExecuted, CallSalt, CallCancelled, MinDelayChanged +}; +use openzeppelin::governance::timelock::TimelockControllerComponent::{ + TimelockImpl, InternalImpl as TimelockInternalImpl +}; +use openzeppelin::governance::timelock::TimelockControllerComponent; +use openzeppelin::governance::timelock::interface::{ + TimelockABIDispatcher, TimelockABIDispatcherTrait +}; +use openzeppelin::governance::timelock::{PROPOSER_ROLE, EXECUTOR_ROLE, CANCELLER_ROLE}; +use openzeppelin::introspection::interface::ISRC5_ID; +use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; +use openzeppelin::tests::mocks::timelock_mocks::MockContract; +use openzeppelin::tests::mocks::timelock_mocks::{ + IMockContractDispatcher, IMockContractDispatcherTrait +}; +use openzeppelin::tests::mocks::timelock_mocks::{ + ITimelockAttackerDispatcher, ITimelockAttackerDispatcherTrait +}; +use openzeppelin::tests::mocks::timelock_mocks::{TimelockControllerMock, TimelockAttackerMock}; +use openzeppelin::tests::utils::constants::{ADMIN, ZERO, OTHER, SALT}; +use openzeppelin::tests::utils; +use openzeppelin::utils::selectors; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::account::Call; +use starknet::contract_address_const; +use starknet::testing; + +type ComponentState = + TimelockControllerComponent::ComponentState; + +fn CONTRACT_STATE() -> TimelockControllerMock::ContractState { + TimelockControllerMock::contract_state_for_testing() +} + +fn COMPONENT_STATE() -> ComponentState { + TimelockControllerComponent::component_state_for_testing() +} + +// +// Constants +// + +const MIN_DELAY: u64 = 1000; +const NEW_DELAY: u64 = 2000; +const VALUE: felt252 = 'VALUE'; +const NO_PREDECESSOR: felt252 = 0; + +// +// Addresses +// + +fn PROPOSER() -> ContractAddress { + contract_address_const::<'PROPOSER'>() +} + +fn EXECUTOR() -> ContractAddress { + contract_address_const::<'EXECUTOR'>() +} + +fn get_proposers() -> (ContractAddress, ContractAddress, ContractAddress) { + let p1 = contract_address_const::<'PROPOSER_1'>(); + let p2 = contract_address_const::<'PROPOSER_2'>(); + let p3 = contract_address_const::<'PROPOSER_3'>(); + (p1, p2, p3) +} + +fn get_executors() -> (ContractAddress, ContractAddress, ContractAddress) { + let e1 = contract_address_const::<'EXECUTOR_1'>(); + let e2 = contract_address_const::<'EXECUTOR_2'>(); + let e3 = contract_address_const::<'EXECUTOR_3'>(); + (e1, e2, e3) +} + +// +// Operations +// + +fn single_operation(target: ContractAddress) -> Call { + let mut calldata = array![]; + calldata.append_serde(VALUE); + + Call { to: target, selector: selector!("set_number"), calldata: calldata.span() } +} + +fn batched_operations(target: ContractAddress) -> Span { + let mut calls = array![]; + let call = single_operation(target); + calls.append(call); + calls.append(call); + calls.append(call); + + calls.span() +} + +fn failing_operation(target: ContractAddress) -> Call { + let mut calldata = array![]; + + Call { to: target, selector: selector!("failing_function"), calldata: calldata.span() } +} + +fn operation_with_bad_selector(target: ContractAddress) -> Call { + let mut calldata = array![]; + + Call { to: target, selector: selector!("bad_selector"), calldata: calldata.span() } +} + +// +// Dispatchers +// + +fn deploy_timelock() -> TimelockABIDispatcher { + let mut calldata = array![]; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin = ADMIN(); + + calldata.append_serde(MIN_DELAY); + calldata.append_serde(proposers); + calldata.append_serde(executors); + calldata.append_serde(admin); + + let address = utils::deploy(TimelockControllerMock::TEST_CLASS_HASH, calldata); + // Events dropped: + // - 5 RoleGranted: self, proposer, canceller, executor, admin + // - MinDelayChanged + utils::drop_events(address, 6); + TimelockABIDispatcher { contract_address: address } +} + +fn deploy_mock_target() -> IMockContractDispatcher { + let mut calldata = array![]; + + let address = utils::deploy(MockContract::TEST_CLASS_HASH, calldata); + IMockContractDispatcher { contract_address: address } +} + +fn setup_dispatchers() -> (TimelockABIDispatcher, IMockContractDispatcher) { + let timelock = deploy_timelock(); + let target = deploy_mock_target(); + + (timelock, target) +} + +fn deploy_attacker() -> ITimelockAttackerDispatcher { + let mut calldata = array![]; + + let address = utils::deploy(TimelockAttackerMock::TEST_CLASS_HASH, calldata); + ITimelockAttackerDispatcher { contract_address: address } +} + +// +// hash_operation +// + +#[test] +fn test_hash_operation() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = 123; + let salt = SALT; + + // Set up call + let mut calldata = array![]; + calldata.append_serde(VALUE); + let mut call = Call { + to: target.contract_address, selector: selector!("set_number"), calldata: calldata.span() + }; + + // Hash operation + let hashed_operation = timelock.hash_operation(call, predecessor, salt); + + // Manually set hash elements + let mut expected_hash = PedersenTrait::new(0) + .update_with(target.contract_address) // call::to + .update_with(selector!("set_number")) // call::selector + .update_with(1) // call::calldata.len + .update_with(VALUE) // call::calldata::number + .update_with(predecessor) // predecessor + .update_with(salt) // salt + .finalize(); + + assert_eq!(hashed_operation, expected_hash); +} + +#[test] +fn test_hash_operation_batch() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = 123; + let salt = SALT; + + // Set up calls + let mut calldata = array![]; + calldata.append_serde(VALUE); + let mut call = Call { + to: target.contract_address, selector: selector!("set_number"), calldata: calldata.span() + }; + let calls = array![call, call, call].span(); + + // Hash operation + let hashed_operation = timelock.hash_operation_batch(calls, predecessor, salt); + + // Manually set hash elements + let mut expected_hash = PedersenTrait::new(0) + .update_with(3) // total number of Calls + .update_with(target.contract_address) // call::to + .update_with(selector!("set_number")) // call::selector + .update_with(1) // call::calldata.len + .update_with(VALUE) // call::calldata::number + .update_with(target.contract_address) // call::to + .update_with(selector!("set_number")) // call::selector + .update_with(1) // call::calldata.len + .update_with(VALUE) // call::calldata::number + .update_with(target.contract_address) // call::to + .update_with(selector!("set_number")) // call::selector + .update_with(1) // call::calldata.len + .update_with(VALUE) // call::calldata::number + .update_with(predecessor) // predecessor + .update_with(salt) // salt + .finalize(); + + assert_eq!(hashed_operation, expected_hash); +} + +// +// schedule +// + +fn schedule_from_proposer(salt: felt252) { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let delay = MIN_DELAY; + let mut salt = salt; + + // Set up call + let call = single_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + + // Check timestamp + let operation_ts = timelock.get_timestamp(target_id); + let expected_ts = starknet::get_block_timestamp() + delay; + assert_eq!(operation_ts, expected_ts); + + // Check event(s) + let event_index = 0; + if salt != 0 { + assert_event_schedule( + timelock.contract_address, target_id, event_index, call, predecessor, delay + ); + assert_only_event_call_salt(timelock.contract_address, target_id, salt); + } else { + assert_only_event_schedule( + timelock.contract_address, target_id, event_index, call, predecessor, delay + ); + } +} + +#[test] +fn test_schedule_from_proposer_with_salt() { + let salt = SALT; + schedule_from_proposer(salt); +} + +#[test] +fn test_schedule_from_proposer_no_salt() { + let salt = 0; + schedule_from_proposer(salt); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Unset op', 'ENTRYPOINT_FAILED'))] +fn test_schedule_overwrite() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + timelock.schedule(call, predecessor, salt, delay); +} + +#[test] +#[should_panic(expected: ('Caller is missing role', 'ENTRYPOINT_FAILED'))] +fn test_schedule_unauthorized() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + + testing::set_contract_address(OTHER()); + timelock.schedule(call, predecessor, salt, delay); +} + +#[test] +#[should_panic(expected: ('Timelock: insufficient delay', 'ENTRYPOINT_FAILED'))] +fn test_schedule_bad_min_delay() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let bad_delay = MIN_DELAY - 1; + + let call = single_operation(target.contract_address); + + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, bad_delay); +} + +// +// schedule_batch +// + +fn schedule_batch_from_proposer(salt: felt252) { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let delay = MIN_DELAY; + let mut salt = salt; + + // Set up calls + let calls = batched_operations(target.contract_address); + let target_id = timelock.hash_operation_batch(calls, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule batch + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + + // Check timestamp + let operation_ts = timelock.get_timestamp(target_id); + let expected_ts = starknet::get_block_timestamp() + delay; + assert_eq!(operation_ts, expected_ts); + + // Check events + if salt != 0 { + assert_events_schedule_batch( + timelock.contract_address, target_id, calls, predecessor, delay + ); + assert_only_event_call_salt(timelock.contract_address, target_id, salt); + } else { + assert_only_events_schedule_batch( + timelock.contract_address, target_id, calls, predecessor, delay + ); + } +} + +#[test] +fn test_schedule_batch_from_proposer_with_salt() { + let salt = SALT; + schedule_batch_from_proposer(salt); +} + +#[test] +fn test_schedule_batch_from_proposer_no_salt() { + let no_salt = 0; + schedule_batch_from_proposer(no_salt); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Unset op', 'ENTRYPOINT_FAILED'))] +fn test_schedule_batch_overwrite() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let delay = MIN_DELAY; + + let calls = batched_operations(target.contract_address); + + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + timelock.schedule_batch(calls, predecessor, salt, delay); +} + +#[test] +#[should_panic(expected: ('Caller is missing role', 'ENTRYPOINT_FAILED'))] +fn test_schedule_batch_unauthorized() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let delay = MIN_DELAY; + + let calls = batched_operations(target.contract_address); + + testing::set_contract_address(OTHER()); + timelock.schedule_batch(calls, predecessor, salt, delay); +} + +#[test] +#[should_panic(expected: ('Timelock: insufficient delay', 'ENTRYPOINT_FAILED'))] +fn test_schedule_batch_bad_min_delay() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = SALT; + let bad_delay = MIN_DELAY - 1; + + let calls = batched_operations(target.contract_address); + + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, bad_delay); +} + +// +// execute +// + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op', 'ENTRYPOINT_FAILED'))] +fn test_execute_when_not_scheduled() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + + let call = single_operation(target.contract_address); + + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); +} + +#[test] +fn test_execute_when_scheduled() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + let event_index = 0; + + // Set up call + let call = single_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + assert_only_event_schedule( + timelock.contract_address, target_id, event_index, call, predecessor, delay + ); + assert_operation_state(timelock, OperationState::Waiting, target_id); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + + // Check initial target state + let check_target = target.get_number(); + assert_eq!(check_target, 0); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); + + assert_operation_state(timelock, OperationState::Done, target_id); + assert_only_event_execute(timelock.contract_address, target_id, event_index, call); + + // Check target state updates + let check_target = target.get_number(); + assert_eq!(check_target, VALUE); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op', 'ENTRYPOINT_FAILED'))] +fn test_execute_early() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + + // Fast-forward + let early_time = delay - 1; + testing::set_block_timestamp(early_time); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Caller is missing role', 'ENTRYPOINT_FAILED'))] +fn test_execute_unauthorized() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Execute + testing::set_contract_address(OTHER()); + timelock.execute(call, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Expected failure', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))] +fn test_execute_failing_tx() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + // Set up call + let call = failing_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND', 'ENTRYPOINT_FAILED'))] +fn test_execute_bad_selector() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + // Set up call + let call = operation_with_bad_selector(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); +} + +#[test] +#[should_panic( + expected: ( + 'Timelock: expected Ready op', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED' + ) +)] +fn test_execute_reentrant_call() { + let mut timelock = deploy_timelock(); + let mut attacker = deploy_attacker(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let reentrant_call = Call { + to: attacker.contract_address, selector: selector!("reenter"), calldata: array![].span() + }; + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(reentrant_call, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Grant executor role to attacker + testing::set_contract_address(ADMIN()); + timelock.grant_role(EXECUTOR_ROLE, attacker.contract_address); + + // Attempt reentrant call + testing::set_contract_address(EXECUTOR()); + timelock.execute(reentrant_call, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Timelock: awaiting predecessor', 'ENTRYPOINT_FAILED'))] +fn test_execute_before_dependency() { + let (mut timelock, mut target) = setup_dispatchers(); + let salt = 0; + let delay = MIN_DELAY; + + // Call 1 + let call_1 = single_operation(target.contract_address); + let predecessor_1 = NO_PREDECESSOR; + let target_id_1 = timelock.hash_operation(call_1, predecessor_1, salt); + + // Call 2 + let call_2 = single_operation(target.contract_address); + let predecessor_2 = target_id_1; + let target_id_2 = timelock.hash_operation(call_2, predecessor_2, salt); + + // Schedule call 1 + testing::set_contract_address(PROPOSER()); + timelock.schedule(call_1, predecessor_1, salt, delay); + + // Schedule call 2 + timelock.schedule(call_2, predecessor_2, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id_1); + assert_operation_state(timelock, OperationState::Ready, target_id_2); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call_2, predecessor_2, salt); +} + +#[test] +fn test_execute_after_dependency() { + let (mut timelock, mut target) = setup_dispatchers(); + let salt = 0; + let delay = MIN_DELAY; + let event_index = 0; + + // Call 1 + let call_1 = single_operation(target.contract_address); + let predecessor_1 = NO_PREDECESSOR; + let target_id_1 = timelock.hash_operation(call_1, predecessor_1, salt); + assert_operation_state(timelock, OperationState::Unset, target_id_1); + + // Call 2 + let call_2 = single_operation(target.contract_address); + let predecessor_2 = target_id_1; + let target_id_2 = timelock.hash_operation(call_2, predecessor_2, salt); + assert_operation_state(timelock, OperationState::Unset, target_id_2); + + // Schedule call 1 + testing::set_contract_address(PROPOSER()); + timelock.schedule(call_1, predecessor_1, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id_1); + assert_only_event_schedule( + timelock.contract_address, target_id_1, event_index, call_1, predecessor_1, delay + ); + + // Schedule call 2 + timelock.schedule(call_2, predecessor_2, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id_2); + assert_only_event_schedule( + timelock.contract_address, target_id_2, event_index, call_2, predecessor_2, delay + ); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id_1); + assert_operation_state(timelock, OperationState::Ready, target_id_2); + + // Execute call 1 + testing::set_contract_address(EXECUTOR()); + timelock.execute(call_1, predecessor_1, salt); + assert_operation_state(timelock, OperationState::Done, target_id_1); + assert_event_execute(timelock.contract_address, target_id_1, event_index, call_1); + + // Execute call 2 + timelock.execute(call_2, predecessor_2, salt); + assert_operation_state(timelock, OperationState::Done, target_id_2); + assert_only_event_execute(timelock.contract_address, target_id_2, event_index, call_2); +} + +// +// execute_batch +// + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op', 'ENTRYPOINT_FAILED'))] +fn test_execute_batch_when_not_scheduled() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + + let calls = batched_operations(target.contract_address); + + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls, predecessor, salt); +} + +#[test] +fn test_execute_batch_when_scheduled() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + // Set up call + let calls = batched_operations(target.contract_address); + let target_id = timelock.hash_operation_batch(calls, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + assert_only_events_schedule_batch( + timelock.contract_address, target_id, calls, predecessor, delay + ); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + + // Check initial target state + let check_target = target.get_number(); + assert_eq!(check_target, 0); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls, predecessor, salt); + assert_operation_state(timelock, OperationState::Done, target_id); + assert_only_events_execute_batch(timelock.contract_address, target_id, calls); + + // Check target state updates + let check_target = target.get_number(); + assert_eq!(check_target, VALUE); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op', 'ENTRYPOINT_FAILED'))] +fn test_execute_batch_early() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let calls = batched_operations(target.contract_address); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + + // Fast-forward + let early_time = delay - 1; + testing::set_block_timestamp(early_time); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Caller is missing role', 'ENTRYPOINT_FAILED'))] +fn test_execute_batch_unauthorized() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let calls = batched_operations(target.contract_address); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Execute + testing::set_contract_address(OTHER()); + timelock.execute_batch(calls, predecessor, salt); +} + +#[test] +#[should_panic( + expected: ( + 'Timelock: expected Ready op', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED' + ) +)] +fn test_execute_batch_reentrant_call() { + let mut timelock = deploy_timelock(); + let mut attacker = deploy_attacker(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let reentrant_call = Call { + to: attacker.contract_address, + selector: selector!("reenter_batch"), + calldata: array![].span() + }; + let calls = array![reentrant_call].span(); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Grant executor role to attacker + testing::set_contract_address(ADMIN()); + timelock.grant_role(EXECUTOR_ROLE, attacker.contract_address); + + // Attempt reentrant call + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Expected failure', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED'))] +fn test_execute_batch_partial_execution() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let good_call = single_operation(target.contract_address); + let bad_call = failing_operation(target.contract_address); + let calls = array![good_call, bad_call].span(); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls, predecessor, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls, predecessor, salt); +} + +#[test] +#[should_panic(expected: ('Timelock: awaiting predecessor', 'ENTRYPOINT_FAILED'))] +fn test_execute_batch_before_dependency() { + let (mut timelock, mut target) = setup_dispatchers(); + let salt = 0; + let delay = MIN_DELAY; + + // Calls 1 + let calls_1 = batched_operations(target.contract_address); + let predecessor_1 = NO_PREDECESSOR; + let target_id_1 = timelock.hash_operation_batch(calls_1, predecessor_1, salt); + + // Calls 2 + let calls_2 = batched_operations(target.contract_address); + let predecessor_2 = target_id_1; + + // Schedule calls 1 + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls_1, predecessor_1, salt, delay); + + // Schedule calls 2 + timelock.schedule_batch(calls_2, predecessor_2, salt, delay); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls_2, predecessor_2, salt); +} + +#[test] +fn test_execute_batch_after_dependency() { + let (mut timelock, mut target) = setup_dispatchers(); + let salt = 0; + let delay = MIN_DELAY; + + // Calls 1 + let calls_1 = batched_operations(target.contract_address); + let predecessor_1 = NO_PREDECESSOR; + let target_id_1 = timelock.hash_operation_batch(calls_1, predecessor_1, salt); + assert_operation_state(timelock, OperationState::Unset, target_id_1); + + // Calls 2 + let calls_2 = batched_operations(target.contract_address); + let predecessor_2 = target_id_1; + let target_id_2 = timelock.hash_operation_batch(calls_2, predecessor_2, salt); + assert_operation_state(timelock, OperationState::Unset, target_id_2); + + // Schedule calls 1 + testing::set_contract_address(PROPOSER()); + timelock.schedule_batch(calls_1, predecessor_1, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id_1); + assert_only_events_schedule_batch( + timelock.contract_address, target_id_1, calls_1, predecessor_1, delay + ); + + // Schedule calls 2 + timelock.schedule_batch(calls_2, predecessor_2, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id_2); + assert_only_events_schedule_batch( + timelock.contract_address, target_id_2, calls_2, predecessor_2, delay + ); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id_1); + assert_operation_state(timelock, OperationState::Ready, target_id_2); + + // Execute calls 1 + testing::set_contract_address(EXECUTOR()); + timelock.execute_batch(calls_1, predecessor_1, salt); + assert_only_events_execute_batch(timelock.contract_address, target_id_1, calls_1); + assert_operation_state(timelock, OperationState::Done, target_id_1); + + // Execute calls 2 + timelock.execute_batch(calls_2, predecessor_2, salt); + assert_operation_state(timelock, OperationState::Done, target_id_2); + assert_only_events_execute_batch(timelock.contract_address, target_id_2, calls_2); +} + +// +// cancel +// + +fn cancel_from_canceller(operation_state: OperationState) { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + let event_index = 0; + + let call = single_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule + testing::set_contract_address(PROPOSER()); // PROPOSER is also CANCELLER + timelock.schedule(call, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + assert_only_event_schedule( + timelock.contract_address, target_id, event_index, call, predecessor, delay + ); + + if operation_state == OperationState::Ready { + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + } + + // Cancel + timelock.cancel(target_id); + assert_only_event_cancel(timelock.contract_address, target_id); + assert_operation_state(timelock, OperationState::Unset, target_id); +} + +#[test] +fn test_cancel_when_waiting() { + let waiting = OperationState::Waiting; + cancel_from_canceller(waiting); +} + +#[test] +fn test_cancel_when_ready() { + let ready = OperationState::Waiting; + cancel_from_canceller(ready); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Pending op', 'ENTRYPOINT_FAILED'))] +fn test_cancel_when_done() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Unset, target_id); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + + // Fast-forward + testing::set_block_timestamp(delay); + assert_operation_state(timelock, OperationState::Ready, target_id); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Done, target_id); + + // Attempt cancel + testing::set_contract_address(PROPOSER()); // PROPOSER is also CANCELLER + timelock.cancel(target_id); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Pending op', 'ENTRYPOINT_FAILED'))] +fn test_cancel_when_unset() { + let (mut timelock, _) = setup_dispatchers(); + let invalid_id = 0; + + // PROPOSER is also CANCELLER + testing::set_contract_address(PROPOSER()); + timelock.cancel(invalid_id); +} + +#[test] +#[should_panic(expected: ('Caller is missing role', 'ENTRYPOINT_FAILED'))] +fn test_cancel_unauthorized() { + let (mut timelock, mut target) = setup_dispatchers(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + + let call = single_operation(target.contract_address); + let target_id = timelock.hash_operation(call, predecessor, salt); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + + // Cancel + testing::set_contract_address(OTHER()); + timelock.cancel(target_id); +} + +// +// update_delay +// + +#[test] +#[should_panic(expected: ('Timelock: unauthorized caller', 'ENTRYPOINT_FAILED'))] +fn test_update_delay_unauthorized() { + let mut timelock = deploy_timelock(); + + timelock.update_delay(NEW_DELAY); +} + +#[test] +fn test_update_delay_scheduled() { + let mut timelock = deploy_timelock(); + let predecessor = NO_PREDECESSOR; + let salt = 0; + let delay = MIN_DELAY; + let event_index = 0; + + let call = Call { + to: timelock.contract_address, + selector: selector!("update_delay"), + calldata: array![NEW_DELAY.into()].span() + }; + let target_id = timelock.hash_operation(call, predecessor, salt); + + // Schedule + testing::set_contract_address(PROPOSER()); + timelock.schedule(call, predecessor, salt, delay); + assert_operation_state(timelock, OperationState::Waiting, target_id); + assert_only_event_schedule( + timelock.contract_address, target_id, event_index, call, predecessor, delay + ); + + // Fast-forward + testing::set_block_timestamp(delay); + + // Execute + testing::set_contract_address(EXECUTOR()); + timelock.execute(call, predecessor, salt); + assert_operation_state(timelock, OperationState::Done, target_id); + assert_event_delay(timelock.contract_address, MIN_DELAY, NEW_DELAY); + assert_only_event_execute(timelock.contract_address, target_id, event_index, call); + + // Check new minimum delay + let get_new_delay = timelock.get_min_delay(); + assert_eq!(get_new_delay, NEW_DELAY); +} + +// +// Internal +// + +// +// initializer +// + +#[test] +fn test_initializer_single_role_and_admin() { + let mut state = COMPONENT_STATE(); + let contract_state = CONTRACT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + assert!(contract_state.has_role(PROPOSER_ROLE, *proposers.at(0))); + assert!(contract_state.has_role(CANCELLER_ROLE, *proposers.at(0))); + assert!(contract_state.has_role(EXECUTOR_ROLE, *executors.at(0))); + assert!(contract_state.has_role(DEFAULT_ADMIN_ROLE, admin)); +} + +#[test] +fn test_initializer_multiple_roles_and_admin() { + let mut state = COMPONENT_STATE(); + let contract_state = CONTRACT_STATE(); + let min_delay = MIN_DELAY; + + let (p1, p2, p3) = get_proposers(); + let mut proposers = array![p1, p2, p3].span(); + + let (e1, e2, e3) = get_executors(); + let mut executors = array![e1, e2, e3].span(); + + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + + // Check assigned roles + assert!(contract_state.has_role(DEFAULT_ADMIN_ROLE, admin)); + + let mut index = 0; + loop { + if index == proposers.len() { + break; + } + + assert!(contract_state.has_role(PROPOSER_ROLE, *proposers.at(index))); + assert!(contract_state.has_role(CANCELLER_ROLE, *proposers.at(index))); + assert!(contract_state.has_role(EXECUTOR_ROLE, *executors.at(index))); + index += 1; + }; +} + +#[test] +fn test_initializer_no_admin() { + let mut state = COMPONENT_STATE(); + let contract_state = CONTRACT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin_zero = ZERO(); + + // The initializer grants the timelock contract address the `DEFAULT_ADMIN_ROLE` + // therefore, we need to set the address since it's not deployed in this context + testing::set_contract_address(contract_address_const::<'TIMELOCK_ADDRESS'>()); + state.initializer(min_delay, proposers, executors, admin_zero); + + let admin_does_not_have_role = !contract_state.has_role(DEFAULT_ADMIN_ROLE, admin_zero); + assert!(admin_does_not_have_role); +} + +#[test] +fn test_initializer_supported_interfaces() { + let mut state = COMPONENT_STATE(); + let contract_state = CONTRACT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + + // Check interface support + let supports_isrc5 = contract_state.src5.supports_interface(ISRC5_ID); + assert!(supports_isrc5); + + let supports_access_control = contract_state.src5.supports_interface(IACCESSCONTROL_ID); + assert!(supports_access_control); +} + +#[test] +fn test_initializer_min_delay() { + let mut state = COMPONENT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin_zero = ZERO(); + + state.initializer(min_delay, proposers, executors, admin_zero); + + // Check minimum delay is set + let delay = state.get_min_delay(); + assert_eq!(delay, MIN_DELAY); + + // The initializer emits 4 `RoleGranted` events prior to `MinDelayChanged`: + // - Self administration + // - 1 proposer + // - 1 canceller + // - 1 executor + utils::drop_events(ZERO(), 4); + assert_only_event_delay_change(ZERO(), 0, MIN_DELAY); +} + +// +// assert_only_role_or_open_role +// + +#[test] +fn test_assert_only_role_or_open_role_when_has_role() { + let mut state = COMPONENT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + + testing::set_caller_address(PROPOSER()); + state.assert_only_role_or_open_role(PROPOSER_ROLE); + + // PROPOSER == CANCELLER + testing::set_caller_address(PROPOSER()); + state.assert_only_role_or_open_role(CANCELLER_ROLE); + + testing::set_caller_address(EXECUTOR()); + state.assert_only_role_or_open_role(EXECUTOR_ROLE); +} + +#[test] +#[should_panic(expected: ('Caller is missing role',))] +fn test_assert_only_role_or_open_role_unauthorized() { + let mut state = COMPONENT_STATE(); + let min_delay = MIN_DELAY; + + let proposers = array![PROPOSER()].span(); + let executors = array![EXECUTOR()].span(); + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + + testing::set_caller_address(OTHER()); + state.assert_only_role_or_open_role(PROPOSER_ROLE); +} + +#[test] +fn test_assert_only_role_or_open_role_with_open_role() { + let mut state = COMPONENT_STATE(); + let contract_state = CONTRACT_STATE(); + let min_delay = MIN_DELAY; + let open_role = ZERO(); + + let proposers = array![PROPOSER()].span(); + let executors = array![open_role].span(); + let admin = ADMIN(); + + state.initializer(min_delay, proposers, executors, admin); + + let is_open_role = contract_state.has_role(EXECUTOR_ROLE, open_role); + assert!(is_open_role); + + testing::set_caller_address(OTHER()); + state.assert_only_role_or_open_role(EXECUTOR_ROLE); +} + +// +// _before_call +// + +#[test] +fn test__before_call() { + let mut state = COMPONENT_STATE(); + let predecessor = NO_PREDECESSOR; + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time); + + state._before_call(target_id, predecessor); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__before_call_nonexistent_operation() { + let mut state = COMPONENT_STATE(); + let predecessor = NO_PREDECESSOR; + + // Mock targets + let target_id = 'TARGET_ID'; + let not_scheduled = 0; + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, not_scheduled); + + state._before_call(target_id, predecessor); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__before_call_insufficient_time() { + let mut state = COMPONENT_STATE(); + let predecessor = NO_PREDECESSOR; + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time - 1); + + state._before_call(target_id, predecessor); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__before_call_when_already_done() { + let mut state = COMPONENT_STATE(); + let predecessor = NO_PREDECESSOR; + + // Mock targets + let target_id = 'TARGET_ID'; + let done_time = 1; + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, done_time); + + // Fast-forward + testing::set_block_timestamp(done_time); + + state._before_call(target_id, predecessor); +} + +#[test] +fn test__before_call_with_predecessor_done() { + let mut state = COMPONENT_STATE(); + + // Mock `Done` predecessor + let predecessor_id = 'DONE'; + let done_time = 1; + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(predecessor_id, done_time); + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time); + + state._before_call(target_id, predecessor_id); +} + +#[test] +#[should_panic(expected: ('Timelock: awaiting predecessor',))] +fn test__before_call_with_predecessor_not_done() { + let mut state = COMPONENT_STATE(); + + // Mock awaiting predecessor + let predecessor_id = 'DONE'; + let not_done_time = 2; + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(predecessor_id, not_done_time); + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time); + + state._before_call(target_id, predecessor_id); +} + +// +// _after_call +// + +#[test] +fn test__after_call() { + let mut state = COMPONENT_STATE(); + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time); + + state._after_call(target_id); + + // Check timestamp is set to done (1) + let done_ts = 1; + let is_done = state.TimelockController_timestamps.read(target_id); + assert_eq!(is_done, done_ts); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__after_call_nonexistent_operation() { + let mut state = COMPONENT_STATE(); + + // Mock targets + let target_id = 'TARGET_ID'; + let not_scheduled = 0; + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, not_scheduled); + + state._after_call(target_id); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__after_call_insufficient_time() { + let mut state = COMPONENT_STATE(); + + // Mock targets + let target_id = 'TARGET_ID'; + let target_time = MIN_DELAY + starknet::get_block_timestamp(); + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, target_time); + + // Fast-forward + testing::set_block_timestamp(target_time - 1); + + state._after_call(target_id); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Ready op',))] +fn test__after_call_already_done() { + let mut state = COMPONENT_STATE(); + + // Mock targets + let target_id = 'TARGET_ID'; + let done_time = 1; + + // Set targets in storage + state.TimelockController_timestamps.write(target_id, done_time); + + // Fast-forward + testing::set_block_timestamp(done_time); + + state._after_call(target_id); +} + +// +// _schedule +// + +#[test] +fn test__schedule() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + let predecessor = NO_PREDECESSOR; + let delay = MIN_DELAY; + let mut salt = 0; + + // Set up call + let call = single_operation(target.contract_address); + let target_id = state.hash_operation(call, predecessor, salt); + + // Schedule + state._schedule(target_id, delay); + + let actual_ts = state.TimelockController_timestamps.read(target_id); + let expected_ts = starknet::get_block_timestamp() + delay; + assert_eq!(actual_ts, expected_ts); +} + +#[test] +#[should_panic(expected: ('Timelock: expected Unset op',))] +fn test__schedule_overwrite() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + let predecessor = NO_PREDECESSOR; + let delay = MIN_DELAY; + let mut salt = 0; + + // Set up call + let call = single_operation(target.contract_address); + let target_id = state.hash_operation(call, predecessor, salt); + + // Schedule and overwrite + state._schedule(target_id, delay); + state._schedule(target_id, delay); +} + +#[test] +#[should_panic(expected: ('Timelock: insufficient delay',))] +fn test__schedule_bad_delay() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + let predecessor = NO_PREDECESSOR; + let mut salt = 0; + let delay = MIN_DELAY; + + // Set up call + let call = single_operation(target.contract_address); + let target_id = state.hash_operation(call, predecessor, salt); + + // Set min delay + state.TimelockController_min_delay.write(delay); + + // Schedule with bad delay + state._schedule(target_id, delay - 1); +} + +// +// _execute +// + +#[test] +fn test__execute() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + + // Set up call + let call = single_operation(target.contract_address); + + let storage_num = target.get_number(); + let expected_num = 0; + assert_eq!(storage_num, expected_num); + + // Execute + state._execute(call); + + let storage_num = target.get_number(); + let expected_num = VALUE; + assert_eq!(storage_num, expected_num); +} + +#[test] +#[should_panic(expected: ('Expected failure', 'ENTRYPOINT_FAILED',))] +fn test__execute_with_failing_tx() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + + // Set up call + let call = failing_operation(target.contract_address); + + // Execute failing tx + state._execute(call); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test__execute_with_bad_selector() { + let mut state = COMPONENT_STATE(); + let mut target = deploy_mock_target(); + + // Set up call + let bad_selector_call = operation_with_bad_selector(target.contract_address); + + // Execute call with bad selector + state._execute(bad_selector_call); +} + +// +// Helpers +// + +fn assert_operation_state(timelock: TimelockABIDispatcher, exp_state: OperationState, id: felt252) { + let operation_state = timelock.get_operation_state(id); + assert_eq!(operation_state, exp_state); + + let is_operation = timelock.is_operation(id); + let is_pending = timelock.is_operation_pending(id); + let is_ready = timelock.is_operation_ready(id); + let is_done = timelock.is_operation_done(id); + + match exp_state { + OperationState::Unset => { + assert!(!is_operation); + assert!(!is_pending); + assert!(!is_ready); + assert!(!is_done); + }, + OperationState::Waiting => { + assert!(is_operation); + assert!(is_pending); + assert!(!is_ready); + assert!(!is_done); + }, + OperationState::Ready => { + assert!(is_operation); + assert!(is_pending); + assert!(is_ready); + assert!(!is_done); + }, + OperationState::Done => { + assert!(is_operation); + assert!(!is_pending); + assert!(!is_ready); + assert!(is_done); + } + }; +} + +// +// Event helpers +// + +// +// MinDelayChanged +// + +fn assert_event_delay_change(contract: ContractAddress, old_duration: u64, new_duration: u64) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::MinDelayChanged( + MinDelayChanged { old_duration, new_duration } + ); + assert!(event == expected); +} + +fn assert_only_event_delay_change(contract: ContractAddress, old_duration: u64, new_duration: u64) { + assert_event_delay_change(contract, old_duration, new_duration); + utils::assert_no_events_left(contract); +} + +// +// CallScheduled +// + +fn assert_event_schedule( + contract: ContractAddress, + id: felt252, + index: felt252, + call: Call, + predecessor: felt252, + delay: u64 +) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::CallScheduled( + CallScheduled { id, index, call, predecessor, delay } + ); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("CallScheduled")); + indexed_keys.append_serde(id); + indexed_keys.append_serde(index); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_only_event_schedule( + contract: ContractAddress, + id: felt252, + index: felt252, + call: Call, + predecessor: felt252, + delay: u64 +) { + assert_event_schedule(contract, id, index, call, predecessor, delay); + utils::assert_no_events_left(contract); +} + +fn assert_events_schedule_batch( + contract: ContractAddress, id: felt252, calls: Span, predecessor: felt252, delay: u64 +) { + let mut i = 0; + loop { + if i == calls.len() { + break; + } + assert_event_schedule(contract, id, i.into(), *calls.at(i), predecessor, delay); + i += 1; + } +} + +fn assert_only_events_schedule_batch( + contract: ContractAddress, id: felt252, calls: Span, predecessor: felt252, delay: u64 +) { + assert_events_schedule_batch(contract, id, calls, predecessor, delay); + utils::assert_no_events_left(contract); +} + +// +// CallSalt +// + +fn assert_event_call_salt(contract: ContractAddress, id: felt252, salt: felt252) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::CallSalt(CallSalt { id, salt }); + assert!(event == expected); +} + +fn assert_only_event_call_salt(contract: ContractAddress, id: felt252, salt: felt252) { + assert_event_call_salt(contract, id, salt); + utils::assert_no_events_left(contract); +} + +// +// CallExecuted +// + +fn assert_event_execute(contract: ContractAddress, id: felt252, index: felt252, call: Call) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::CallExecuted( + CallExecuted { id, index, call } + ); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("CallExecuted")); + indexed_keys.append_serde(id); + indexed_keys.append_serde(index); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_only_event_execute(contract: ContractAddress, id: felt252, index: felt252, call: Call) { + assert_event_execute(contract, id, index, call); + utils::assert_no_events_left(contract); +} + +fn assert_events_execute_batch(contract: ContractAddress, id: felt252, calls: Span) { + let mut i = 0; + loop { + if i == calls.len() { + break; + } + assert_event_execute(contract, id, i.into(), *calls.at(i)); + i += 1; + } +} + +fn assert_only_events_execute_batch(contract: ContractAddress, id: felt252, calls: Span) { + assert_events_execute_batch(contract, id, calls); + utils::assert_no_events_left(contract); +} + +// +// Cancelled +// + +fn assert_event_cancel(contract: ContractAddress, id: felt252) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::CallCancelled(CallCancelled { id }); + assert!(event == expected); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(selector!("CallCancelled")); + indexed_keys.append_serde(id); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_only_event_cancel(contract: ContractAddress, id: felt252) { + assert_event_cancel(contract, id); + utils::assert_no_events_left(contract); +} + +// +// MinDelayChanged +// + +fn assert_event_delay(contract: ContractAddress, old_duration: u64, new_duration: u64) { + let event = utils::pop_log::(contract).unwrap(); + let expected = TimelockControllerComponent::Event::MinDelayChanged( + MinDelayChanged { old_duration, new_duration } + ); + assert!(event == expected); +} + +fn assert_only_event_delay(contract: ContractAddress, old_duration: u64, new_duration: u64) { + assert_event_delay(contract, old_duration, new_duration); + utils::assert_no_events_left(contract); +} diff --git a/src/tests/governance/test_utils.cairo b/src/tests/governance/test_utils.cairo new file mode 100644 index 000000000..874fe2615 --- /dev/null +++ b/src/tests/governance/test_utils.cairo @@ -0,0 +1,146 @@ +use openzeppelin::governance::timelock::utils::call_impls::CallPartialEq; +use starknet::account::Call; +use starknet::contract_address_const; + +// +// eq +// + +#[test] +fn test_eq_calls_no_calldata() { + let call_1 = Call { to: contract_address_const::<1>(), selector: 1, calldata: array![].span() }; + let call_2 = Call { to: contract_address_const::<1>(), selector: 1, calldata: array![].span() }; + assert_eq!(call_1, call_2); +} + +#[test] +fn test_eq_calls_with_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + assert_eq!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_eq_calls_ne_to() { + let call_1 = Call { to: contract_address_const::<1>(), selector: 1, calldata: array![].span() }; + let call_2 = Call { to: contract_address_const::<2>(), selector: 1, calldata: array![].span() }; + assert_eq!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_eq_calls_ne_selector() { + let call_1 = Call { to: contract_address_const::<1>(), selector: 1, calldata: array![].span() }; + let call_2 = Call { to: contract_address_const::<1>(), selector: 2, calldata: array![].span() }; + assert_eq!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_eq_calls_gt_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1].span() + }; + let call_2 = Call { to: contract_address_const::<1>(), selector: 2, calldata: array![].span() }; + assert_eq!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_eq_calls_lt_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 2, calldata: array![1, 2].span() + }; + assert_eq!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_eq_calls_ne_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 2, calldata: array![2, 1].span() + }; + assert_eq!(call_1, call_2); +} + +// +// ne +// + +#[test] +fn test_ne_calls_to() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<2>(), selector: 1, calldata: array![1, 2, 3].span() + }; + assert_ne!(call_1, call_2); +} + +#[test] +fn test_ne_calls_selector() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 2, calldata: array![1, 2, 3].span() + }; + assert_ne!(call_1, call_2); +} + +#[test] +fn test_ne_calls_gt_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2].span() + }; + assert_ne!(call_1, call_2); +} + +#[test] +fn test_ne_calls_lt_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + assert_ne!(call_1, call_2); +} + +#[test] +fn test_ne_calls_eq_len_calldata() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![3, 2, 1].span() + }; + assert_ne!(call_1, call_2); +} + +#[test] +#[should_panic] +fn test_ne_calls_when_eq() { + let call_1 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + let call_2 = Call { + to: contract_address_const::<1>(), selector: 1, calldata: array![1, 2, 3].span() + }; + assert_ne!(call_1, call_2); +} diff --git a/src/tests/mocks.cairo b/src/tests/mocks.cairo index 6e6e38156..daaf65baf 100644 --- a/src/tests/mocks.cairo +++ b/src/tests/mocks.cairo @@ -14,4 +14,5 @@ pub(crate) mod ownable_mocks; pub(crate) mod pausable_mocks; pub(crate) mod reentrancy_mocks; pub(crate) mod src5_mocks; +pub(crate) mod timelock_mocks; pub(crate) mod upgrades_mocks; diff --git a/src/tests/mocks/timelock_mocks.cairo b/src/tests/mocks/timelock_mocks.cairo new file mode 100644 index 000000000..a60a9da19 --- /dev/null +++ b/src/tests/mocks/timelock_mocks.cairo @@ -0,0 +1,151 @@ +#[starknet::contract] +pub(crate) mod TimelockControllerMock { + use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::governance::timelock::TimelockControllerComponent; + use openzeppelin::introspection::src5::SRC5Component; + use starknet::ContractAddress; + + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); + + // Timelock Mixin + #[abi(embed_v0)] + impl TimelockMixinImpl = + TimelockControllerComponent::TimelockMixinImpl; + impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + timelock: TimelockControllerComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + TimelockEvent: TimelockControllerComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + min_delay: u64, + proposers: Span, + executors: Span, + admin: ContractAddress + ) { + self.timelock.initializer(min_delay, proposers, executors, admin); + } +} + +#[starknet::interface] +pub(crate) trait IMockContract { + fn set_number(ref self: TState, new_number: felt252); + fn get_number(self: @TState) -> felt252; + fn failing_function(self: @TState); +} + +#[starknet::contract] +pub(crate) mod MockContract { + use super::IMockContract; + + #[storage] + struct Storage { + number: felt252, + } + + #[abi(embed_v0)] + impl MockContractImpl of IMockContract { + fn set_number(ref self: ContractState, new_number: felt252) { + self.number.write(new_number); + } + + fn get_number(self: @ContractState) -> felt252 { + self.number.read() + } + + fn failing_function(self: @ContractState) { + core::panic_with_felt252('Expected failure'); + } + } +} + +#[starknet::interface] +pub(crate) trait ITimelockAttacker { + fn reenter(ref self: TState); + fn reenter_batch(ref self: TState); +} + +#[starknet::contract] +pub(crate) mod TimelockAttackerMock { + use openzeppelin::governance::timelock::interface::{ + ITimelockDispatcher, ITimelockDispatcherTrait + }; + use starknet::ContractAddress; + use starknet::account::Call; + use super::ITimelockAttacker; + + const NO_PREDECESSOR: felt252 = 0; + const NO_SALT: felt252 = 0; + + #[storage] + struct Storage { + balance: felt252, + count: felt252 + } + + #[abi(embed_v0)] + impl TimelockAttackerImpl of ITimelockAttacker { + fn reenter(ref self: ContractState) { + let new_balance = self.balance.read() + 1; + self.balance.write(new_balance); + + let sender = starknet::get_caller_address(); + let this = starknet::get_contract_address(); + + let current_count = self.count.read(); + if current_count != 2 { + self.count.write(current_count + 1); + + let reentrant_call = Call { + to: this, selector: selector!("reenter"), calldata: array![].span() + }; + + let timelock = ITimelockDispatcher { contract_address: sender }; + timelock.execute(reentrant_call, NO_PREDECESSOR, NO_SALT); + } + } + + fn reenter_batch(ref self: ContractState) { + let new_balance = self.balance.read() + 1; + self.balance.write(new_balance); + + let sender = starknet::get_caller_address(); + let this = starknet::get_contract_address(); + + let current_count = self.count.read(); + if current_count != 2 { + self.count.write(current_count + 1); + + let reentrant_call = Call { + to: this, selector: selector!("reenter_batch"), calldata: array![].span() + }; + + let calls = array![reentrant_call].span(); + + let timelock = ITimelockDispatcher { contract_address: sender }; + timelock.execute_batch(calls, NO_PREDECESSOR, NO_SALT); + } + } + } +} From fa708bd2ade2969be3131180174093c3b9bd0666 Mon Sep 17 00:00:00 2001 From: JChoy Date: Fri, 26 Jul 2024 01:00:43 +0900 Subject: [PATCH 07/12] bump scarb to 2.7.0-rc.4 (#1064) * bump scarb to 2.7.0-rc.4 * add changelog * bump scarb in installation page --- CHANGELOG.md | 1 + Scarb.toml | 8 ++++---- docs/modules/ROOT/pages/index.adoc | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 983569924..a1ff29a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump scarb to v2.7.0-rc.1 (#1025) - Bump scarb to v2.7.0-rc.2 (#1052) +- Bump scarb to v2.7.0-rc.4 (#1064) ## 0.15.0-rc.0 (2024-07-8) diff --git a/Scarb.toml b/Scarb.toml index 21be19db4..bae8c2933 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -2,8 +2,8 @@ name = "openzeppelin" version = "0.15.0-rc.0" edition = "2023_11" -cairo-version = "2.7.0-rc.2" -scarb-version = "2.7.0-rc.2" +cairo-version = "2.7.0-rc.3" +scarb-version = "2.7.0-rc.4" authors = ["OpenZeppelin Community "] description = "OpenZeppelin Contracts written in Cairo for StarkNet, a decentralized ZK Rollup" documentation = "https://docs.openzeppelin.com/contracts-cairo" @@ -13,10 +13,10 @@ license-file = "LICENSE" keywords = ["openzeppelin", "starknet", "cairo", "contracts", "security", "standards"] [dependencies] -starknet = "2.7.0-rc.2" +starknet = "2.7.0-rc.3" [dev-dependencies] -cairo_test = "2.7.0-rc.2" +cairo_test = "2.7.0-rc.3" [lib] diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 1bfd1a442..41e20d036 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -20,8 +20,8 @@ before proceeding, and run the following command to check that the installation ---- $ scarb --version -scarb 2.7.0-rc.2 (55754b5d3 2024-07-12) -cairo: 2.7.0-rc.2 (https://crates.io/crates/cairo-lang-compiler/2.7.0-rc.2) +scarb 2.7.0-rc.4 (88bf93564 2024-07-19) +cairo: 2.7.0-rc.3 (https://crates.io/crates/cairo-lang-compiler/2.7.0-rc.3) sierra: 1.6.0 ---- @@ -113,4 +113,4 @@ You can now compile it: [,bash] ---- scarb build ----- \ No newline at end of file +---- From 49816b6763fea500396a97b5178c3e30a85eecc7 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Fri, 26 Jul 2024 23:26:58 +0200 Subject: [PATCH 08/12] feat: update workflow and fix warning (#1066) --- .github/workflows/prepare-release.yml | 4 +++- src/utils/structs/checkpoint.cairo | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 6b0481046..7d10bc7a0 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -16,8 +16,10 @@ jobs: run: | CURRENT_VERSION=$(grep '^version = ' Scarb.toml | sed 's/version = "\(.*\)"/\1/') SCARB_VERSION=$(grep 'scarb-version = ' Scarb.toml | sed 's/scarb-version = "\(.*\)"/\1/') + CAIRO_VERSION=$(grep 'cairo-version = ' Scarb.toml | sed 's/cairo-version = "\(.*\)"/\1/') echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV echo "SCARB_VERSION=$SCARB_VERSION" >> $GITHUB_ENV + echo "CAIRO_VERSION=$CAIRO_VERSION" >> $GITHUB_ENV - name: Extract new version number run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> $GITHUB_ENV @@ -43,7 +45,7 @@ jobs: - name: Update presets page run: | - class_hash get --json | sed -e '1,4d' | python3 scripts/get_hashes_page.py $SCARB_VERSION \ + class_hash get --json | sed -e '1,4d' | python3 scripts/get_hashes_page.py $CAIRO_VERSION \ > ./docs/modules/ROOT/pages/utils/_class_hashes.adoc - name: Auto-commit changes diff --git a/src/utils/structs/checkpoint.cairo b/src/utils/structs/checkpoint.cairo index c26938dd8..ec2ec0ab4 100644 --- a/src/utils/structs/checkpoint.cairo +++ b/src/utils/structs/checkpoint.cairo @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (utils/structs/checkpoint.cairo) -use core::integer::u32_sqrt; +use core::num::traits::Sqrt; use openzeppelin::utils::math; use starknet::storage_access::StorePacking; use super::storage_array::{StorageArray, StorageArrayTrait}; @@ -55,7 +55,7 @@ pub impl TraceImpl of TraceTrait { let mut high = len; if (len > 5) { - let mid = len - u32_sqrt(len).into(); + let mid = len - len.sqrt().into(); if (key < checkpoints.read_at(mid).key) { high = mid; } else { From f72cab90c7f07f735f116722d5c01dc46b45606c Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 30 Jul 2024 12:09:55 -0400 Subject: [PATCH 09/12] Add actionlint (#1067) * add actionlint for workflows * remove codecov and gitmodules * bump checkout to v4, add double quotes, fmt * bump md lint * re-add cairo version * group redirects * fix link in security * add local actionlint matcher json --- .codecov.yml | 11 ----------- .github/actionlint-matcher.json | 17 +++++++++++++++++ .github/workflows/actionlint.yml | 15 +++++++++++++++ .github/workflows/prepare-release.yml | 16 +++++++++------- .github/workflows/test.yml | 6 +++--- .gitmodules | 3 --- SECURITY.md | 2 +- 7 files changed, 45 insertions(+), 25 deletions(-) delete mode 100644 .codecov.yml create mode 100644 .github/actionlint-matcher.json create mode 100644 .github/workflows/actionlint.yml delete mode 100644 .gitmodules diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index 54616a49c..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,11 +0,0 @@ -comment: off -github_checks: - annotations: false -coverage: - status: - patch: - default: - target: 95% - project: - default: - threshold: 1% diff --git a/.github/actionlint-matcher.json b/.github/actionlint-matcher.json new file mode 100644 index 000000000..09211db2f --- /dev/null +++ b/.github/actionlint-matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "actionlint", + "pattern": [ + { + "regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + "code": 5 + } + ] + } + ] + } diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 000000000..667a77682 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,15 @@ +name: Lint workflows + +on: + pull_request: + paths: + - '.github/**/*.ya?ml' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Add problem matchers + run: echo "::add-matcher::.github/actionlint-matcher.json" + - uses: docker://rhysd/actionlint:latest diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 7d10bc7a0..23a2a7f95 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -10,25 +10,27 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Extract current versions run: | CURRENT_VERSION=$(grep '^version = ' Scarb.toml | sed 's/version = "\(.*\)"/\1/') SCARB_VERSION=$(grep 'scarb-version = ' Scarb.toml | sed 's/scarb-version = "\(.*\)"/\1/') CAIRO_VERSION=$(grep 'cairo-version = ' Scarb.toml | sed 's/cairo-version = "\(.*\)"/\1/') - echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_ENV - echo "SCARB_VERSION=$SCARB_VERSION" >> $GITHUB_ENV - echo "CAIRO_VERSION=$CAIRO_VERSION" >> $GITHUB_ENV + { + echo "CURRENT_VERSION=$CURRENT_VERSION" + echo "SCARB_VERSION=$SCARB_VERSION" + echo "CAIRO_VERSION=$CAIRO_VERSION" + } >> "$GITHUB_ENV" - name: Extract new version number - run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> $GITHUB_ENV + run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> "$GITHUB_ENV" - name: Replace version in files run: | echo "Current version: $CURRENT_VERSION" echo "New version: $NEW_VERSION" - ESCAPED_CURRENT_VERSION=$(echo $CURRENT_VERSION | sed 's/\./\\./g') + ESCAPED_CURRENT_VERSION="${CURRENT_VERSION//\./\\.}" find . -type f -not -path '*/\.*' -not -path './CHANGELOG.md' -not -path './docs/package-lock.json' \ -not -path './RELEASING.md' -exec sed -i "s/$ESCAPED_CURRENT_VERSION/$NEW_VERSION/g" {} + @@ -45,7 +47,7 @@ jobs: - name: Update presets page run: | - class_hash get --json | sed -e '1,4d' | python3 scripts/get_hashes_page.py $CAIRO_VERSION \ + class_hash get --json | sed -e '1,4d' | python3 scripts/get_hashes_page.py "$CAIRO_VERSION" \ > ./docs/modules/ROOT/pages/utils/_class_hashes.adoc - name: Auto-commit changes diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 023569891..8cfc979cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,16 +13,16 @@ jobs: name: Lint and test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Extract scarb version run: | SCARB_VERSION=$(grep 'scarb-version = ' Scarb.toml | sed 's/scarb-version = "\(.*\)"/\1/') - echo "SCARB_VERSION=$SCARB_VERSION" >> $GITHUB_ENV + echo "SCARB_VERSION=$SCARB_VERSION" >> "$GITHUB_ENV" - uses: software-mansion/setup-scarb@v1 with: scarb-version: ${{ env.SCARB_VERSION }} - name: Markdown lint - uses: DavidAnson/markdownlint-cli2-action@5b7c9f74fec47e6b15667b2cc23c63dff11e449e # v9 + uses: DavidAnson/markdownlint-cli2-action@b4c9feab76d8025d1e83c653fa3990936df0e6c8 # v16 with: globs: | *.md diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 269eb8546..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "cairo"] - path = cairo - url = https://github.com/starkware-libs/cairo.git diff --git a/SECURITY.md b/SECURITY.md index 8bd19014b..7084b6069 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,4 +3,4 @@ > ⚠️ Warning! ⚠️ > This project is still in a very early and experimental phase. It has never been audited nor thoroughly reviewed for security vulnerabilities. Do not use in production. -Please report any security issues you find to security@openzeppelin.com. +Please report any security issues you find to . From 15549143fbf1c5ae2875da0220a762b158b9f409 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Jul 2024 13:07:14 -0500 Subject: [PATCH 10/12] re-add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1ff29a21..9589aca28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ERC1155Component and ERC1155ReceiverComponent mixins (#941) - ERC721ReceiverComponent documentation (#945) +### Changed + +- Bump scarb to v2.6.3 (#946) + ### Fixed - ERC721ReceiverComponent mixin embeddable implementation name (#945) From 5e10f37a1456d841e29504757fa02d3831bdfc4a Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Jul 2024 13:43:59 -0500 Subject: [PATCH 11/12] add tmp usc install to ci --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 214c17849..c305deb65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,5 +34,8 @@ jobs: !PULL_REQUEST_TEMPLATE.md - name: Cairo lint run: scarb fmt --check + - name: Temporary USC manually install + run: | + curl -L https://raw.githubusercontent.com/software-mansion/universal-sierra-compiler/master/scripts/install.sh | sh -s -- v2.2.0-rc.1 - name: Cairo test run: snforge test From c30e279bd3f4094fa1d472e2dd3a8f599631f1e6 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Jul 2024 13:47:52 -0500 Subject: [PATCH 12/12] fix ci --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c305deb65..7e7d36e0b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Extract foundry version run: | FOUNDRY_VERSION=$(grep 'snforge_std = ' Scarb.toml | sed 's/snforge_std = .\+ tag = "v\(.*\)".*/\1/') - echo "FOUNDRY_VERSION=$FOUNDRY_VERSION" >> $GITHUB_ENV + echo "FOUNDRY_VERSION=$FOUNDRY_VERSION" >> "$GITHUB_ENV" - uses: foundry-rs/setup-snfoundry@v3 with: starknet-foundry-version: ${{ env.FOUNDRY_VERSION }}