-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: State update component / events (#20)
* Added new component state and related events + testing * Implemented state update method + testing
- Loading branch information
Showing
8 changed files
with
320 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
//! SPDX-License-Identifier: MIT | ||
//! | ||
//! Appchain - Starknet state component. | ||
|
||
/// Errors. | ||
mod errors { | ||
const INVALID_BLOCK_NUMBER: felt252 = 'State: invalid block number'; | ||
const INVALID_PREVIOUS_ROOT: felt252 = 'State: invalid previous root'; | ||
} | ||
|
||
/// State component. | ||
#[starknet::component] | ||
mod state_cpt { | ||
use piltover::snos_output::ProgramOutput; | ||
use piltover::state::interface::IState; | ||
use super::errors; | ||
|
||
type StateRoot = felt252; | ||
type BlockNumber = felt252; | ||
type BlockHash = felt252; | ||
|
||
#[storage] | ||
struct Storage { | ||
state_root: StateRoot, | ||
block_number: BlockNumber, | ||
block_hash: BlockHash, | ||
} | ||
|
||
#[event] | ||
#[derive(Drop, starknet::Event)] | ||
enum Event {} | ||
|
||
#[embeddable_as(StateImpl)] | ||
impl State< | ||
TContractState, +HasComponent<TContractState>, | ||
> of IState<ComponentState<TContractState>> { | ||
fn update(ref self: ComponentState<TContractState>, program_output: Span<felt252>,) { | ||
let mut program_output = program_output; | ||
let program_output: ProgramOutput = Serde::deserialize(ref program_output).unwrap(); | ||
|
||
// Check the blockNumber first as the error is less ambiguous then INVALID_PREVIOUS_ROOT. | ||
self.block_number.write(self.block_number.read() + 1); | ||
assert( | ||
self.block_number.read() == program_output.block_number, | ||
errors::INVALID_BLOCK_NUMBER | ||
); | ||
|
||
self.block_hash.write(program_output.block_hash); | ||
|
||
assert( | ||
self.state_root.read() == program_output.prev_state_root, | ||
errors::INVALID_PREVIOUS_ROOT | ||
); | ||
|
||
self.state_root.write(program_output.new_state_root); | ||
} | ||
|
||
fn get_state(self: @ComponentState<TContractState>) -> (StateRoot, BlockNumber, BlockHash) { | ||
(self.state_root.read(), self.block_number.read(), self.block_hash.read()) | ||
} | ||
} | ||
|
||
#[generate_trait] | ||
impl InternalImpl< | ||
TContractState, +HasComponent<TContractState>, | ||
> of InternalTrait<TContractState> { | ||
/// Initialized the messaging component. | ||
/// # Arguments | ||
/// | ||
/// * `state_root` - The state root. | ||
/// * `block_number` - The current block number. | ||
/// * `block_hash` - The hash of the current block. | ||
fn initialize( | ||
ref self: ComponentState<TContractState>, | ||
state_root: StateRoot, | ||
block_number: BlockNumber, | ||
block_hash: BlockHash, | ||
) { | ||
self.state_root.write(state_root); | ||
self.block_number.write(block_number); | ||
self.block_hash.write(block_hash); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
//! SPDX-License-Identifier: MIT | ||
//! | ||
//! Interface for Appchain - Starknet state. | ||
|
||
#[starknet::interface] | ||
trait IState<T> { | ||
/// Validates that the 'blockNumber' and the previous root are consistent with the | ||
/// current state and updates the state. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `program_output` - The StarknetOS state update output. | ||
fn update(ref self: T, program_output: Span<felt252>,); | ||
|
||
/// Gets the current state. | ||
/// | ||
/// # Returns | ||
/// | ||
/// The state root, the block number and the block hash. | ||
fn get_state(self: @T) -> (felt252, felt252, felt252); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#[starknet::contract] | ||
mod state_mock { | ||
use piltover::state::{state_cpt, state_cpt::InternalTrait as StateInternal, IState}; | ||
|
||
component!(path: state_cpt, storage: state, event: StateEvent); | ||
|
||
#[abi(embed_v0)] | ||
impl StateImpl = state_cpt::StateImpl<ContractState>; | ||
|
||
#[storage] | ||
struct Storage { | ||
#[substorage(v0)] | ||
state: state_cpt::Storage, | ||
} | ||
|
||
#[event] | ||
#[derive(Drop, starknet::Event)] | ||
enum Event { | ||
#[flat] | ||
StateEvent: state_cpt::Event, | ||
} | ||
|
||
#[constructor] | ||
fn constructor( | ||
ref self: ContractState, state_root: felt252, block_number: felt252, block_hash: felt252, | ||
) { | ||
self.state.initialize(state_root, block_number, block_hash); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
use piltover::state::{ | ||
state_cpt, state_cpt::InternalTrait as StateInternal, IState, IStateDispatcher, | ||
IStateDispatcherTrait, state_mock, | ||
}; | ||
use snforge_std as snf; | ||
use snforge_std::{ContractClassTrait}; | ||
|
||
/// Deploys the mock with a specific state. | ||
fn deploy_mock_with_state( | ||
state_root: felt252, block_number: felt252, block_hash: felt252, | ||
) -> IStateDispatcher { | ||
let contract = snf::declare('state_mock'); | ||
let calldata = array![state_root, block_number, block_hash]; | ||
let contract_address = contract.deploy(@calldata).unwrap(); | ||
IStateDispatcher { contract_address } | ||
} | ||
|
||
#[test] | ||
fn state_update_ok() { | ||
let mock = deploy_mock_with_state( | ||
state_root: 'state_root', block_number: 1, block_hash: 'block_hash_1', | ||
); | ||
|
||
let mut valid_state_update = array![ | ||
'state_root', | ||
'new_state_root', | ||
2, | ||
'block_hash_2', | ||
'config_hash', // Header. | ||
0, // appc to sn messages segment. | ||
0, // sn to appc messages segment. | ||
] | ||
.span(); | ||
|
||
mock.update(valid_state_update); | ||
|
||
let (state_root, block_number, block_hash) = mock.get_state(); | ||
|
||
assert(state_root == 'new_state_root', 'invalid state root'); | ||
assert(block_number == 2, 'invalid block number'); | ||
assert(block_hash == 'block_hash_2', 'invalid block hash'); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('State: invalid block number',))] | ||
fn state_update_invalid_block_number() { | ||
let mock = deploy_mock_with_state( | ||
state_root: 'state_root', block_number: 1, block_hash: 'block_hash_1', | ||
); | ||
|
||
let mut invalid_state_update = array![ | ||
'state_root', | ||
'new_state_root', | ||
99999, | ||
'block_hash_2', | ||
'config_hash', // Header. | ||
0, // appc to sn messages segment. | ||
0, // sn to appc messages segment. | ||
] | ||
.span(); | ||
|
||
mock.update(invalid_state_update); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('State: invalid previous root',))] | ||
fn state_update_invalid_previous_root() { | ||
let mock = deploy_mock_with_state( | ||
state_root: 'state_root', block_number: 1, block_hash: 'block_hash_1', | ||
); | ||
|
||
let mut invalid_state_update = array![ | ||
'invalid_state_root', | ||
'new_state_root', | ||
2, | ||
'block_hash_2', | ||
'config_hash', // Header. | ||
0, // appc to sn messages segment. | ||
0, // sn to appc messages segment. | ||
] | ||
.span(); | ||
|
||
mock.update(invalid_state_update); | ||
} |
Oops, something went wrong.