Skip to content

Commit

Permalink
test: deploy account on katana via beerus, scarb and starkli (#827)
Browse files Browse the repository at this point in the history
  • Loading branch information
ICavlek authored Nov 6, 2024
1 parent c767f51 commit 76e4240
Show file tree
Hide file tree
Showing 8 changed files with 1,321 additions and 124 deletions.
1,117 changes: 1,016 additions & 101 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 @@ -73,6 +73,7 @@ web-time = "1.1.0"
anyhow = "1.0.92"
alloy-primitives = { version = "0.8.5", default-features = false }
chrono = "0.4.38"
clap = "4.5.20"
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" }
Expand All @@ -81,6 +82,8 @@ 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"] }
starkli = { git = "https://github.com/ICavlek/starkli", branch = "ic/starkli_as_lib" }
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs", rev = "db1fa598232f0698d942cc974f481b5d888ac080", features = ["ledger"] }
wiremock = "0.6.2"

[patch.crates-io]
Expand Down
75 changes: 69 additions & 6 deletions tests/account_katana.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::{thread, time};
use std::{sync::Arc, thread, time};

use ::starknet::core::types::PriceUnit;
use beerus::{
client::Http,
client::{Http, State},
gen::{
client::Client, Address, BlockId, BlockTag, BroadcastedDeclareTxn,
BroadcastedDeployAccountTxn, BroadcastedInvokeTxn, BroadcastedTxn,
Expand All @@ -10,12 +11,20 @@ use beerus::{
InvokeTxnV1Type, InvokeTxnV1Version, Rpc, SimulationFlagForEstimateFee,
TxnHash,
},
rpc::{serve, Server},
};
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::err::Error;
use starknet::{
constants::{
CLASS_HASH, COMPILED_ACCOUNT_CONTRACT_V2, COMPILED_ACCOUNT_CONTRACT_V3,
CONTRACT_ADDRESS, DECLARE_ACCOUNT_V2, DECLARE_ACCOUNT_V3,
SENDER_ADDRESS,
},
starkli::{PreFundedAccount, Starkli},
utils,
};
use starknet::katana::Katana;
use starknet::{katana::Katana, scarb};
use tokio::sync::RwLock;

mod common;
mod starknet;
Expand All @@ -27,6 +36,22 @@ async fn setup() -> (Katana, Client<Http>) {
(katana, client)
}

async fn setup_beerus_with_katana() -> Result<(Server, Katana), Error> {
let katana = Katana::init("http://127.0.0.1:0").await?;
let state = State {
block_number: 0,
block_hash: Felt::try_new("0x0")?,
root: Felt::try_new("0x0")?,
};
let beerus = serve(
&format!("http://127.0.0.1:{}", katana.port()),
"127.0.0.1:0",
Arc::new(RwLock::new(state)),
)
.await?;
Ok((beerus, katana))
}

#[tokio::test]
async fn declare_account_v3() {
let (_katana, client) = setup().await;
Expand All @@ -42,6 +67,44 @@ async fn declare_deploy_account_v2() {
deploy(client).await;
}

#[tokio::test]
async fn deploy_account_on_katana() -> Result<(), Error> {
let (beerus, katana) = setup_beerus_with_katana().await?;

let account = utils::prepare_account()?;
scarb::compile_blocking(account.toml).await?;

let mut starkli = Starkli::new(
&format!("http://127.0.0.1:{}/rpc", beerus.port()),
&account.folder,
PreFundedAccount::Katana,
);
let key = starkli.create_keystore()?;
let class_hash = starkli.extract_class_hash()?;
let address = starkli.create_account(key.clone(), class_hash).await?;
starkli.declare_account().await?;
starkli
.invoke_eth_transfer(address, 5000000000000000000, PriceUnit::Wei)
.await?;
starkli.deploy_account().await?;

// Redirect starkli to katana in verification because katana does not support
// pathfinder methods which are being called in beerus stateless call execution
// TODO: Use beerus when test node with supported pathfinder methods is used
starkli.rpc = format!("http://127.0.0.1:{}", katana.port());

let res_id = starkli.call(address, "id").await?;
assert_eq!(res_id.len(), 2);
assert_eq!(res_id[0].to_string(), account.id);
assert_eq!(res_id[1], starknet_crypto::Felt::ZERO);

let res_public_key = starkli.call(address, "public_key").await?;
assert_eq!(res_public_key.len(), 1);
assert_eq!(res_public_key[0], key.verifying_key().scalar());

Ok(())
}

async fn declare(
client: &Client<Http>,
compiled_contract: &str,
Expand Down
23 changes: 15 additions & 8 deletions tests/starknet/contract/account/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ 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;
fn id(self: @T) -> u256;
}

#[starknet::contract]
#[starknet::interface]
trait ProtocolTrait<T> {
fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
fn __validate__(self: @T, calls: Array<Call>) -> felt252;
fn __validate_declare__(self: @T, class_hash: felt252) -> felt252;
fn __validate_deploy__(self: @T, class_hash: felt252, salt: felt252, public_key: felt252) -> felt252;
}

#[starknet::contract(account)]
mod Account {
use super::{Call, IAccount, SUPPORTED_TX_VERSION};
use super::{Call, IAccount, ProtocolTrait, SUPPORTED_TX_VERSION};
use starknet::{get_caller_address, call_contract_syscall, get_tx_info, VALIDATED};
use zeroable::Zeroable;
use array::{ArrayTrait, SpanTrait};
Expand All @@ -29,7 +37,7 @@ mod Account {
#[storage]
struct Storage {
public_key: felt252,
id: u128
id: u256,
}

#[constructor]
Expand All @@ -53,14 +61,13 @@ mod Account {
self.public_key.read()
}

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

#[abi(per_item)]
#[generate_trait]
impl ProtocolImpl of ProtocolTrait {
#[abi(embed_v0)]
impl ProtocolImpl of ProtocolTrait<ContractState> {
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
self.only_protocol();
self.only_supported_tx_version(SUPPORTED_TX_VERSION::INVOKE);
Expand Down
1 change: 1 addition & 0 deletions tests/starknet/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod constants;
pub mod katana;
pub mod scarb;
pub mod starkli;
pub mod utils;
6 changes: 6 additions & 0 deletions tests/starknet/scarb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ use scarb::{
use semver::Version;
use std::{fs::canonicalize, path::PathBuf};

#[allow(dead_code)]
pub async fn compile_blocking(toml: String) -> Result<(), Error> {
tokio::task::spawn_blocking(move || -> Result<(), Error> { compile(toml) })
.await?
}

#[allow(dead_code)]
pub fn compile(toml: String) -> Result<(), Error> {
let toml_absolute = canonicalize(PathBuf::from(toml))?;
Expand Down
195 changes: 195 additions & 0 deletions tests/starknet/starkli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::io::Write;

use anyhow::{anyhow, Error};
use clap::Parser;
use starkli::{
account::{
AccountConfig, AccountVariant, DeploymentStatus, OzAccountConfig,
UndeployedStatus,
},
signer::AnySigner,
utils::{Cli, Subcommands},
};
use starknet::{
core::types::{contract::SierraClass, PriceUnit},
signers::{LocalWallet, Signer, SigningKey},
};
use starknet_crypto::Felt;

#[allow(dead_code)]
pub struct Starkli {
pub rpc: String,
account_folder: String,
prefunded_account: String,
persist_logger: bool,
}

#[allow(dead_code)]
pub enum PreFundedAccount {
Katana,
Sepolia,
}

const ACCOUNT: &str = "account.json";
const COMPILED_ACCOUNT: &str = "target/dev/account_Account.contract_class.json";
const KEY: &str = "key.json";
const PASSWORD: &str = "password";

#[allow(dead_code)]
impl Starkli {
pub fn new(
rpc: &str,
account_folder: &str,
prefunded_account: PreFundedAccount,
) -> Self {
let prefunded_account = match prefunded_account {
PreFundedAccount::Katana => "katana-0".to_string(),
PreFundedAccount::Sepolia => unimplemented!(),
};
Self {
rpc: rpc.into(),
account_folder: account_folder.into(),
prefunded_account,
persist_logger: false,
}
}

pub fn create_keystore(&self) -> Result<SigningKey, Error> {
let key = SigningKey::from_random();
let key_file = self.account_folder.clone() + KEY;
key.save_as_keystore(key_file, PASSWORD)?;
Ok(key)
}

pub fn extract_class_hash(&self) -> Result<Felt, Error> {
let compiled = self.account_folder.clone() + COMPILED_ACCOUNT;
let class = serde_json::from_reader::<_, SierraClass>(
std::fs::File::open(compiled)?,
)?;
Ok(class.class_hash()?)
}

pub async fn create_account(
&self,
key: SigningKey,
class_hash: Felt,
) -> Result<Felt, Error> {
let signer = AnySigner::LocalWallet(LocalWallet::from_signing_key(key));
let salt = SigningKey::from_random().secret_scalar();
let account_config = AccountConfig {
version: 1,
variant: AccountVariant::OpenZeppelin(OzAccountConfig {
version: 1,
public_key: signer.get_public_key().await?.scalar(),
legacy: false,
}),
deployment: DeploymentStatus::Undeployed(UndeployedStatus {
class_hash,
salt,
context: None,
}),
};
let target_deployment_address =
account_config.deploy_account_address()?;
let mut file =
std::fs::File::create(self.account_folder.clone() + ACCOUNT)?;
serde_json::to_writer_pretty(&mut file, &account_config)?;
file.write_all(b"\n")?;
Ok(target_deployment_address)
}

pub async fn declare_account(&mut self) -> Result<(), Error> {
let compiled_contract = self.account_folder.clone() + COMPILED_ACCOUNT;
let rpc = self.rpc.clone();
let account = self.prefunded_account.clone();
let input = vec![
"starkli",
"declare",
&compiled_contract,
"--compiler-version",
"2.8.2",
"--rpc",
&rpc,
"--account",
&account,
];
self.run_command(input).await
}

pub async fn invoke_eth_transfer(
&mut self,
to_address: Felt,
amount: u64,
unit: PriceUnit,
) -> Result<(), Error> {
let address = &format!("{:#064x}", to_address);
let amount = &format!("u256:{amount}");
let unit = match unit {
PriceUnit::Wei => "--eth",
PriceUnit::Fri => "--strk",
};
let rpc = self.rpc.clone();
let account = self.prefunded_account.clone();
let input = vec![
"starkli",
"invoke",
unit,
"eth",
"transfer",
address,
amount,
"--rpc",
&rpc,
"--account",
&account,
];
self.run_command(input).await
}

pub async fn deploy_account(&mut self) -> Result<(), Error> {
let account = self.account_folder.clone() + "account.json";
let key = self.account_folder.clone() + "key.json";
let rpc = self.rpc.clone();
let input = vec![
"starkli",
"account",
"deploy",
&account,
"--rpc",
&rpc,
"--keystore",
&key,
"--keystore-password",
"password",
"--skip-manual-confirmation",
];
self.run_command(input).await
}

async fn run_command(&mut self, mut input: Vec<&str>) -> Result<(), Error> {
if !self.persist_logger {
self.persist_logger = true;
} else {
input.push("--persist-logger");
}
starkli::utils::run_command(Cli::parse_from(input)).await
}

pub async fn call(
&self,
address: Felt,
func: &str,
) -> Result<Vec<Felt>, Error> {
let address = &format!("{:#064x}", address);
let input = vec!["starkli", "call", address, func, "--rpc", &self.rpc];
let cli = Cli::parse_from(input);
let cmd = match cli.command {
Some(command) => match command {
Subcommands::Call(cmd) => cmd,
_ => return Err(anyhow!("Wrong subcommand")),
},
None => return Err(anyhow!("Wrong command")),
};
cmd.call().await
}
}
Loading

0 comments on commit 76e4240

Please sign in to comment.