From 7717098b98b4836aa5a2b6c11ecd62c36d1b73f0 Mon Sep 17 00:00:00 2001 From: Chris De Leon Date: Tue, 27 Feb 2024 20:20:24 -0800 Subject: [PATCH] updates examples directory and removes proxy-consumer --- .../contracts/aggregator_consumer/.gitignore | 2 + .../contracts/aggregator_consumer/Makefile | 12 + .../contracts/aggregator_consumer/README.md | 27 ++ .../contracts/aggregator_consumer/Scarb.lock | 28 ++ .../contracts/aggregator_consumer/Scarb.toml | 20 ++ .../aggregator_consumer/scripts/Scarb.lock | 14 + .../aggregator_consumer/scripts/Scarb.toml | 10 + .../scripts/src/deploy.cairo | 5 + .../scripts/src/example.cairo | 9 + .../aggregator_consumer/scripts/src/lib.cairo | 2 + .../aggregator_consumer/snfoundry.toml | 8 + .../aggregator_consumer/src/emergency.cairo | 1 + .../src/emergency/sequencer_uptime_feed.cairo | 287 ++++++++++++++++++ .../aggregator_consumer/src/lib.cairo | 5 + .../aggregator_consumer/src/libraries.cairo | 1 + .../src/libraries/access_control.cairo | 152 ++++++++++ .../aggregator_consumer/src/mocks.cairo | 1 + .../src/mocks/mock_aggregator.cairo | 121 ++++++++ .../aggregator_consumer/src/ocr2.cairo | 2 + .../src/ocr2/consumer.cairo | 35 +++ .../src/ocr2/price_consumer.cairo | 66 ++++ .../tests/test_consumer.cairo | 66 ++++ .../test_price_consumer_with_sequencer.cairo | 88 ++++++ examples/contracts/proxy-consumer/.gitignore | 1 - examples/contracts/proxy-consumer/README.md | 65 ---- examples/contracts/proxy-consumer/Scarb.toml | 16 - .../contracts/proxy-consumer/package.json | 15 - .../proxy-consumer/scripts/deployConsumer.ts | 64 ---- .../proxy-consumer/scripts/readLatestRound.ts | 80 ----- .../scripts/readLatestRoundOffChain.ts | 39 --- .../contracts/proxy-consumer/src/lib.cairo | 1 - .../proxy-consumer/src/proxy_consumer.cairo | 52 ---- examples/contracts/proxy-consumer/yarn.lock | 95 ------ 33 files changed, 962 insertions(+), 428 deletions(-) create mode 100644 examples/contracts/aggregator_consumer/.gitignore create mode 100644 examples/contracts/aggregator_consumer/Makefile create mode 100644 examples/contracts/aggregator_consumer/README.md create mode 100644 examples/contracts/aggregator_consumer/Scarb.lock create mode 100644 examples/contracts/aggregator_consumer/Scarb.toml create mode 100644 examples/contracts/aggregator_consumer/scripts/Scarb.lock create mode 100644 examples/contracts/aggregator_consumer/scripts/Scarb.toml create mode 100644 examples/contracts/aggregator_consumer/scripts/src/deploy.cairo create mode 100644 examples/contracts/aggregator_consumer/scripts/src/example.cairo create mode 100644 examples/contracts/aggregator_consumer/scripts/src/lib.cairo create mode 100644 examples/contracts/aggregator_consumer/snfoundry.toml create mode 100644 examples/contracts/aggregator_consumer/src/emergency.cairo create mode 100644 examples/contracts/aggregator_consumer/src/emergency/sequencer_uptime_feed.cairo create mode 100644 examples/contracts/aggregator_consumer/src/lib.cairo create mode 100644 examples/contracts/aggregator_consumer/src/libraries.cairo create mode 100644 examples/contracts/aggregator_consumer/src/libraries/access_control.cairo create mode 100644 examples/contracts/aggregator_consumer/src/mocks.cairo create mode 100644 examples/contracts/aggregator_consumer/src/mocks/mock_aggregator.cairo create mode 100644 examples/contracts/aggregator_consumer/src/ocr2.cairo create mode 100644 examples/contracts/aggregator_consumer/src/ocr2/consumer.cairo create mode 100644 examples/contracts/aggregator_consumer/src/ocr2/price_consumer.cairo create mode 100644 examples/contracts/aggregator_consumer/tests/test_consumer.cairo create mode 100644 examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo delete mode 100644 examples/contracts/proxy-consumer/.gitignore delete mode 100644 examples/contracts/proxy-consumer/README.md delete mode 100644 examples/contracts/proxy-consumer/Scarb.toml delete mode 100644 examples/contracts/proxy-consumer/package.json delete mode 100644 examples/contracts/proxy-consumer/scripts/deployConsumer.ts delete mode 100644 examples/contracts/proxy-consumer/scripts/readLatestRound.ts delete mode 100644 examples/contracts/proxy-consumer/scripts/readLatestRoundOffChain.ts delete mode 100644 examples/contracts/proxy-consumer/src/lib.cairo delete mode 100644 examples/contracts/proxy-consumer/src/proxy_consumer.cairo delete mode 100644 examples/contracts/proxy-consumer/yarn.lock diff --git a/examples/contracts/aggregator_consumer/.gitignore b/examples/contracts/aggregator_consumer/.gitignore new file mode 100644 index 000000000..73aa31e60 --- /dev/null +++ b/examples/contracts/aggregator_consumer/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/examples/contracts/aggregator_consumer/Makefile b/examples/contracts/aggregator_consumer/Makefile new file mode 100644 index 000000000..dccb98269 --- /dev/null +++ b/examples/contracts/aggregator_consumer/Makefile @@ -0,0 +1,12 @@ +# TODO: these helper commands may be useful for the deploy script(s) +# +# ACCOUNT_NAME="test-acct" +# +# create-account: +# @sncast account create --name $(ACCOUNT_NAME) +# +# deploy-account: +# @sncast account deploy --name $(ACCOUNT_NAME) + +run-script: + @cd ./scripts && sncast script run $(NAME) diff --git a/examples/contracts/aggregator_consumer/README.md b/examples/contracts/aggregator_consumer/README.md new file mode 100644 index 000000000..bea900f50 --- /dev/null +++ b/examples/contracts/aggregator_consumer/README.md @@ -0,0 +1,27 @@ +# Examples + +## Overview + +## Prerequisites + + +To get started, ensure that you have the following tools installed on your machine: + +- [starknet-foundry (v0.18.0)](https://github.com/foundry-rs/starknet-foundry/releases/tag/v0.18.0) +- [scarb (v2.5.4)](https://github.com/software-mansion/scarb/releases/tag/v2.5.4) + +## Running Test Cases + +To run all test cases, you can use the following command: + +```sh +snforge test +``` + +## Running Scripts + +To run a script, you can use the following command replacing the `NAME` argument with the name of the script to run: + +```sh +make run-script NAME=example +``` diff --git a/examples/contracts/aggregator_consumer/Scarb.lock b/examples/contracts/aggregator_consumer/Scarb.lock new file mode 100644 index 000000000..80cccaf0d --- /dev/null +++ b/examples/contracts/aggregator_consumer/Scarb.lock @@ -0,0 +1,28 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "aggregator_consumer" +version = "0.1.0" +dependencies = [ + "chainlink", + "openzeppelin", + "snforge_std", +] + +[[package]] +name = "chainlink" +version = "0.1.0" +dependencies = [ + "openzeppelin", +] + +[[package]] +name = "openzeppelin" +version = "0.9.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.9.0#861fc416f87addbe23a3b47f9d19ab27c10d5dc8" + +[[package]] +name = "snforge_std" +version = "0.18.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.18.0#48f909a56b08cbdc5ca6a21a836b0fbc6c36d55b" diff --git a/examples/contracts/aggregator_consumer/Scarb.toml b/examples/contracts/aggregator_consumer/Scarb.toml new file mode 100644 index 000000000..233211981 --- /dev/null +++ b/examples/contracts/aggregator_consumer/Scarb.toml @@ -0,0 +1,20 @@ +# This project was generated using snforge init +# +# https://foundry-rs.github.io/starknet-foundry/appendix/snforge/init.html +# + +[package] +name = "aggregator_consumer" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.9.0" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.18.0" } +chainlink = { path = "../../../contracts" } +starknet = "2.5.4" + +[[target.starknet-contract]] +casm = true diff --git a/examples/contracts/aggregator_consumer/scripts/Scarb.lock b/examples/contracts/aggregator_consumer/scripts/Scarb.lock new file mode 100644 index 000000000..4dca6a9b5 --- /dev/null +++ b/examples/contracts/aggregator_consumer/scripts/Scarb.lock @@ -0,0 +1,14 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "sncast_std" +version = "0.18.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.18.0#48f909a56b08cbdc5ca6a21a836b0fbc6c36d55b" + +[[package]] +name = "src" +version = "0.1.0" +dependencies = [ + "sncast_std", +] diff --git a/examples/contracts/aggregator_consumer/scripts/Scarb.toml b/examples/contracts/aggregator_consumer/scripts/Scarb.toml new file mode 100644 index 000000000..d912a6217 --- /dev/null +++ b/examples/contracts/aggregator_consumer/scripts/Scarb.toml @@ -0,0 +1,10 @@ +[package] +name = "src" +version = "0.1.0" +edition = "2023_11" + +# 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", tag = "v0.18.0" } +starknet = ">=2.5.4" diff --git a/examples/contracts/aggregator_consumer/scripts/src/deploy.cairo b/examples/contracts/aggregator_consumer/scripts/src/deploy.cairo new file mode 100644 index 000000000..13218da2f --- /dev/null +++ b/examples/contracts/aggregator_consumer/scripts/src/deploy.cairo @@ -0,0 +1,5 @@ +use sncast_std::{call, CallResult}; + +fn main() { + println!("replace this with a deploy script!"); +} diff --git a/examples/contracts/aggregator_consumer/scripts/src/example.cairo b/examples/contracts/aggregator_consumer/scripts/src/example.cairo new file mode 100644 index 000000000..b995a7004 --- /dev/null +++ b/examples/contracts/aggregator_consumer/scripts/src/example.cairo @@ -0,0 +1,9 @@ +use sncast_std::{call, CallResult}; + +// The example below uses a contract deployed to the Goerli testnet +fn main() { + let contract_address = 0x7ad10abd2cc24c2e066a2fee1e435cd5fa60a37f9268bfbaf2e98ce5ca3c436; + let call_result = call(contract_address.try_into().unwrap(), 'get_greeting', array![]); + assert(*call_result.data[0] == 'Hello, Starknet!', *call_result.data[0]); + println!("{:?}", call_result); +} diff --git a/examples/contracts/aggregator_consumer/scripts/src/lib.cairo b/examples/contracts/aggregator_consumer/scripts/src/lib.cairo new file mode 100644 index 000000000..a58158aab --- /dev/null +++ b/examples/contracts/aggregator_consumer/scripts/src/lib.cairo @@ -0,0 +1,2 @@ +mod example; +mod deploy; diff --git a/examples/contracts/aggregator_consumer/snfoundry.toml b/examples/contracts/aggregator_consumer/snfoundry.toml new file mode 100644 index 000000000..253c2f3c5 --- /dev/null +++ b/examples/contracts/aggregator_consumer/snfoundry.toml @@ -0,0 +1,8 @@ +# A full list of RPC endpoints can be found here: +# +# https://blastapi.io/public-api/starknet +# + +[sncast.default] +url = "https://starknet-testnet.public.blastapi.io/rpc/v0_6" + diff --git a/examples/contracts/aggregator_consumer/src/emergency.cairo b/examples/contracts/aggregator_consumer/src/emergency.cairo new file mode 100644 index 000000000..870a7f708 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/emergency.cairo @@ -0,0 +1 @@ +pub mod sequencer_uptime_feed; diff --git a/examples/contracts/aggregator_consumer/src/emergency/sequencer_uptime_feed.cairo b/examples/contracts/aggregator_consumer/src/emergency/sequencer_uptime_feed.cairo new file mode 100644 index 000000000..7aebf16fc --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/emergency/sequencer_uptime_feed.cairo @@ -0,0 +1,287 @@ +use starknet::EthAddress; +#[starknet::interface] +pub trait ISequencerUptimeFeed { + fn l1_sender(self: @TContractState) -> EthAddress; + fn set_l1_sender(ref self: TContractState, address: EthAddress); +} + +#[starknet::contract] +mod SequencerUptimeFeed { + use starknet::EthAddress; + use starknet::ContractAddress; + use starknet::SyscallResult; + use starknet::syscalls::storage_read_syscall; + use starknet::syscalls::storage_write_syscall; + use starknet::storage_access::storage_address_from_base_and_offset; + use starknet::class_hash::ClassHash; + + use core::option::OptionTrait; + use core::traits::TryInto; + + use openzeppelin::access::ownable::ownable::OwnableComponent; + + use aggregator_consumer::libraries::access_control::{AccessControlComponent, IAccessController}; + use aggregator_consumer::libraries::access_control::AccessControlComponent::InternalTrait as AccessControlInternalTrait; + use chainlink::libraries::type_and_version::ITypeAndVersion; + use chainlink::ocr2::aggregator::Round; + use chainlink::ocr2::aggregator::IAggregator; + use chainlink::ocr2::aggregator::{Transmission}; + use chainlink::libraries::upgradeable::Upgradeable; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableTwoStepImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + // l1 sender is an starknet validator ethereum address + _l1_sender: felt252, + // maps round id to round transmission + _round_transmissions: LegacyMap, + _latest_round_id: u128, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + RoundUpdated: RoundUpdated, + NewRound: NewRound, + AnswerUpdated: AnswerUpdated, + UpdateIgnored: UpdateIgnored, + L1SenderTransferred: L1SenderTransferred, + } + + #[derive(Drop, starknet::Event)] + struct RoundUpdated { + status: u128, + updated_at: u64 + } + + #[derive(Drop, starknet::Event)] + struct NewRound { + round_id: u128, + started_by: ContractAddress, + started_at: u64 + } + + #[derive(Drop, starknet::Event)] + struct AnswerUpdated { + current: u128, + round_id: u128, + timestamp: u64 + } + + #[derive(Drop, starknet::Event)] + struct UpdateIgnored { + latest_status: u128, + latest_timestamp: u64, + incoming_status: u128, + incoming_timestamp: u64 + } + + #[derive(Drop, starknet::Event)] + struct L1SenderTransferred { + from_address: EthAddress, + to_address: EthAddress + } + + #[abi(embed_v0)] + impl TypeAndVersion of ITypeAndVersion { + fn type_and_version(self: @ContractState) -> felt252 { + 'SequencerUptimeFeed 1.0.0' + } + } + + #[abi(embed_v0)] + impl AggregatorImpl of IAggregator { + fn latest_round_data(self: @ContractState) -> Round { + self._require_read_access(); + let latest_round_id = self._latest_round_id.read(); + let round_transmission = self._round_transmissions.read(latest_round_id); + Round { + round_id: latest_round_id.into(), + answer: round_transmission.answer, + block_num: round_transmission.block_num, + started_at: round_transmission.observation_timestamp, + updated_at: round_transmission.transmission_timestamp, + } + } + + fn round_data(self: @ContractState, round_id: u128) -> Round { + self._require_read_access(); + assert(round_id < self._latest_round_id.read(), 'invalid round id'); + let round_transmission = self._round_transmissions.read(round_id); + Round { + round_id: round_id.into(), + answer: round_transmission.answer, + block_num: round_transmission.block_num, + started_at: round_transmission.observation_timestamp, + updated_at: round_transmission.transmission_timestamp, + } + } + + fn description(self: @ContractState) -> felt252 { + 'L2 Sequencer Uptime Status Feed' + } + + fn decimals(self: @ContractState) -> u8 { + 0_u8 + } + } + + #[constructor] + fn constructor(ref self: ContractState, initial_status: u128, owner_address: ContractAddress) { + self._initializer(initial_status, owner_address); + } + + #[l1_handler] + fn update_status(ref self: ContractState, from_address: felt252, status: u128, timestamp: u64) { + assert(self._l1_sender.read() == from_address, 'EXPECTED_FROM_BRIDGE_ONLY'); + + let latest_round_id = self._latest_round_id.read(); + let latest_round = self._round_transmissions.read(latest_round_id); + + if timestamp <= latest_round.observation_timestamp { + self + .emit( + Event::UpdateIgnored( + UpdateIgnored { + latest_status: latest_round.answer, + latest_timestamp: latest_round.transmission_timestamp, + incoming_status: status, + incoming_timestamp: timestamp + } + ) + ); + return (); + } + + if latest_round.answer == status { + self._update_round(latest_round_id, latest_round); + } else { + // only increment round when status flips + let round_id = latest_round_id + 1_u128; + self._record_round(round_id, status, timestamp); + } + } + + #[abi(embed_v0)] + impl SequencerUptimeFeedImpl of super::ISequencerUptimeFeed { + fn set_l1_sender(ref self: ContractState, address: EthAddress) { + self.ownable.assert_only_owner(); + + // TODO: + // assert(!address.is_zero(), '0 address not allowed'); + + let old_address = self._l1_sender.read(); + + if old_address != address.into() { + self._l1_sender.write(address.into()); + self + .emit( + Event::L1SenderTransferred( + L1SenderTransferred { + from_address: old_address.try_into().unwrap(), to_address: address + } + ) + ); + } + } + + fn l1_sender(self: @ContractState) -> EthAddress { + self._l1_sender.read().try_into().unwrap() + } + } + + /// + /// Upgradeable + /// + + #[abi(embed_v0)] + fn upgrade(ref self: ContractState, new_impl: ClassHash) { + self.ownable.assert_only_owner(); + Upgradeable::upgrade(new_impl) + } + + /// + /// Internals + /// + + #[generate_trait] + impl Internals of InternalTrait { + fn _require_read_access(self: @ContractState) { + let sender = starknet::get_caller_address(); + self.access_control.check_read_access(sender); + } + + fn _initializer( + ref self: ContractState, initial_status: u128, owner_address: ContractAddress + ) { + self.ownable.initializer(owner_address); + self.access_control.initializer(); + let round_id = 1_u128; + let timestamp = starknet::get_block_timestamp(); + self._record_round(round_id, initial_status, timestamp); + } + + fn _record_round(ref self: ContractState, round_id: u128, status: u128, timestamp: u64) { + self._latest_round_id.write(round_id); + let block_info = starknet::get_block_info().unbox(); + let block_number = block_info.block_number; + let block_timestamp = block_info.block_timestamp; + + let round = Transmission { + answer: status, + block_num: block_number, + observation_timestamp: timestamp, + transmission_timestamp: block_timestamp, + }; + self._round_transmissions.write(round_id, round); + + let sender = starknet::get_caller_address(); + + self + .emit( + Event::NewRound( + NewRound { round_id: round_id, started_by: sender, started_at: timestamp } + ) + ); + self + .emit( + Event::AnswerUpdated( + AnswerUpdated { current: status, round_id: round_id, timestamp: timestamp } + ) + ); + } + + fn _update_round(ref self: ContractState, round_id: u128, mut round: Transmission) { + round.transmission_timestamp = starknet::get_block_timestamp(); + self._round_transmissions.write(round_id, round); + + self + .emit( + Event::RoundUpdated( + RoundUpdated { + status: round.answer, updated_at: round.transmission_timestamp + } + ) + ); + } + } +} diff --git a/examples/contracts/aggregator_consumer/src/lib.cairo b/examples/contracts/aggregator_consumer/src/lib.cairo new file mode 100644 index 000000000..cc02a54bd --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/lib.cairo @@ -0,0 +1,5 @@ +pub mod libraries; +pub mod emergency; +pub mod mocks; +pub mod ocr2; + diff --git a/examples/contracts/aggregator_consumer/src/libraries.cairo b/examples/contracts/aggregator_consumer/src/libraries.cairo new file mode 100644 index 000000000..18e237199 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/libraries.cairo @@ -0,0 +1 @@ +pub mod access_control; diff --git a/examples/contracts/aggregator_consumer/src/libraries/access_control.cairo b/examples/contracts/aggregator_consumer/src/libraries/access_control.cairo new file mode 100644 index 000000000..a44fee769 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/libraries/access_control.cairo @@ -0,0 +1,152 @@ +use starknet::ContractAddress; +#[starknet::interface] +pub trait IAccessController { + fn has_access(self: @TContractState, user: ContractAddress, data: Array) -> bool; + fn has_read_access(self: @TContractState, user: ContractAddress, data: Array) -> bool; + fn add_access(ref self: TContractState, user: ContractAddress); + fn remove_access(ref self: TContractState, user: ContractAddress); + fn enable_access_check(ref self: TContractState); + fn disable_access_check(ref self: TContractState); +} + +// Requires Ownable subcomponent. +#[starknet::component] +pub mod AccessControlComponent { + use starknet::ContractAddress; + use starknet::class_hash::ClassHash; + + use openzeppelin::access::ownable::ownable::OwnableComponent; + + use OwnableComponent::InternalImpl as OwnableInternalImpl; + + #[storage] + struct Storage { + _check_enabled: bool, + _access_list: LegacyMap, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + AddedAccess: AddedAccess, + RemovedAccess: RemovedAccess, + AccessControlEnabled: AccessControlEnabled, + AccessControlDisabled: AccessControlDisabled, + } + + #[derive(Drop, starknet::Event)] + struct AddedAccess { + user: ContractAddress + } + + #[derive(Drop, starknet::Event)] + struct RemovedAccess { + user: ContractAddress + } + + #[derive(Drop, starknet::Event)] + struct AccessControlEnabled {} + + #[derive(Drop, starknet::Event)] + struct AccessControlDisabled {} + + #[embeddable_as(AccessControlImpl)] + pub impl AccessControl< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + +Drop, + > of super::IAccessController> { + fn has_access( + self: @ComponentState, user: ContractAddress, data: Array + ) -> bool { + let has_access = self._access_list.read(user); + if has_access { + return true; + } + + let check_enabled = self._check_enabled.read(); + if !check_enabled { + return true; + } + + false + } + + fn has_read_access( + self: @ComponentState, user: ContractAddress, data: Array + ) -> bool { + let _has_access = self.has_access(user, data); + if _has_access { + return true; + } + + // TODO: + // NOTICE: read access is granted to direct calls, to enable off-chain reads. + // if user.is_zero() { + // return true; + //} + + false + } + + fn add_access(ref self: ComponentState, user: ContractAddress) { + get_dep_component!(@self, Ownable).assert_only_owner(); + let has_access = self._access_list.read(user); + if !has_access { + self._access_list.write(user, true); + self.emit(Event::AddedAccess(AddedAccess { user: user })); + } + } + + fn remove_access(ref self: ComponentState, user: ContractAddress) { + get_dep_component!(@self, Ownable).assert_only_owner(); + let has_access = self._access_list.read(user); + if has_access { + self._access_list.write(user, false); + self.emit(Event::RemovedAccess(RemovedAccess { user: user })); + } + } + + fn enable_access_check(ref self: ComponentState) { + get_dep_component!(@self, Ownable).assert_only_owner(); + let check_enabled = self._check_enabled.read(); + if !check_enabled { + self._check_enabled.write(true); + self.emit(Event::AccessControlEnabled(AccessControlEnabled {})); + } + } + + fn disable_access_check(ref self: ComponentState) { + get_dep_component!(@self, Ownable).assert_only_owner(); + let check_enabled = self._check_enabled.read(); + if check_enabled { + self._check_enabled.write(false); + self.emit(Event::AccessControlDisabled(AccessControlDisabled {})); + } + } + } + + #[generate_trait] + pub impl InternalImpl< + TContractState, + +HasComponent, + impl Ownable: OwnableComponent::HasComponent, + +Drop, + > of InternalTrait { + fn initializer(ref self: ComponentState) { + self._check_enabled.write(true); + self.emit(Event::AccessControlEnabled(AccessControlEnabled {})); + } + + fn check_access(self: @ComponentState, user: ContractAddress) { + let allowed = AccessControl::has_access(self, user, ArrayTrait::new()); + assert(allowed, 'user does not have access'); + } + + fn check_read_access(self: @ComponentState, user: ContractAddress) { + let allowed = AccessControl::has_read_access(self, user, ArrayTrait::new()); + assert(allowed, 'user does not have read access'); + } + } +} diff --git a/examples/contracts/aggregator_consumer/src/mocks.cairo b/examples/contracts/aggregator_consumer/src/mocks.cairo new file mode 100644 index 000000000..b42bbb0d5 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/mocks.cairo @@ -0,0 +1 @@ +pub mod mock_aggregator; diff --git a/examples/contracts/aggregator_consumer/src/mocks/mock_aggregator.cairo b/examples/contracts/aggregator_consumer/src/mocks/mock_aggregator.cairo new file mode 100644 index 000000000..36b220181 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/mocks/mock_aggregator.cairo @@ -0,0 +1,121 @@ +#[starknet::interface] +pub trait IMockAggregator { + fn set_latest_round_data( + ref self: TContractState, + answer: u128, + block_num: u64, + observation_timestamp: u64, + transmission_timestamp: u64 + ); +} + +#[starknet::contract] +mod MockAggregator { + use starknet::contract_address_const; + use core::panic_with_felt252; + + use chainlink::libraries::type_and_version::ITypeAndVersion; + + use chainlink::ocr2::aggregator::Aggregator::{Transmission, NewTransmission}; + use chainlink::ocr2::aggregator::IAggregator; + use chainlink::ocr2::aggregator::Round; + + #[event] + use chainlink::ocr2::aggregator::Aggregator::Event; + + #[storage] + struct Storage { + _transmissions: LegacyMap, + _latest_aggregator_round_id: u128, + _decimals: u8 + } + + #[constructor] + fn constructor(ref self: ContractState, decimals: u8) { + self._decimals.write(decimals); + } + + #[abi(embed_v0)] + impl MockImpl of super::IMockAggregator { + fn set_latest_round_data( + ref self: ContractState, + answer: u128, + block_num: u64, + observation_timestamp: u64, + transmission_timestamp: u64 + ) { + let new_round_id = self._latest_aggregator_round_id.read() + 1_u128; + self + ._transmissions + .write( + new_round_id, + Transmission { + answer: answer, + block_num: block_num, + observation_timestamp: observation_timestamp, + transmission_timestamp: transmission_timestamp + } + ); + + let mut observations = ArrayTrait::new(); + observations.append(2_u128); + observations.append(3_u128); + + self._latest_aggregator_round_id.write(new_round_id); + + self + .emit( + Event::NewTransmission( + NewTransmission { + round_id: new_round_id, + answer: answer, + transmitter: contract_address_const::<42>(), + observation_timestamp: observation_timestamp, + observers: 3, + observations: observations, + juels_per_fee_coin: 18_u128, + gas_price: 1_u128, + config_digest: 777, + epoch_and_round: 20_u64, + reimbursement: 100_u128 + } + ) + ); + } + } + + #[abi(embed_v0)] + impl TypeAndVersionImpl of ITypeAndVersion { + fn type_and_version(self: @ContractState) -> felt252 { + 'mock_aggregator.cairo 1.0.0' + } + } + + #[abi(embed_v0)] + impl Aggregator of IAggregator { + fn round_data(self: @ContractState, round_id: u128) -> Round { + panic_with_felt252('unimplemented') + } + + fn latest_round_data(self: @ContractState) -> Round { + let latest_round_id = self._latest_aggregator_round_id.read(); + let transmission = self._transmissions.read(latest_round_id); + + Round { + round_id: latest_round_id.into(), + answer: transmission.answer, + block_num: transmission.block_num, + started_at: transmission.observation_timestamp, + updated_at: transmission.transmission_timestamp + } + } + + fn decimals(self: @ContractState) -> u8 { + self._decimals.read() + } + + fn description(self: @ContractState) -> felt252 { + 'mock' + } + } +} diff --git a/examples/contracts/aggregator_consumer/src/ocr2.cairo b/examples/contracts/aggregator_consumer/src/ocr2.cairo new file mode 100644 index 000000000..8947aa72c --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/ocr2.cairo @@ -0,0 +1,2 @@ +pub mod price_consumer; +pub mod consumer; diff --git a/examples/contracts/aggregator_consumer/src/ocr2/consumer.cairo b/examples/contracts/aggregator_consumer/src/ocr2/consumer.cairo new file mode 100644 index 000000000..f0448b309 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/ocr2/consumer.cairo @@ -0,0 +1,35 @@ +#[starknet::interface] +pub trait IAggregatorConsumer { + fn read_latest_round(self: @TContractState) -> chainlink::ocr2::aggregator::Round; + fn read_decimals(self: @TContractState) -> u8; +} + +#[starknet::contract] +mod AggregatorConsumer { + use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcherTrait; + use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcher; + use chainlink::ocr2::aggregator_proxy::IAggregator; + use chainlink::ocr2::aggregator::Round; + use starknet::ContractAddress; + + #[storage] + struct Storage { + _ocr_address: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, ocr_address: ContractAddress) { + self._ocr_address.write(ocr_address); + } + + #[abi(embed_v0)] + impl AggregatorConsumerImpl of super::IAggregatorConsumer { + fn read_latest_round(self: @ContractState) -> Round { + IAggregatorDispatcher { contract_address: self._ocr_address.read() }.latest_round_data() + } + + fn read_decimals(self: @ContractState) -> u8 { + IAggregatorDispatcher { contract_address: self._ocr_address.read() }.decimals() + } + } +} diff --git a/examples/contracts/aggregator_consumer/src/ocr2/price_consumer.cairo b/examples/contracts/aggregator_consumer/src/ocr2/price_consumer.cairo new file mode 100644 index 000000000..ca8c5cec5 --- /dev/null +++ b/examples/contracts/aggregator_consumer/src/ocr2/price_consumer.cairo @@ -0,0 +1,66 @@ +#[starknet::interface] +pub trait IAggregatorPriceConsumer { + fn get_latest_price(self: @TContractState) -> u128; +} + +#[starknet::contract] +mod AggregatorPriceConsumer { + use core::starknet::ContractAddress; + use core::starknet::get_block_info; + use core::box::BoxTrait; + use core::traits::Into; + + use chainlink::ocr2::aggregator::Round; + use chainlink::ocr2::aggregator_proxy::IAggregator; + use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcher; + use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcherTrait; + + #[storage] + struct Storage { + _uptime_feed_address: ContractAddress, + _aggregator_address: ContractAddress, + } + + // Sequencer-aware aggregator consumer + // retrieves the latest price from the data feed only if + // the uptime feed is not stale (stale = older than 60 seconds) + #[constructor] + fn constructor( + ref self: ContractState, + uptime_feed_address: ContractAddress, + aggregator_address: ContractAddress + ) { + self._uptime_feed_address.write(uptime_feed_address); + self._aggregator_address.write(aggregator_address); + } + + + #[abi(embed_v0)] + impl AggregatorPriceConsumerImpl of super::IAggregatorPriceConsumer { + fn get_latest_price(self: @ContractState) -> u128 { + assert_sequencer_healthy(self); + let round = IAggregatorDispatcher { contract_address: self._aggregator_address.read() } + .latest_round_data(); + round.answer + } + } + + fn assert_sequencer_healthy(self: @ContractState) { + let round = IAggregatorDispatcher { contract_address: self._uptime_feed_address.read() } + .latest_round_data(); + let timestamp = get_block_info().unbox().block_timestamp; + + // After 60 sec the report is considered stale + let report_stale = timestamp - round.updated_at > 60_u64; + + // 0 if the sequencer is up and 1 if it is down. No other options besides 1 and 0 + match round.answer.into() { + 0 => { assert(!report_stale, 'L2 seq up & report stale'); }, + _ => { + assert(!report_stale, 'L2 seq down & report stale'); + assert(false, 'L2 seq down & report ok'); + } + } + } +} + diff --git a/examples/contracts/aggregator_consumer/tests/test_consumer.cairo b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo new file mode 100644 index 000000000..47d48b9fa --- /dev/null +++ b/examples/contracts/aggregator_consumer/tests/test_consumer.cairo @@ -0,0 +1,66 @@ +use snforge_std::{declare, ContractClassTrait}; + +use aggregator_consumer::mocks::mock_aggregator::IMockAggregatorDispatcherTrait; +use aggregator_consumer::mocks::mock_aggregator::IMockAggregatorDispatcher; +use aggregator_consumer::ocr2::consumer::IAggregatorConsumerDispatcherTrait; +use aggregator_consumer::ocr2::consumer::IAggregatorConsumerDispatcher; + +use starknet::ContractAddress; + +fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { + let mut calldata = ArrayTrait::new(); + calldata.append(decimals.into()); + return declare('MockAggregator').deploy(@calldata).unwrap(); +} + +fn deploy_consumer(aggregator_address: ContractAddress) -> ContractAddress { + let mut calldata = ArrayTrait::new(); + calldata.append(aggregator_address.into()); + return declare('AggregatorConsumer').deploy(@calldata).unwrap(); +} + +#[test] +fn test_read_decimals() { + let decimals = 16; + let mock_aggregator_address = deploy_mock_aggregator(decimals); + let consumer_address = deploy_consumer(mock_aggregator_address); + let consumer_dispatcher = IAggregatorConsumerDispatcher { contract_address: consumer_address }; + assert(decimals == consumer_dispatcher.read_decimals(), 'Invalid decimals'); +} + +#[test] +fn test_read_latest_round() { + // Deploys the mock aggregator + let mock_aggregator_address = deploy_mock_aggregator(16); + let mock_aggregator_dispatcher = IMockAggregatorDispatcher { + contract_address: mock_aggregator_address + }; + + // Deploys the consumer + let consumer_address = deploy_consumer(mock_aggregator_address); + let consumer_dispatcher = IAggregatorConsumerDispatcher { contract_address: consumer_address }; + + // No round data has been initialized, so reading the latest round should return no data + let empty_latest_round = consumer_dispatcher.read_latest_round(); + assert(empty_latest_round.round_id == 0, 'round_id != 0'); + assert(empty_latest_round.answer == 0, 'answer != 0'); + assert(empty_latest_round.block_num == 0, 'block_num != 0'); + assert(empty_latest_round.started_at == 0, 'started_at != 0'); + assert(empty_latest_round.updated_at == 0, 'updated_at != 0'); + + // Now let's set the latest round data to some random values + let answer = 1; + let block_num = 12345; + let observation_timestamp = 100000; + let transmission_timestamp = 200000; + mock_aggregator_dispatcher + .set_latest_round_data(answer, block_num, observation_timestamp, transmission_timestamp); + + // The latest round should now have some data + let latest_round = consumer_dispatcher.read_latest_round(); + assert(latest_round.round_id == 1, 'round_id != 1'); + assert(latest_round.answer == answer, 'bad answer'); + assert(latest_round.block_num == block_num, 'bad block_num'); + assert(latest_round.started_at == observation_timestamp, 'bad started_at'); + assert(latest_round.updated_at == transmission_timestamp, 'bad updated_at'); +} 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 new file mode 100644 index 000000000..17de9dd35 --- /dev/null +++ b/examples/contracts/aggregator_consumer/tests/test_price_consumer_with_sequencer.cairo @@ -0,0 +1,88 @@ +use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; + +use aggregator_consumer::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcherTrait; +use aggregator_consumer::emergency::sequencer_uptime_feed::ISequencerUptimeFeedDispatcher; +use aggregator_consumer::ocr2::price_consumer::IAggregatorPriceConsumerDispatcherTrait; +use aggregator_consumer::ocr2::price_consumer::IAggregatorPriceConsumerDispatcher; +use aggregator_consumer::libraries::access_control::IAccessControllerDispatcherTrait; +use aggregator_consumer::libraries::access_control::IAccessControllerDispatcher; +use aggregator_consumer::mocks::mock_aggregator::IMockAggregatorDispatcherTrait; +use aggregator_consumer::mocks::mock_aggregator::IMockAggregatorDispatcher; + +use starknet::contract_address_const; +use starknet::get_caller_address; +use starknet::ContractAddress; + +fn deploy_mock_aggregator(decimals: u8) -> ContractAddress { + let mut calldata = ArrayTrait::new(); + calldata.append(decimals.into()); + return declare('MockAggregator').deploy(@calldata).unwrap(); +} + +fn deploy_uptime_feed(initial_status: u128, owner_address: ContractAddress) -> ContractAddress { + let mut calldata = ArrayTrait::new(); + calldata.append(initial_status.into()); + calldata.append(owner_address.into()); + return declare('SequencerUptimeFeed').deploy(@calldata).unwrap(); +} + +fn deploy_price_consumer( + uptime_feed_address: ContractAddress, aggregator_address: ContractAddress +) -> ContractAddress { + let mut calldata = ArrayTrait::new(); + calldata.append(uptime_feed_address.into()); + calldata.append(aggregator_address.into()); + return declare('AggregatorPriceConsumer').deploy(@calldata).unwrap(); +} + +#[test] +fn test_get_latest_price() { + // Defines helper variables + let owner = contract_address_const::<1>(); + let init_status = 0; + let decimals = 18; + + // Deploys contracts + let mock_aggregator_address = deploy_mock_aggregator(decimals); + let uptime_feed_address = deploy_uptime_feed(init_status, owner); + let price_consumer_address = deploy_price_consumer( + uptime_feed_address, mock_aggregator_address + ); + + // Adds the price consumer contract to the sequencer uptime feed access control list + // which allows the price consumer to call the get_latest_price function + start_prank(CheatTarget::All, owner); + 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 + // feed's constructor. + start_prank(CheatTarget::All, price_consumer_address); + let latest_price = IAggregatorPriceConsumerDispatcher { + contract_address: price_consumer_address + } + .get_latest_price(); + assert(latest_price == init_status, 'latest price is incorrect'); + + // Now let's update the round + stop_prank(CheatTarget::All); + let answer = 1; + let block_num = 12345; + let observation_timestamp = 100000; + let transmission_timestamp = 200000; + IMockAggregatorDispatcher { contract_address: mock_aggregator_address } + .set_latest_round_data(answer, block_num, observation_timestamp, transmission_timestamp); + + // This should now return the updated answer + start_prank(CheatTarget::All, price_consumer_address); + let updated_latest_price = IAggregatorPriceConsumerDispatcher { + contract_address: price_consumer_address + } + .get_latest_price(); + assert(updated_latest_price == answer, 'updlatest price is incorrect'); +} + diff --git a/examples/contracts/proxy-consumer/.gitignore b/examples/contracts/proxy-consumer/.gitignore deleted file mode 100644 index eb5a316cb..000000000 --- a/examples/contracts/proxy-consumer/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/contracts/proxy-consumer/README.md b/examples/contracts/proxy-consumer/README.md deleted file mode 100644 index 4cce785e9..000000000 --- a/examples/contracts/proxy-consumer/README.md +++ /dev/null @@ -1,65 +0,0 @@ -This is a simple example for how to read Chainlink data feeds on Starknet. - -### Requirements - -Set up your environment to run the examples. Make sure to clone this repo before you start these instructions - -1. Clone the [smartcontractkit/chainlink-starknet](https://github.com/smartcontractkit/chainlink-starknet) repository, which includes the example contracts for this guide: - ``` - git clone https://github.com/smartcontractkit chainlink-starknet.git - git submodule update --init --recursive - ``` - We use git submodules to pin specific versions of cairo and scarb (you'll see this come into play later). - -1. Setup your local Starknet environment. We will install starknet cli tools, the rust cairo compiler, and scarb which is a framework and dependency manager for cairo. If you already have them installed, feel free to skip this step (if you later find that your versions are not working, follow the steps below because they are pinned to specific versions). - ``` - # Part 1: Install starknet cli tools via virtualenv - - cd chainlink-starknet - # tested on python 3.9 and onwards - python -m venv venv - source ./venv/bin/activate - pip install -r contracts/requirements.txt - ``` - Next we'll install cairo. If you've already installed cairo, make sure to disable that path first. - ``` - # Part 2: Install cairo - cd vendor/cairo && cargo build --all --release - # Add cairo executable to your path - export PATH="$HOME/path/to/chainlink-starknet/vendor/cairo/target/release:$PATH" - ``` - Lastly, we'll install scarb. You should be able to install the scarb 0.2.0-alpha.2 binary from [here](https://github.com/software-mansion/scarb/releases/tag/v0.2.0-alpha.2) for your operating system. Install it in your `$HOME` directory and add it to your path. - ``` - # assuming you've downloaded the scarb artifact already to your $HOME directory - export PATH="$HOME/scarb/bin:$PATH" - ``` - Awesome, that was a lot of work, but now we're ready to start! - -1. Set up a Starknet account. Follow instructions to [set up environment variables](https://docs.starknet.io/documentation/getting_started/deploying_contracts/#setting_up_environment_variables) and [deploy an account](https://docs.starknet.io/documentation/getting_started/deploying_contracts/#setting_up_an_account). This deploys an account on Starknet's `alpha-goerli` network and funds it with [testnet ETH](https://faucet.goerli.starknet.io/). These examples expect the OpenZeppelin wallet, which stores your addresses and private keys at `~/.starknet_accounts/` by default. - -1. [Install NodeJS](https://nodejs.org/en/download/) in the version in the `>=14 <=18` version range. -1. [Install Yarn](https://classic.yarnpkg.com/lang/en/docs/install/). -1. Change directories to the proxy consumer example: `cd ./chainlink-starknet/examples/new_contracts/proxy_consumer/` -1. Run `yarn install` to install the required packages including [Starknet.js](https://www.starknetjs.com/) - -### Running the on-chain example - -1. Find the your account address and private key for your funded Starknet testnet account. By default, the OpenZeppelin wallet contains these values at `~/.starknet_accounts/starknet_open_zeppelin_accounts.json`. -1. Export your address to the `DEPLOYER_ACCOUNT_ADDRESS` environment variable and your private key to the `DEPLOYER_PRIVATE_KEY` environment variable. - - ```shell - export DEPLOYER_ACCOUNT_ADDRESS= - ``` - - ```shell - export DEPLOYER_PRIVATE_KEY= - ``` -1. Run `yarn build` to build the cairo artifacts via scarb. These will be put in the target/ directory -1. Run `yarn deploy` to deploy the example consumer contract to the Starknet Goerli testnet. The console prints the contract address and transaction hash. -1. Run `yarn readLatestRound ` to send an invoke transaction to the deployed contract. Specify the contract address printed by the deploy step. The deployed contract reads the latest round data from the proxy, stores the values, and prints the resulting values. - -### Running the off-chain example - -This example simply reads the proxy contract to get the latest values with no account or contract compiling steps required. - -1. Run `yarn readLatestRoundOffChain`. diff --git a/examples/contracts/proxy-consumer/Scarb.toml b/examples/contracts/proxy-consumer/Scarb.toml deleted file mode 100644 index b497f3adb..000000000 --- a/examples/contracts/proxy-consumer/Scarb.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "proxy_consumer" -version = "0.1.0" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest - -[dependencies] - chainlink = { path = "../../../contracts" } - - -[[target.starknet-contract]] -# note these two options only work on scarb 0.2.0 and forward -casm = true -# Emit Python-powered hints in order to run compiled CASM class with legacy Cairo VM. -casm-add-pythonic-hints = true - diff --git a/examples/contracts/proxy-consumer/package.json b/examples/contracts/proxy-consumer/package.json deleted file mode 100644 index 676bed3ef..000000000 --- a/examples/contracts/proxy-consumer/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "proxy-consumer", - "version": "1.0.0", - "description": "Simple contracts to read Chainlink data feeds on-chain and off-chain", - "scripts": { - "build": "scarb --profile release build", - "deploy": "yarn ts-node ./scripts/deployConsumer.ts", - "readLatestRound": "yarn ts-node ./scripts/readLatestRound.ts", - "readLatestRoundOffChain": "yarn ts-node ./scripts/readLatestRoundOffChain.ts" - }, - "license": "MIT", - "dependencies": { - "starknet": "^6.0.0-beta.11" - } -} diff --git a/examples/contracts/proxy-consumer/scripts/deployConsumer.ts b/examples/contracts/proxy-consumer/scripts/deployConsumer.ts deleted file mode 100644 index 4326c4a98..000000000 --- a/examples/contracts/proxy-consumer/scripts/deployConsumer.ts +++ /dev/null @@ -1,64 +0,0 @@ -import fs from 'fs' -import { Account, Provider, Contract, json, ec, constants } from 'starknet' - -// The Cairo contract that is compiled and ready to declare and deploy -const consumerContractName = 'ProxyConsumer' - -/** - * Network: Starknet Goerli testnet - * Aggregator: LINK/USD - * Address: 0x2579940ca3c41e7119283ceb82cd851c906cbb1510908a913d434861fdcb245 - * Find more feed address at: - * https://docs.chain.link/data-feeds/price-feeds/addresses?network=starknet - */ -const dataFeedAddress = '0x2579940ca3c41e7119283ceb82cd851c906cbb1510908a913d434861fdcb245' - -/** Environment variables for a deployed and funded account to use for deploying contracts - * Find your OpenZeppelin account address and private key at: - * ~/.starknet_accounts/starknet_open_zeppelin_accounts.json - */ -const accountAddress = process.env.DEPLOYER_ACCOUNT_ADDRESS as string -const accountPrivateKey = process.env.DEPLOYER_PRIVATE_KEY as string -const starkKeyPub = ec.starkCurve.getStarkKey(accountPrivateKey) - -export async function deployContract() { - const provider = new Provider({ - sequencer: { - // Starknet network: Either goerli-alpha or mainnet-alpha - network: constants.NetworkName.SN_GOERLI, - }, - }) - - const account = new Account(provider, accountAddress, accountPrivateKey) - - const consumerContract = json.parse( - fs - .readFileSync( - `${__dirname}/../target/release/proxy_consumer_${consumerContractName}.sierra.json`, - ) - .toString('ascii'), - ) - - const declareDeployConsumer = await account.declareAndDeploy({ - contract: consumerContract, - casm: json.parse( - fs - .readFileSync( - `${__dirname}/../target/release/proxy_consumer_${consumerContractName}.casm.json`, - ) - .toString('ascii'), - ), - constructorCalldata: [dataFeedAddress as string], - }) - - const consumerDeploy = new Contract( - consumerContract.abi, - declareDeployConsumer.deploy.contract_address, - provider, - ) - - console.log('Contract address: ' + consumerDeploy.address) - console.log('Transaction hash: ' + declareDeployConsumer.deploy.transaction_hash) -} - -deployContract() diff --git a/examples/contracts/proxy-consumer/scripts/readLatestRound.ts b/examples/contracts/proxy-consumer/scripts/readLatestRound.ts deleted file mode 100644 index 4516e86fd..000000000 --- a/examples/contracts/proxy-consumer/scripts/readLatestRound.ts +++ /dev/null @@ -1,80 +0,0 @@ -import fs from 'fs' -import { Account, Provider, Contract, CallContractResponse, json, ec, constants } from 'starknet' - -/** Environment variables for a deployed and funded account to use for deploying contracts - * Find your OpenZeppelin account address and private key at: - * ~/.starknet_accounts/starknet_open_zeppelin_accounts.json - */ -const accountAddress = process.env.DEPLOYER_ACCOUNT_ADDRESS as string -const privateKey = process.env.DEPLOYER_PRIVATE_KEY as string -const starkKeyPub = ec.starkCurve.getStarkKey(privateKey) - -const consumerContractName = 'ProxyConsumer' - -const contractAddress = process.argv.at(2) as string - -const provider = new Provider({ - sequencer: { - // Starknet network: Either goerli-alpha or mainnet-alpha - network: constants.NetworkName.SN_GOERLI, - }, -}) - -const account = new Account(provider, accountAddress, privateKey) - -export async function updateStoredRound(account: Account, contractAddress: string) { - const consumerContract = json.parse( - fs - .readFileSync( - `${__dirname}/../target/release/proxy_consumer_${consumerContractName}.sierra.json`, - ) - .toString('ascii'), - ) - - const targetContract = new Contract(consumerContract.abi, contractAddress, account) - - const response = await targetContract.invoke('get_latest_round_data') - - console.log('\nInvoking the get_latest_round_data function.') - console.log('Transaction hash: ' + response.transaction_hash) - - console.log('Waiting for transaction...') - let transactionStatus = (await provider.getTransactionReceipt(response.transaction_hash)).status - while (transactionStatus !== 'REJECTED' && transactionStatus !== 'ACCEPTED_ON_L2') { - console.log('Transaction status is: ' + transactionStatus) - await new Promise((f) => setTimeout(f, 10000)) - transactionStatus = (await provider.getTransactionReceipt(response.transaction_hash)).status - } - console.log('Transaction is: ' + transactionStatus) - readStoredRound(account, contractAddress) -} - -export async function readStoredRound(account: Account, contractAddress: string) { - const round = await account.callContract({ - contractAddress: contractAddress, - entrypoint: 'get_stored_round', - }) - - console.log('\nStored values are:') - printResult(round) - return round -} - -export async function readStoredProxy(account: Account, contractAddress: string) { - const feed = await account.callContract({ - contractAddress: contractAddress, - entrypoint: 'get_stored_feed_address', - }) - - return feed -} - -function printResult(latestRound: CallContractResponse) { - console.log('round_id =', parseInt(latestRound.result[0], 16)) - console.log('answer =', parseInt(latestRound.result[1], 16)) - console.log('block_num =', parseInt(latestRound.result[2], 16)) - console.log('observation_timestamp =', parseInt(latestRound.result[3], 16)) - console.log('transmission_timestamp =', parseInt(latestRound.result[4], 16)) -} - -updateStoredRound(account, contractAddress) diff --git a/examples/contracts/proxy-consumer/scripts/readLatestRoundOffChain.ts b/examples/contracts/proxy-consumer/scripts/readLatestRoundOffChain.ts deleted file mode 100644 index 4fd27ebc7..000000000 --- a/examples/contracts/proxy-consumer/scripts/readLatestRoundOffChain.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Provider, CallContractResponse, constants } from 'starknet' - -// Starknet network: Either goerli-alpha or mainnet-alpha -const network = 'goerli-alpha' - -/** - * Network: Starknet Goerli testnet - * Aggregator: LINK/USD - * Address: 0x2579940ca3c41e7119283ceb82cd851c906cbb1510908a913d434861fdcb245 - * Find more proxy address at: - * https://docs.chain.link/data-feeds/price-feeds/addresses?network=starknet - */ -const dataFeedAddress = '0x2579940ca3c41e7119283ceb82cd851c906cbb1510908a913d434861fdcb245' - -export async function readLatestRoundOffChain() { - const provider = new Provider({ - sequencer: { - network: constants.NetworkName.SN_GOERLI, - }, - }) - - const latestRound = await provider.callContract({ - contractAddress: dataFeedAddress, - entrypoint: 'latest_round_data', - }) - - printResult(latestRound) - return latestRound -} - -function printResult(latestRound: CallContractResponse) { - console.log('round_id =', parseInt(latestRound.result[0], 16)) - console.log('answer =', parseInt(latestRound.result[1], 16)) - console.log('block_num =', parseInt(latestRound.result[2], 16)) - console.log('observation_timestamp =', parseInt(latestRound.result[3], 16)) - console.log('transmission_timestamp =', parseInt(latestRound.result[4], 16)) -} - -readLatestRoundOffChain() diff --git a/examples/contracts/proxy-consumer/src/lib.cairo b/examples/contracts/proxy-consumer/src/lib.cairo deleted file mode 100644 index bf37de90f..000000000 --- a/examples/contracts/proxy-consumer/src/lib.cairo +++ /dev/null @@ -1 +0,0 @@ -mod proxy_consumer; diff --git a/examples/contracts/proxy-consumer/src/proxy_consumer.cairo b/examples/contracts/proxy-consumer/src/proxy_consumer.cairo deleted file mode 100644 index 5be522cd5..000000000 --- a/examples/contracts/proxy-consumer/src/proxy_consumer.cairo +++ /dev/null @@ -1,52 +0,0 @@ -#[starknet::contract] -mod ProxyConsumer { - use zeroable::Zeroable; - use traits::Into; - use traits::TryInto; - use option::OptionTrait; - - use starknet::ContractAddress; - 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 chainlink::ocr2::aggregator::Round; - - use chainlink::ocr2::aggregator_proxy::IAggregator; - use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcher; - use chainlink::ocr2::aggregator_proxy::IAggregatorDispatcherTrait; - - - #[storage] - struct Storage { - _proxy_address: ContractAddress, - _feed_data: Round, - } - - #[constructor] - fn constructor(ref self: ContractState, proxy_address: ContractAddress) { - assert(!proxy_address.is_zero(), 'proxy address 0'); - self._proxy_address.write(proxy_address); - get_latest_round_data(ref self); - } - - #[external(v0)] - fn get_latest_round_data(ref self: ContractState) -> Round { - let round = IAggregatorDispatcher { contract_address: self._proxy_address.read() } - .latest_round_data(); - self._feed_data.write(round); - round - } - - #[external(v0)] - fn get_stored_round(self: @ContractState) -> Round { - self._feed_data.read() - } - - #[external(v0)] - fn get_stored_feed_address(self: @ContractState) -> ContractAddress { - self._proxy_address.read() - } -} diff --git a/examples/contracts/proxy-consumer/yarn.lock b/examples/contracts/proxy-consumer/yarn.lock deleted file mode 100644 index 818935665..000000000 --- a/examples/contracts/proxy-consumer/yarn.lock +++ /dev/null @@ -1,95 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@noble/curves@^0.8.2": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.8.3.tgz#ad6d48baf2599cf1d58dcb734c14d5225c8996e0" - integrity sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ== - dependencies: - "@noble/hashes" "1.3.0" - -"@noble/curves@~1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" - integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw== - dependencies: - "@noble/hashes" "1.3.0" - -"@noble/hashes@1.3.0", "@noble/hashes@~1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== - -isomorphic-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" - integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== - dependencies: - node-fetch "^2.6.1" - whatwg-fetch "^3.4.1" - -lossless-json@^2.0.8: - version "2.0.9" - resolved "https://registry.yarnpkg.com/lossless-json/-/lossless-json-2.0.9.tgz#2e9a71a3dcbc6c59dee565e537b9084107b7fe37" - integrity sha512-PUfJ5foxULG1x/dXpSckmt0woBDqyq/WFoI885vEqjGwuP41K2EBYh2IT3zYx9dWqcTLIfXiCE5AjhF1jk9Sbg== - -micro-starknet@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/micro-starknet/-/micro-starknet-0.2.3.tgz#ff4e7caf599255d2110e9c57bb483dfaf493ccb3" - integrity sha512-6XBcC+GerlwJSR4iA0VaeXtS2wrayWFcA4PEzrJPMuFmWCaUtuGIq5K/DB5F/XgnL54/zl2Bxo690Lj7mYVA8A== - dependencies: - "@noble/curves" "~1.0.0" - "@noble/hashes" "~1.3.0" - -node-fetch@^2.6.1: - version "2.6.11" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" - integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== - dependencies: - whatwg-url "^5.0.0" - -pako@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" - integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== - -starknet@^5.2.0: - version "5.9.2" - resolved "https://registry.yarnpkg.com/starknet/-/starknet-5.9.2.tgz#2f82f2eb5e24912468e00df64cb349fd0f6fdc9c" - integrity sha512-zCoMQOlmaNeYlNjvVjYevaYcZv+6Uvivtn1n9IuF8cGtZKHVYf/7wqha8rjpFgGGCgDthJjqj+B7Zxu9oh0GFg== - dependencies: - "@noble/curves" "^0.8.2" - isomorphic-fetch "^3.0.0" - lossless-json "^2.0.8" - micro-starknet "^0.2.1" - pako "^2.0.4" - url-join "^4.0.1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-fetch@^3.4.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0"