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

test: scarb library with template account contract #813

Merged
merged 3 commits into from
Oct 26, 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
1,763 changes: 1,745 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,15 @@ gloo-timers = { version = "0.3.0", features = ["futures"] }
[dev-dependencies]
anyhow = "1.0.89"
alloy-primitives = { version = "0.8.5", default-features = false }
chrono = "0.4.38"
katana-core = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
katana-executor = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
katana-node = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
katana-primitives = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
katana-rpc = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
katana-rpc-api = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" }
scarb = { git = "https://github.com/software-mansion/scarb/", tag = "v2.8.3" }
semver = { version = "1", features = ["serde"] }
wiremock = "0.6.2"

[patch.crates-io]
Expand Down
5 changes: 3 additions & 2 deletions tests/account_katana.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use beerus::{
TxnHash,
},
};
use common::constants::{
use starknet::constants::{
CLASS_HASH, COMPILED_ACCOUNT_CONTRACT_V2, COMPILED_ACCOUNT_CONTRACT_V3,
CONTRACT_ADDRESS, DECLARE_ACCOUNT_V2, DECLARE_ACCOUNT_V3, SENDER_ADDRESS,
};
use common::katana::Katana;
use starknet::katana::Katana;

mod common;
mod starknet;

async fn setup() -> (Katana, Client<Http>) {
let katana = Katana::init("http://127.0.0.1:0").await.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions tests/account_mock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use beerus::gen::{Address, BlockId, BlockTag, Felt, Rpc, TxnHash};

use common::{
use mock::{
dummies::{
declare_transaction, deploy_transaction, estimate_fee_transaction,
invoke_transaction,
Expand All @@ -15,7 +15,7 @@ use common::{
node::setup_client_with_mock_starknet_node,
};

mod common;
mod mock;

#[tokio::test]
async fn chain_id_test() {
Expand Down
4 changes: 4 additions & 0 deletions tests/common/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ pub enum Error {
Json(#[from] serde_json::Error),
#[error("starknet api error: {0:?}")]
Api(#[from] starknet_api::StarknetApiError),
#[error("IO error: {0:?}")]
IO(#[from] std::io::Error),
#[error("Anyhow error: {0:?}")]
Anyhow(#[from] anyhow::Error),
}
5 changes: 0 additions & 5 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
pub mod constants;
pub mod ctx;
pub mod dummies;
pub mod err;
pub mod katana;
pub mod matchers;
pub mod node;
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions tests/mock/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod dummies;
pub mod matchers;
pub mod node;
File renamed without changes.
1 change: 1 addition & 0 deletions tests/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use beerus::{
};

mod common;
mod starknet;

use common::err::Error;

Expand Down
8 changes: 4 additions & 4 deletions tests/common/constants.rs → tests/starknet/constants.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#[allow(dead_code)]
pub const COMPILED_ACCOUNT_CONTRACT_V2: &str =
include_str!("../clob/compiled_account_contract_v2.txt");
include_str!("./clob/compiled_account_contract_v2.txt");
#[allow(dead_code)]
pub const COMPILED_ACCOUNT_CONTRACT_V3: &str =
include_str!("../clob/compiled_account_contract_v3.txt");
include_str!("./clob/compiled_account_contract_v3.txt");
#[allow(dead_code)]
pub const DECLARE_ACCOUNT_V2: &str =
include_str!("../clob/declare_account_v2.txt");
include_str!("./clob/declare_account_v2.txt");
#[allow(dead_code)]
pub const DECLARE_ACCOUNT_V3: &str =
include_str!("../clob/declare_account_v3.txt");
include_str!("./clob/declare_account_v3.txt");

#[allow(dead_code)]
pub const CLASS_HASH: &str =
Expand Down
11 changes: 11 additions & 0 deletions tests/starknet/contract/account/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "account"
version = "0.1.0"
cairo-version = "2.5.4"

[dependencies]
starknet = ">=2.5.4"

[[target.starknet-contract]]
sierra = true
casm = true
149 changes: 149 additions & 0 deletions tests/starknet/contract/account/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use starknet::account::Call;

mod SUPPORTED_TX_VERSION {
const DEPLOY_ACCOUNT: felt252 = 1;
const DECLARE: felt252 = 2;
const INVOKE: felt252 = 1;
}

#[starknet::interface]
trait IAccount<T> {
fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
fn supports_interface(self: @T, interface_id: felt252) -> bool;
fn public_key(self: @T) -> felt252;
fn id(self: @T) -> u128;
}

#[starknet::contract]
mod Account {
use super::{Call, IAccount, SUPPORTED_TX_VERSION};
use starknet::{get_caller_address, call_contract_syscall, get_tx_info, VALIDATED};
use zeroable::Zeroable;
use array::{ArrayTrait, SpanTrait};
use ecdsa::check_ecdsa_signature;
use box::BoxTrait;

const SIMULATE_TX_VERSION_OFFSET: felt252 = 340282366920938463463374607431768211456; // 2**128
const SRC6_TRAIT_ID: felt252 = 1270010605630597976495846281167968799381097569185364931397797212080166453709; // hash of SNIP-6 trait

#[storage]
struct Storage {
public_key: felt252,
id: u128
}

#[constructor]
fn constructor(ref self: ContractState, public_key: felt252) {
self.public_key.write(public_key);
self.id.write(<ID>);
}

#[abi(embed_v0)]
impl AccountImpl of IAccount<ContractState> {
fn is_valid_signature(self: @ContractState, hash: felt252, signature: Array<felt252>) -> felt252 {
let is_valid = self.is_valid_signature_bool(hash, signature.span());
if is_valid { VALIDATED } else { 0 }
}

fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
interface_id == SRC6_TRAIT_ID
}

fn public_key(self: @ContractState) -> felt252 {
self.public_key.read()
}

fn id(self: @ContractState) -> u128 {
self.id.read()
}
}

#[abi(per_item)]
#[generate_trait]
impl ProtocolImpl of ProtocolTrait {
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
self.only_protocol();
self.only_supported_tx_version(SUPPORTED_TX_VERSION::INVOKE);
self.execute_multiple_calls(calls)
}

fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
self.only_protocol();
self.only_supported_tx_version(SUPPORTED_TX_VERSION::INVOKE);
self.validate_transaction()
}

fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
self.only_protocol();
self.only_supported_tx_version(SUPPORTED_TX_VERSION::DECLARE);
self.validate_transaction()
}

fn __validate_deploy__(self: @ContractState, class_hash: felt252, salt: felt252, public_key: felt252) -> felt252 {
self.only_protocol();
self.only_supported_tx_version(SUPPORTED_TX_VERSION::DEPLOY_ACCOUNT);
self.validate_transaction()
}
}

#[generate_trait]
impl PrivateImpl of PrivateTrait {
fn only_protocol(self: @ContractState) {
let sender = get_caller_address();
assert(sender.is_zero(), 'Account: invalid caller');
}

fn is_valid_signature_bool(self: @ContractState, hash: felt252, signature: Span<felt252>) -> bool {
let is_valid_length = signature.len() == 2_u32;

if !is_valid_length {
return false;
}

check_ecdsa_signature(
hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32)
)
}

fn validate_transaction(self: @ContractState) -> felt252 {
let tx_info = get_tx_info().unbox();
let tx_hash = tx_info.transaction_hash;
let signature = tx_info.signature;

let is_valid = self.is_valid_signature_bool(tx_hash, signature);
assert(is_valid, 'Account: Incorrect tx signature');
VALIDATED
}

fn execute_single_call(self: @ContractState, call: Call) -> Span<felt252> {
let Call{to, selector, calldata} = call;
call_contract_syscall(to, selector, calldata.into()).unwrap()
}

fn execute_multiple_calls(self: @ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
let mut res = ArrayTrait::new();
loop {
match calls.pop_front() {
Option::Some(call) => {
let _res = self.execute_single_call(call);
res.append(_res);
},
Option::None(_) => {
break ();
},
};
};
res
}

fn only_supported_tx_version(self: @ContractState, supported_tx_version: felt252) {
let tx_info = get_tx_info().unbox();
let version = tx_info.version;
assert(
version == supported_tx_version ||
version == SIMULATE_TX_VERSION_OFFSET + supported_tx_version,
'Account: Unsupported tx version'
);
}
}
}
File renamed without changes.
4 changes: 4 additions & 0 deletions tests/starknet/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod constants;
pub mod katana;
pub mod scarb;
pub mod utils;
30 changes: 30 additions & 0 deletions tests/starknet/scarb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use anyhow::Error;
use scarb::{
core::{Config, PackageId, PackageName, SourceId, TargetKind},
ops::{self, CompileOpts, FeaturesOpts, FeaturesSelector},
};
use semver::Version;
use std::{fs::canonicalize, path::PathBuf};

#[allow(dead_code)]
pub fn compile(toml: String) -> Result<(), Error> {
let toml_absolute = canonicalize(PathBuf::from(toml))?;
let opts = CompileOpts {
include_target_kinds: vec![],
exclude_target_kinds: vec![TargetKind::new("test")],
include_target_names: vec![],
features: FeaturesOpts {
features: FeaturesSelector::Features(vec![]),
no_default_features: false,
},
};
let packages = vec![PackageId::new(
PackageName::new("account"),
Version::new(0, 1, 0),
SourceId::for_path(toml_absolute.to_str().unwrap().into())?,
)];
let config = Config::builder(toml_absolute.to_str().unwrap()).build()?;
let ws = ops::read_workspace(config.manifest_path(), &config)?;

scarb::ops::compile(packages, opts, &ws)
}
28 changes: 28 additions & 0 deletions tests/starknet/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::fs;

use anyhow::Error;
use chrono;

const SOURCE_LIB: &str = "./tests/starknet/contract/account/src/lib.cairo";
const SOURCE_SCARB: &str = "./tests/starknet/contract/account/Scarb.toml";

#[allow(dead_code)]
pub fn prepare_account() -> Result<String, Error> {
let now = chrono::offset::Local::now();
let id = now.format("%Y%m%y%H%M%S").to_string();
let target = "./target/account-".to_string() + &id;
let target_lib = target.clone() + "/src/lib.cairo";
let target_scarb = target.clone() + "/Scarb.toml";
let target_src = target.clone() + "/src";

fs::create_dir(target)?;
fs::create_dir(target_src)?;
fs::copy(SOURCE_LIB, target_lib.clone())?;
fs::copy(SOURCE_SCARB, target_scarb.clone())?;

let account_template = fs::read_to_string(target_lib.clone())?;
let account_new = account_template.replace("<ID>", &id);
fs::write(target_lib, account_new)?;

Ok(target_scarb)
}
Loading