Skip to content

Commit

Permalink
feat: add config and messaging base of contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Feb 6, 2024
1 parent cecc61f commit 0280cf4
Show file tree
Hide file tree
Showing 18 changed files with 817 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
scarb 2.5.0
scarb 2.4.4
starknet-foundry 0.16.0
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@

Starknet Core Contract components in Cairo.

## Repository architecture

The different tasks of the Starknet Core Contract are here mapped to what we can call an "Appchain" Core Contract.

The functionalities of the core contract are split over several components. Each component is usually placed into a directory, event if it is simple. Doing so allow a good separation of additional files that may be written in the context of a component.

Due to a limitation of `starknet foundry`, we can't declare a contract that is defined under the `tests` directory. For this reason, `mock` contract are defined in their respective component location. This is also a good way for the component write to illustrate the minimum required to use the component.

* `appchain.cairo`: core contract of the appchain on Starknet.

* `config`: base configuration for the core contract.

* `messaging`: messaging between Appchain - Starknet.

## Build

To build the project, run:
Expand All @@ -27,3 +41,34 @@ To test the project, run:
```bash
snforge test
```

## Code style (cairo)

* Use `snake_case` for module name and not `PascalCase`.

* Don't import directly a function from a module.
```rust
// Prefer that:
let addr = starknet::contract_address_const::<0>();

// Instead of:
use starknet::contract_address_const;
let addr = contract_address_const::<0>();
```

* Order import as much as you can (and avoid relative import, if not inside tests).
```rust
// Core or built-in plugins like starknet.
use starknet::...;

// Third party modules
use openzeppelin::...;

// Package
use piltover::...;

// Relative
super::...;
```

* Document functions inside the trait, and add details if needed in the implementation.
6 changes: 6 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "openzeppelin"
version = "0.8.1"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.8.1#5c7a022f333dca721fe490c2191a811fa2566a89"

[[package]]
name = "piltover"
version = "0.1.0"
dependencies = [
"openzeppelin",
"snforge_std",
]

Expand Down
4 changes: 3 additions & 1 deletion Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ edition = "2023_10"

[dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.16.0" }
starknet = "2.5.0"
starknet = "=2.4.4"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.8.1" }

[[target.starknet-contract]]
casm = true
sierra = true
51 changes: 51 additions & 0 deletions src/appchain.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! SPDX-License-Identifier: MIT
//!
//!

mod Errors {
const INVALID_ADDRESS: felt252 = 'Config: invalid address';
}

/// Appchain settlement contract on starknet.
#[starknet::contract]
mod appchain {
use starknet::ContractAddress;
use openzeppelin::access::ownable::{OwnableComponent as ownable_cpt, interface::IOwnable};

use piltover::config::{config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig};

component!(path: ownable_cpt, storage: ownable, event: OwnableEvent);
component!(path: config_cpt, storage: config, event: ConfigEvent);

#[abi(embed_v0)]
impl ConfigImpl = config_cpt::ConfigImpl<ContractState>;

#[storage]
struct Storage {
#[substorage(v0)]
ownable: ownable_cpt::Storage,
#[substorage(v0)]
config: config_cpt::Storage,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: ownable_cpt::Event,
#[flat]
ConfigEvent: config_cpt::Event,
}

/// Initializes the contract.
///
/// # Arguments
///
/// * `address` - The contract address of the owner.
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.ownable.transfer_ownership(owner);

assert(self.config.is_owner_or_operator(owner), 'bad');
}
}
102 changes: 102 additions & 0 deletions src/config/component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! SPDX-License-Identifier: MIT
//!
//! Base configuration for appchain contract.

/// Errors.
mod errors {
const INVALID_CALLER: felt252 = 'Config: not owner or operator';
}

/// Configuration component.
///
/// Depends on `ownable` to ensure the configuration is
/// only editable by contract's owner.
#[starknet::component]
mod config_cpt {
use starknet::ContractAddress;

use openzeppelin::access::ownable::{
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal,
interface::IOwnable,
};

use piltover::config::interface::IConfig;

use super::errors;

#[storage]
struct Storage {
/// Appchain operator that is allowed to update the state.
operator: ContractAddress,
/// Program info (StarknetOS), with program hash and config hash.
program_info: (felt252, felt252),
/// Facts registry contract address.
facts_registry: ContractAddress,
}

#[embeddable_as(ConfigImpl)]
impl Config<
TContractState,
+HasComponent<TContractState>,
impl Ownable: ownable_cpt::HasComponent<TContractState>,
+Drop<TContractState>
> of IConfig<ComponentState<TContractState>> {
fn set_operator(ref self: ComponentState<TContractState>, address: ContractAddress) {
get_dep_component!(self, Ownable).assert_only_owner();
self.operator.write(address)
}

fn get_operator(self: @ComponentState<TContractState>) -> ContractAddress {
self.operator.read()
}

fn set_program_info(
ref self: ComponentState<TContractState>, program_hash: felt252, config_hash: felt252
) {
self.assert_only_owner_or_operator();
self.program_info.write((program_hash, config_hash))
}

fn get_program_info(self: @ComponentState<TContractState>) -> (felt252, felt252) {
self.program_info.read()
}

fn set_facts_registry(ref self: ComponentState<TContractState>, address: ContractAddress) {
self.assert_only_owner_or_operator();
self.facts_registry.write(address)
}

fn get_facts_registry(self: @ComponentState<TContractState>) -> ContractAddress {
self.facts_registry.read()
}
}

#[generate_trait]
impl InternalImpl<
TContractState,
+HasComponent<TContractState>,
impl Ownable: ownable_cpt::HasComponent<TContractState>,
+Drop<TContractState>
> of InternalTrait<TContractState> {
/// Asserts if the caller is the owner of the contract or
/// the authorized operator. Reverts otherwise.
fn assert_only_owner_or_operator(ref self: ComponentState<TContractState>) {
assert(
self.is_owner_or_operator(starknet::get_caller_address()), errors::INVALID_CALLER
);
}

/// Verifies if the given address is the owner of the contract
/// or the authorized operator.
///
/// # Arguments
///
/// * `address` - The contrat address to verify.
fn is_owner_or_operator(
ref self: ComponentState<TContractState>, address: ContractAddress
) -> bool {
let owner = get_dep_component!(self, Ownable).owner();
address == owner || address == self.operator.read()
}
}
}
53 changes: 53 additions & 0 deletions src/config/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! SPDX-License-Identifier: MIT
//!
//! Interface for appchain settlement contract configuration.
use starknet::ContractAddress;

#[starknet::interface]
trait IConfig<T> {
/// Sets the operator that is in charge to push state updates.
///
/// # Arguments
///
/// * `address` - The operator account address.
fn set_operator(ref self: T, address: ContractAddress);

/// Gets the operator address.
///
/// # Returns
///
/// The operator's address.
fn get_operator(self: @T) -> ContractAddress;

/// Sets the information of the program that generates the
/// state transition trace (namely StarknetOS).
///
/// # Arguments
///
/// * `program_hash` - The program hash.
/// * `config_hash` - The program's config hash.
fn set_program_info(ref self: T, program_hash: felt252, config_hash: felt252);

/// Gets the information of the program that generates the
/// state transition trace (namely StarknetOS).
///
/// # Returns
///
/// The program hash and it's configuration hash.
fn get_program_info(self: @T) -> (felt252, felt252);

/// Sets the facts registry contract address, which is already
/// initialized with the verifier information.
///
/// # Arguments
///
/// * `address` - The facts registry contract's address.
fn set_facts_registry(ref self: T, address: ContractAddress);

/// Gets the facts registry contract address.
///
/// # Returns
///
/// The contract address of the facts registry.
fn get_facts_registry(self: @T) -> ContractAddress;
}
38 changes: 38 additions & 0 deletions src/config/mock.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#[starknet::contract]
mod config_mock {
use starknet::ContractAddress;

use openzeppelin::access::ownable::{
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal
};

use piltover::config::{config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig};

component!(path: ownable_cpt, storage: ownable, event: OwnableEvent);
component!(path: config_cpt, storage: config, event: ConfigEvent);

#[abi(embed_v0)]
impl ConfigImpl = config_cpt::ConfigImpl<ContractState>;

#[storage]
struct Storage {
#[substorage(v0)]
ownable: ownable_cpt::Storage,
#[substorage(v0)]
config: config_cpt::Storage
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: ownable_cpt::Event,
#[flat]
ConfigEvent: config_cpt::Event
}

#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
self.ownable.initializer(owner);
}
}
53 changes: 53 additions & 0 deletions src/interface.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! SPDX-License-Identifier: MIT
//!
//! Interface for appchain settlement contract.
use starknet::ContractAddress;

#[starknet::interface]
trait IAppchain<T> {
/// Sets the operator that is in charge to push state updates.
///
/// # Arguments
///
/// * `address` - The operator account address.
fn set_operator(ref self: T, address: ContractAddress);

/// Gets the operator address.
///
/// # Returns
///
/// The operator's address.
fn get_operator(self: @T) -> ContractAddress;

/// Sets the information of the program that generates the
/// state transition trace (namely StarknetOS).
///
/// # Arguments
///
/// * `program_hash` - The program hash.
/// * `config_hash` - The program's config hash.
fn set_program_info(ref self: T, program_hash: felt252, config_hash: felt252);

/// Gets the information of the program that generates the
/// state transition trace (namely StarknetOS).
///
/// # Returns
///
/// The program hash and it's configuration hash.
fn get_program_info(self: @T) -> (felt252, felt252);

/// Sets the facts registry contract address, which is already
/// initialized with the verifier information.
///
/// # Arguments
///
/// * `address` - The facts registry contract's address.
fn set_facts_registry(ref self: T, address: ContractAddress);

/// Gets the facts registry contract address.
///
/// # Returns
///
/// The contract address of the facts registry.
fn get_facts_registry(self: @T) -> ContractAddress;
}
Loading

0 comments on commit 0280cf4

Please sign in to comment.