Skip to content
This repository has been archived by the owner on Aug 2, 2024. It is now read-only.

Declare/Deploy Tx 🚀 #89

Merged
merged 16 commits into from
Mar 27, 2023
2 changes: 1 addition & 1 deletion crates/node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,6 @@ fn testnet_genesis(
},
transaction_payment: Default::default(),
/// Starknet Genesis configuration.
starknet: madara_runtime::pallet_starknet::GenesisConfig { contracts: vec![], _phantom: Default::default() },
starknet: madara_runtime::pallet_starknet::GenesisConfig { contracts: vec![], classes: vec![], _phantom: Default::default() },
}
}
212 changes: 161 additions & 51 deletions crates/pallets/starknet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,12 @@ pub mod pallet {

// use blockifier::execution::contract_class::ContractClass;
use blockifier::state::cached_state::{CachedState, ContractClassMapping, ContractStorageKey};
use blockifier::test_utils::{get_contract_class, get_test_contract_class, ACCOUNT_CONTRACT_PATH};
use frame_support::pallet_prelude::*;
use frame_support::traits::{OriginTrait, Randomness, Time};
use frame_system::pallet_prelude::*;
use mp_starknet::crypto::commitment;
use mp_starknet::crypto::hash::pedersen::PedersenHasher;
use mp_starknet::execution::{ClassHashWrapper, ContractAddressWrapper};
use mp_starknet::execution::{ClassHashWrapper, ContractAddressWrapper, ContractClassWrapper};
use mp_starknet::starknet_block::block::Block;
use mp_starknet::starknet_block::header::Header;
use mp_starknet::state::DictStateReader;
Expand All @@ -87,6 +86,7 @@ pub mod pallet {
use crate::types::{ContractStorageKeyWrapper, EthLogs, NonceWrapper, StarkFeltWrapper};

#[pallet::pallet]
#[pallet::without_storage_info] // TODO: Remove this when we have bounded every storage item.
AbdelStark marked this conversation as resolved.
Show resolved Hide resolved
pub struct Pallet<T>(_);

/// Configure the pallet by specifying the parameters and types on which it depends.
Expand Down Expand Up @@ -161,10 +161,16 @@ pub mod pallet {

/// Mapping from Starknet contract address to the contract's class hash.
#[pallet::storage]
#[pallet::getter(fn contract_class)]
#[pallet::getter(fn contracts)]
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
pub(super) type ContractClassHashes<T: Config> =
StorageMap<_, Twox64Concat, ContractAddressWrapper, ClassHashWrapper, ValueQuery>;

/// Mapping from Starknet class hash to contract class.
#[pallet::storage]
#[pallet::getter(fn contract_class)]
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
pub(super) type ContractClasses<T: Config> =
StorageMap<_, Twox64Concat, ClassHashWrapper, ContractClassWrapper, ValueQuery>;

/// Mapping from Starknet contract address to its nonce.
#[pallet::storage]
#[pallet::getter(fn nonce)]
Expand All @@ -184,13 +190,14 @@ pub mod pallet {
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub contracts: Vec<(ContractAddressWrapper, ClassHashWrapper)>,
pub classes: Vec<(ClassHashWrapper, ContractClassWrapper)>,
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
pub _phantom: PhantomData<T>,
}

#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { contracts: vec![], _phantom: PhantomData }
Self { contracts: vec![], classes: vec![], _phantom: PhantomData }
}
}

Expand All @@ -207,6 +214,10 @@ pub mod pallet {
ContractClassHashes::<T>::insert(address, class_hash);
}

for (class_hash, contract_class) in self.classes.iter() {
ContractClasses::<T>::insert(class_hash, contract_class);
}

LastKnownEthBlock::<T>::set(None);
}
}
Expand All @@ -228,8 +239,13 @@ pub mod pallet {
pub enum Error<T> {
AccountNotDeployed,
TransactionExecutionFailed,
ContractClassHashAlreadyAssociated,
ClassHashAlreadyDeclared,
ContractClassHashUnknown,
ContractClassAlreadyAssociated,
ContractClassMustBeSpecified,
AccountAlreadyDeployed,
ContractAddressAlreadyAssociated,
InvalidContractClass,
}

/// The Starknet pallet external functions.
Expand Down Expand Up @@ -273,7 +289,7 @@ pub mod pallet {

let block = Self::current_block().unwrap();
let state = &mut Self::create_state_reader();
match transaction.execute(state, block, TxType::InvokeTx) {
match transaction.execute(state, block, TxType::InvokeTx, None) {
Ok(v) => {
log!(info, "Transaction executed successfully: {:?}", v.unwrap());
}
Expand All @@ -291,6 +307,115 @@ pub mod pallet {
Ok(())
}

// Submit a Starknet declare transaction.
///
/// # Arguments
///
/// * `origin` - The origin of the transaction.
/// * `transaction` - The Starknet transaction.
///
/// # Returns
///
/// * `DispatchResult` - The result of the transaction.
///
/// # TODO
/// * Compute weight
#[pallet::call_index(3)]
#[pallet::weight(0)]
pub fn add_declare_transaction(_origin: OriginFor<T>, transaction: Transaction) -> DispatchResult {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
// TODO: add origin check when proxy pallet added

// Check if contract is deployed
ensure!(ContractClassHashes::<T>::contains_key(transaction.sender_address), Error::<T>::AccountNotDeployed);

// Check class hash is not already declared
ensure!(!ContractClasses::<T>::contains_key(transaction.call_entrypoint.class_hash.unwrap()), Error::<T>::ClassHashAlreadyDeclared);

// Check that contract class is not None
ensure!(transaction.contract_class.is_some(), Error::<T>::ContractClassMustBeSpecified);


let block = Self::current_block().unwrap();
let state = &mut Self::create_state_reader();
let contract_class = transaction.clone().contract_class.unwrap().to_starknet_contract_class();
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
match contract_class {
Ok(ref _v) => {
log!(info, "Contract class parsed successfully.");
}
Err(e) => {
log!(error, "Contract class parsing failed: {:?}", e);
return Err(Error::<T>::InvalidContractClass.into());
}
}

match transaction.execute(state, block, TxType::DeclareTx, Some(contract_class.unwrap())) {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
Ok(v) => {
log!(info, "Transaction executed successfully: {:?}", v.unwrap_or_default());
}
Err(e) => {
log!(error, "Transaction execution failed: {:?}", e);
return Err(Error::<T>::TransactionExecutionFailed.into());
}
}

// Append the transaction to the pending transactions.
Pending::<T>::try_append(transaction.clone()).unwrap();

// Associate contract class to class hash
Self::associate_class_hash(transaction.clone().call_entrypoint.class_hash.unwrap(), transaction.clone().contract_class.unwrap())?;
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Update class hashes root
AbdelStark marked this conversation as resolved.
Show resolved Hide resolved

Ok(())
}

// Submit a Starknet deploy transaction.
///
/// # Arguments
///
/// * `origin` - The origin of the transaction.
/// * `transaction` - The Starknet transaction.
///
/// # Returns
///
/// * `DispatchResult` - The result of the transaction.
///
/// # TODO
/// * Compute weight
#[pallet::call_index(4)]
#[pallet::weight(0)]
pub fn add_deploy_account_transaction(_origin: OriginFor<T>, transaction: Transaction) -> DispatchResult {
// TODO: add origin check when proxy pallet added

// Check if contract is deployed
ensure!(!ContractClassHashes::<T>::contains_key(transaction.sender_address), Error::<T>::AccountAlreadyDeployed);


let block = Self::current_block().unwrap();
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
let state = &mut Self::create_state_reader();
match transaction.execute(state, block, TxType::DeployTx, None) {
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
Ok(v) => {
log!(info, "Transaction executed successfully: {:?}", v.unwrap());
}
Err(e) => {
log!(error, "Transaction execution failed: {:?}", e);
return Err(Error::<T>::TransactionExecutionFailed.into());
}
}

// Append the transaction to the pending transactions.
Pending::<T>::try_append(transaction.clone()).unwrap();

// Associate contract class to class hash
Self::associate_contract_class(transaction.clone().sender_address, transaction.clone().call_entrypoint.class_hash.unwrap())?;

// TODO: Apply state diff and update state root

Ok(())
}



/// Consume a message from L1.
///
/// # Arguments
Expand All @@ -313,7 +438,7 @@ pub mod pallet {

let block = Self::current_block().unwrap();
let state = &mut Self::create_state_reader();
match transaction.execute(state, block, TxType::L1HandlerTx) {
match transaction.execute(state, block, TxType::L1HandlerTx, None) {
Ok(v) => {
log!(info, "Transaction executed successfully: {:?}", v.unwrap());
}
Expand Down Expand Up @@ -423,24 +548,45 @@ pub mod pallet {
Pending::<T>::kill();
}

/// Associate a contract class hash with a contract class info
///
/// # Arguments
///
/// * `contract_class_hash` - The contract class hash.
/// * `class_info` - The contract class info.
fn associate_class_hash(
contract_class_hash: ClassHashWrapper,
class_info: ContractClassWrapper,
) -> Result<(), DispatchError> {
// Check if the contract address is already associated with a contract class hash.
ensure!(
!ContractClasses::<T>::contains_key(contract_class_hash),
Error::<T>::ContractClassAlreadyAssociated
);

ContractClasses::<T>::insert(contract_class_hash, class_info);

Ok(())
}

/// Associate a contract address with a contract class hash.
///
/// # Arguments
///
/// * `contract_address` - The contract address.
/// * `contract_class_hash` - The contract class hash.
fn _associate_contract_class(
fn associate_contract_class(
contract_address: ContractAddressWrapper,
contract_class_hash: ClassHashWrapper,
) -> Result<(), DispatchError> {
// Check if the contract address is already associated with a contract class hash.
ensure!(
!ContractClassHashes::<T>::contains_key(contract_address),
Error::<T>::ContractClassHashAlreadyAssociated
Error::<T>::ContractAddressAlreadyAssociated
);

// Check if the contract class hash is known.
ensure!(ContractClassHashes::<T>::contains_key(contract_class_hash), Error::<T>::ContractClassHashUnknown);
ensure!(ContractClasses::<T>::contains_key(contract_class_hash), Error::<T>::ContractClassHashUnknown);

ContractClassHashes::<T>::insert(contract_address, contract_class_hash);

Expand Down Expand Up @@ -483,47 +629,11 @@ pub mod pallet {
})
.collect();

// let class_hash_to_class: ContractClassMapping = ContractClasses::<T>::iter().map(|(key, value)| {
// let class_hash = ClassHash(StarkFelt::new(key).unwrap());
// let contract_class = ContractClass::try_from(value).unwrap();
// (class_hash, contract_class)
// }).collect();
// TODO: remove this when declare is implemented
let class_hash_to_class: ContractClassMapping = HashMap::from([
(
ClassHash(
StarkFelt::try_from(
"0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", /* TEST ACCOUNT
* CONTRACT CLASS
* HASH */
)
.unwrap(),
),
get_contract_class(ACCOUNT_CONTRACT_PATH),
),
(
ClassHash(
StarkFelt::try_from(
"0x025ec026985a3bf9d0cc1fe17326b245bfdc3ff89b8fde106242a3ea56c5a918", /* TEST FEATURES
* CONTRACT CLASS
* HASH */
)
.unwrap(),
),
get_test_contract_class(),
),
(
ClassHash(
StarkFelt::try_from(
"0x1cb5d0b5b5146e1aab92eb9fc9883a32a33a604858bb0275ac0ee65d885bba8", /* TEST EMPTY L1
* HANDLER CONTRACT
* CLASS HASH */
)
.unwrap(),
),
get_contract_class(include_bytes!("../../../../ressources/l1_handler.json")),
),
]);
let class_hash_to_class: ContractClassMapping = ContractClasses::<T>::iter().map(|(key, value)| {
let class_hash = ClassHash(StarkFelt::new(key).unwrap());
let contract_class = value.to_starknet_contract_class().unwrap();
EvolveArt marked this conversation as resolved.
Show resolved Hide resolved
(class_hash, contract_class)
}).collect();

CachedState::new(DictStateReader {
address_to_class_hash,
Expand Down
2 changes: 2 additions & 0 deletions crates/pallets/starknet/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Message {
sender_address,
nonce,
call_entrypoint,
contract_class: None,
})
}
}
Expand Down Expand Up @@ -146,6 +147,7 @@ mod test {
storage_address: H256::from_low_u64_be(1).to_fixed_bytes(),
caller_address: ContractAddressWrapper::default(),
},
contract_class: None,
};
pretty_assertions::assert_eq!(test_message.try_into_transaction().unwrap(), expected_tx);
}
Expand Down
11 changes: 11 additions & 0 deletions crates/pallets/starknet/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use blockifier::test_utils::{get_contract_class, ACCOUNT_CONTRACT_PATH, get_test_contract_class};
use frame_support::traits::{ConstU16, ConstU64, GenesisBuild, Hooks};
use hex::FromHex;
use mp_starknet::execution::ContractClassWrapper;
use sp_core::H256;
use sp_runtime::testing::Header;
use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
Expand Down Expand Up @@ -71,6 +73,10 @@ impl pallet_starknet::Config for Test {
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();

let account_class = get_contract_class(ACCOUNT_CONTRACT_PATH);
let test_class = get_test_contract_class();
let l1_handler_class = get_contract_class(include_bytes!("../../../../ressources/l1_handler.json"));

// ACCOUNT CONTRACT
let contract_address_str = "02356b628D108863BAf8644c945d97bAD70190AF5957031f4852d00D0F690a77";
let contract_address_bytes = <[u8; 32]>::from_hex(contract_address_str).unwrap();
Expand Down Expand Up @@ -98,6 +104,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
(other_contract_address_bytes, other_class_hash_bytes),
(l1_handler_contract_address_bytes, l1_handler_class_hash_bytes),
],
classes: vec![
(class_hash_bytes, ContractClassWrapper::from(account_class)),
(other_class_hash_bytes, ContractClassWrapper::from(test_class)),
(l1_handler_class_hash_bytes, ContractClassWrapper::from(l1_handler_class)),
],
..Default::default()
}
.assimilate_storage(&mut t)
Expand Down
Loading