Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config and messaging base of contracts #1

Merged
merged 18 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.5.3
starknet-foundry 0.16.0
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
<div align="center">
<img src="book/src/assets/piltover.png" height="300"/>

[![Check Workflow Status](https://github.com/keep-starknet-strange/piltover/actions/workflows/check.yml/badge.svg)](https://github.com/keep-starknet-strange/piltover/actions/workflows/check.yml)
[![Build Workflow Status](https://github.com/keep-starknet-strange/piltover/actions/workflows/build.yml/badge.svg)](https://github.com/keep-starknet-strange/piltover/actions/workflows/build.yml)
[![Check Workflow Status](https://github.com/keep-starknet-strange/piltover/actions/workflows/check.yml/badge.svg)](https://github.com/keep-starknet-strange/piltover/actions/workflows/check.yml)
[![Build Workflow Status](https://github.com/keep-starknet-strange/piltover/actions/workflows/build.yml/badge.svg)](https://github.com/keep-starknet-strange/piltover/actions/workflows/build.yml)

[![Exploration_Team](https://img.shields.io/badge/Exploration_Team-29296E.svg?&style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJhIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODEgMTgxIj48ZGVmcz48c3R5bGU+LmJ7ZmlsbDojZmZmO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iYiIgZD0iTTE3Ni43Niw4OC4xOGwtMzYtMzcuNDNjLTEuMzMtMS40OC0zLjQxLTIuMDQtNS4zMS0xLjQybC0xMC42MiwyLjk4LTEyLjk1LDMuNjNoLjc4YzUuMTQtNC41Nyw5LjktOS41NSwxNC4yNS0xNC44OSwxLjY4LTEuNjgsMS44MS0yLjcyLDAtNC4yN0w5Mi40NSwuNzZxLTEuOTQtMS4wNC00LjAxLC4xM2MtMTIuMDQsMTIuNDMtMjMuODMsMjQuNzQtMzYsMzcuNjktMS4yLDEuNDUtMS41LDMuNDQtLjc4LDUuMThsNC4yNywxNi41OGMwLDIuNzIsMS40Miw1LjU3LDIuMDcsOC4yOS00LjczLTUuNjEtOS43NC0xMC45Ny0xNS4wMi0xNi4wNi0xLjY4LTEuODEtMi41OS0xLjgxLTQuNCwwTDQuMzksODguMDVjLTEuNjgsMi4zMy0xLjgxLDIuMzMsMCw0LjUzbDM1Ljg3LDM3LjNjMS4zNiwxLjUzLDMuNSwyLjEsNS40NCwxLjQybDExLjQtMy4xMSwxMi45NS0zLjYzdi45MWMtNS4yOSw0LjE3LTEwLjIyLDguNzYtMTQuNzYsMTMuNzNxLTMuNjMsMi45OC0uNzgsNS4zMWwzMy40MSwzNC44NGMyLjIsMi4yLDIuOTgsMi4yLDUuMTgsMGwzNS40OC0zNy4xN2MxLjU5LTEuMzgsMi4xNi0zLjYsMS40Mi01LjU3LTEuNjgtNi4wOS0zLjI0LTEyLjMtNC43OS0xOC4zOS0uNzQtMi4yNy0xLjIyLTQuNjItMS40Mi02Ljk5LDQuMyw1LjkzLDkuMDcsMTEuNTIsMTQuMjUsMTYuNzEsMS42OCwxLjY4LDIuNzIsMS42OCw0LjQsMGwzNC4zMi0zNS43NHExLjU1LTEuODEsMC00LjAxWm0tNzIuMjYsMTUuMTVjLTMuMTEtLjc4LTYuMDktMS41NS05LjE5LTIuNTktMS43OC0uMzQtMy42MSwuMy00Ljc5LDEuNjhsLTEyLjk1LDEzLjg2Yy0uNzYsLjg1LTEuNDUsMS43Ni0yLjA3LDIuNzJoLS42NWMxLjMtNS4zMSwyLjcyLTEwLjYyLDQuMDEtMTUuOGwxLjY4LTYuNzNjLjg0LTIuMTgsLjE1LTQuNjUtMS42OC02LjA5bC0xMi45NS0xNC4xMmMtLjY0LS40NS0xLjE0LTEuMDgtMS40Mi0xLjgxbDE5LjA0LDUuMTgsMi41OSwuNzhjMi4wNCwuNzYsNC4zMywuMTQsNS43LTEuNTVsMTIuOTUtMTQuMzhzLjc4LTEuMDQsMS42OC0xLjE3Yy0xLjgxLDYuNi0yLjk4LDE0LjEyLTUuNDQsMjAuNDYtMS4wOCwyLjk2LS4wOCw2LjI4LDIuNDYsOC4xNiw0LjI3LDQuMTQsOC4yOSw4LjU1LDEyLjk1LDEyLjk1LDAsMCwxLjMsLjkxLDEuNDIsMi4wN2wtMTMuMzQtMy42M1oiLz48L3N2Zz4=)](https://github.com/keep-starknet-strange)
[![Exploration_Team](https://img.shields.io/badge/Exploration_Team-29296E.svg?&style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJhIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODEgMTgxIj48ZGVmcz48c3R5bGU+LmJ7ZmlsbDojZmZmO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iYiIgZD0iTTE3Ni43Niw4OC4xOGwtMzYtMzcuNDNjLTEuMzMtMS40OC0zLjQxLTIuMDQtNS4zMS0xLjQybC0xMC42MiwyLjk4LTEyLjk1LDMuNjNoLjc4YzUuMTQtNC41Nyw5LjktOS41NSwxNC4yNS0xNC44OSwxLjY4LTEuNjgsMS44MS0yLjcyLDAtNC4yN0w5Mi40NSwuNzZxLTEuOTQtMS4wNC00LjAxLC4xM2MtMTIuMDQsMTIuNDMtMjMuODMsMjQuNzQtMzYsMzcuNjktMS4yLDEuNDUtMS41LDMuNDQtLjc4LDUuMThsNC4yNywxNi41OGMwLDIuNzIsMS40Miw1LjU3LDIuMDcsOC4yOS00LjczLTUuNjEtOS43NC0xMC45Ny0xNS4wMi0xNi4wNi0xLjY4LTEuODEtMi41OS0xLjgxLTQuNCwwTDQuMzksODguMDVjLTEuNjgsMi4zMy0xLjgxLDIuMzMsMCw0LjUzbDM1Ljg3LDM3LjNjMS4zNiwxLjUzLDMuNSwyLjEsNS40NCwxLjQybDExLjQtMy4xMSwxMi45NS0zLjYzdi45MWMtNS4yOSw0LjE3LTEwLjIyLDguNzYtMTQuNzYsMTMuNzNxLTMuNjMsMi45OC0uNzgsNS4zMWwzMy40MSwzNC44NGMyLjIsMi4yLDIuOTgsMi4yLDUuMTgsMGwzNS40OC0zNy4xN2MxLjU5LTEuMzgsMi4xNi0zLjYsMS40Mi01LjU3LTEuNjgtNi4wOS0zLjI0LTEyLjMtNC43OS0xOC4zOS0uNzQtMi4yNy0xLjIyLTQuNjItMS40Mi02Ljk5LDQuMyw1LjkzLDkuMDcsMTEuNTIsMTQuMjUsMTYuNzEsMS42OCwxLjY4LDIuNzIsMS42OCw0LjQsMGwzNC4zMi0zNS43NHExLjU1LTEuODEsMC00LjAxWm0tNzIuMjYsMTUuMTVjLTMuMTEtLjc4LTYuMDktMS41NS05LjE5LTIuNTktMS43OC0uMzQtMy42MSwuMy00Ljc5LDEuNjhsLTEyLjk1LDEzLjg2Yy0uNzYsLjg1LTEuNDUsMS43Ni0yLjA3LDIuNzJoLS42NWMxLjMtNS4zMSwyLjcyLTEwLjYyLDQuMDEtMTUuOGwxLjY4LTYuNzNjLjg0LTIuMTgsLjE1LTQuNjUtMS42OC02LjA5bC0xMi45NS0xNC4xMmMtLjY0LS40NS0xLjE0LTEuMDgtMS40Mi0xLjgxbDE5LjA0LDUuMTgsMi41OSwuNzhjMi4wNCwuNzYsNC4zMywuMTQsNS43LTEuNTVsMTIuOTUtMTQuMzhzLjc4LTEuMDQsMS42OC0xLjE3Yy0xLjgxLDYuNi0yLjk4LDE0LjEyLTUuNDQsMjAuNDYtMS4wOCwyLjk2LS4wOCw2LjI4LDIuNDYsOC4xNiw0LjI3LDQuMTQsOC4yOSw4LjU1LDEyLjk1LDEyLjk1LDAsMCwxLjMsLjkxLDEuNDIsMi4wN2wtMTMuMzQtMy42M1oiLz48L3N2Zz4=)](https://github.com/keep-starknet-strange)

</div>

## Overview

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,20 @@ 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>();
```

- 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.9.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.9.0#861fc416f87addbe23a3b47f9d19ab27c10d5dc8"

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

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

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

[scripts]
test = "snforge test"

drspacemn marked this conversation as resolved.
Show resolved Hide resolved
[[target.starknet-contract]]
casm = true
sierra = true

[tool.fmt]
sort-module-level-items = true

103 changes: 103 additions & 0 deletions src/appchain.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//! SPDX-License-Identifier: MIT
//!
//!

mod errors {
const INVALID_ADDRESS: felt252 = 'Config: invalid address';
const SNOS_INVALID_PROGRAM_OUTPUT_SIZE: felt252 = 'snos: invalid output size';
const SNOS_INVALID_MESSAGES_SEGMENTS: felt252 = 'snos: invalid messages segments';
}

/// Appchain settlement contract on starknet.
#[starknet::contract]
mod appchain {
use openzeppelin::access::ownable::{
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal,
interface::IOwnable
};
use piltover::config::{config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig};
use piltover::interface::IAppchain;
use piltover::messaging::{
messaging_cpt, messaging_cpt::InternalTrait as MessagingInternal, IMessaging,
output_process, output_process::{MessageToStarknet, MessageToAppchain},
};
use piltover::snos_output;
use starknet::ContractAddress;
use super::errors;

/// The default cancellation delay of 5 days.
const CANCELLATION_DELAY_SECS: u64 = 432000;

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

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

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

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

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

#[abi(embed_v0)]
impl Appchain of IAppchain<ContractState> {
fn update_state(ref self: ContractState, program_output: Span<felt252>) {
self.config.assert_only_owner_or_operator();

// TODO(#2): reentrancy guard.
// TODO(#3): facts verification.
// TODO(#4): update the current state (component needed).

// Header size + 2 messages segments len.
assert(
program_output.len() > snos_output::HEADER_SIZE + 2,
errors::SNOS_INVALID_PROGRAM_OUTPUT_SIZE
);

let mut offset = snos_output::HEADER_SIZE;

// TODO(#7): We should update SNOS output to have the messages count
// instead of the messages segment len.

let mut messages_segments = program_output.slice(offset, program_output.len() - offset);

let (messages_to_starknet, messages_to_appchain) =
output_process::gather_messages_from_output(
messages_segments
);

self.messaging.process_messages_to_starknet(messages_to_starknet);
self.messaging.process_messages_to_appchain(messages_to_appchain);
}
}
}
99 changes: 99 additions & 0 deletions src/config/component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! 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 openzeppelin::access::ownable::{
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal,
interface::IOwnable,
};
use piltover::config::interface::IConfig;
use starknet::ContractAddress;
use super::errors;

#[storage]
struct Storage {
/// Appchain operator that is allowed to update the state.
// TODO(#9): Multiple Operators
operator: ContractAddress,
drspacemn marked this conversation as resolved.
Show resolved Hide resolved
/// Program info (StarknetOS), with program hash and config hash.
program_info: (felt252, felt252),
/// Facts registry contract address.
facts_registry: ContractAddress,
drspacemn marked this conversation as resolved.
Show resolved Hide resolved
}

#[embeddable_as(ConfigImpl)]
impl Config<
TContractState,
+HasComponent<TContractState>,
impl Ownable: ownable_cpt::HasComponent<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))
// TODO(#6): ProgramHashChanged Event
}

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>,
> 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;
}
36 changes: 36 additions & 0 deletions src/config/mock.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#[starknet::contract]
mod config_mock {
use openzeppelin::access::ownable::{
OwnableComponent as ownable_cpt, OwnableComponent::InternalTrait as OwnableInternal
};
use piltover::config::{config_cpt, config_cpt::InternalTrait as ConfigInternal, IConfig};
use starknet::ContractAddress;

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);
}
}
Loading