diff --git a/.github/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml index 2ab2bc2e7..1a5f4f2b0 100644 --- a/.github/actions/build-test-image/action.yml +++ b/.github/actions/build-test-image/action.yml @@ -27,6 +27,8 @@ runs: steps: - name: Install Cairo uses: ./.github/actions/install-cairo + with: + use_musl_libc: "true" - name: Check if image exists id: check-image uses: smartcontractkit/chainlink-github-actions/docker/image-exists@fc3e0df622521019f50d772726d6bf8dc919dd38 # v2.3.19 diff --git a/.github/actions/install-cairo/action.yml b/.github/actions/install-cairo/action.yml index d3f1524d6..5e994cf0f 100644 --- a/.github/actions/install-cairo/action.yml +++ b/.github/actions/install-cairo/action.yml @@ -2,32 +2,43 @@ name: Install Cairo and Scarb description: A composite action that installs cairo and scarb binaries inputs: - cairo_version: - description: Cairo release version - default: "v2.6.4" - required: false + # cairo_version: + # description: Cairo release version + # default: "v2.8.2" + # required: false scarb_version: description: Scarb release version - default: "v2.6.5" + default: "v2.8.2" + required: false + use_musl_libc: + description: "C library implementation" + default: "false" required: false runs: using: composite steps: - - name: Setup Cairo for Linux - id: install-cairo - shell: bash - run: | - wget https://github.com/starkware-libs/cairo/releases/download/${{ inputs.cairo_version }}/release-x86_64-unknown-linux-musl.tar.gz - tar -xvzf release-x86_64-unknown-linux-musl.tar.gz - mv -vf cairo cairo-build - echo "$GITHUB_WORKSPACE/cairo-build/bin" >> $GITHUB_PATH + # - name: Setup Cairo for Linux + # id: install-cairo + # shell: bash + # run: | + # wget https://github.com/starkware-libs/cairo/releases/download/${{ inputs.cairo_version }}/release-x86_64-unknown-linux-musl.tar.gz + # tar -xvzf release-x86_64-unknown-linux-musl.tar.gz + # mv -vf cairo cairo-build + # echo "$GITHUB_WORKSPACE/cairo-build/bin" >> $GITHUB_PATH + # echo "$GITHUB_WORKSPACE" + # echo "this is the github workspace" - name: Setup Scarb for Linux id: install-scarb shell: bash run: | - wget https://github.com/software-mansion/scarb/releases/download/${{ inputs.scarb_version }}/scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-musl.tar.gz - tar -xvzf scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-musl.tar.gz - mv -vf scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-musl scarb-build + if [ "${{ inputs.use_musl_libc }}" = "true" ]; then + libc_version="musl" + else + libc_version="gnu" + fi + wget https://github.com/software-mansion/scarb/releases/download/${{ inputs.scarb_version }}/scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-${libc_version}.tar.gz + tar -xvzf scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-${libc_version}.tar.gz + mv -vf scarb-${{ inputs.scarb_version }}-x86_64-unknown-linux-${libc_version} scarb-build echo "$GITHUB_WORKSPACE/scarb-build/bin" >> $GITHUB_PATH diff --git a/.github/actions/install-starknet-foundry/action.yml b/.github/actions/install-starknet-foundry/action.yml index 8e4360edf..88693664c 100644 --- a/.github/actions/install-starknet-foundry/action.yml +++ b/.github/actions/install-starknet-foundry/action.yml @@ -4,7 +4,7 @@ description: A composite action that installs the snforge and sncast binaries inputs: starknet_foundry_version: description: Starknet Foundry release version - default: "0.27.0" + default: "0.31.0" required: false runs: diff --git a/.tool-versions b/.tool-versions index 121b6f80a..eec90d803 100644 --- a/.tool-versions +++ b/.tool-versions @@ -9,9 +9,9 @@ mockery 2.22.1 golangci-lint 1.62.2 actionlint 1.6.12 shellcheck 0.8.0 -scarb 2.6.5 +scarb 2.8.2 postgres 15.1 -starknet-foundry 0.27.0 +starknet-foundry 0.31.0 # Kubernetes k3d 5.4.4 diff --git a/Makefile b/Makefile index bea87f567..5abb7c301 100644 --- a/Makefile +++ b/Makefile @@ -152,13 +152,13 @@ generate: mockery gomods .PHONY: format-cairo format-cairo: - cairo-format -i ./contracts/src/**/*.cairo - cairo-format -i ./examples/**/*.cairo + cd contracts && scarb fmt + cd examples/contracts/aggregator_consumer && scarb fmt .PHONY: format-cairo-check format-cairo-check: - cairo-format -c ./contracts/src/**/*.cairo - cairo-format -c ./examples/**/*.cairo + cd contracts && scarb fmt -c + cd examples/contracts/aggregator_consumer && scarb fmt -c .PHONY: format-ts format-ts: @@ -233,7 +233,7 @@ test-integration-soak-ci: .PHONY: test-examples test-examples: cd ./examples/contracts/aggregator_consumer && \ - snforge test + scarb test .PHONY: test-integration-gauntlet # TODO: fix example diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock index 900ef28e5..d7ee13fd5 100644 --- a/contracts/Scarb.lock +++ b/contracts/Scarb.lock @@ -4,7 +4,7 @@ version = 1 [[package]] name = "alexandria_bytes" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" dependencies = [ "alexandria_data_structures", "alexandria_math", @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "alexandria_data_structures" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" dependencies = [ "alexandria_encoding", ] @@ -21,7 +21,7 @@ dependencies = [ [[package]] name = "alexandria_encoding" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" dependencies = [ "alexandria_bytes", "alexandria_math", @@ -30,16 +30,13 @@ dependencies = [ [[package]] name = "alexandria_math" -version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" -dependencies = [ - "alexandria_data_structures", -] +version = "0.2.1" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" [[package]] name = "alexandria_numeric" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" dependencies = [ "alexandria_math", "alexandria_searching", @@ -48,7 +45,7 @@ dependencies = [ [[package]] name = "alexandria_searching" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=6a989d636243494a0529fc3af06a924493499564#6a989d636243494a0529fc3af06a924493499564" dependencies = [ "alexandria_data_structures", ] @@ -66,10 +63,115 @@ dependencies = [ [[package]] name = "openzeppelin" -version = "0.10.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_finance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_token", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_presets" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_token" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_utils" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" [[package]] name = "snforge_std" -version = "0.27.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 97e8ee5d4..01b8a6874 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "chainlink" version = "0.1.0" -cairo-version = "2.6.3" +cairo-version = "2.8.2" description = "Chainlink contracts for Starknet" homepage = "https://github.com/smartcontractkit/chainlink-starknet" @@ -13,14 +13,14 @@ test = "snforge test" # Uncomment if you want to use dependencies # Note: currently testing doesn't work with dependencies [dependencies] -starknet = ">=2.6.3" -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.10.0" } -alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } -alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } +starknet = "2.8.2" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.17.0" } +alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "6a989d636243494a0529fc3af06a924493499564" } +alexandria_encoding = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "6a989d636243494a0529fc3af06a924493499564" } [dev-dependencies] -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "bcdca70afdf59c9976148e95cebad5cf63d75a7f" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev = "6a989d636243494a0529fc3af06a924493499564" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } [lib] diff --git a/contracts/cairo_project.toml b/contracts/cairo_project.toml deleted file mode 100644 index df050dd0c..000000000 --- a/contracts/cairo_project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[crate_roots] -chainlink = "src" diff --git a/contracts/src/access_control/access_controller.cairo b/contracts/src/access_control/access_controller.cairo index 37d9ef909..c6a19ba8d 100644 --- a/contracts/src/access_control/access_controller.cairo +++ b/contracts/src/access_control/access_controller.cairo @@ -4,13 +4,18 @@ mod AccessController { use starknet::class_hash::ClassHash; use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::UpgradeableComponent; use chainlink::libraries::access_control::{AccessControlComponent, IAccessController}; use chainlink::libraries::type_and_version::ITypeAndVersion; - use chainlink::libraries::upgradeable::{Upgradeable, IUpgradeable}; + use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: OwnerUpgradeableComponent, storage: owner_upgradeable, event: OwnerUpgradeableEvent + ); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; @@ -21,6 +26,12 @@ mod AccessController { AccessControlComponent::AccessControlImpl; impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnerUpgradeableImpl = + OwnerUpgradeableComponent::OwnerUpgradeableImpl; + #[event] #[derive(Drop, starknet::Event)] enum Event { @@ -28,6 +39,10 @@ mod AccessController { OwnableEvent: OwnableComponent::Event, #[flat] AccessControlEvent: AccessControlComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnerUpgradeableEvent: OwnerUpgradeableComponent::Event } #[storage] @@ -36,12 +51,16 @@ mod AccessController { ownable: OwnableComponent::Storage, #[substorage(v0)] access_control: AccessControlComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + owner_upgradeable: OwnerUpgradeableComponent::Storage } #[constructor] fn constructor(ref self: ContractState, owner_address: ContractAddress) { self.ownable.initializer(owner_address); - self.access_control.initializer(); + self.access_control.initializer(true); } #[abi(embed_v0)] @@ -50,12 +69,4 @@ mod AccessController { 'AccessController 1.0.0' } } - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_impl: ClassHash) { - self.ownable.assert_only_owner(); - Upgradeable::upgrade(new_impl); - } - } } diff --git a/contracts/src/access_control/rbac_timelock.cairo b/contracts/src/access_control/rbac_timelock.cairo index 5fe078996..ded7c1c86 100644 --- a/contracts/src/access_control/rbac_timelock.cairo +++ b/contracts/src/access_control/rbac_timelock.cairo @@ -2,6 +2,7 @@ use starknet::ContractAddress; use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; +use chainlink::utils::{keccak}; #[derive(Copy, Drop, Serde)] struct Call { @@ -14,20 +15,20 @@ fn _hash_operation_batch(calls: Span, predecessor: u256, salt: u256) -> u2 let mut encoded: Bytes = BytesTrait::new_empty(); let mut i = 0; - while i < calls - .len() { - let call = *calls.at(i); - encoded = encoded.encode(call.target).encode(call.selector); - let mut j = 0; - while j < call.data.len() { - encoded = encoded.encode(*call.data.at(j)); - j += 1; - }; - i += 1; + while i < calls.len() { + let call = *calls.at(i); + encoded = encoded.encode(call.target).encode(call.selector); + let mut j = 0; + while j < call.data.len() { + encoded = encoded.encode(*call.data.at(j)); + j += 1; }; + i += 1; + }; encoded = encoded.encode(predecessor).encode(salt); - encoded.keccak() + + keccak(@encoded.into()) } #[starknet::interface] @@ -41,8 +42,8 @@ trait IRBACTimelock { fn update_delay(ref self: TContractState, new_delay: u256); fn block_function_selector(ref self: TContractState, selector: felt252); fn unblock_function_selector(ref self: TContractState, selector: felt252); - fn get_blocked_function_selector_count(self: @TContractState) -> u256; - fn get_blocked_function_selector_at(self: @TContractState, index: u256) -> felt252; + fn get_blocked_function_selector_count(self: @TContractState) -> usize; + fn get_blocked_function_selector_at(self: @TContractState, index: usize) -> felt252; fn is_operation(self: @TContractState, id: u256) -> bool; fn is_operation_pending(self: @TContractState, id: u256) -> bool; fn is_operation_ready(self: @TContractState, id: u256) -> bool; @@ -58,7 +59,13 @@ trait IRBACTimelock { mod RBACTimelock { use core::traits::TryInto; use core::starknet::SyscallResultTrait; - use starknet::{ContractAddress, call_contract_syscall}; + use starknet::{ + ContractAddress, call_contract_syscall, StorageAddress, + storage::{ + Map, StoragePointerReadAccess, StoragePointerWriteAccess, StorageMapReadAccess, + StorageMapWriteAccess, StoragePathEntry + } + }; use openzeppelin::{ access::accesscontrol::AccessControlComponent, introspection::src5::SRC5Component, token::erc1155::erc1155_receiver::ERC1155ReceiverComponent, @@ -104,7 +111,7 @@ mod RBACTimelock { // EnumerableSet impl EnumerableSetInternalImpl = EnumerableSetComponent::InternalImpl; - // we use sn_keccak intead of keccak256 + // we use sn_keccak instead of keccak256 const ADMIN_ROLE: felt252 = selector!("ADMIN_ROLE"); const PROPOSER_ROLE: felt252 = selector!("PROPOSER_ROLE"); const EXECUTOR_ROLE: felt252 = selector!("EXECUTOR_ROLE"); @@ -112,7 +119,7 @@ mod RBACTimelock { const BYPASSER_ROLE: felt252 = selector!("BYPASSER_ROLE"); const _DONE_TIMESTAMP: u256 = 0x1; - const BLOCKED_FUNCTIONS: u256 = 'BLOCKED_FUNCTION_SELECTORS'; + const BLOCKED_FUNCTIONS: felt252 = 'BLOCKED_FUNCTION_SELECTORS'; #[storage] struct Storage { @@ -127,7 +134,7 @@ mod RBACTimelock { #[substorage(v0)] access_control: AccessControlComponent::Storage, // id -> timestamp - _timestamps: LegacyMap, // timestamp at which operation is ready to be executed + _timestamps: Map, // timestamp at which operation is ready to be executed _min_delay: u256 } @@ -225,40 +232,36 @@ mod RBACTimelock { self.access_control.initializer(); self.erc1155_receiver.initializer(); self.erc721_receiver.initializer(); - self.access_control._set_role_admin(ADMIN_ROLE, ADMIN_ROLE); - self.access_control._set_role_admin(PROPOSER_ROLE, ADMIN_ROLE); - self.access_control._set_role_admin(EXECUTOR_ROLE, ADMIN_ROLE); - self.access_control._set_role_admin(CANCELLER_ROLE, ADMIN_ROLE); - self.access_control._set_role_admin(BYPASSER_ROLE, ADMIN_ROLE); + self.access_control.set_role_admin(ADMIN_ROLE, ADMIN_ROLE); + self.access_control.set_role_admin(PROPOSER_ROLE, ADMIN_ROLE); + self.access_control.set_role_admin(EXECUTOR_ROLE, ADMIN_ROLE); + self.access_control.set_role_admin(CANCELLER_ROLE, ADMIN_ROLE); + self.access_control.set_role_admin(BYPASSER_ROLE, ADMIN_ROLE); self.access_control._grant_role(ADMIN_ROLE, admin); let mut i = 0; - while i < proposers - .len() { - self.access_control._grant_role(PROPOSER_ROLE, *proposers.at(i)); - i += 1; - }; + while i < proposers.len() { + self.access_control._grant_role(PROPOSER_ROLE, *proposers.at(i)); + i += 1; + }; let mut i = 0; - while i < executors - .len() { - self.access_control._grant_role(EXECUTOR_ROLE, *executors.at(i)); - i += 1; - }; + while i < executors.len() { + self.access_control._grant_role(EXECUTOR_ROLE, *executors.at(i)); + i += 1; + }; let mut i = 0; - while i < cancellers - .len() { - self.access_control._grant_role(CANCELLER_ROLE, *cancellers.at(i)); - i += 1 - }; + while i < cancellers.len() { + self.access_control._grant_role(CANCELLER_ROLE, *cancellers.at(i)); + i += 1 + }; let mut i = 0; - while i < bypassers - .len() { - self.access_control._grant_role(BYPASSER_ROLE, *bypassers.at(i)); - i += 1 - }; + while i < bypassers.len() { + self.access_control._grant_role(BYPASSER_ROLE, *bypassers.at(i)); + i += 1 + }; self._min_delay.write(min_delay); @@ -279,32 +282,28 @@ mod RBACTimelock { self._schedule(id, delay); let mut i = 0; - while i < calls - .len() { - let call = *calls.at(i); - assert( - !self.set.contains(BLOCKED_FUNCTIONS, call.selector.into()), - 'selector is blocked' + while i < calls.len() { + let call = *calls.at(i); + assert(!self.set.contains(BLOCKED_FUNCTIONS, call.selector), 'selector is blocked'); + + self + .emit( + Event::CallScheduled( + CallScheduled { + id: id, + index: i.into(), + target: call.target, + selector: call.selector, + data: call.data, + predecessor: predecessor, + salt: salt, + delay: delay + } + ) ); - self - .emit( - Event::CallScheduled( - CallScheduled { - id: id, - index: i.into(), - target: call.target, - selector: call.selector, - data: call.data, - predecessor: predecessor, - salt: salt, - delay: delay - } - ) - ); - - i += 1; - } + i += 1; + } } fn cancel(ref self: ContractState, id: u256) { @@ -327,24 +326,23 @@ mod RBACTimelock { self._before_call(id, predecessor); let mut i = 0; - while i < calls - .len() { - let call = *(calls.at(i)); - self._execute(call); - self - .emit( - Event::CallExecuted( - CallExecuted { - id: id, - index: i.into(), - target: call.target, - selector: call.selector, - data: call.data - } - ) - ); - i += 1; - }; + while i < calls.len() { + let call = *(calls.at(i)); + self._execute(call); + self + .emit( + Event::CallExecuted( + CallExecuted { + id: id, + index: i.into(), + target: call.target, + selector: call.selector, + data: call.data + } + ) + ); + i += 1; + }; self._after_call(id); } @@ -353,24 +351,23 @@ mod RBACTimelock { self._assert_only_role_or_admin_role(BYPASSER_ROLE); let mut i = 0; - while i < calls - .len() { - let call = *calls.at(i); - self._execute(call); - self - .emit( - Event::BypasserCallExecuted( - BypasserCallExecuted { - index: i.into(), - target: call.target, - selector: call.selector, - data: call.data - } - ) - ); - - i += 1; - } + while i < calls.len() { + let call = *calls.at(i); + self._execute(call); + self + .emit( + Event::BypasserCallExecuted( + BypasserCallExecuted { + index: i.into(), + target: call.target, + selector: call.selector, + data: call.data + } + ) + ); + + i += 1; + } } // @@ -394,8 +391,8 @@ mod RBACTimelock { fn block_function_selector(ref self: ContractState, selector: felt252) { self.access_control.assert_only_role(ADMIN_ROLE); - // cast to u256 because that's what set stores - if self.set.add(BLOCKED_FUNCTIONS, selector.into()) { + // cast to u256 because that's what set stores + if self.set.add(BLOCKED_FUNCTIONS, selector) { self .emit( Event::FunctionSelectorBlocked( @@ -408,7 +405,7 @@ mod RBACTimelock { fn unblock_function_selector(ref self: ContractState, selector: felt252) { self.access_control.assert_only_role(ADMIN_ROLE); - if self.set.remove(BLOCKED_FUNCTIONS, selector.into()) { + if self.set.remove(BLOCKED_FUNCTIONS, selector) { self .emit( Event::FunctionSelectorUnblocked( @@ -422,13 +419,13 @@ mod RBACTimelock { // VIEW ONLY // - fn get_blocked_function_selector_count(self: @ContractState) -> u256 { + fn get_blocked_function_selector_count(self: @ContractState) -> usize { self.set.length(BLOCKED_FUNCTIONS) } - fn get_blocked_function_selector_at(self: @ContractState, index: u256) -> felt252 { + fn get_blocked_function_selector_at(self: @ContractState, index: usize) -> felt252 { // cast from u256 to felt252 should never error - self.set.at(BLOCKED_FUNCTIONS, index).try_into().unwrap() + self.set.at(BLOCKED_FUNCTIONS, index) } fn is_operation(self: @ContractState, id: u256) -> bool { diff --git a/contracts/src/account.cairo b/contracts/src/account.cairo index dcda746e8..3318c2c7c 100644 --- a/contracts/src/account.cairo +++ b/contracts/src/account.cairo @@ -1,11 +1,14 @@ -// copied from https://raw.githubusercontent.com/OpenZeppelin/cairo-contracts/861fc416f87addbe23a3b47f9d19ab27c10d5dc8/src/presets/account.cairo (0.9.0) +// copied from +// https://raw.githubusercontent.com/OpenZeppelin/cairo-contracts/861fc416f87addbe23a3b47f9d19ab27c10d5dc8/src/presets/account.cairo +// (0.9.0) // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.9.0 (presets/account.cairo) /// # Account Preset /// -/// OpenZeppelin's basic account which can change its public key and declare, deploy, or call contracts. +/// OpenZeppelin's basic account which can change its public key and declare, deploy, or call +/// contracts. #[starknet::contract(account)] mod Account { use openzeppelin::account::AccountComponent; diff --git a/contracts/src/cairo_project.toml b/contracts/src/cairo_project.toml deleted file mode 100644 index df2c45c01..000000000 --- a/contracts/src/cairo_project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[crate_roots] -chainlink = "." diff --git a/contracts/src/emergency/sequencer_uptime_feed.cairo b/contracts/src/emergency/sequencer_uptime_feed.cairo index fad5ad8f7..afb8ecf4f 100644 --- a/contracts/src/emergency/sequencer_uptime_feed.cairo +++ b/contracts/src/emergency/sequencer_uptime_feed.cairo @@ -20,6 +20,7 @@ mod SequencerUptimeFeed { use starknet::storage_write_syscall; use starknet::storage_address_from_base_and_offset; use starknet::class_hash::ClassHash; + use starknet::storage::Map; use box::BoxTrait; use traits::Into; @@ -35,7 +36,7 @@ mod SequencerUptimeFeed { use chainlink::ocr2::aggregator::Round; use chainlink::ocr2::aggregator::IAggregator; use chainlink::ocr2::aggregator::{Transmission}; - use chainlink::libraries::upgradeable::Upgradeable; + use chainlink::libraries::upgrades::v1::upgradeable::Upgradeable; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); @@ -58,7 +59,7 @@ mod SequencerUptimeFeed { // l1 sender is an starknet validator ethereum address _l1_sender: EthAddress, // maps round id to round transmission - _round_transmissions: LegacyMap, + _round_transmissions: Map, _latest_round_id: u128, } @@ -177,7 +178,8 @@ mod SequencerUptimeFeed { #[l1_handler] fn update_status(ref self: ContractState, from_address: felt252, status: u128, timestamp: u64) { - // Cairo enforces from_address to be a felt252 on the method signature, but we can cast it right after + // Cairo enforces from_address to be a felt252 on the method signature, but we can cast it + // right after let from_address: EthAddress = from_address.try_into().unwrap(); assert(self._l1_sender.read() == from_address, 'EXPECTED_FROM_BRIDGE_ONLY'); @@ -258,7 +260,7 @@ mod SequencerUptimeFeed { ref self: ContractState, initial_status: u128, owner_address: ContractAddress ) { self.ownable.initializer(owner_address); - self.access_control.initializer(); + self.access_control.initializer(true); let round_id = 1_u128; let timestamp = starknet::info::get_block_timestamp(); let from_address = EthAddress { diff --git a/contracts/src/libraries.cairo b/contracts/src/libraries.cairo index fbf6911cd..b40c45682 100644 --- a/contracts/src/libraries.cairo +++ b/contracts/src/libraries.cairo @@ -1,6 +1,6 @@ mod access_control; mod token; -mod upgradeable; +mod upgrades; mod mocks; mod type_and_version; mod enumerable_set; diff --git a/contracts/src/libraries/access_control.cairo b/contracts/src/libraries/access_control.cairo index b0a910d55..2d9e248eb 100644 --- a/contracts/src/libraries/access_control.cairo +++ b/contracts/src/libraries/access_control.cairo @@ -14,6 +14,7 @@ trait IAccessController { mod AccessControlComponent { use starknet::ContractAddress; use starknet::class_hash::ClassHash; + use starknet::storage::Map; use zeroable::Zeroable; use openzeppelin::access::ownable::OwnableComponent; @@ -23,7 +24,7 @@ mod AccessControlComponent { #[storage] struct Storage { _check_enabled: bool, - _access_list: LegacyMap, + _access_list: Map, } #[event] @@ -136,8 +137,8 @@ mod AccessControlComponent { impl Ownable: OwnableComponent::HasComponent, +Drop, > of InternalTrait { - fn initializer(ref self: ComponentState) { - self._check_enabled.write(true); + fn initializer(ref self: ComponentState, check_enabled: bool) { + self._check_enabled.write(check_enabled); self.emit(Event::AccessControlEnabled(AccessControlEnabled {})); } diff --git a/contracts/src/libraries/enumerable_set.cairo b/contracts/src/libraries/enumerable_set.cairo index 791b35606..5436d6065 100644 --- a/contracts/src/libraries/enumerable_set.cairo +++ b/contracts/src/libraries/enumerable_set.cairo @@ -1,6 +1,13 @@ #[starknet::component] mod EnumerableSetComponent { use core::array::ArrayTrait; + use starknet::{ + StorageAddress, + storage::{ + Map, StoragePointerReadAccess, StoragePointerWriteAccess, StorageMapReadAccess, + StorageMapWriteAccess, StoragePathEntry + } + }; // set is 1-indexed, not 0-indexed #[storage] @@ -8,14 +15,15 @@ mod EnumerableSetComponent { // access index by value // set_id -> item_value -> item_index // note: item_index is +1 because 0 means item is not in set - pub _indexes: LegacyMap::<(u256, u256), u256>, + pub _indexes: Map>, // access value by index - // set_id -> item_id -> item_value + // set_id -> item_index -> item_value // note: item_index is +1 because 0 means item is not in set - // note: _values.read(set_id, item_id) == 0, is only valid iff item_id <= _length.read(set_id) - pub _values: LegacyMap::<(u256, u256), u256>, + // note: _values.read(set_id, item_id) == 0, is only valid iff item_id <= + // _length.read(set_id) + pub _values: Map>, // set_id -> size of set - pub _length: LegacyMap + pub _length: Map } #[event] @@ -27,13 +35,13 @@ mod EnumerableSetComponent { pub impl InternalImpl< TContractState, +HasComponent > of InternalTrait { - fn add(ref self: ComponentState, set_id: u256, value: u256) -> bool { + fn add(ref self: ComponentState, set_id: felt252, value: felt252) -> bool { if !self.contains(set_id, value) { // The value is stored at _length-1, but we add 1 to all indexes let index = self._length.read(set_id) + 1; - self._indexes.write((set_id, value), index); - self._values.write((set_id, index), value); - self._length.write(set_id, index); + self._indexes.entry(set_id).entry(value).write(index); + self._values.entry(set_id).entry(index).write(value); + self._length.entry(set_id).write(index); true } else { false @@ -42,52 +50,54 @@ mod EnumerableSetComponent { // swap target value with the last value in the set fn remove( - ref self: ComponentState, set_id: u256, target_value: u256 + ref self: ComponentState, set_id: felt252, target_value: felt252 ) -> bool { - let target_index = self._indexes.read((set_id, target_value)); + let target_index = self._indexes.entry(set_id).entry(target_value).read(); if target_index == 0 { false } else { - let last_index = self._length.read(set_id); - let last_value = self._values.read((set_id, last_index)); + let last_index = self._length.entry(set_id).read(); + let last_value = self._values.entry(set_id).entry(last_index).read(); // if we are NOT trying to remove the last element // update the last element mappings if last_index != target_index { - self._indexes.write((set_id, last_value), target_index); - self._values.write((set_id, target_index), last_value); + self._indexes.entry(set_id).entry(last_value).write(target_index); + self._values.entry(set_id).entry(target_index).write(last_value); } - // if we are removing the last element both target value and last_index - // refer to the same item. - self._indexes.write((set_id, target_value), 0); - self._values.write((set_id, last_index), 0); + // if we are removing the last element both target value and last_index + // refer to the same item. + self._indexes.entry(set_id).entry(target_value).write(0); + self._values.entry(set_id).entry(last_index).write(0); // decrement length of set by 1 - self._length.write(set_id, last_index - 1); + self._length.entry(set_id).write(last_index - 1); true } } - fn contains(self: @ComponentState, set_id: u256, value: u256) -> bool { - self._indexes.read((set_id, value)) != 0 + fn contains( + self: @ComponentState, set_id: felt252, value: felt252 + ) -> bool { + self._indexes.entry(set_id).entry(value).read() != 0 } - fn length(self: @ComponentState, set_id: u256) -> u256 { - self._length.read(set_id) + fn length(self: @ComponentState, set_id: felt252) -> usize { + self._length.entry(set_id).read() } - fn at(self: @ComponentState, set_id: u256, index: u256) -> u256 { + fn at(self: @ComponentState, set_id: felt252, index: usize) -> felt252 { assert(index != 0, 'set is 1-indexed'); - assert(index <= self._length.read(set_id), 'index out of bounds'); - self._values.read((set_id, index)) + assert(index <= self._length.entry(set_id).read(), 'index out of bounds'); + self._values.entry(set_id).entry(index).read() } - fn values(self: @ComponentState, set_id: u256) -> Array { + fn values(self: @ComponentState, set_id: felt252) -> Array { let len = self.length(set_id); - let mut result: Array = ArrayTrait::new(); + let mut result: Array = ArrayTrait::new(); let mut i = 1; while i <= len { diff --git a/contracts/src/libraries/mocks.cairo b/contracts/src/libraries/mocks.cairo index 5042203bc..8bdc83ac0 100644 --- a/contracts/src/libraries/mocks.cairo +++ b/contracts/src/libraries/mocks.cairo @@ -1,4 +1,5 @@ mod mock_upgradeable; +mod mock_owner_upgradeable; mod mock_non_upgradeable; mod mock_multisig_target; mod mock_enumerable_set; diff --git a/contracts/src/libraries/mocks/mock_enumerable_set.cairo b/contracts/src/libraries/mocks/mock_enumerable_set.cairo index e71c12cd6..51905a98b 100644 --- a/contracts/src/libraries/mocks/mock_enumerable_set.cairo +++ b/contracts/src/libraries/mocks/mock_enumerable_set.cairo @@ -1,11 +1,11 @@ #[starknet::interface] trait IMockEnumerableSet { - fn add(ref self: TContractState, set_id: u256, value: u256) -> bool; - fn remove(ref self: TContractState, set_id: u256, target_value: u256) -> bool; - fn contains(self: @TContractState, set_id: u256, value: u256) -> bool; - fn length(self: @TContractState, set_id: u256) -> u256; - fn at(self: @TContractState, set_id: u256, index: u256) -> u256; - fn values(self: @TContractState, set_id: u256) -> Array; + fn add(ref self: TContractState, set_id: felt252, value: felt252) -> bool; + fn remove(ref self: TContractState, set_id: felt252, target_value: felt252) -> bool; + fn contains(self: @TContractState, set_id: felt252, value: felt252) -> bool; + fn length(self: @TContractState, set_id: felt252) -> usize; + fn at(self: @TContractState, set_id: felt252, index: usize) -> felt252; + fn values(self: @TContractState, set_id: felt252) -> Array; } #[starknet::contract] @@ -32,22 +32,22 @@ mod MockEnumerableSet { #[abi(embed_v0)] impl MockEnumerableSetImpl of super::IMockEnumerableSet { - fn add(ref self: ContractState, set_id: u256, value: u256) -> bool { + fn add(ref self: ContractState, set_id: felt252, value: felt252) -> bool { self.set.add(set_id, value) } - fn remove(ref self: ContractState, set_id: u256, target_value: u256) -> bool { + fn remove(ref self: ContractState, set_id: felt252, target_value: felt252) -> bool { self.set.remove(set_id, target_value) } - fn contains(self: @ContractState, set_id: u256, value: u256) -> bool { + fn contains(self: @ContractState, set_id: felt252, value: felt252) -> bool { self.set.contains(set_id, value) } - fn length(self: @ContractState, set_id: u256) -> u256 { + fn length(self: @ContractState, set_id: felt252) -> usize { self.set.length(set_id) } - fn at(self: @ContractState, set_id: u256, index: u256) -> u256 { + fn at(self: @ContractState, set_id: felt252, index: usize) -> felt252 { self.set.at(set_id, index) } - fn values(self: @ContractState, set_id: u256) -> Array { + fn values(self: @ContractState, set_id: felt252) -> Array { self.set.values(set_id) } } diff --git a/contracts/src/libraries/mocks/mock_owner_upgradeable.cairo b/contracts/src/libraries/mocks/mock_owner_upgradeable.cairo new file mode 100644 index 000000000..1f7a878a7 --- /dev/null +++ b/contracts/src/libraries/mocks/mock_owner_upgradeable.cairo @@ -0,0 +1,66 @@ +use starknet::class_hash::ClassHash; + +#[starknet::interface] +trait IFoo { + fn foo(self: @TContractState) -> bool; +} + +#[starknet::contract] +mod MockOwnerUpgradeable { + use starknet::class_hash::ClassHash; + use starknet::ContractAddress; + + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::UpgradeableComponent; + + use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: OwnerUpgradeableComponent, storage: owner_upgradeable, event: OwnerUpgradeableEvent + ); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnerUpgradeableImpl = + OwnerUpgradeableComponent::OwnerUpgradeableImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + owner_upgradeable: OwnerUpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnerUpgradeableEvent: OwnerUpgradeableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl FooImpl of super::IFoo { + fn foo(self: @ContractState) -> bool { + true + } + } +} diff --git a/contracts/src/libraries/mocks/mock_upgradeable.cairo b/contracts/src/libraries/mocks/mock_upgradeable.cairo index b83fa7f84..7fcb52b3d 100644 --- a/contracts/src/libraries/mocks/mock_upgradeable.cairo +++ b/contracts/src/libraries/mocks/mock_upgradeable.cairo @@ -10,7 +10,7 @@ trait IMockUpgradeable { mod MockUpgradeable { use starknet::class_hash::ClassHash; - use chainlink::libraries::upgradeable::Upgradeable; + use chainlink::libraries::upgrades::v1::upgradeable::Upgradeable; #[storage] struct Storage {} diff --git a/contracts/src/libraries/token/v1/erc677.cairo b/contracts/src/libraries/token/v1/erc677.cairo index 20fd6c54a..26ad289b5 100644 --- a/contracts/src/libraries/token/v1/erc677.cairo +++ b/contracts/src/libraries/token/v1/erc677.cairo @@ -12,7 +12,8 @@ trait IERC677Receiver { fn on_token_transfer( ref self: TContractState, sender: ContractAddress, value: u256, data: Array ); - // implements EIP-165, where function selectors are defined by Ethereum ABI using the ethereum function signatures + // implements EIP-165, where function selectors are defined by Ethereum ABI using the ethereum + // function signatures fn supports_interface(ref self: TContractState, interface_id: u32) -> bool; } diff --git a/contracts/src/libraries/upgrades.cairo b/contracts/src/libraries/upgrades.cairo new file mode 100644 index 000000000..f029fd6d3 --- /dev/null +++ b/contracts/src/libraries/upgrades.cairo @@ -0,0 +1,3 @@ +mod v1; +mod v2; +mod utils; diff --git a/contracts/src/libraries/upgrades/utils.cairo b/contracts/src/libraries/upgrades/utils.cairo new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/contracts/src/libraries/upgrades/utils.cairo @@ -0,0 +1 @@ + diff --git a/contracts/src/libraries/upgrades/v1.cairo b/contracts/src/libraries/upgrades/v1.cairo new file mode 100644 index 000000000..892aaa57e --- /dev/null +++ b/contracts/src/libraries/upgrades/v1.cairo @@ -0,0 +1 @@ +mod upgradeable; diff --git a/contracts/src/libraries/upgradeable.cairo b/contracts/src/libraries/upgrades/v1/upgradeable.cairo similarity index 79% rename from contracts/src/libraries/upgradeable.cairo rename to contracts/src/libraries/upgrades/v1/upgradeable.cairo index 6b9e6af73..1435bc469 100644 --- a/contracts/src/libraries/upgradeable.cairo +++ b/contracts/src/libraries/upgrades/v1/upgradeable.cairo @@ -1,6 +1,8 @@ use starknet::class_hash::ClassHash; -// TODO: drop for OZ upgradeable +// DEPRECATED: Kept around for the starknet multisig. +// Use OZ for internal upgradeability and v2/owner_upgradeable.cairo for public owner +// upgradeablility #[starknet::interface] trait IUpgradeable { @@ -25,11 +27,11 @@ mod Upgradeable { // this method assumes replace_class_syscall has a very low possibility of being deprecated // but if it does, we will either have upgraded the contract to be non-upgradeable by then - // because the starknet ecosystem has stabilized or we will be able to upgrade the contract to the proxy pattern - // #[internal] + // because the starknet ecosystem has stabilized or we will be able to upgrade the contract to + // the proxy pattern #[internal] fn upgrade(new_impl: ClassHash) { assert(!new_impl.is_zero(), 'Class hash cannot be zero'); replace_class_syscall(new_impl).unwrap_syscall(); - // TODO: Upgraded(new_impl); + // TODO: Upgraded(new_impl); } } diff --git a/contracts/src/libraries/upgrades/v2.cairo b/contracts/src/libraries/upgrades/v2.cairo new file mode 100644 index 000000000..3179fa0f9 --- /dev/null +++ b/contracts/src/libraries/upgrades/v2.cairo @@ -0,0 +1 @@ +mod owner_upgradeable; diff --git a/contracts/src/libraries/upgrades/v2/owner_upgradeable.cairo b/contracts/src/libraries/upgrades/v2/owner_upgradeable.cairo new file mode 100644 index 000000000..12fdf2a5f --- /dev/null +++ b/contracts/src/libraries/upgrades/v2/owner_upgradeable.cairo @@ -0,0 +1,41 @@ +#[starknet::component] +mod OwnerUpgradeableComponent { + use openzeppelin::{ + access::ownable::{ + OwnableComponent, OwnableComponent::InternalTrait as OwnableInternalTrait + }, + upgrades::{ + upgradeable::{ + UpgradeableComponent, + UpgradeableComponent::InternalTrait as UpgradeableInternalTrait + }, + interface::IUpgradeable, + }, + }; + use starknet::class_hash::ClassHash; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event {} + + #[embeddable_as(OwnerUpgradeableImpl)] + pub impl OwnerUpgradeable< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + impl Upgradeable: UpgradeableComponent::HasComponent, + +Drop + > of IUpgradeable> { + fn upgrade(ref self: ComponentState, new_class_hash: ClassHash) { + let mut ownable_component = get_dep_component_mut!(ref self, Ownable); + ownable_component.assert_only_owner(); + + let mut upgradeable_component = get_dep_component_mut!(ref self, Upgradeable); + upgradeable_component.upgrade(new_class_hash); + } + } +} + diff --git a/contracts/src/mcms.cairo b/contracts/src/mcms.cairo index f795914a3..97a9c485f 100644 --- a/contracts/src/mcms.cairo +++ b/contracts/src/mcms.cairo @@ -9,6 +9,9 @@ use starknet::{ use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; +use core::byte_array::ByteArrayTrait; +use core::traits::{Into, TryInto}; +use chainlink::utils::{keccak}; #[starknet::interface] trait IManyChainMultiSig { @@ -77,7 +80,8 @@ struct ExpiringRootAndOpCount { op_count: u64 } -// based of https://github.com/starkware-libs/cairo/blob/1b747da1ec7e43a6fd0c0a4cbce302616408bc72/corelib/src/starknet/eth_signature.cairo#L25 +// based off +// https://github.com/starkware-libs/cairo/blob/1b747da1ec7e43a6fd0c0a4cbce302616408bc72/corelib/src/starknet/eth_signature.cairo#L25 pub fn recover_eth_ecdsa(msg_hash: u256, signature: Signature) -> Result { if !is_signature_entry_valid::(signature.r) { return Result::Err('Signature out of range'); @@ -96,6 +100,7 @@ pub fn to_u256(address: EthAddress) -> u256 { temp.into() } + pub fn verify_merkle_proof(proof: Span, root: u256, leaf: u256) -> bool { let mut computed_hash = leaf; @@ -115,7 +120,8 @@ fn hash_pair(a: u256, b: u256) -> u256 { } else { (b, a) }; - BytesTrait::new_empty().encode(lower).encode(higher).keccak() + let encoded = BytesTrait::new_empty().encode(lower).encode(higher); + keccak(@encoded.into()) } fn hash_op(op: Op) -> u256 { @@ -128,7 +134,7 @@ fn hash_op(op: Op) -> u256 { .encode(op.to) .encode(op.selector) // dynamic byte offset of data array (relative to beginning of op struct) - // (note: domain seperator not part of the op struct) + // (note: domain separator not part of the op struct) .encode(0xc0) // length prefix .encode(op.data.len()); @@ -139,7 +145,7 @@ fn hash_op(op: Op) -> u256 { encoded_leaf = encoded_leaf.encode(*op.data.at(i)); i += 1; }; - encoded_leaf.keccak() + keccak(@encoded_leaf.into()) } // keccak256("MANY_CHAIN_MULTI_SIG_DOMAIN_SEPARATOR_OP") @@ -158,52 +164,15 @@ fn hash_metadata(metadata: RootMetadata) -> u256 { .encode(metadata.post_op_count) .encode(metadata.override_previous_root); - encoded_metadata.keccak() + keccak(@encoded_metadata.into()) } fn eip_191_message_hash(msg: u256) -> u256 { - let mut eip_191_msg: Bytes = BytesTrait::new_empty(); - - // '\x19Ethereum Signed Message:\n32' in byte array - let prefix = array![ - 0x19, - 0x45, - 0x74, - 0x68, - 0x65, - 0x72, - 0x65, - 0x75, - 0x6d, - 0x20, - 0x53, - 0x69, - 0x67, - 0x6e, - 0x65, - 0x64, - 0x20, - 0x4d, - 0x65, - 0x73, - 0x73, - 0x61, - 0x67, - 0x65, - 0x3a, - 0x0a, - 0x33, - 0x32 - ]; + let mut eip_191_msg: ByteArray = "\x19Ethereum Signed Message:\n32"; + eip_191_msg.append_word(msg.high.into(), 16); + eip_191_msg.append_word(msg.low.into(), 16); - let mut i = 0; - while i < prefix.len() { - eip_191_msg.append_u8(*prefix.at(i)); - i += 1; - }; - eip_191_msg.append_u256(msg); - - eip_191_msg.keccak() + keccak(@eip_191_msg) } #[starknet::contract] @@ -213,6 +182,7 @@ mod ManyChainMultiSig { use core::array::SpanTrait; use core::dict::Felt252Dict; use core::traits::PanicDestruct; + use chainlink::utils::{keccak}; use super::{ ExpiringRootAndOpCount, Config, Signer, RootMetadata, Op, Signature, recover_eth_ecdsa, to_u256, verify_merkle_proof, hash_op, hash_metadata, eip_191_message_hash, @@ -220,7 +190,12 @@ mod ManyChainMultiSig { }; use starknet::{ EthAddress, EthAddressZeroable, EthAddressIntoFelt252, ContractAddress, - call_contract_syscall + call_contract_syscall, + storage::{ + Map, StoragePointerReadAccess, StoragePointerWriteAccess, StorageMapReadAccess, + StorageMapWriteAccess, StoragePathEntry + }, + StorageAddress }; use openzeppelin::access::ownable::OwnableComponent; @@ -242,15 +217,15 @@ mod ManyChainMultiSig { #[substorage(v0)] ownable: OwnableComponent::Storage, // s_signers is used to easily validate the existence of the signer by its address. - s_signers: LegacyMap, - // begin s_config (defined in storage bc Config struct cannot support maps) + s_signers: Map, + // begin s_config (defined in storage bc Config struct cannot support maps) _s_config_signers_len: u8, - _s_config_signers: LegacyMap, + _s_config_signers: Map, // no _s_config_group_len because there are always 32 groups - _s_config_group_quorums: LegacyMap, - _s_config_group_parents: LegacyMap, + _s_config_group_quorums: Map, + _s_config_group_parents: Map, // end s_config - s_seen_signed_hashes: LegacyMap, + s_seen_signed_hashes: Map, s_expiring_root_and_op_count: ExpiringRootAndOpCount, s_root_metadata: RootMetadata } @@ -270,7 +245,7 @@ mod ManyChainMultiSig { to: ContractAddress, selector: felt252, data: Span, - // no value because value is sent through ERC20 tokens, even the native STRK token + // no "value" field because native STRK token is an ERC-20 token } #[derive(Drop, starknet::Event)] @@ -305,44 +280,42 @@ mod ManyChainMultiSig { // note: v is a boolean and not uint8 mut signatures: Array ) { - let encoded_root: Bytes = BytesTrait::new_empty().encode(root).encode(valid_until); + let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); - let msg_hash = eip_191_message_hash(encoded_root.keccak()); + let msg_hash = eip_191_message_hash(keccak(@encoded_root.into())); assert(!self.s_seen_signed_hashes.read(msg_hash), 'signed hash already seen'); let mut prev_address = EthAddressZeroable::zero(); let mut group_vote_counts: Felt252Dict = Default::default(); - while let Option::Some(signature) = signatures - .pop_front() { - let signer_address = match recover_eth_ecdsa(msg_hash, signature) { - Result::Ok(signer_address) => signer_address, - Result::Err(e) => panic_with_felt252(e), - }; - - assert( - to_u256(prev_address) < to_u256(signer_address.clone()), - 'signer address must increase' - ); - prev_address = signer_address; + while let Option::Some(signature) = signatures.pop_front() { + let signer_address = match recover_eth_ecdsa(msg_hash, signature) { + Result::Ok(signer_address) => signer_address, + Result::Err(e) => panic_with_felt252(e), + }; + assert( + to_u256(prev_address) < to_u256(signer_address.clone()), + 'signer address must increase' + ); + prev_address = signer_address; - let signer = self.get_signer_by_address(signer_address); - assert(signer.address == signer_address, 'invalid signer'); + let signer = self.get_signer_by_address(signer_address); + assert(signer.address == signer_address, 'invalid signer'); - let mut group = signer.group; - loop { - let counts = group_vote_counts.get(group.into()); - group_vote_counts.insert(group.into(), counts + 1); - if counts + 1 != self._s_config_group_quorums.read(group) { - break; - } - if group == 0 { - // reached root - break; - } - group = self._s_config_group_parents.read(group) - }; + let mut group = signer.group; + loop { + let counts = group_vote_counts.get(group.into()); + group_vote_counts.insert(group.into(), counts + 1); + if counts + 1 != self._s_config_group_quorums.read(group) { + break; + } + if group == 0 { + // reached root + break; + } + group = self._s_config_group_parents.read(group) }; + }; let root_group_quorum = self._s_config_group_quorums.read(0); assert(root_group_quorum > 0, 'root group missing quorum'); @@ -372,7 +345,8 @@ mod ManyChainMultiSig { let op_count = self.s_expiring_root_and_op_count.read().op_count; let current_root_metadata = self.s_root_metadata.read(); - // new root can be set only if the current op_count is the expected post op count (unless an override is requested) + // new root can be set only if the current op_count is the expected post op count + // (unless an override is requested) assert( op_count == current_root_metadata.post_op_count || current_root_metadata.override_previous_root, @@ -475,15 +449,14 @@ mod ManyChainMultiSig { let mut group_children_counts: Felt252Dict = Default::default(); let mut i = 0; - while i < signer_groups - .len() { - let group = *signer_groups.at(i); - assert(group < NUM_GROUPS, 'out of bounds group'); - // increment count for each group - group_children_counts - .insert(group.into(), group_children_counts.get(group.into()) + 1); - i += 1; - }; + while i < signer_groups.len() { + let group = *signer_groups.at(i); + assert(group < NUM_GROUPS, 'out of bounds group'); + // increment count for each group + group_children_counts + .insert(group.into(), group_children_counts.get(group.into()) + 1); + i += 1; + }; let mut j = 0; while j < NUM_GROUPS { @@ -509,7 +482,8 @@ mod ManyChainMultiSig { .insert( group_parent.into(), group_children_counts.get(group_parent.into()) + 1 ); - // the above line clobbers group_children_counts[0] in last iteration, don't use it after the loop ends + // the above line clobbers group_children_counts[0] in last iteration, don't use + // it after the loop ends } j += 1; }; @@ -525,7 +499,7 @@ mod ManyChainMultiSig { }; // reset s_signers self.s_signers.write(old_signer.address, empty_signer); - // reset _s_config_signers + // reset _s_config_signers self._s_config_signers.write(i.into(), empty_signer); i += 1; }; @@ -543,27 +517,25 @@ mod ManyChainMultiSig { let mut signers = ArrayTrait::::new(); let mut prev_signer_address = EthAddressZeroable::zero(); let mut i: u8 = 0; - while i - .into() < signer_addresses - .len() { - let signer_address = *signer_addresses.at(i.into()); - assert( - to_u256(prev_signer_address) < to_u256(signer_address), - 'signer addresses not sorted' - ); + while i.into() < signer_addresses.len() { + let signer_address = *signer_addresses.at(i.into()); + assert( + to_u256(prev_signer_address) < to_u256(signer_address), + 'signer addresses not sorted' + ); - let signer = Signer { - address: signer_address, index: i, group: *signer_groups.at(i.into()) - }; + let signer = Signer { + address: signer_address, index: i, group: *signer_groups.at(i.into()) + }; - self.s_signers.write(signer_address, signer); - self._s_config_signers.write(i.into(), signer); + self.s_signers.write(signer_address, signer); + self._s_config_signers.write(i.into(), signer); - signers.append(signer); + signers.append(signer); - prev_signer_address = signer_address; - i += 1; - }; + prev_signer_address = signer_address; + i += 1; + }; // length will always be less than MAX_NUM_SIGNERS so try_into will never panic self._s_config_signers_len.write(signer_addresses.len().try_into().unwrap()); diff --git a/contracts/src/multisig.cairo b/contracts/src/multisig.cairo index b1422df45..7e9d064cb 100644 --- a/contracts/src/multisig.cairo +++ b/contracts/src/multisig.cairo @@ -90,9 +90,10 @@ mod Multisig { use starknet::storage_read_syscall; use starknet::storage_write_syscall; use starknet::class_hash::ClassHash; + use starknet::storage::Map; use chainlink::libraries::type_and_version::ITypeAndVersion; - use chainlink::libraries::upgradeable::{Upgradeable, IUpgradeable}; + use chainlink::libraries::upgrades::v1::upgradeable::{Upgradeable, IUpgradeable}; #[event] #[derive(Drop, starknet::Event)] @@ -154,14 +155,14 @@ mod Multisig { #[storage] struct Storage { _threshold: usize, - _signers: LegacyMap, - _is_signer: LegacyMap, + _signers: Map, + _is_signer: Map, _signers_len: usize, _tx_valid_since: u128, _next_nonce: u128, - _transactions: LegacyMap, - _transaction_calldata: LegacyMap<(u128, usize), felt252>, - _is_confirmed: LegacyMap<(u128, ContractAddress), bool>, + _transactions: Map, + _transaction_calldata: Map<(u128, usize), felt252>, + _is_confirmed: Map<(u128, ContractAddress), bool>, } #[constructor] @@ -342,10 +343,11 @@ mod Multisig { ) .unwrap_syscall(); - // TODO: this shouldn't be necessary. call_contract_syscall returns a Span, which - // is a serialized result, but returning a Span results in an error: + // TODO: this shouldn't be necessary. call_contract_syscall returns a Span, + // which is a serialized result, but returning a Span results in an error: // - // Trait has no implementation in context: core::serde::Serde::> + // Trait has no implementation in context: + // core::serde::Serde::> // // Cairo docs also have an example that returns a Span: // https://github.com/starkware-libs/cairo/blob/fe425d0893ff93a936bb3e8bbbac771033074bdb/docs/reference/src/components/cairo/modules/language_constructs/pages/contracts.adoc#L226 diff --git a/contracts/src/ocr2/aggregator.cairo b/contracts/src/ocr2/aggregator.cairo index d494c81e8..3310c9b6f 100644 --- a/contracts/src/ocr2/aggregator.cairo +++ b/contracts/src/ocr2/aggregator.cairo @@ -170,41 +170,32 @@ impl SpanLegacyHash, impl TCopy: Copy> of Legacy #[starknet::contract] mod Aggregator { - use super::Round; - use super::{Transmission}; - use super::SpanLegacyHash; - use super::pow; + use super::{Round, Transmission, SpanLegacyHash, pow}; - use array::ArrayTrait; - use array::SpanTrait; + use array::{ArrayTrait, SpanTrait}; use box::BoxTrait; use hash::LegacyHash; - use integer::U128IntoFelt252; - use integer::u128s_from_felt252; - use integer::U128sFromFelt252Result; + use integer::{U128IntoFelt252, u128s_from_felt252, U128sFromFelt252Result}; use zeroable::Zeroable; - use traits::Into; - use traits::TryInto; + use traits::{Into, TryInto}; use option::OptionTrait; - use starknet::ContractAddress; - use starknet::get_caller_address; - use starknet::contract_address_const; - use starknet::StorageBaseAddress; - use starknet::SyscallResult; - use starknet::storage_read_syscall; - use starknet::storage_write_syscall; - use starknet::storage_address_from_base_and_offset; - use starknet::class_hash::ClassHash; + use starknet::{ + ContractAddress, get_caller_address, contract_address_const, StorageBaseAddress, + SyscallResult, storage_read_syscall, storage_write_syscall, + storage_address_from_base_and_offset, class_hash::ClassHash, storage::Map + }; use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::UpgradeableComponent; use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use chainlink::utils::split_felt; - use chainlink::libraries::access_control::{AccessControlComponent, IAccessController}; - use chainlink::libraries::access_control::AccessControlComponent::InternalTrait as AccessControlInternalTrait; - use chainlink::libraries::upgradeable::{Upgradeable, IUpgradeable}; - + use chainlink::libraries::access_control::{ + AccessControlComponent, IAccessController, + AccessControlComponent::InternalTrait as AccessControlInternalTrait + }; + use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent; use chainlink::libraries::access_control::{ IAccessControllerDispatcher, IAccessControllerDispatcherTrait }; @@ -212,6 +203,10 @@ mod Aggregator { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: OwnerUpgradeableComponent, storage: owner_upgradeable, event: OwnerUpgradeableEvent + ); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; @@ -222,6 +217,12 @@ mod Aggregator { AccessControlComponent::AccessControlImpl; impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnerUpgradeableImpl = + OwnerUpgradeableComponent::OwnerUpgradeableImpl; + const GIGA: u128 = 1000000000_u128; const MAX_ORACLES: u32 = 31_u32; @@ -233,6 +234,10 @@ mod Aggregator { OwnableEvent: OwnableComponent::Event, #[flat] AccessControlEvent: AccessControlComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnerUpgradeableEvent: OwnerUpgradeableComponent::Event, NewTransmission: NewTransmission, ConfigSet: ConfigSet, LinkTokenSet: LinkTokenSet, @@ -293,6 +298,10 @@ mod Aggregator { ownable: OwnableComponent::Storage, #[substorage(v0)] access_control: AccessControlComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + owner_upgradeable: OwnerUpgradeableComponent::Storage, /// Maximum number of faulty oracles _f: u8, _latest_epoch_and_round: u64, // (u32, u32) @@ -306,22 +315,20 @@ mod Aggregator { _latest_config_digest: felt252, // _oracles: Array, // NOTE: array can't be used in storage _oracles_len: usize, - _transmitters: LegacyMap, // - _signers: LegacyMap, // - _signers_list: LegacyMap, - _transmitters_list: LegacyMap, - _reward_from_aggregator_round_id: LegacyMap, // - _transmissions: LegacyMap, + _transmitters: Map, // + _signers: Map, // + _signers_list: Map, + _transmitters_list: Map, + _reward_from_aggregator_round_id: Map, // + _transmissions: Map, // link token _link_token: ContractAddress, // billing _billing_access_controller: ContractAddress, _billing: BillingConfig, // payee management - _payees: LegacyMap, // - _proposed_payees: LegacyMap< - ContractAddress, ContractAddress - > // + _payees: Map, // + _proposed_payees: Map // } #[generate_trait] @@ -398,7 +405,7 @@ mod Aggregator { description: felt252 ) { self.ownable.initializer(owner); - self.access_control.initializer(); + self.access_control.initializer(true); self._link_token.write(link); self._billing_access_controller.write(billing_access_controller); @@ -410,16 +417,6 @@ mod Aggregator { self._description.write(description); } - // --- Upgradeable --- - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_impl: ClassHash) { - self.ownable.assert_only_owner(); - Upgradeable::upgrade(new_impl) - } - } - // --- Validation --- // NOTE: Currently unimplemented: @@ -615,30 +612,29 @@ mod Aggregator { ) { let mut index = 0; let mut span = oracles.span(); - while let Option::Some(oracle) = span - .pop_front() { - // NOTE: index should start with 1 here because storage is 0-initialized. - // That way signers(pkey) => 0 indicates "not present" - // This is why we increment first, before using the index - index += 1; - - // check for duplicates - let existing_signer = self._signers.read(*oracle.signer); - assert(existing_signer == 0_usize, 'repeated signer'); - - let existing_transmitter = self._transmitters.read(*oracle.transmitter); - assert(existing_transmitter.index == 0_usize, 'repeated transmitter'); - - self._signers.write(*oracle.signer, index); - self._signers_list.write(index, *oracle.signer); - - self - ._transmitters - .write(*oracle.transmitter, Oracle { index, payment_juels: 0_u128 }); - self._transmitters_list.write(index, *oracle.transmitter); - - self._reward_from_aggregator_round_id.write(index, latest_round_id); - }; + while let Option::Some(oracle) = span.pop_front() { + // NOTE: index should start with 1 here because storage is 0-initialized. + // That way signers(pkey) => 0 indicates "not present" + // This is why we increment first, before using the index + index += 1; + + // check for duplicates + let existing_signer = self._signers.read(*oracle.signer); + assert(existing_signer == 0_usize, 'repeated signer'); + + let existing_transmitter = self._transmitters.read(*oracle.transmitter); + assert(existing_transmitter.index == 0_usize, 'repeated transmitter'); + + self._signers.write(*oracle.signer, index); + self._signers_list.write(index, *oracle.signer); + + self + ._transmitters + .write(*oracle.transmitter, Oracle { index, payment_juels: 0_u128 }); + self._transmitters_list.write(index, *oracle.transmitter); + + self._reward_from_aggregator_round_id.write(index, latest_round_id); + }; self._oracles_len.write(index); } } @@ -739,12 +735,12 @@ mod Aggregator { ); // Check all signatures are unique (we only saw each pubkey once) - // NOTE: This relies on protocol-level design constraints (MAX_ORACLES = 31, f = 10) which - // ensures we have enough bits to store a count for each oracle. Whenever the MAX_ORACLES - // is updated, the signed_count parameter should be reconsidered. + // NOTE: This relies on protocol-level design constraints (MAX_ORACLES = 31, f = 10) + // which ensures we have enough bits to store a count for each oracle. Whenever the + // MAX_ORACLES is updated, the signed_count parameter should be reconsidered. // - // Although 31 bits is enough, we use a u128 here for simplicity because BitAnd and BitOr - // operators are defined only for u128 and u256. + // Although 31 bits is enough, we use a u128 here for simplicity because BitAnd and + // BitOr operators are defined only for u128 and u256. assert(MAX_ORACLES == 31_u32, ''); self.verify_signatures(msg, ref signatures, 0_u128); @@ -827,22 +823,21 @@ mod Aggregator { mut signed_count: u128 ) { let mut span = signatures.span(); - while let Option::Some(signature) = span - .pop_front() { - let index = self._signers.read(*signature.public_key); - assert(index != 0_usize, 'invalid signer'); // 0 index == uninitialized - - let indexed_bit = pow(2_u128, index.into() - 1_u128); - let prev_signed_count = signed_count; - signed_count = signed_count | indexed_bit; - assert(prev_signed_count != signed_count, 'duplicate signer'); - - let is_valid = ecdsa::check_ecdsa_signature( - msg, *signature.public_key, *signature.r, *signature.s - ); + while let Option::Some(signature) = span.pop_front() { + let index = self._signers.read(*signature.public_key); + assert(index != 0_usize, 'invalid signer'); // 0 index == uninitialized + + let indexed_bit = pow(2_u128, index.into() - 1_u128); + let prev_signed_count = signed_count; + signed_count = signed_count | indexed_bit; + assert(prev_signed_count != signed_count, 'duplicate signer'); - assert(is_valid, ''); - }; + let is_valid = ecdsa::check_ecdsa_signature( + msg, *signature.public_key, *signature.r, *signature.s + ); + + assert(is_valid, ''); + }; } } @@ -1189,26 +1184,25 @@ mod Aggregator { impl PayeeManagementImpl of super::PayeeManagement { fn set_payees(ref self: ContractState, mut payees: Array) { self.ownable.assert_only_owner(); - while let Option::Some(payee) = payees - .pop_front() { - let current_payee = self._payees.read(payee.transmitter); - let is_unset = current_payee.is_zero(); - let is_same = current_payee == payee.payee; - assert(is_unset | is_same, 'payee already set'); - - self._payees.write(payee.transmitter, payee.payee); - - self - .emit( - Event::PayeeshipTransferred( - PayeeshipTransferred { - transmitter: payee.transmitter, - previous: current_payee, - current: payee.payee - } - ) - ); - } + while let Option::Some(payee) = payees.pop_front() { + let current_payee = self._payees.read(payee.transmitter); + let is_unset = current_payee.is_zero(); + let is_same = current_payee == payee.payee; + assert(is_unset | is_same, 'payee already set'); + + self._payees.write(payee.transmitter, payee.payee); + + self + .emit( + Event::PayeeshipTransferred( + PayeeshipTransferred { + transmitter: payee.transmitter, + previous: current_payee, + current: payee.payee + } + ) + ); + } } fn transfer_payeeship( diff --git a/contracts/src/ocr2/aggregator_proxy.cairo b/contracts/src/ocr2/aggregator_proxy.cairo index 9859c9ccd..8085bdcbf 100644 --- a/contracts/src/ocr2/aggregator_proxy.cairo +++ b/contracts/src/ocr2/aggregator_proxy.cairo @@ -24,40 +24,32 @@ trait IAggregatorProxyInternal { #[starknet::contract] mod AggregatorProxy { - use super::IAggregatorProxy; - use super::IAggregatorDispatcher; - use super::IAggregatorDispatcherTrait; + use super::{IAggregatorProxy, IAggregatorDispatcher, IAggregatorDispatcherTrait}; - use integer::u128s_from_felt252; + use integer::{u128s_from_felt252, Felt252TryIntoU128, U128IntoFelt252, U128sFromFelt252Result}; use option::OptionTrait; - use traits::Into; - use traits::TryInto; + use traits::{Into, TryInto}; use zeroable::Zeroable; - use starknet::ContractAddress; - use starknet::ContractAddressIntoFelt252; - use starknet::Felt252TryIntoContractAddress; - use integer::Felt252TryIntoU128; - use starknet::StorageBaseAddress; - use starknet::SyscallResult; - use integer::U128IntoFelt252; - use integer::U128sFromFelt252Result; - use starknet::storage_read_syscall; - use starknet::storage_write_syscall; - use starknet::storage_address_from_base_and_offset; - use starknet::class_hash::ClassHash; + use starknet::{ + ContractAddress, ContractAddressIntoFelt252, Felt252TryIntoContractAddress, + StorageBaseAddress, SyscallResult, storage_read_syscall, storage_write_syscall, + storage_address_from_base_and_offset, class_hash::ClassHash, storage::Map + }; use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::UpgradeableComponent; - use chainlink::ocr2::aggregator::IAggregator; - use chainlink::ocr2::aggregator::Round; - use chainlink::libraries::access_control::{AccessControlComponent, IAccessController}; - use chainlink::libraries::access_control::AccessControlComponent::InternalTrait as AccessControlInternalTrait; + use chainlink::ocr2::aggregator::{IAggregator, Round}; + use chainlink::libraries::access_control::{ + AccessControlComponent, IAccessController, + AccessControlComponent::InternalTrait as AccessControlInternalTrait + }; + use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent; use chainlink::utils::split_felt; use chainlink::libraries::type_and_version::{ ITypeAndVersion, ITypeAndVersionDispatcher, ITypeAndVersionDispatcherTrait }; - use chainlink::libraries::upgradeable::{Upgradeable, IUpgradeable}; const SHIFT: felt252 = 0x100000000000000000000000000000000; const MAX_ID: felt252 = 0xffffffffffffffffffffffffffffffff; @@ -70,6 +62,11 @@ mod AggregatorProxy { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: OwnerUpgradeableComponent, storage: owner_upgradeable, event: OwnerUpgradeableEvent + ); + #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; @@ -80,15 +77,25 @@ mod AggregatorProxy { AccessControlComponent::AccessControlImpl; impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnerUpgradeableImpl = + OwnerUpgradeableComponent::OwnerUpgradeableImpl; + #[storage] struct Storage { #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] access_control: AccessControlComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + owner_upgradeable: OwnerUpgradeableComponent::Storage, _current_phase: Phase, _proposed_aggregator: ContractAddress, - _phases: LegacyMap + _phases: Map } #[event] @@ -98,6 +105,10 @@ mod AggregatorProxy { OwnableEvent: OwnableComponent::Event, #[flat] AccessControlEvent: AccessControlComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnerUpgradeableEvent: OwnerUpgradeableComponent::Event } // TODO: refactor these events @@ -174,19 +185,10 @@ mod AggregatorProxy { #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress, address: ContractAddress) { self.ownable.initializer(owner); - self.access_control.initializer(); + self.access_control.initializer(false); self._set_aggregator(address); } - // -- Upgradeable -- - - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_impl: ClassHash) { - self.ownable.assert_only_owner(); - Upgradeable::upgrade(new_impl) - } - } // diff --git a/contracts/src/ocr2/mocks/mock_aggregator.cairo b/contracts/src/ocr2/mocks/mock_aggregator.cairo index eceb7d3c8..310fbc206 100644 --- a/contracts/src/ocr2/mocks/mock_aggregator.cairo +++ b/contracts/src/ocr2/mocks/mock_aggregator.cairo @@ -13,6 +13,7 @@ trait IMockAggregator { mod MockAggregator { use array::ArrayTrait; use starknet::contract_address_const; + use starknet::storage::Map; use traits::Into; use chainlink::ocr2::aggregator::IAggregator; @@ -25,7 +26,7 @@ mod MockAggregator { #[storage] struct Storage { - _transmissions: LegacyMap, + _transmissions: Map, _latest_aggregator_round_id: u128, _decimals: u8 } diff --git a/contracts/src/tests.cairo b/contracts/src/tests.cairo index fb561654c..07abf9ee2 100644 --- a/contracts/src/tests.cairo +++ b/contracts/src/tests.cairo @@ -7,9 +7,11 @@ mod test_ownable; mod test_erc677; mod test_link_token; mod test_upgradeable; +mod test_owner_upgradeable; mod test_access_controller; mod test_mock_aggregator; mod test_sequencer_uptime_feed; mod test_mcms; mod test_enumerable_set; mod test_rbac_timelock; + diff --git a/contracts/src/tests/test_access_controller.cairo b/contracts/src/tests/test_access_controller.cairo index a78715cf4..80dcc0e1d 100644 --- a/contracts/src/tests/test_access_controller.cairo +++ b/contracts/src/tests/test_access_controller.cairo @@ -1,29 +1,24 @@ -use starknet::ContractAddress; -use starknet::testing::set_caller_address; -use starknet::testing::set_contract_address; -use starknet::contract_address_const; -use starknet::class_hash::class_hash_const; -use starknet::class_hash::Felt252TryIntoClassHash; -use starknet::syscalls::deploy_syscall; +use starknet::{ + ContractAddress, testing::{set_caller_address, set_contract_address}, contract_address_const, + class_hash::{class_hash_const, Felt252TryIntoClassHash}, syscalls::deploy_syscall +}; use array::ArrayTrait; -use traits::Into; -use traits::TryInto; +use traits::{Into, TryInto}; use option::OptionTrait; use core::result::ResultTrait; use chainlink::access_control::access_controller::AccessController; -use chainlink::access_control::access_controller::AccessController::UpgradeableImpl; - +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent::OwnerUpgradeableImpl; use chainlink::libraries::access_control::{ IAccessController, IAccessControllerDispatcher, IAccessControllerDispatcherTrait }; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; - fn STATE() -> AccessController::ContractState { AccessController::contract_state_for_testing() } @@ -40,7 +35,7 @@ fn test_upgrade_not_owner() { let _ = setup(); let mut state = STATE(); - UpgradeableImpl::upgrade(ref state, class_hash_const::<2>()); + OwnerUpgradeableImpl::upgrade(ref state, class_hash_const::<2>()); } #[test] @@ -50,7 +45,9 @@ fn test_access_control() { let calldata = array![owner.into(), // owner ]; - let (accessControllerAddr, _) = declare("AccessController").unwrap().deploy(@calldata).unwrap(); + let contract = declare("AccessController").unwrap().contract_class(); + + let (accessControllerAddr, _) = contract.deploy(@calldata).unwrap(); should_implement_access_control(accessControllerAddr, owner); } diff --git a/contracts/src/tests/test_aggregator.cairo b/contracts/src/tests/test_aggregator.cairo index bed9dad22..4a5176a33 100644 --- a/contracts/src/tests/test_aggregator.cairo +++ b/contracts/src/tests/test_aggregator.cairo @@ -1,25 +1,19 @@ -use starknet::testing::set_caller_address; -use starknet::testing::set_contract_address; -use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::class_hash::class_hash_const; -use starknet::class_hash::Felt252TryIntoClassHash; -use starknet::syscalls::deploy_syscall; +use starknet::{ + ContractAddress, contract_address_const, testing::{set_caller_address, set_contract_address}, + class_hash::{class_hash_const, Felt252TryIntoClassHash}, syscalls::deploy_syscall +}; use array::ArrayTrait; use clone::Clone; -use traits::Into; -use traits::TryInto; +use traits::{Into, TryInto}; use option::OptionTrait; -use core::result::ResultTrait; +use core::{result::ResultTrait, panic_with_felt252}; -use chainlink::ocr2::aggregator::pow; -use chainlink::ocr2::aggregator::Aggregator; -use chainlink::ocr2::aggregator::Aggregator::{ - AggregatorImpl, BillingImpl, PayeeManagementImpl, UpgradeableImpl +use chainlink::ocr2::aggregator::{ + pow, Aggregator, + Aggregator::{BillingConfig, PayeeConfig, AggregatorImpl, BillingImpl, PayeeManagementImpl} }; -use chainlink::ocr2::aggregator::Aggregator::BillingConfig; -use chainlink::ocr2::aggregator::Aggregator::PayeeConfig; +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent::OwnerUpgradeableImpl; use chainlink::access_control::access_controller::AccessController; use chainlink::token::v2::link_token::LinkToken; use chainlink::tests::{ @@ -27,9 +21,9 @@ use chainlink::tests::{ test_link_token::link_deploy_args }; - use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; #[test] @@ -93,6 +87,7 @@ fn setup() -> ( let (billingAccessControllerAddr, _) = declare("AccessController") .unwrap() + .contract_class() .deploy(@calldata) .unwrap(); @@ -102,7 +97,11 @@ fn setup() -> ( let calldata = link_deploy_args(acc1, acc1); - let (linkTokenAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); + let (linkTokenAddr, _) = declare("LinkToken") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); let linkToken = ILinkTokenDispatcher { contract_address: linkTokenAddr }; @@ -124,7 +123,11 @@ fn test_ownable() { 123, // description ]; - let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); + let (aggregatorAddr, _) = declare("Aggregator") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); should_implement_ownable(aggregatorAddr, account); } @@ -143,19 +146,22 @@ fn test_access_control() { 123, // description ]; - let (aggregatorAddr, _) = declare("Aggregator").unwrap().deploy(@calldata).unwrap(); + let (aggregatorAddr, _) = declare("Aggregator") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); should_implement_access_control(aggregatorAddr, account); } - #[test] #[should_panic(expected: ('Caller is not the owner',))] fn test_upgrade_non_owner() { let _ = setup(); let mut state = STATE(); - UpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); + OwnerUpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); } // --- Billing tests --- diff --git a/contracts/src/tests/test_aggregator_proxy.cairo b/contracts/src/tests/test_aggregator_proxy.cairo index eb7e350bc..33d52e985 100644 --- a/contracts/src/tests/test_aggregator_proxy.cairo +++ b/contracts/src/tests/test_aggregator_proxy.cairo @@ -16,16 +16,21 @@ use chainlink::ocr2::mocks::mock_aggregator::{ }; use chainlink::ocr2::aggregator_proxy::AggregatorProxy; use chainlink::ocr2::aggregator_proxy::AggregatorProxy::{ - AggregatorProxyImpl, AggregatorProxyInternal, UpgradeableImpl + AggregatorProxyImpl, AggregatorProxyInternal }; -use chainlink::libraries::access_control::AccessControlComponent::AccessControlImpl; +use chainlink::libraries::access_control::{ + IAccessControllerDispatcher, IAccessControllerDispatcherTrait, + AccessControlComponent::AccessControlImpl +}; +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent::OwnerUpgradeableImpl; use chainlink::ocr2::aggregator::Round; use chainlink::utils::split_felt; use chainlink::tests::test_ownable::should_implement_ownable; use chainlink::tests::test_access_controller::should_implement_access_control; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; @@ -49,19 +54,20 @@ fn setup() -> ( let mut calldata = ArrayTrait::new(); calldata.append(8); // decimals = 8 - let contract_class = declare("MockAggregator").unwrap(); + let contract = declare("MockAggregator").unwrap().contract_class(); - let (mockAggregatorAddr1, _) = contract_class.deploy(@calldata).unwrap(); + let (mockAggregatorAddr1, _) = contract.deploy(@calldata).unwrap(); let mockAggregator1 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr1 }; // Deploy mock aggregator 2 // note: deployment address is deterministic based on deploy_syscall parameters - // so we need to change the decimals parameter to avoid an address conflict with mock aggregator 1 + // so we need to change the decimals parameter to avoid an address conflict with mock aggregator + // 1 let mut calldata2 = ArrayTrait::new(); calldata2.append(10); // decimals = 10 - let (mockAggregatorAddr2, _) = contract_class.deploy(@calldata).unwrap(); + let (mockAggregatorAddr2, _) = contract.deploy(@calldata).unwrap(); let mockAggregator2 = IMockAggregatorDispatcher { contract_address: mockAggregatorAddr2 }; @@ -75,7 +81,11 @@ fn test_ownable() { // Deploy aggregator proxy let calldata = array![account.into(), // owner = account mockAggregatorAddr.into(),]; - let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); + let (aggregatorProxyAddr, _) = declare("AggregatorProxy") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); should_implement_ownable(aggregatorProxyAddr, account); } @@ -87,7 +97,14 @@ fn test_access_control() { let calldata = array![account.into(), // owner = account mockAggregatorAddr.into(),]; - let (aggregatorProxyAddr, _) = declare("AggregatorProxy").unwrap().deploy(@calldata).unwrap(); + let (aggregatorProxyAddr, _) = declare("AggregatorProxy") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); + + // proxy by default disables the access check, so we re-enable for testing purposes + IAccessControllerDispatcher { contract_address: aggregatorProxyAddr }.enable_access_check(); should_implement_access_control(aggregatorProxyAddr, account); } @@ -97,7 +114,7 @@ fn test_access_control() { fn test_upgrade_non_owner() { let (_, _, _, _, _) = setup(); let mut state = STATE(); - UpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); + OwnerUpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); } fn test_query_latest_round_data() { @@ -118,13 +135,12 @@ fn test_query_latest_round_data() { assert(round.started_at == 9, 'started_at should be 9'); assert(round.updated_at == 8, 'updated_at should be 8'); - // latest_answer matches up with latest_round_data + // latest_answer matches up with latest_round_data let latest_answer = AggregatorProxyImpl::latest_answer(@state); assert(latest_answer == 10, '(latest) answer should be 10'); } #[test] -#[should_panic(expected: ('user does not have read access',))] fn test_query_latest_round_data_without_access() { let (owner, mockAggregatorAddr, mockAggregator, _, _) = setup(); let mut state = STATE(); @@ -140,7 +156,6 @@ fn test_query_latest_round_data_without_access() { } #[test] -#[should_panic(expected: ('user does not have read access',))] fn test_query_latest_answer_without_access() { let (owner, mockAggregatorAddr, mockAggregator, _, _) = setup(); let mut state = STATE(); diff --git a/contracts/src/tests/test_enumerable_set.cairo b/contracts/src/tests/test_enumerable_set.cairo index 1f4e008b8..8d36fa813 100644 --- a/contracts/src/tests/test_enumerable_set.cairo +++ b/contracts/src/tests/test_enumerable_set.cairo @@ -4,10 +4,10 @@ use chainlink::libraries::mocks::mock_enumerable_set::{ IMockEnumerableSetDispatcherTrait, IMockEnumerableSetSafeDispatcher, IMockEnumerableSetSafeDispatcherTrait }; -use snforge_std::{declare, ContractClassTrait}; +use snforge_std::{declare, ContractClassTrait, DeclareResultTrait}; -const MOCK_SET_ID: u256 = 'adfasdf'; -const OTHER_SET_ID: u256 = 'fakeasdf'; +const MOCK_SET_ID: felt252 = 'adfasdf'; +const OTHER_SET_ID: felt252 = 'fakeasdf'; fn expect_out_of_bounds>(result: Result>) { match result { @@ -31,7 +31,11 @@ fn setup_mock() -> ( ContractAddress, IMockEnumerableSetDispatcher, IMockEnumerableSetSafeDispatcher ) { let calldata = array![]; - let (mock_address, _) = declare("MockEnumerableSet").unwrap().deploy(@calldata).unwrap(); + let (mock_address, _) = declare("MockEnumerableSet") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); ( mock_address, diff --git a/contracts/src/tests/test_erc677.cairo b/contracts/src/tests/test_erc677.cairo index 128d8c15d..5c3db4d6f 100644 --- a/contracts/src/tests/test_erc677.cairo +++ b/contracts/src/tests/test_erc677.cairo @@ -17,7 +17,8 @@ use chainlink::libraries::token::v2::erc677::ERC677Component; use chainlink::libraries::token::v2::erc677::ERC677Component::ERC677Impl; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; #[starknet::interface] @@ -29,7 +30,8 @@ use chainlink::token::mock::valid_erc667_receiver::{ MockValidReceiver, MockValidReceiverDispatcher, MockValidReceiverDispatcherTrait }; -// Ignored tests are dependent on upgrading our version of cairo to include this PR https://github.com/starkware-libs/cairo/pull/2912/files +// Ignored tests are dependent on upgrading our version of cairo to include this PR +// https://github.com/starkware-libs/cairo/pull/2912/files fn setup() -> ContractAddress { let account: ContractAddress = contract_address_const::<1>(); @@ -41,7 +43,11 @@ fn setup() -> ContractAddress { fn setup_valid_receiver() -> (ContractAddress, MockValidReceiverDispatcher) { let calldata = ArrayTrait::new(); - let (address, _) = declare("ValidReceiver").unwrap().deploy(@calldata).unwrap(); + let (address, _) = declare("ValidReceiver") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); let contract = MockValidReceiverDispatcher { contract_address: address }; (address, contract) @@ -51,7 +57,11 @@ fn setup_valid_receiver() -> (ContractAddress, MockValidReceiverDispatcher) { fn setup_invalid_receiver() -> (ContractAddress, MockInvalidReceiverDispatcher) { let calldata = ArrayTrait::new(); - let (address, _) = declare("InvalidReceiver").unwrap().deploy(@calldata).unwrap(); + let (address, _) = declare("InvalidReceiver") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); let contract = MockInvalidReceiverDispatcher { contract_address: address }; (address, contract) @@ -62,7 +72,8 @@ type ComponentState = fn transfer_and_call(receiver: ContractAddress) { let data = ArrayTrait::::new(); - // have to send 0 because ERC20 is not initialized with starting supply when using this library by itself + // have to send 0 because ERC20 is not initialized with starting supply when using this library + // by itself let mut state: ComponentState = ERC677Component::component_state_for_testing(); state.transfer_and_call(receiver, u256 { high: 0, low: 0 }, data); } diff --git a/contracts/src/tests/test_link_token.cairo b/contracts/src/tests/test_link_token.cairo index 7e6f9a923..e03f5d7f6 100644 --- a/contracts/src/tests/test_link_token.cairo +++ b/contracts/src/tests/test_link_token.cairo @@ -9,18 +9,18 @@ use zeroable::Zeroable; use option::OptionTrait; use core::result::ResultTrait; -use chainlink::token::v2::link_token::{ - LinkToken, LinkToken::{MintableToken, UpgradeableImpl, Minter} -}; +use chainlink::token::v2::link_token::{LinkToken, LinkToken::{MintableToken, Minter}}; +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent::OwnerUpgradeableImpl; use openzeppelin::token::erc20::ERC20Component::{ERC20Impl, ERC20MetadataImpl}; use chainlink::tests::test_ownable::should_implement_ownable; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; -// only tests link token specific functionality +// only tests link token specific functionality // erc20 and erc677 functionality is already tested elsewhere fn STATE() -> LinkToken::ContractState { @@ -62,7 +62,7 @@ fn test_ownable() { account // owner ); - let (linkAddr, _) = declare("LinkToken").unwrap().deploy(@calldata).unwrap(); + let (linkAddr, _) = declare("LinkToken").unwrap().contract_class().deploy(@calldata).unwrap(); should_implement_ownable(linkAddr, account); } @@ -119,7 +119,7 @@ fn test_permissioned_mint_from_nonminter() { } #[test] -#[should_panic(expected: ('u256_sub Overflow',))] +#[should_panic(expected: ('ERC20: insufficient balance',))] fn test_permissioned_burn_from_minter() { let zero = 0; let sender = setup(); @@ -168,7 +168,7 @@ fn test_upgrade_non_owner() { ref state, 0, 0, 0, 0, Zeroable::zero(), sender, contract_address_const::<111>(), 0 ); - UpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); + OwnerUpgradeableImpl::upgrade(ref state, class_hash_const::<123>()); } #[test] @@ -195,6 +195,18 @@ fn test_set_minter_already() { Minter::set_minter(ref state, minter); } +#[test] +#[should_panic(expected: ('minter is 0',))] +fn test_set_minter_zero() { + let sender = setup(); + let mut state = STATE(); + + let minter = contract_address_const::<111>(); + LinkToken::constructor(ref state, 0, 0, 0, 0, Zeroable::zero(), minter, sender, 0); + + Minter::set_minter(ref state, Zeroable::zero()); +} + #[test] fn test_set_minter_success() { let sender = setup(); diff --git a/contracts/src/tests/test_mcms/test_execute.cairo b/contracts/src/tests/test_mcms/test_execute.cairo index e4c588867..8e82fc079 100644 --- a/contracts/src/tests/test_mcms/test_execute.cairo +++ b/contracts/src/tests/test_mcms/test_execute.cairo @@ -21,13 +21,13 @@ fn test_success() { mut spy, mcms_address, mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -100,16 +100,16 @@ fn test_success() { #[feature("safe_dispatcher")] fn test_no_more_ops_to_execute() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -144,16 +144,16 @@ fn test_no_more_ops_to_execute() { #[feature("safe_dispatcher")] fn test_wrong_chain_id() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -184,16 +184,16 @@ fn test_wrong_chain_id() { #[feature("safe_dispatcher")] fn test_wrong_multisig_address() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -225,16 +225,16 @@ fn test_wrong_multisig_address() { #[feature("safe_dispatcher")] fn test_root_expired() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -265,16 +265,16 @@ fn test_root_expired() { #[feature("safe_dispatcher")] fn test_wrong_nonce() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -305,23 +305,23 @@ fn test_wrong_nonce() { #[feature("safe_dispatcher")] fn test_proof_verification_failed() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, ops, - ops_proof + _ ) = setup_mcms_deploy_set_config_and_set_root(); diff --git a/contracts/src/tests/test_mcms/test_set_config.cairo b/contracts/src/tests/test_mcms/test_set_config.cairo index b1039aabd..cabb618d3 100644 --- a/contracts/src/tests/test_mcms/test_set_config.cairo +++ b/contracts/src/tests/test_mcms/test_set_config.cairo @@ -5,11 +5,7 @@ use starknet::{ }; use chainlink::mcms::{ ExpiringRootAndOpCount, RootMetadata, Config, Signer, ManyChainMultiSig, - ManyChainMultiSig::{ - InternalFunctionsTrait, contract_state_for_testing, s_signersContractMemberStateTrait, - s_expiring_root_and_op_countContractMemberStateTrait, - s_root_metadataContractMemberStateTrait - }, + ManyChainMultiSig::{InternalFunctionsTrait, contract_state_for_testing}, IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, @@ -60,7 +56,7 @@ fn test_not_owner() { #[test] #[feature("safe_dispatcher")] fn test_set_config_out_of_bound_signers() { - // 1. test if len(signer_address) = 0 => revert + // 1. test if len(signer_address) = 0 => revert let (_, _, mcms_safe) = setup_mcms_deploy(); let signer_addresses = array![]; @@ -407,16 +403,17 @@ fn test_set_config_signer_addresses_not_sorted() { } // test success, root not cleared, event emitted -// 12. successful => test without clearing root. test the state of storage variables and that event was emitted +// 12. successful => test without clearing root. test the state of storage variables and that event +// was emitted // // ┌──────┐ // ┌─►│2-of-2│ -// │ └──────┘ -// │ ▲ -// │ │ -// ┌──┴───┐ ┌──┴───┐ -// signer 1 signer 2 -// └──────┘ └──────┘ +// │ └──────┘ +// │ ▲ +// │ │ +// ┌──┴───┐ ┌──┴───┐ +// signer 1 signer 2 +// └──────┘ └──────┘ #[test] fn test_set_config_success_dont_clear_root() { let signer_address_1: EthAddress = (0x141).try_into().unwrap(); @@ -536,7 +533,7 @@ fn test_set_config_success_dont_clear_root() { } -// test that the config was reset +// test that the config was reset #[test] fn test_set_config_success_and_clear_root() { // mock the contract state diff --git a/contracts/src/tests/test_mcms/test_set_root.cairo b/contracts/src/tests/test_mcms/test_set_root.cairo index 3cce4db88..5e7c49202 100644 --- a/contracts/src/tests/test_mcms/test_set_root.cairo +++ b/contracts/src/tests/test_mcms/test_set_root.cairo @@ -15,11 +15,7 @@ use starknet::{ use chainlink::mcms::{ recover_eth_ecdsa, hash_pair, hash_op, hash_metadata, ExpiringRootAndOpCount, RootMetadata, Config, Signer, eip_191_message_hash, ManyChainMultiSig, Op, - ManyChainMultiSig::{ - NewRoot, InternalFunctionsTrait, contract_state_for_testing, - s_signersContractMemberStateTrait, s_expiring_root_and_op_countContractMemberStateTrait, - s_root_metadataContractMemberStateTrait - }, + ManyChainMultiSig::{NewRoot, InternalFunctionsTrait, contract_state_for_testing,}, IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, @@ -28,7 +24,7 @@ use chainlink::tests::test_mcms::utils::{ insecure_sign, setup_signers, SignerMetadata, setup_mcms_deploy_and_set_config_2_of_2, setup_mcms_deploy_set_config_and_set_root, set_root_args, merkle_root }; - +use chainlink::utils::{keccak}; use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, stop_cheat_caller_address, stop_cheat_caller_address_global, start_cheat_chain_id_global, @@ -37,7 +33,7 @@ use snforge_std::{ start_cheat_chain_id, cheatcodes::{events::{EventSpy}}, start_cheat_block_timestamp_global, start_cheat_block_timestamp, start_cheat_account_contract_address_global, - start_cheat_account_contract_address + start_cheat_account_contract_address, DeclareResultTrait }; // sets up root but with wrong multisig address in metadata @@ -60,8 +56,7 @@ fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( Array, Span>, ) { - let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = - setup_signers(); + let (signer_address_1, private_key_1, signer_address_2, private_key_2, _) = setup_signers(); let ( mut spy, @@ -80,7 +75,7 @@ fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( ); let calldata = ArrayTrait::new(); - let mock_target_contract = declare("MockMultisigTarget").unwrap(); + let mock_target_contract = declare("MockMultisigTarget").unwrap().contract_class(); let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); // mock chain id & timestamp @@ -132,7 +127,7 @@ fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( let (root, metadata_proof, ops_proof) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); - let message_hash = eip_191_message_hash(encoded_root.keccak()); + let message_hash = eip_191_message_hash(keccak(@encoded_root.into())); let (r_1, s_1, y_parity_1) = insecure_sign(message_hash, private_key_1); let (r_2, s_2, y_parity_2) = insecure_sign(message_hash, private_key_2); @@ -171,26 +166,39 @@ fn setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG() -> ( ) } +#[test] +fn test_eip_191_message_hash() { + let mut msg: ByteArray = Default::default(); + msg.append_byte(0x11); + + let msg_hash = eip_191_message_hash(keccak(@msg)); + + let expected_msg_hash: u256 = + 0x01f83f8506ac29b9cbd12376cf298e9b02961776e960e7f768933386c75f5d02; + + assert(msg_hash == expected_msg_hash, 'invalid msg hash') +} + #[test] fn test_set_root_success() { let ( mut spy, mcms_address, mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -218,27 +226,28 @@ fn test_set_root_success() { ] ); } + #[test] #[feature("safe_dispatcher")] fn test_set_root_hash_seen() { let ( - mut spy, - mcms_address, + _, + _, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -258,23 +267,23 @@ fn test_set_root_hash_seen() { #[feature("safe_dispatcher")] fn test_set_root_signatures_wrong_order() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -295,23 +304,7 @@ fn test_set_root_signatures_wrong_order() { #[feature("safe_dispatcher")] fn test_set_root_signatures_invalid_signer() { let ( - mut spy, - mcms_address, - mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, - root, - valid_until, - metadata, - metadata_proof, - signatures, - ops, - ops_proof + _, _, _, safe_mcms, _, _, _, _, _, _, root, valid_until, metadata, metadata_proof, _, _, _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -351,23 +344,23 @@ fn test_set_root_signatures_invalid_signer() { #[feature("safe_dispatcher")] fn test_insufficient_signers() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -388,23 +381,23 @@ fn test_insufficient_signers() { #[feature("safe_dispatcher")] fn test_valid_until_expired() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -425,23 +418,23 @@ fn test_valid_until_expired() { #[feature("safe_dispatcher")] fn test_invalid_metadata_proof() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -462,23 +455,23 @@ fn test_invalid_metadata_proof() { #[feature("safe_dispatcher")] fn test_invalid_chain_id() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -498,23 +491,23 @@ fn test_invalid_chain_id() { #[feature("safe_dispatcher")] fn test_invalid_multisig_address() { let ( - mut spy, - mcms_address, - mcms, + _, + _, + _, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root_WRONG_MULTISIG(); @@ -532,23 +525,23 @@ fn test_invalid_multisig_address() { #[feature("safe_dispatcher")] fn test_pending_ops_remain() { let ( - mut spy, + _, mcms_address, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, metadata_proof, signatures, - ops, - ops_proof + _, + _ ) = setup_mcms_deploy_set_config_and_set_root(); @@ -556,8 +549,7 @@ fn test_pending_ops_remain() { mcms.set_root(root, valid_until, metadata, metadata_proof, signatures.clone()); // sign a different set of operations with same signers - let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = - setup_signers(); + let (_, _, _, _, signer_metadata) = setup_signers(); let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( mcms_address, contract_address_const::<123123>(), signer_metadata, 0, 2 ); @@ -577,30 +569,11 @@ fn test_pending_ops_remain() { #[test] #[feature("safe_dispatcher")] fn test_wrong_pre_op_count() { - let ( - mut spy, - mcms_address, - mcms, - safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, - root, - valid_until, - metadata, - metadata_proof, - signatures, - ops, - _ - ) = + let (_, mcms_address, _, safe_mcms, _, _, _, _, _, _, _, _, _, _, _, _, _) = setup_mcms_deploy_set_config_and_set_root(); // sign a different set of operations with same signers - let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = - setup_signers(); + let (_, _, _, _, signer_metadata) = setup_signers(); let wrong_pre_op_count = 1; let (root, valid_until, metadata, metadata_proof, signatures, _, _) = set_root_args( mcms_address, @@ -626,16 +599,16 @@ fn test_wrong_pre_op_count() { #[feature("safe_dispatcher")] fn test_wrong_post_ops_count() { let ( - mut spy, + _, mcms_address, mcms, safe_mcms, - config, - signer_addresses, - signer_groups, - group_quorums, - group_parents, - clear_root, + _, + _, + _, + _, + _, + _, root, valid_until, metadata, @@ -650,8 +623,7 @@ fn test_wrong_post_ops_count() { // sign a different set of operations with same signers - let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = - setup_signers(); + let (_, _, _, _, signer_metadata) = setup_signers(); let op1 = *ops.at(0); let op1_proof = *ops_proof.at(0); @@ -662,7 +634,7 @@ fn test_wrong_post_ops_count() { mcms.execute(op1, op1_proof); mcms.execute(op2, op2_proof); - let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( + let (root, valid_until, metadata, metadata_proof, signatures, _, _) = set_root_args( mcms_address, contract_address_const::<123123>(), signer_metadata, diff --git a/contracts/src/tests/test_mcms/utils.cairo b/contracts/src/tests/test_mcms/utils.cairo index 10854aa4a..aaed78d14 100644 --- a/contracts/src/tests/test_mcms/utils.cairo +++ b/contracts/src/tests/test_mcms/utils.cairo @@ -1,12 +1,13 @@ -use core::integer::{u512, u256_wide_mul}; +use core::num::traits::WideMul; +use core::integer::{u512, u256}; use alexandria_bytes::{Bytes, BytesTrait}; use alexandria_encoding::sol_abi::sol_bytes::SolBytesTrait; use alexandria_encoding::sol_abi::encode::SolAbiEncodeTrait; use alexandria_math::u512_arithmetics; use core::math::{u256_mul_mod_n, u256_div_mod_n}; use core::zeroable::{IsZeroResult, NonZero, zero_based}; -use alexandria_math::u512_arithmetics::{u512_add, u512_sub, U512Intou256X2,}; - +use alexandria_math::u512_arithmetics::{u512_add, u512_sub, U512Intou256X2}; +use chainlink::utils::{keccak}; use starknet::{ ContractAddress, EthAddress, EthAddressIntoFelt252, EthAddressZeroable, contract_address_const, eth_signature::public_key_point_to_eth_address, @@ -19,19 +20,15 @@ use starknet::{ use chainlink::mcms::{ recover_eth_ecdsa, hash_pair, hash_op, hash_metadata, ExpiringRootAndOpCount, RootMetadata, Config, Signer, eip_191_message_hash, ManyChainMultiSig, Op, - ManyChainMultiSig::{ - NewRoot, InternalFunctionsTrait, contract_state_for_testing, - s_signersContractMemberStateTrait, s_expiring_root_and_op_countContractMemberStateTrait, - s_root_metadataContractMemberStateTrait - }, + ManyChainMultiSig::{NewRoot, InternalFunctionsTrait, contract_state_for_testing}, IManyChainMultiSigDispatcher, IManyChainMultiSigDispatcherTrait, IManyChainMultiSigSafeDispatcher, IManyChainMultiSigSafeDispatcherTrait, IManyChainMultiSig, ManyChainMultiSig::{MAX_NUM_SIGNERS}, }; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, - stop_cheat_caller_address, stop_cheat_caller_address_global, spy_events, - EventSpyAssertionsTrait, // Add for assertions on the EventSpy + DeclareResultTrait, declare, ContractClassTrait, start_cheat_caller_address_global, + start_cheat_caller_address, stop_cheat_caller_address, stop_cheat_caller_address_global, + spy_events, EventSpyAssertionsTrait, // Add for assertions on the EventSpy test_address, // the contract being tested, start_cheat_chain_id, start_cheat_chain_id_global, start_cheat_block_timestamp_global, cheatcodes::{events::{EventSpy}} @@ -43,9 +40,9 @@ use snforge_std::{ // returns a length 32 array // give (index, value) tuples to fill array with -// +// // ex: fill_array(array!(0, 1)) will fill the 0th index with value 1 -// +// // assumes that values array is sorted in ascending order of the index fn fill_array(mut values: Array<(u32, u8)>) -> Array { let mut result: Array = ArrayTrait::new(); @@ -142,14 +139,14 @@ impl U512PartialOrd of PartialOrd { // therefore this method is only meant to be used for tests // arg z: message hash, arg e: private key fn insecure_sign(z: u256, e: u256) -> (u256, u256, bool) { - let z_u512: u512 = u256_wide_mul(z, (0x1).into()); + let z_u512: u512 = z.wide_mul((0x1).into()); // order of the finite group - let N = Secp256k1Impl::get_curve_size().try_into().unwrap(); - let n_u512: u512 = u256_wide_mul(N, (0x1).into()); + let N: u256 = Secp256k1Impl::get_curve_size().try_into().unwrap(); + let n_u512: u512 = N.wide_mul((0x1).into()); // "random" number k would be generated by a pseudo-random number generator - // in secure applications it's important that k is random, or else the private key can + // in secure applications it's important that k is random, or else the private key can // be derived from r and s let k = 777; @@ -160,7 +157,7 @@ fn insecure_sign(z: u256, e: u256) -> (u256, u256, bool) { // calculate s = ( z + r*e ) / k (finite element operations) // where product = r*e and sum = z + r*re let product = u256_mul_mod_n(r_x, e, N.try_into().unwrap()); - let product_u512: u512 = u256_wide_mul(product, (0x1).into()); + let product_u512: u512 = product.wide_mul((0x1).into()); // sum = z + product (finite element operations) // avoid u256 overflow by casting to u512 @@ -199,17 +196,15 @@ fn merkle_root(leafs: Array) -> (u256, Span, Span>) { let proof2 = array![*level.at(0), metadata]; // level length is always even (except when it's 1) - while level - .len() > 1 { - let mut i = 0; - let mut new_level: Array = ArrayTrait::new(); - while i < level - .len() { - new_level.append(hash_pair(*(level.at(i)), *level.at(i + 1))); - i += 2 - }; - level = new_level.span(); + while level.len() > 1 { + let mut i = 0; + let mut new_level: Array = ArrayTrait::new(); + while i < level.len() { + new_level.append(hash_pair(*(level.at(i)), *level.at(i + 1))); + i += 2 }; + level = new_level.span(); + }; let mut metadata_proof = *level.at(0); @@ -270,21 +265,20 @@ fn set_root_args( let (root, metadata_proof, ops_proof) = merkle_root(array![op1_hash, op2_hash, metadata_hash]); let encoded_root = BytesTrait::new_empty().encode(root).encode(valid_until); - let message_hash = eip_191_message_hash(encoded_root.keccak()); + let message_hash = eip_191_message_hash(keccak(@encoded_root.into())); let mut signatures: Array = ArrayTrait::new(); - while let Option::Some(signer_metadata) = signers_metadata - .pop_front() { - let (r, s, y_parity) = insecure_sign(message_hash, signer_metadata.private_key); - let signature = Signature { r: r, s: s, y_parity: y_parity }; - let address = recover_eth_ecdsa(message_hash, signature).unwrap(); + while let Option::Some(signer_metadata) = signers_metadata.pop_front() { + let (r, s, y_parity) = insecure_sign(message_hash, signer_metadata.private_key); + let signature = Signature { r: r, s: s, y_parity: y_parity }; + let address = recover_eth_ecdsa(message_hash, signature).unwrap(); - // sanity check - assert(address == signer_metadata.address, 'signer not equal'); + // sanity check + assert(address == signer_metadata.address, 'signer not equal'); - signatures.append(signature); - }; + signatures.append(signature); + }; let ops = array![op1.clone(), op2.clone()]; @@ -303,7 +297,11 @@ fn setup_mcms_deploy() -> ( let calldata = array![owner.into()]; - let (mcms_address, _) = declare("ManyChainMultiSig").unwrap().deploy(@calldata).unwrap(); + let (mcms_address, _) = declare("ManyChainMultiSig") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); ( mcms_address, @@ -382,8 +380,7 @@ fn setup_mcms_deploy_set_config_and_set_root() -> ( Array, Span> ) { - let (signer_address_1, private_key_1, signer_address_2, private_key_2, signer_metadata) = - setup_signers(); + let (signer_address_1, _, signer_address_2, _, signer_metadata) = setup_signers(); let ( mut spy, @@ -402,7 +399,7 @@ fn setup_mcms_deploy_set_config_and_set_root() -> ( ); let calldata = ArrayTrait::new(); - let mock_target_contract = declare("MockMultisigTarget").unwrap(); + let mock_target_contract = declare("MockMultisigTarget").unwrap().contract_class(); let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); let (root, valid_until, metadata, metadata_proof, signatures, ops, ops_proof) = set_root_args( diff --git a/contracts/src/tests/test_multisig.cairo b/contracts/src/tests/test_multisig.cairo index 77eeb17b7..499eb7fe1 100644 --- a/contracts/src/tests/test_multisig.cairo +++ b/contracts/src/tests/test_multisig.cairo @@ -19,7 +19,7 @@ use chainlink::multisig::{IMultisigDispatcher}; use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, - stop_cheat_caller_address_global, cheat_caller_address, CheatSpan + stop_cheat_caller_address_global, cheat_caller_address, CheatSpan, DeclareResultTrait }; fn STATE() -> Multisig::ContractState { @@ -123,7 +123,7 @@ fn test_submit_transaction() { assert(transaction.calldata_len == sample_calldata().len(), 'should match calldata length'); assert(!transaction.executed, 'should not be executed'); assert(transaction.confirmations == 0, 'should not have confirmations'); -// TODO: compare calldata when loops are supported + // TODO: compare calldata when loops are supported } #[test] @@ -274,7 +274,11 @@ fn test_execute() { let calldata = ArrayTrait::new(); - let (test_address, _) = declare("MockMultisigTarget").unwrap().deploy(@calldata).unwrap(); + let (test_address, _) = declare("MockMultisigTarget") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); start_cheat_caller_address_global(signer1); let increment_calldata = array![42, 100]; @@ -331,7 +335,11 @@ fn test_execute_after_set_signers() { Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -363,7 +371,11 @@ fn test_execute_after_set_signers_and_threshold() { Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -395,7 +407,11 @@ fn test_execute_after_set_threshold() { Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -427,7 +443,11 @@ fn test_set_threshold() { Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; assert(multisig.get_threshold() == init_threshold, 'invalid init threshold'); @@ -451,16 +471,20 @@ fn test_recursive_set_threshold() { Serde::serialize(@signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; // Checks that the threshold was correctly initialized on deployment assert(multisig.get_threshold() == init_threshold, 'invalid init threshold'); - // Recursive call occurs here - this code proposes a transaction to the - // multisig contract that calls the set_threshold function on the multisig - // contract. + // Recursive call occurs here - this code proposes a transaction to the + // multisig contract that calls the set_threshold function on the multisig + // contract. let mut set_threshold_calldata = ArrayTrait::new(); Serde::serialize(@new_threshold, ref set_threshold_calldata); start_cheat_caller_address_global(s1); @@ -497,7 +521,11 @@ fn test_set_signers() { Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -531,7 +559,11 @@ fn test_recursive_set_signers() { Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -543,9 +575,9 @@ fn test_recursive_set_signers() { assert(*returned_signers.at(1) == s2, 'should match signer 2'); assert(multisig.get_threshold() == 2, 'wrong init threshold'); - // Recursive call occurs here - this code proposes a transaction to the - // multisig contract that calls the set_signers function on the multisig - // contract. + // Recursive call occurs here - this code proposes a transaction to the + // multisig contract that calls the set_signers function on the multisig + // contract. let mut set_signers_calldata = ArrayTrait::new(); Serde::serialize(@new_signers, ref set_signers_calldata); start_cheat_caller_address_global(s1); @@ -585,7 +617,11 @@ fn test_set_signers_and_threshold() { Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -623,7 +659,11 @@ fn test_recursive_set_signers_and_threshold() { Serde::serialize(@init_signers, ref deploy_calldata); Serde::serialize(@init_threshold, ref deploy_calldata); - let (multisig_address, _) = declare("Multisig").unwrap().deploy(@deploy_calldata).unwrap(); + let (multisig_address, _) = declare("Multisig") + .unwrap() + .contract_class() + .deploy(@deploy_calldata) + .unwrap(); // Gets a dispatcher (so we can call methods on the deployed contract) let multisig = IMultisigDispatcher { contract_address: multisig_address }; @@ -636,8 +676,8 @@ fn test_recursive_set_signers_and_threshold() { assert(*returned_signers.at(2) == s3, 'should match signer 3'); assert(multisig.get_threshold() == 3, 'wrong init threshold'); - // Recursive call occurs here - this code proposes a transaction to the - // multisig contract that calls the set_signers_and_threshold function + // Recursive call occurs here - this code proposes a transaction to the + // multisig contract that calls the set_signers_and_threshold function // on the multisig contract. let mut set_signers_and_threshold_calldata = ArrayTrait::new(); Serde::serialize(@new_signers, ref set_signers_and_threshold_calldata); diff --git a/contracts/src/tests/test_owner_upgradeable.cairo b/contracts/src/tests/test_owner_upgradeable.cairo new file mode 100644 index 000000000..629e960e8 --- /dev/null +++ b/contracts/src/tests/test_owner_upgradeable.cairo @@ -0,0 +1,95 @@ +use traits::Into; +use zeroable::Zeroable; + +use starknet::testing::set_caller_address; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::class_hash::class_hash_const; +use starknet::syscalls::deploy_syscall; + +use openzeppelin::upgrades::interface::{ + IUpgradeable, IUpgradeableDispatcher, IUpgradeableDispatcherTrait +}; + +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent::OwnerUpgradeableImpl; +use chainlink::libraries::upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent; +use chainlink::libraries::mocks::mock_owner_upgradeable::{ + MockOwnerUpgradeable, IFoo, IFooDispatcher, IFooDispatcherTrait +}; +use chainlink::libraries::mocks::mock_non_upgradeable::{ + MockNonUpgradeable, IMockNonUpgradeableDispatcher, IMockNonUpgradeableDispatcherTrait, + IMockNonUpgradeableDispatcherImpl +}; + +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait +}; + +fn setup() -> ContractAddress { + let account: ContractAddress = contract_address_const::<777>(); + start_cheat_caller_address_global(account); + account +} + +fn STATE() -> MockOwnerUpgradeable::ContractState { + MockOwnerUpgradeable::contract_state_for_testing() +} + +#[test] +fn test_upgrade_and_call() { + let account = setup(); + + let calldata = array![account.into()]; + + let (contractAddr, _) = declare("MockOwnerUpgradeable") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); + + let mockUpgradeable = IFooDispatcher { contract_address: contractAddr }; + assert(mockUpgradeable.foo() == true, 'should call foo'); + + let contract = declare("MockNonUpgradeable").unwrap().contract_class(); + + let mockUpgradeable = IUpgradeableDispatcher { contract_address: contractAddr }; + + mockUpgradeable.upgrade(*(contract.class_hash)); + + // now, contract should be different + let mockNonUpgradeable = IMockNonUpgradeableDispatcher { contract_address: contractAddr }; + assert(mockNonUpgradeable.bar() == true, 'should call bar'); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_upgrade_non_owner() { + let _ = setup(); + + let mut state = STATE(); + + OwnerUpgradeableImpl::upgrade(ref state, class_hash_const::<0>()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero',))] +fn test_upgrade_zero() { + let account = setup(); + + let calldata = array![account.into()]; + + let (contractAddr, _) = declare("MockOwnerUpgradeable") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); + + let mockUpgradeable = IFooDispatcher { contract_address: contractAddr }; + assert(mockUpgradeable.foo() == true, 'should call foo'); + + let mockUpgradeable = IUpgradeableDispatcher { contract_address: contractAddr }; + + mockUpgradeable.upgrade(Zeroable::zero()); +} + diff --git a/contracts/src/tests/test_rbac_timelock.cairo b/contracts/src/tests/test_rbac_timelock.cairo index dd29ccc62..ee4c784d9 100644 --- a/contracts/src/tests/test_rbac_timelock.cairo +++ b/contracts/src/tests/test_rbac_timelock.cairo @@ -24,7 +24,7 @@ use openzeppelin::{ use chainlink::tests::test_enumerable_set::{expect_out_of_bounds, expect_set_is_1_indexed}; use snforge_std::{ declare, ContractClassTrait, spy_events, EventSpyAssertionsTrait, - start_cheat_caller_address_global, start_cheat_block_timestamp_global + start_cheat_caller_address_global, start_cheat_block_timestamp_global, DeclareResultTrait }; fn deploy_args() -> ( @@ -41,7 +41,7 @@ fn deploy_args() -> ( fn setup_mock_target() -> (ContractAddress, IMockMultisigTargetDispatcher) { let calldata = ArrayTrait::new(); - let mock_target_contract = declare("MockMultisigTarget").unwrap(); + let mock_target_contract = declare("MockMultisigTarget").unwrap().contract_class(); let (target_address, _) = mock_target_contract.deploy(@calldata).unwrap(); (target_address, IMockMultisigTargetDispatcher { contract_address: target_address }) } @@ -61,7 +61,11 @@ fn setup_timelock() -> (ContractAddress, IRBACTimelockDispatcher, IRBACTimelockS Serde::serialize(@cancellers, ref calldata); Serde::serialize(@bypassers, ref calldata); - let (timelock_address, _) = declare("RBACTimelock").unwrap().deploy(@calldata).unwrap(); + let (timelock_address, _) = declare("RBACTimelock") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); ( timelock_address, diff --git a/contracts/src/tests/test_sequencer_uptime_feed.cairo b/contracts/src/tests/test_sequencer_uptime_feed.cairo index a7b44ff24..e99d3b4c4 100644 --- a/contracts/src/tests/test_sequencer_uptime_feed.cairo +++ b/contracts/src/tests/test_sequencer_uptime_feed.cairo @@ -30,7 +30,8 @@ use chainlink::emergency::sequencer_uptime_feed::{ }; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; @@ -52,7 +53,11 @@ fn setup() -> (ContractAddress, ContractAddress, ISequencerUptimeFeedDispatcher) account.into() // owner ]; - let (sequencerFeedAddr, _) = declare("SequencerUptimeFeed").unwrap().deploy(@calldata).unwrap(); + let (sequencerFeedAddr, _) = declare("SequencerUptimeFeed") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); let sequencerUptimeFeed = ISequencerUptimeFeedDispatcher { contract_address: sequencerFeedAddr @@ -74,7 +79,7 @@ fn test_access_control() { } #[test] -#[should_panic()] +#[should_panic] fn test_set_l1_sender_not_owner() { let (_, _, sequencerUptimeFeed) = setup(); start_cheat_caller_address_global(contract_address_const::<111>()); @@ -120,7 +125,7 @@ fn test_aggregator_proxy_response() { let latest_round_data = proxy.latest_round_data(); assert(latest_round_data.answer == 0, 'latest_round_data should be 0'); - // latest answer + // latest answer let latest_answer = proxy.latest_answer(); assert(latest_answer == 0, 'latest_answer should be 0'); diff --git a/contracts/src/tests/test_upgradeable.cairo b/contracts/src/tests/test_upgradeable.cairo index fee927616..e782dc6a2 100644 --- a/contracts/src/tests/test_upgradeable.cairo +++ b/contracts/src/tests/test_upgradeable.cairo @@ -6,7 +6,7 @@ use starknet::contract_address_const; use starknet::class_hash::class_hash_const; use starknet::syscalls::deploy_syscall; -use chainlink::libraries::upgradeable::Upgradeable; +use chainlink::libraries::upgrades::v1::upgradeable::Upgradeable; use chainlink::libraries::mocks::mock_upgradeable::{ MockUpgradeable, IMockUpgradeableDispatcher, IMockUpgradeableDispatcherTrait, IMockUpgradeableDispatcherImpl @@ -17,7 +17,8 @@ use chainlink::libraries::mocks::mock_non_upgradeable::{ }; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; @@ -33,14 +34,18 @@ fn test_upgrade_and_call() { let calldata = array![]; - let (contractAddr, _) = declare("MockUpgradeable").unwrap().deploy(@calldata).unwrap(); + let (contractAddr, _) = declare("MockUpgradeable") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); let mockUpgradeable = IMockUpgradeableDispatcher { contract_address: contractAddr }; assert(mockUpgradeable.foo() == true, 'should call foo'); - let contract_class = declare("MockNonUpgradeable").unwrap(); + let contract = declare("MockNonUpgradeable").unwrap().contract_class(); - mockUpgradeable.upgrade(contract_class.class_hash); + mockUpgradeable.upgrade(*(contract.class_hash)); // now, contract should be different let mockNonUpgradeable = IMockNonUpgradeableDispatcher { contract_address: contractAddr }; diff --git a/contracts/src/token/v1/link_token.cairo b/contracts/src/token/v1/link_token.cairo index 6a6df4a18..fb794fed7 100644 --- a/contracts/src/token/v1/link_token.cairo +++ b/contracts/src/token/v1/link_token.cairo @@ -1,6 +1,6 @@ use starknet::ContractAddress; -// https://github.com/starknet-io/starkgate-contracts/blob/v2.0/src/cairo/mintable_token_interface.cairo +// https://github.com/starknet-io/starkgate-contracts/blob/eedee8304e8c407c2e0e03c83187dbc5dcc6787e/src/cairo/mintable_token_interface.cairo #[starknet::interface] trait IMintableToken { fn permissioned_mint(ref self: TContractState, account: ContractAddress, amount: u256); @@ -21,7 +21,7 @@ mod LinkToken { use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; use chainlink::libraries::token::v1::erc677::ERC677Component; use chainlink::libraries::type_and_version::ITypeAndVersion; - use chainlink::libraries::upgradeable::{Upgradeable, IUpgradeable}; + use chainlink::libraries::upgrades::v1::upgradeable::{Upgradeable, IUpgradeable}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC20Component, storage: erc20, event: ERC20Event); @@ -69,15 +69,31 @@ mod LinkToken { impl MintableToken of IMintableToken { fn permissioned_mint(ref self: ContractState, account: ContractAddress, amount: u256) { only_minter(@self); - self.erc20._mint(account, amount); + self.erc20.mint(account, amount); } fn permissioned_burn(ref self: ContractState, account: ContractAddress, amount: u256) { only_minter(@self); - self.erc20._burn(account, amount); + self.erc20.burn(account, amount); } } + impl HooksImpl of ERC20Component::ERC20HooksTrait { + fn before_update( + ref self: ERC20Component::ComponentState::, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) {} + + fn after_update( + ref self: ERC20Component::ComponentState::, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) {} + } + #[constructor] fn constructor(ref self: ContractState, minter: ContractAddress, owner: ContractAddress) { @@ -109,12 +125,14 @@ mod LinkToken { } } - // fn increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + // fn increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) + // -> bool { // let mut state = ERC20::unsafe_new_contract_state(); // ERC20::ERC20Impl::increase_allowance(ref state, spender, added_value) // } - // fn decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + // fn decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: + // u256) -> bool { // let mut state = ERC20::unsafe_new_contract_state(); // ERC20::ERC20Impl::decrease_allowance(ref state, spender, subtracted_value) // } diff --git a/contracts/src/token/v2/link_token.cairo b/contracts/src/token/v2/link_token.cairo index 638d96bbd..12bc92ad2 100644 --- a/contracts/src/token/v2/link_token.cairo +++ b/contracts/src/token/v2/link_token.cairo @@ -2,7 +2,7 @@ use starknet::ContractAddress; // This token is deployed by the StarkGate bridge -// https://github.com/starknet-io/starkgate-contracts/blob/v2.0/src/cairo/mintable_token_interface.cairo +// https://github.com/starknet-io/starkgate-contracts/blob/eedee8304e8c407c2e0e03c83187dbc5dcc6787e/src/cairo/mintable_token_interface.cairo #[starknet::interface] trait IMintableToken { fn permissioned_mint(ref self: TContractState, account: ContractAddress, amount: u256); @@ -24,21 +24,26 @@ mod LinkToken { token::erc20::{ ERC20Component, interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait} }, - access::ownable::OwnableComponent + access::ownable::OwnableComponent, upgrades::UpgradeableComponent, }; use super::{IMintableToken, IMinter}; use chainlink::libraries::{ token::v2::erc677::ERC677Component, type_and_version::ITypeAndVersion, - upgradeable::{Upgradeable, IUpgradeable} + upgrades::v1::upgradeable::{Upgradeable, IUpgradeable}, + upgrades::v2::owner_upgradeable::OwnerUpgradeableComponent }; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: ERC20Component, storage: erc20, event: ERC20Event); component!(path: ERC677Component, storage: erc677, event: ERC677Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + component!( + path: OwnerUpgradeableComponent, storage: owner_upgradeable, event: OwnerUpgradeableEvent + ); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; - impl InternalImpl = OwnableComponent::InternalImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; #[abi(embed_v0)] impl ERC20Impl = ERC20Component::ERC20Impl; @@ -49,6 +54,12 @@ mod LinkToken { #[abi(embed_v0)] impl ERC677Impl = ERC677Component::ERC677Impl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[abi(embed_v0)] + impl OwnerUpgradeableImpl = + OwnerUpgradeableComponent::OwnerUpgradeableImpl; + #[storage] struct Storage { LinkTokenV2_minter: ContractAddress, @@ -58,6 +69,10 @@ mod LinkToken { erc20: ERC20Component::Storage, #[substorage(v0)] erc677: ERC677Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + #[substorage(v0)] + owner_upgradeable: OwnerUpgradeableComponent::Storage } #[derive(Drop, starknet::Event)] @@ -66,7 +81,6 @@ mod LinkToken { new_minter: ContractAddress } - #[event] #[derive(Drop, starknet::Event)] enum Event { @@ -76,7 +90,11 @@ mod LinkToken { #[flat] ERC20Event: ERC20Component::Event, #[flat] - ERC677Event: ERC677Component::Event + ERC677Event: ERC677Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + #[flat] + OwnerUpgradeableEvent: OwnerUpgradeableComponent::Event } #[constructor] @@ -110,17 +128,32 @@ mod LinkToken { ); } + impl HooksImpl of ERC20Component::ERC20HooksTrait { + fn before_update( + ref self: ERC20Component::ComponentState::, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) {} + + fn after_update( + ref self: ERC20Component::ComponentState::, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) {} + } #[abi(embed_v0)] impl MintableToken of IMintableToken { fn permissioned_mint(ref self: ContractState, account: ContractAddress, amount: u256) { self._only_minter(); - self.erc20._mint(account, amount); + self.erc20.mint(account, amount); } fn permissioned_burn(ref self: ContractState, account: ContractAddress, amount: u256) { self._only_minter(); - self.erc20._burn(account, amount); + self.erc20.burn(account, amount); } } @@ -131,6 +164,7 @@ mod LinkToken { let prev_minter = self.LinkTokenV2_minter.read(); assert(new_minter != prev_minter, 'is minter already'); + assert(!new_minter.is_zero(), 'minter is 0'); self.LinkTokenV2_minter.write(new_minter); @@ -154,14 +188,6 @@ mod LinkToken { } } - #[abi(embed_v0)] - impl UpgradeableImpl of IUpgradeable { - fn upgrade(ref self: ContractState, new_impl: ClassHash) { - self.ownable.assert_only_owner(); - Upgradeable::upgrade(new_impl) - } - } - // // Internal // diff --git a/contracts/src/utils.cairo b/contracts/src/utils.cairo index 248917648..08cd9850e 100644 --- a/contracts/src/utils.cairo +++ b/contracts/src/utils.cairo @@ -1,9 +1,26 @@ use integer::U128IntoFelt252; use integer::u128s_from_felt252; use integer::U128sFromFelt252Result; +use core::integer::u128_byte_reverse; +use core::keccak::compute_keccak_byte_array; +use alexandria_bytes::{Bytes, BytesTrait}; + fn split_felt(felt: felt252) -> (u128, u128) { match u128s_from_felt252(felt) { U128sFromFelt252Result::Narrow(low) => (0_u128, low), U128sFromFelt252Result::Wide((high, low)) => (high, low), } } + + +pub fn u256_reverse_endian(input: u256) -> u256 { + let low = u128_byte_reverse(input.high); + let high = u128_byte_reverse(input.low); + u256 { low, high } +} + +// never use compute_keccak_byte_array directly because it +// returns little-endian while evm implementations use big-endian +pub fn keccak(input: @ByteArray) -> u256 { + u256_reverse_endian(compute_keccak_byte_array(input)) +} diff --git a/examples/contracts/aggregator_consumer/Scarb.lock b/examples/contracts/aggregator_consumer/Scarb.lock index 5bdff8546..e7dec7b77 100644 --- a/examples/contracts/aggregator_consumer/Scarb.lock +++ b/examples/contracts/aggregator_consumer/Scarb.lock @@ -12,7 +12,7 @@ dependencies = [ [[package]] name = "alexandria_bytes" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_data_structures", "alexandria_math", @@ -21,7 +21,7 @@ dependencies = [ [[package]] name = "alexandria_data_structures" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_encoding", ] @@ -29,7 +29,7 @@ dependencies = [ [[package]] name = "alexandria_encoding" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_bytes", "alexandria_math", @@ -39,7 +39,7 @@ dependencies = [ [[package]] name = "alexandria_math" version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_data_structures", ] @@ -47,7 +47,7 @@ dependencies = [ [[package]] name = "alexandria_numeric" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_math", "alexandria_searching", @@ -56,7 +56,7 @@ dependencies = [ [[package]] name = "alexandria_searching" version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=bcdca70afdf59c9976148e95cebad5cf63d75a7f#bcdca70afdf59c9976148e95cebad5cf63d75a7f" +source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=e1b080577aaa6889116fc8be5dde72b2fd21e397#e1b080577aaa6889116fc8be5dde72b2fd21e397" dependencies = [ "alexandria_data_structures", ] @@ -68,15 +68,119 @@ dependencies = [ "alexandria_bytes", "alexandria_encoding", "openzeppelin", - "snforge_std", ] [[package]] name = "openzeppelin" -version = "0.10.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_governance", + "openzeppelin_introspection", + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_access" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_finance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_token", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_presets" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_finance", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_token" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "openzeppelin_utils" +version = "0.17.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.17.0#bf5d02c25c989ccc24f3ab42ec649617d3f21289" + +[[package]] +name = "snforge_scarb_plugin" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" [[package]] name = "snforge_std" -version = "0.27.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.27.0#2d99b7c00678ef0363881ee0273550c44a9263de" +version = "0.31.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.31.0#72ea785ca354e9e506de3e5d687da9fb2c1b3c67" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/examples/contracts/aggregator_consumer/Scarb.toml b/examples/contracts/aggregator_consumer/Scarb.toml index 8da76d784..69b8c3f9a 100644 --- a/examples/contracts/aggregator_consumer/Scarb.toml +++ b/examples/contracts/aggregator_consumer/Scarb.toml @@ -6,7 +6,13 @@ [package] name = "aggregator_consumer" version = "0.1.0" -cairo-version = "2.6.3" +cairo-version = "2.8.2" + +[scripts] +test = "snforge test" + +# [scripts] +# test = "snforge test" # [scripts] # test = "snforge test" @@ -14,9 +20,9 @@ cairo-version = "2.6.3" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" } chainlink = { path = "../../../contracts" } -starknet = ">=2.6.3" +starknet = "2.8.2" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.31.0" } [lib] diff --git a/examples/contracts/aggregator_consumer/scripts/Scarb.toml b/examples/contracts/aggregator_consumer/scripts/Scarb.toml index bf06f1d83..dc7f2f831 100644 --- a/examples/contracts/aggregator_consumer/scripts/Scarb.toml +++ b/examples/contracts/aggregator_consumer/scripts/Scarb.toml @@ -1,15 +1,15 @@ [package] name = "src" version = "0.1.0" -cairo-version = "2.6.3" +cairo-version = "2.8.2" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -sncast_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.21.0" } +sncast_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.28.0" } chainlink = { path = "../../../../contracts" } aggregator_consumer = { path = "../" } -starknet = ">=2.6.3" +starknet = "2.8.2" [lib] casm = true @@ -19,6 +19,5 @@ casm = true build-external-contracts = [ "chainlink::emergency::sequencer_uptime_feed::SequencerUptimeFeed", "chainlink::ocr2::mocks::mock_aggregator::MockAggregator", - "aggregator_consumer::ocr2::consumer::AggregatorConsumer" + "aggregator_consumer::ocr2::consumer::AggregatorConsumer", ] - diff --git a/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/set_latest_round.cairo b/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/set_latest_round.cairo index d5a2a0150..8b853bd43 100644 --- a/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/set_latest_round.cairo +++ b/examples/contracts/aggregator_consumer/scripts/src/mock_aggregator/set_latest_round.cairo @@ -9,7 +9,7 @@ fn main() { .try_into() .unwrap(); - // Feel free to modify these + // Feel free to modify these let answer = 1; let block_num = 12345; let observation_timestamp = 1711716556; diff --git a/examples/contracts/aggregator_consumer/tests/test_consumer.cairo b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo index b7afc0b70..6cd305c8c 100644 --- a/examples/contracts/aggregator_consumer/tests/test_consumer.cairo +++ b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo @@ -1,4 +1,4 @@ -use snforge_std::{declare, ContractClassTrait}; +use starknet::ContractAddress; use chainlink::ocr2::mocks::mock_aggregator::IMockAggregatorDispatcherTrait; use chainlink::ocr2::mocks::mock_aggregator::IMockAggregatorDispatcher; @@ -8,13 +8,14 @@ use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcher; use aggregator_consumer::ocr2::consumer::IAggregatorConsumerDispatcherTrait; use aggregator_consumer::ocr2::consumer::IAggregatorConsumerDispatcher; -use starknet::ContractAddress; +use snforge_std::{declare, ContractClassTrait, DeclareResultTrait}; + fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(decimals.into()); - let contract = declare("MockAggregator").unwrap(); + let contract = declare("MockAggregator").unwrap().contract_class(); let (contract_address, _) = contract.deploy(@calldata).unwrap(); @@ -25,7 +26,7 @@ fn deploy_consumer(aggregator_address: ContractAddress) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(aggregator_address.into()); - let contract = declare("AggregatorConsumer").unwrap(); + let contract = declare("AggregatorConsumer").unwrap().contract_class(); let (contract_address, _) = contract.deploy(@calldata).unwrap(); @@ -118,7 +119,7 @@ fn test_set_and_read_answer() { assert(latest_round.started_at == observation_timestamp, 'bad started_at'); assert(latest_round.updated_at == transmission_timestamp, 'bad updated_at'); - // Now let's test that we can set the answer + // Now let's test that we can set the answer consumer_dispatcher.set_answer(latest_round.answer); assert(answer == consumer_dispatcher.read_answer(), 'Invalid answer'); } diff --git a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo index 91874ba2e..0b59e0243 100644 --- a/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo +++ b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo @@ -13,13 +13,18 @@ use starknet::get_caller_address; use starknet::ContractAddress; use snforge_std::{ - declare, ContractClassTrait, start_cheat_caller_address_global, stop_cheat_caller_address_global + declare, ContractClassTrait, start_cheat_caller_address_global, + stop_cheat_caller_address_global, DeclareResultTrait }; fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { let mut calldata = ArrayTrait::new(); calldata.append(decimals.into()); - let (contract_address, _) = declare("MockAggregator").unwrap().deploy(@calldata).unwrap(); + let (contract_address, _) = declare("MockAggregator") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); contract_address } @@ -27,7 +32,11 @@ fn deploy_uptime_feed(initial_status: u128, owner_address: ContractAddress) -> C let mut calldata = ArrayTrait::new(); calldata.append(initial_status.into()); calldata.append(owner_address.into()); - let (contract_address, _) = declare("SequencerUptimeFeed").unwrap().deploy(@calldata).unwrap(); + let (contract_address, _) = declare("SequencerUptimeFeed") + .unwrap() + .contract_class() + .deploy(@calldata) + .unwrap(); contract_address } @@ -39,6 +48,7 @@ fn deploy_price_consumer( calldata.append(aggregator_address.into()); let (contract_address, _) = declare("AggregatorPriceConsumer") .unwrap() + .contract_class() .deploy(@calldata) .unwrap(); contract_address @@ -65,11 +75,11 @@ fn test_get_latest_price() { IAccessControllerDispatcher { contract_address: uptime_feed_address } .add_access(price_consumer_address); - // The get_latest_price function returns the mock aggregator's latest round answer. At - // this point in the test, there is only one round that is initialized and that is the - // one that the sequencer uptime feed creates when it is deployed. In its constructor, - // a new round is initialized using its initial status as the round's answer, so the - // latest price should be the initial status that was passed into the sequencer uptime + // The get_latest_price function returns the mock aggregator's latest round answer. At + // this point in the test, there is only one round that is initialized and that is the + // one that the sequencer uptime feed creates when it is deployed. In its constructor, + // a new round is initialized using its initial status as the round's answer, so the + // latest price should be the initial status that was passed into the sequencer uptime // feed's constructor. start_cheat_caller_address_global(price_consumer_address); // start_prank(CheatTarget::All, price_consumer_address); diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index f47e53d37..124aa4e14 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -1,24 +1,30 @@ # This is the default configuration so OCR2 tests can run without issues [ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" -version="2.9.0" +image = "public.ecr.aws/chainlink/chainlink" +version = "2.9.0" [Logging] -test_log_collect=false +test_log_collect = false [Logging.LogStream] -log_targets=["file"] -log_producer_timeout="10s" -log_producer_retry_limit=10 +log_targets = ["file"] +log_producer_timeout = "10s" +log_producer_retry_limit = 10 [Network] -selected_networks=["SIMULATED"] # Not needed for Starknet but mandatory from CTF (do not change) +selected_networks = [ + "SIMULATED", +] # Not needed for Starknet but mandatory from CTF (do not change) [Network.RpcHttpUrls] -simulated = ["http://127.0.0.1"] # Not needed for Starknet but mandatory from CTF (do not change) +simulated = [ + "http://127.0.0.1", +] # Not needed for Starknet but mandatory from CTF (do not change) [Network.RpcWsUrls] -simulated = ["wss://127.0.0.1"] # Not needed for Starknet but mandatory from CTF (do not change) +simulated = [ + "wss://127.0.0.1", +] # Not needed for Starknet but mandatory from CTF (do not change) [Common] internal_docker_repo = "public.ecr.aws/chainlink" @@ -26,7 +32,7 @@ inside_k8 = false network = "localnet" user = "satoshi" stateful_db = false -devnet_image = "shardlabs/starknet-devnet-rs:a147b4cd72f9ce9d1fa665d871231370db0f51c7" +devnet_image = "shardlabs/starknet-devnet-rs:a7e193d41833d221550e8ba7246566f50f507e27" postgres_version = "15.7" gauntlet_plus_plus_port = "5234" gauntlet_plus_plus_image = "gauntlet-plus-plus:latest" diff --git a/ops/charts/devnet/templates/deployment.yaml b/ops/charts/devnet/templates/deployment.yaml index f3c841bb6..ef1565df5 100644 --- a/ops/charts/devnet/templates/deployment.yaml +++ b/ops/charts/devnet/templates/deployment.yaml @@ -29,7 +29,7 @@ spec: {{- if eq .Values.real_node true }} image: "{{ .Values.repository | default "eqlabs/pathfinder"}}:{{ .Values.tag | default "v0.1.8-alpha"}}" {{- else }} - image: "{{ .Values.repository | default "shardlabs/starknet-devnet-rs"}}:{{ .Values.tag | default "a147b4cd72f9ce9d1fa665d871231370db0f51c7"}}" + image: "{{ .Values.repository | default "shardlabs/starknet-devnet-rs"}}:{{ .Values.tag | default "a7e193d41833d221550e8ba7246566f50f507e27"}}" args: ["--port", {{ .Values.service.internalPort | quote}}, "--seed", {{ .Values.seed | quote}}, "--account-class", "cairo1", "--gas-price", "1", "--data-gas-price", "1"] {{- end }} imagePullPolicy: IfNotPresent diff --git a/ops/devnet/environment.go b/ops/devnet/environment.go index cc375c3ca..9692f41cf 100644 --- a/ops/devnet/environment.go +++ b/ops/devnet/environment.go @@ -79,7 +79,7 @@ func defaultProps() map[string]any { "starknet-dev": map[string]any{ "image": map[string]any{ "image": "shardlabs/starknet-devnet-rs", - "version": "a147b4cd72f9ce9d1fa665d871231370db0f51c7", + "version": "a7e193d41833d221550e8ba7246566f50f507e27", }, "resources": map[string]any{ "requests": map[string]any{ diff --git a/ops/scripts/devnet-hardhat.sh b/ops/scripts/devnet-hardhat.sh index c56d67880..d125fd55b 100755 --- a/ops/scripts/devnet-hardhat.sh +++ b/ops/scripts/devnet-hardhat.sh @@ -13,10 +13,10 @@ bash "$(dirname -- "$0")/devnet-hardhat-down.sh" echo "Checking CPU structure..." if [[ $cpu_struct == *"arm"* ]]; then echo "Starting arm devnet container..." - container_version="${CONTAINER_VERSION:-a147b4cd72f9ce9d1fa665d871231370db0f51c7}-arm" + container_version="${CONTAINER_VERSION:-a7e193d41833d221550e8ba7246566f50f507e27}-arm" else echo "Starting i386 devnet container..." - container_version="${CONTAINER_VERSION:-a147b4cd72f9ce9d1fa665d871231370db0f51c7}" + container_version="${CONTAINER_VERSION:-a7e193d41833d221550e8ba7246566f50f507e27}" fi echo "Starting starknet-devnet" diff --git a/scripts/devnet.sh b/scripts/devnet.sh index 70fb3a3a2..c10c5f01b 100755 --- a/scripts/devnet.sh +++ b/scripts/devnet.sh @@ -13,10 +13,10 @@ echo "Checking CPU structure..." if [[ $cpu_struct == *"arm"* ]] then echo "Starting arm devnet container..." - container_version="a147b4cd72f9ce9d1fa665d871231370db0f51c7-arm" + container_version="a7e193d41833d221550e8ba7246566f50f507e27-arm" else echo "Starting i386 devnet container..." - container_version="a147b4cd72f9ce9d1fa665d871231370db0f51c7" + container_version="a7e193d41833d221550e8ba7246566f50f507e27" fi echo "Starting starknet-devnet"