diff --git a/crates/jstz_cli/src/account.rs b/crates/jstz_cli/src/account.rs index 7d73477dd..795550143 100644 --- a/crates/jstz_cli/src/account.rs +++ b/crates/jstz_cli/src/account.rs @@ -346,6 +346,9 @@ pub enum Command { // // TODO: https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type fn parse_smart_function_address(s: &str) -> Result { let address = NewAddress::from_str(s).map_err(|e| user_error!("{}", e))?; + if !matches!(address, NewAddress::SmartFunction(_)) { + bail_user_error!("Invalid smart function address: {}", s); + } Ok(address) } @@ -369,4 +372,10 @@ mod tests { parse_smart_function_address("KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5").unwrap(); assert!(matches!(address, NewAddress::SmartFunction(_))); } + + #[test] + fn test_parse_user_address() { + let address = parse_smart_function_address("tz1VJk1a4Y4FdZyFtsm6zL2k9KjYtS5tYF9"); + assert!(address.is_err()); + } } diff --git a/crates/jstz_cli/src/deploy.rs b/crates/jstz_cli/src/deploy.rs index da00e58e9..a794cf50e 100644 --- a/crates/jstz_cli/src/deploy.rs +++ b/crates/jstz_cli/src/deploy.rs @@ -1,7 +1,7 @@ use boa_engine::JsError; use jstz_crypto::public_key_hash::PublicKeyHash; use jstz_proto::{ - context::{account::ParsedCode, new_account::NewAddress}, + context::{new_account::NewAddress, new_account::ParsedCode}, operation::{Content, DeployFunction, Operation, SignedOperation}, receipt::{ReceiptContent, ReceiptResult}, }; diff --git a/crates/jstz_cli/src/jstz.rs b/crates/jstz_cli/src/jstz.rs index de4f784d8..cc078dee9 100644 --- a/crates/jstz_cli/src/jstz.rs +++ b/crates/jstz_cli/src/jstz.rs @@ -3,7 +3,7 @@ use std::time::Duration; use anyhow::{bail, Result}; use jstz_proto::{ api::KvValue, - context::{account::Nonce, new_account::NewAddress}, + context::{new_account::NewAddress, new_account::Nonce}, operation::{OperationHash, SignedOperation}, receipt::Receipt, }; diff --git a/crates/jstz_cli/src/repl/debug_api/account.rs b/crates/jstz_cli/src/repl/debug_api/account.rs index ee72b1018..d38138b00 100644 --- a/crates/jstz_cli/src/repl/debug_api/account.rs +++ b/crates/jstz_cli/src/repl/debug_api/account.rs @@ -5,7 +5,7 @@ use boa_engine::{ JsResult, JsValue, NativeFunction, }; use jstz_core::runtime; -use jstz_proto::context::{account::Account, new_account::NewAddress}; +use jstz_proto::context::{new_account::Account, new_account::NewAddress}; fn try_parse_address(account: &str) -> JsResult { NewAddress::from_base58(account).map_err(|_| { @@ -62,8 +62,8 @@ impl AccountApi { runtime::with_js_hrt_and_tx(|hrt, tx| -> JsResult { match Account::function_code(hrt.deref(), tx, &address)? { - Some(value) => Ok(JsValue::String(value.to_string().into())), - None => Ok(JsValue::null()), + "" => Ok(JsValue::null()), + value => Ok(JsValue::String(value.to_string().into())), } }) } diff --git a/crates/jstz_kernel/src/inbox.rs b/crates/jstz_kernel/src/inbox.rs index 2c2e79a18..f16ddc16b 100644 --- a/crates/jstz_kernel/src/inbox.rs +++ b/crates/jstz_kernel/src/inbox.rs @@ -192,7 +192,7 @@ pub fn read_message( #[cfg(test)] mod test { use jstz_crypto::hash::Hash; - use jstz_crypto::public_key_hash::PublicKeyHash; + use jstz_crypto::smart_function_hash::SmartFunctionHash; use jstz_mock::message::native_deposit::MockNativeDeposit; use jstz_mock::{host::JstzMockHost, message::fa_deposit::MockFaDeposit}; use jstz_proto::context::new_account::NewAddress; @@ -294,11 +294,13 @@ mod test { assert_eq!(300, amount); assert_eq!(fa_deposit.receiver.to_b58check(), receiver.to_base58()); assert_eq!( - Some(PublicKeyHash::from_base58(jstz_mock::host::MOCK_PROXY).unwrap()), + Some( + SmartFunctionHash::from_base58(jstz_mock::host::MOCK_PROXY).unwrap() + ), proxy_smart_function.map(|p| { match p { - NewAddress::User(pkh) => pkh, - NewAddress::SmartFunction(_) => panic!("Unexpected proxy"), + NewAddress::User(_) => panic!("Unexpected proxy"), + NewAddress::SmartFunction(sfh) => sfh, } }) ); diff --git a/crates/jstz_kernel/src/lib.rs b/crates/jstz_kernel/src/lib.rs index 76eb478f0..858fed873 100644 --- a/crates/jstz_kernel/src/lib.rs +++ b/crates/jstz_kernel/src/lib.rs @@ -66,8 +66,8 @@ mod test { message::{fa_deposit::MockFaDeposit, native_deposit::MockNativeDeposit}, }; use jstz_proto::context::{ - account::{Account, ParsedCode}, new_account::NewAddress, + new_account::{Account, ParsedCode}, ticket_table::TicketTable, }; use tezos_smart_rollup::types::{Contract, PublicKeyHash}; @@ -128,11 +128,9 @@ mod test { .unwrap(); tx.commit(host.rt()).unwrap(); - // TODO: use sf address - // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type let proxy = match proxy { - NewAddress::User(pkh) => pkh, - NewAddress::SmartFunction(_) => panic!("Unexpected proxy"), + NewAddress::User(_) => panic!("proxy is not a user address"), + NewAddress::SmartFunction(sfh) => sfh, }; let deposit = MockFaDeposit { @@ -149,7 +147,7 @@ mod test { let proxy_balance = TicketTable::get_balance( host.rt(), tx, - &NewAddress::User(proxy), + &NewAddress::SmartFunction(proxy), &ticket_hash, ) .unwrap(); @@ -180,7 +178,7 @@ mod test { let proxy_balance = TicketTable::get_balance( host.rt(), &mut tx, - &NewAddress::User(proxy), + &NewAddress::SmartFunction(proxy), &ticket_hash, ) .unwrap(); diff --git a/crates/jstz_kernel/src/parsing.rs b/crates/jstz_kernel/src/parsing.rs index 562263a17..6fbc01433 100644 --- a/crates/jstz_kernel/src/parsing.rs +++ b/crates/jstz_kernel/src/parsing.rs @@ -8,8 +8,6 @@ use tezos_smart_rollup::types::{Contract, PublicKeyHash as TezosPublicKeyHash}; pub fn try_parse_contract(contract: &Contract) -> Result { match contract { - // TODO: remove implicit account? - // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type Contract::Implicit(TezosPublicKeyHash::Ed25519(tz1)) => { Ok(NewAddress::User(PublicKeyHash::Tz1(tz1.clone().into()))) } diff --git a/crates/jstz_mock/src/host.rs b/crates/jstz_mock/src/host.rs index 9c0a821c0..99b47ca5b 100644 --- a/crates/jstz_mock/src/host.rs +++ b/crates/jstz_mock/src/host.rs @@ -20,7 +20,7 @@ pub const MOCK_RECEIVER: &str = "tz1PCXYfph1FQBy1jBEXVAhzgzoBww4vkjC8"; pub const MOCK_SENDER: &str = "KT1R7WEtNNim3YgkxPt8wPMczjH3eyhbJMtz"; pub const MOCK_SOURCE: &str = "tz1WXDeZmSpaCCJqes9GknbeUtdKhJJ8QDA2"; -pub const MOCK_PROXY: &str = "tz1ZpKsSVbrQmUeFEjMFK7zWaZ1N9AudDWqy"; +pub const MOCK_PROXY: &str = "KT1RJ6PbjHpwc3M5rw5s2Nbmefwbuwbdxton"; pub const MOCK_PROXY_FUNCTION: &str = r#" export default (request) => { const url = new URL(request.url) diff --git a/crates/jstz_mock/src/message/fa_deposit.rs b/crates/jstz_mock/src/message/fa_deposit.rs index 8bf0858c3..8cd836baf 100644 --- a/crates/jstz_mock/src/message/fa_deposit.rs +++ b/crates/jstz_mock/src/message/fa_deposit.rs @@ -23,7 +23,7 @@ pub struct MockFaDeposit { pub ticket_amount: u32, pub ticket_content: (u32, Option>), pub smart_rollup: Option, - pub proxy_contract: Option, // proxy must be tz1 for nows + pub proxy_contract: Option, } impl Default for MockFaDeposit { @@ -47,8 +47,10 @@ impl Default for MockFaDeposit { // use sf hash // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type proxy_contract: Some( - jstz_crypto::public_key_hash::PublicKeyHash::from_base58(MOCK_PROXY) - .unwrap(), + jstz_crypto::smart_function_hash::SmartFunctionHash::from_base58( + MOCK_PROXY, + ) + .unwrap(), ), } } diff --git a/crates/jstz_node/src/services/accounts.rs b/crates/jstz_node/src/services/accounts.rs index bdbbbcd60..df7bffb45 100644 --- a/crates/jstz_node/src/services/accounts.rs +++ b/crates/jstz_node/src/services/accounts.rs @@ -6,7 +6,10 @@ use axum::{ use jstz_core::BinEncodable; use jstz_proto::{ api::KvValue, - context::account::{Account, Nonce, ParsedCode}, + context::new_account::{ + Account, Nonce, ParsedCode, SmartFunctionAccount, UserAccount, + ACCOUNTS_PATH_PREFIX, + }, }; use serde::Deserialize; use utoipa_axum::{router::OpenApiRouter, routes}; @@ -30,6 +33,10 @@ fn deserialize_account(data: &[u8]) -> ServiceResult { Ok(Account::decode(data).map_err(|_| anyhow!("Failed to deserialize account"))?) } +fn construct_accounts_key(address: &str) -> String { + format!("{}/{}", ACCOUNTS_PATH_PREFIX, address) +} + #[derive(Deserialize)] struct KvQuery { key: Option, @@ -52,10 +59,13 @@ async fn get_nonce( State(AppState { rollup_client, .. }): State, Path(address): Path, ) -> ServiceResult> { - let key = format!("/jstz_account/{}", address); + let key = construct_accounts_key(&address); let value = rollup_client.get_value(&key).await?; let account_nonce = match value { - Some(value) => deserialize_account(value.as_slice())?.nonce, + Some(value) => match deserialize_account(value.as_slice())? { + Account::User(UserAccount { nonce, .. }) => nonce, + Account::SmartFunction(SmartFunctionAccount { nonce, .. }) => nonce, + }, None => Err(ServiceError::NotFound)?, }; Ok(Json(account_nonce)) @@ -77,15 +87,19 @@ async fn get_code( State(AppState { rollup_client, .. }): State, Path(address): Path, ) -> ServiceResult> { - let key = format!("/jstz_account/{}", address); + let key = construct_accounts_key(&address); let value = rollup_client.get_value(&key).await?; let account_code = match value { - Some(value) => deserialize_account(value.as_slice())?.function_code, + Some(value) => match deserialize_account(value.as_slice())? { + Account::User { .. } => Err(ServiceError::BadRequest( + "Account is not a smart function".to_string(), + ))?, + Account::SmartFunction(SmartFunctionAccount { function_code, .. }) => { + function_code + } + }, None => Err(ServiceError::NotFound)?, - } - .ok_or_else(|| { - ServiceError::BadRequest("Account is not a smart function".to_string()) - })?; + }; Ok(Json(account_code)) } @@ -104,10 +118,13 @@ async fn get_balance( State(AppState { rollup_client, .. }): State, Path(address): Path, ) -> ServiceResult> { - let key = format!("/jstz_account/{}", address); + let key = construct_accounts_key(&address); let value = rollup_client.get_value(&key).await?; let account_balance = match value { - Some(value) => deserialize_account(value.as_slice())?.amount, + Some(value) => match deserialize_account(value.as_slice())? { + Account::User(UserAccount { amount, .. }) => amount, + Account::SmartFunction(SmartFunctionAccount { amount, .. }) => amount, + }, None => Err(ServiceError::NotFound)?, }; Ok(Json(account_balance)) diff --git a/crates/jstz_proto/src/api/ledger.rs b/crates/jstz_proto/src/api/ledger.rs index 8444f5661..f3dcd6753 100644 --- a/crates/jstz_proto/src/api/ledger.rs +++ b/crates/jstz_proto/src/api/ledger.rs @@ -15,8 +15,8 @@ use jstz_core::{ use crate::{ context::{ - account::{Account, Amount}, new_account::NewAddress, + new_account::{Account, Amount}, }, error::Result, }; diff --git a/crates/jstz_proto/src/api/smart_function.rs b/crates/jstz_proto/src/api/smart_function.rs index cd1b8efd9..e4aa38947 100644 --- a/crates/jstz_proto/src/api/smart_function.rs +++ b/crates/jstz_proto/src/api/smart_function.rs @@ -15,8 +15,8 @@ use jstz_core::{ use crate::{ context::{ - account::{Account, Amount, ParsedCode}, new_account::NewAddress, + new_account::{Account, Amount, ParsedCode}, }, executor::{ smart_function::{headers, HostScript, Script}, @@ -260,14 +260,15 @@ mod test { use jstz_crypto::{ hash::{Blake2b, Hash}, public_key_hash::PublicKeyHash, + smart_function_hash::SmartFunctionHash, }; use jstz_mock::host::JstzMockHost; use serde_json::json; use crate::{ context::{ - account::{Account, ParsedCode}, new_account::NewAddress, + new_account::{Account, ParsedCode}, ticket_table::TicketTable, }, executor::smart_function::{self, register_web_apis, Script}, @@ -289,8 +290,9 @@ mod test { // TODO: Use sf address instead // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type - let self_address = - NewAddress::User(PublicKeyHash::digest(b"random bytes").unwrap()); + let self_address = NewAddress::SmartFunction( + SmartFunctionHash::digest(b"random bytes").unwrap(), + ); let amount = 100; let operation_hash = Blake2b::from(b"operation_hash".as_ref()); diff --git a/crates/jstz_proto/src/context/account.rs b/crates/jstz_proto/src/context/account.rs deleted file mode 100644 index ec00be9fd..000000000 --- a/crates/jstz_proto/src/context/account.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::{ - fmt::{self, Display}, - result, -}; - -use crate::error::{Error, Result}; -use bincode::{Decode, Encode}; -use boa_engine::{Context, JsError, JsResult, Module, Source}; -use jstz_core::{ - host::HostRuntime, - kv::{Entry, Transaction}, -}; -use serde::{Deserialize, Serialize}; -use tezos_smart_rollup::storage::path::{self, OwnedPath, RefPath}; -use utoipa::ToSchema; - -use super::new_account::NewAddress; - -pub type Amount = u64; - -#[derive( - Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize, ToSchema, -)] -pub struct Nonce(pub u64); - -impl Nonce { - pub fn next(&self) -> Nonce { - Nonce(self.0 + 1) - } - - pub fn increment(&mut self) { - self.0 += 1 - } -} - -impl Display for Nonce { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -// Invariant: if code is present it parses successfully -#[derive(Default, PartialEq, Eq, Debug, Clone, Serialize, Deserialize, ToSchema)] -#[schema( - format = "javascript", - example = "export default (request) => new Response('Hello world!')" -)] -pub struct ParsedCode(pub String); -impl From for String { - fn from(ParsedCode(code): ParsedCode) -> Self { - code - } -} -impl Display for ParsedCode { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> { - Display::fmt(&self.0, formatter) - } -} -impl TryFrom for ParsedCode { - type Error = JsError; - fn try_from(code: String) -> JsResult { - let src = Source::from_bytes(code.as_bytes()); - let mut context = Context::default(); - Module::parse(src, None, &mut context)?; - Ok(Self(code)) - } -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize, Encode, Decode)] -pub struct Account { - #[bincode(with_serde)] - pub nonce: Nonce, - #[bincode(with_serde)] - pub amount: Amount, - #[bincode(with_serde)] - pub function_code: Option, -} - -const ACCOUNTS_PATH: RefPath = RefPath::assert_from(b"/jstz_account"); - -impl Account { - pub fn path(pkh: &NewAddress) -> Result { - let account_path = OwnedPath::try_from(format!("/{}", pkh))?; - - Ok(path::concat(&ACCOUNTS_PATH, &account_path)?) - } - - fn get_mut<'a, 'b>( - hrt: &impl HostRuntime, - tx: &'b mut Transaction, - addr: &NewAddress, - ) -> Result<&'b mut Self> - where - 'a: 'b, - { - let account_entry = tx.entry::(hrt, Self::path(addr)?)?; - Ok(account_entry.or_insert_default()) - } - - fn try_insert( - self, - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - ) -> Result<()> { - match tx.entry::(hrt, Self::path(addr)?)? { - Entry::Occupied(ntry) => { - let acc: &Self = ntry.get(); - hrt.write_debug(&format!("📜 already exists: {:?}\n", acc.function_code)); - Err(Error::InvalidAddress) - } - Entry::Vacant(entry) => { - entry.insert(self); - Ok(()) - } - } - } - - pub fn nonce<'a>( - hrt: &impl HostRuntime, - tx: &'a mut Transaction, - addr: &NewAddress, - ) -> Result<&'a mut Nonce> { - let account = Self::get_mut(hrt, tx, addr)?; - Ok(&mut account.nonce) - } - - pub fn function_code<'a>( - hrt: &impl HostRuntime, - tx: &'a mut Transaction, - addr: &NewAddress, - ) -> Result> { - let account = Self::get_mut(hrt, tx, addr)?; - let function_code = account.function_code.as_mut().map(|code| &mut code.0); - Ok(function_code) - } - - pub fn set_function_code( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - function_code: String, - ) -> Result<()> { - let account = Self::get_mut(hrt, tx, addr)?; - account.function_code = Some(function_code.try_into()?); - Ok(()) - } - - pub fn balance( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - ) -> Result { - let account = Self::get_mut(hrt, tx, addr)?; - Ok(account.amount) - } - - pub fn add_balance( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - amount: Amount, - ) -> Result { - let account = Self::get_mut(hrt, tx, addr)?; - let checked_balance = account - .amount - .checked_add(amount) - .ok_or(crate::error::Error::BalanceOverflow)?; - - account.amount = checked_balance; - Ok(account.amount) - } - - pub fn sub_balance( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - amount: Amount, - ) -> Result { - let account = Self::get_mut(hrt, tx, addr)?; - if account.amount < amount { - return Err(Error::InsufficientFunds)?; - } - account.amount -= amount; - Ok(account.amount) - } - - pub fn set_balance( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - amount: Amount, - ) -> Result<()> { - let account = Self::get_mut(hrt, tx, addr)?; - account.amount = amount; - Ok(()) - } - - pub fn create( - hrt: &impl HostRuntime, - tx: &mut Transaction, - addr: &NewAddress, - amount: Amount, - function_code: Option, - ) -> Result<()> { - Self { - nonce: Nonce::default(), - amount, - function_code, - } - .try_insert(hrt, tx, addr) - } - - pub fn transfer( - hrt: &impl HostRuntime, - tx: &mut Transaction, - src: &NewAddress, - dst: &NewAddress, - amt: Amount, - ) -> Result<()> { - { - let src = tx - .entry::(hrt, Self::path(src)?)? - .or_insert_default(); - match src.amount.checked_sub(amt) { - Some(amt) => src.amount = amt, - None => return Err(Error::BalanceOverflow), - } - } - - { - let dst = Self::get_mut(hrt, tx, dst)?; - match dst.amount.checked_add(amt) { - Some(amt) => dst.amount = amt, - None => return Err(Error::BalanceOverflow), - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use jstz_crypto::{hash::Hash, public_key_hash::PublicKeyHash}; - use tezos_smart_rollup_mock::MockHost; - - #[test] - fn test_zero_account_balance_for_new_accounts() -> Result<()> { - let hrt = &mut MockHost::default(); - let tx = &mut Transaction::default(); - - tx.begin(); - - let pkh = PublicKeyHash::from_base58("tz1XQjK1b3P72kMcHsoPhnAg3dvX1n8Ainty") - .expect("Could not parse pkh"); - - // Act - let amt = { - tx.entry::(hrt, Account::path(&NewAddress::User(pkh))?)? - .or_insert_default() - .amount - }; - { - tx.commit(hrt).expect("Could not commit tx"); - } - - // Assert - assert_eq!(amt, 0); - - Ok(()) - } - - #[test] - fn test_sub_balance() { - let hrt = &mut MockHost::default(); - let tx = &mut Transaction::default(); - - tx.begin(); - - let pkh = NewAddress::User( - PublicKeyHash::from_base58("tz1XQjK1b3P72kMcHsoPhnAg3dvX1n8Ainty") - .expect("Could not parse pkh"), - ); - - Account::create(hrt, tx, &pkh, 10, None).unwrap(); - Account::sub_balance(hrt, tx, &pkh, 10).unwrap(); - - assert_eq!(0, Account::balance(hrt, tx, &pkh).unwrap()); - - let result = Account::sub_balance(hrt, tx, &pkh, 11); - - assert!(matches!(result, Err(Error::InsufficientFunds))); - } -} diff --git a/crates/jstz_proto/src/context/mod.rs b/crates/jstz_proto/src/context/mod.rs index da8175e00..331dc772f 100644 --- a/crates/jstz_proto/src/context/mod.rs +++ b/crates/jstz_proto/src/context/mod.rs @@ -1,6 +1,5 @@ // TODO: remove account and rename new_account to account // https://linear.app/tezos/issue/JSTZ-253/remove-old-accountrs -pub mod account; pub mod new_account; pub mod receipt; pub mod ticket_table; diff --git a/crates/jstz_proto/src/context/ticket_table.rs b/crates/jstz_proto/src/context/ticket_table.rs index e8ba07cd2..3950d1c68 100644 --- a/crates/jstz_proto/src/context/ticket_table.rs +++ b/crates/jstz_proto/src/context/ticket_table.rs @@ -6,7 +6,7 @@ use tezos_smart_rollup::{ storage::path::{self, OwnedPath, RefPath}, }; -use super::{account::Amount, new_account::NewAddress}; +use super::{new_account::Amount, new_account::NewAddress}; use crate::error::Result; diff --git a/crates/jstz_proto/src/executor/deposit.rs b/crates/jstz_proto/src/executor/deposit.rs index 5d59af9d8..a53a847c7 100644 --- a/crates/jstz_proto/src/executor/deposit.rs +++ b/crates/jstz_proto/src/executor/deposit.rs @@ -2,7 +2,7 @@ use jstz_core::{host::HostRuntime, kv::Transaction}; use jstz_crypto::hash::Blake2b; use crate::{ - context::account::Account, + context::new_account::Account, operation::external::Deposit, receipt::{DepositReceipt, Receipt}, }; diff --git a/crates/jstz_proto/src/executor/fa_deposit.rs b/crates/jstz_proto/src/executor/fa_deposit.rs index ab5488d95..125552b89 100644 --- a/crates/jstz_proto/src/executor/fa_deposit.rs +++ b/crates/jstz_proto/src/executor/fa_deposit.rs @@ -9,7 +9,7 @@ use tezos_smart_rollup::{michelson::ticket::TicketHash, prelude::debug_msg}; use utoipa::ToSchema; use crate::{ - context::{account::Amount, new_account::NewAddress, ticket_table::TicketTable}, + context::{new_account::Amount, new_account::NewAddress, ticket_table::TicketTable}, executor::smart_function, operation::{external::FaDeposit, RunFunction}, receipt::Receipt, @@ -175,23 +175,23 @@ mod test { use std::io::empty; use jstz_core::kv::Transaction; - use jstz_crypto::public_key_hash::PublicKeyHash; + use jstz_crypto::smart_function_hash::SmartFunctionHash; use tezos_smart_rollup_mock::MockHost; use crate::{ context::{ - account::ParsedCode, new_account::NewAddress, ticket_table::TicketTable, + new_account::NewAddress, new_account::ParsedCode, ticket_table::TicketTable, }, executor::fa_deposit::{FaDeposit, FaDepositReceipt}, receipt::{Receipt, ReceiptContent, ReceiptResult}, }; - fn mock_fa_deposit(proxy: Option) -> FaDeposit { + fn mock_fa_deposit(proxy: Option) -> FaDeposit { FaDeposit { inbox_id: 34, amount: 42, receiver: NewAddress::User(jstz_mock::account2()), - proxy_smart_function: proxy.map(NewAddress::User), + proxy_smart_function: proxy.map(NewAddress::SmartFunction), ticket_hash: jstz_mock::ticket_hash1(), } } @@ -299,12 +299,12 @@ mod test { // TODO: use sf address // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type - let proxy_pkh = match proxy.clone() { - NewAddress::User(pkh) => pkh, - NewAddress::SmartFunction(_) => panic!("Unexpected proxy"), + let proxy_sfh = match proxy.clone() { + NewAddress::User(_) => panic!("proxy is not a user address"), + NewAddress::SmartFunction(sfh) => sfh, }; - let fa_deposit = mock_fa_deposit(Some(proxy_pkh.clone())); + let fa_deposit = mock_fa_deposit(Some(proxy_sfh.clone())); let ticket_hash = fa_deposit.ticket_hash.clone(); let Receipt { result: inner, .. } = @@ -356,17 +356,17 @@ mod test { // TODO: use sf address // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type - let proxy_pkh = match proxy.clone() { - NewAddress::User(pkh) => pkh, - NewAddress::SmartFunction(_) => panic!("Unexpected proxy"), + let proxy_sfh = match proxy.clone() { + NewAddress::User(_) => panic!("proxy is not a user address"), + NewAddress::SmartFunction(sfh) => sfh, }; - let fa_deposit1 = mock_fa_deposit(Some(proxy_pkh.clone())); + let fa_deposit1 = mock_fa_deposit(Some(proxy_sfh.clone())); let ticket_hash = fa_deposit1.ticket_hash.clone(); let _ = super::execute(&mut host, &mut tx, fa_deposit1); - let fa_deposit2 = mock_fa_deposit(Some(proxy_pkh.clone())); + let fa_deposit2 = mock_fa_deposit(Some(proxy_sfh.clone())); let Receipt { result: inner, .. } = super::execute(&mut host, &mut tx, fa_deposit2); @@ -415,8 +415,8 @@ mod test { // TODO: use sf address // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type let proxy = match proxy { - NewAddress::User(pkh) => pkh, - NewAddress::SmartFunction(_) => panic!("Unexpected proxy"), + NewAddress::User(_) => panic!("proxy is not a user address"), + NewAddress::SmartFunction(sfh) => sfh, }; let fa_deposit = mock_fa_deposit(Some(proxy.clone())); @@ -438,7 +438,7 @@ mod test { let proxy_balance = TicketTable::get_balance( &mut host, &mut tx, - &NewAddress::User(proxy), + &NewAddress::SmartFunction(proxy), &ticket_hash, ) .unwrap(); diff --git a/crates/jstz_proto/src/executor/fa_withdraw.rs b/crates/jstz_proto/src/executor/fa_withdraw.rs index 9f0add9ce..dea583a6e 100644 --- a/crates/jstz_proto/src/executor/fa_withdraw.rs +++ b/crates/jstz_proto/src/executor/fa_withdraw.rs @@ -18,7 +18,7 @@ use tezos_smart_rollup::{ use utoipa::ToSchema; use crate::{ - context::{account::Amount, new_account::NewAddress, ticket_table::TicketTable}, + context::{new_account::Amount, new_account::NewAddress, ticket_table::TicketTable}, Error, Result, }; diff --git a/crates/jstz_proto/src/executor/smart_function.rs b/crates/jstz_proto/src/executor/smart_function.rs index 9bac762e5..e348985d0 100644 --- a/crates/jstz_proto/src/executor/smart_function.rs +++ b/crates/jstz_proto/src/executor/smart_function.rs @@ -21,14 +21,13 @@ use jstz_core::{ host::HostRuntime, host_defined, kv::Transaction, native::JsNativeObject, runtime, Module, Realm, }; -use jstz_crypto::{hash::Hash, public_key_hash::PublicKeyHash}; use tezos_smart_rollup::prelude::debug_msg; use crate::{ api::{self, TraceData}, context::{ - account::{Account, Amount, ParsedCode}, new_account::NewAddress, + new_account::{Account, Amount, ParsedCode}, }, js_logger::JsonLogger, operation::{OperationHash, RunFunction}, @@ -190,10 +189,9 @@ impl Script { address: &NewAddress, context: &mut Context, ) -> Result { - let src = - Account::function_code(hrt, tx, address)?.ok_or(Error::InvalidAddress)?; + let src = Account::function_code(hrt, tx, address)?; - Ok(Self::parse(Source::from_bytes(&src), context)?) + Ok(Self::parse(Source::from_bytes(src), context)?) } pub fn parse( @@ -240,26 +238,22 @@ impl Script { code: ParsedCode, balance: Amount, ) -> Result { - let nonce = Account::nonce(hrt, tx, source)?; - - // TODO: use sf address - // https://linear.app/tezos/issue/JSTZ-260/add-validation-check-for-address-type - let address = NewAddress::User(PublicKeyHash::digest( - format!("{}{}{}", source, code, nonce).as_bytes(), - )?); - - let account = Account::create(hrt, tx, &address, balance, Some(code)); - if account.is_ok() { - debug_msg!(hrt, "[📜] Smart function deployed: {address}\n"); - } else if let Err(Error::InvalidAddress) = account { - debug_msg!(hrt, "[📜] Smart function was already deployed: {address}\n"); + let address = Account::create_smart_function(hrt, tx, source, balance, code); + + if address.is_ok() { + debug_msg!( + hrt, + "[📜] Smart function deployed: {}\n", + address.as_ref().unwrap() + ); + } else if let Err(Error::AccountExists) = address { + debug_msg!(hrt, "[📜] Smart function was already deployed\n"); } else { // Unreachable? debug_msg!(hrt, "[📜] Smart function deployment failed. \n"); - account? } - Ok(address) + address } /// Runs the script @@ -929,4 +923,36 @@ pub mod deploy { Ok(receipt::DeployFunctionReceipt { address }) } + + #[cfg(test)] + mod test { + use super::*; + use jstz_core::kv::Transaction; + use jstz_mock::host::JstzMockHost; + use operation::DeployFunction; + use receipt::DeployFunctionReceipt; + + #[test] + fn execute_deploy_deploys_smart_function_with_kt1_account1() { + let mut host = JstzMockHost::default(); + let mut tx = Transaction::default(); + let source = NewAddress::User(jstz_mock::account1()); + let hrt = host.rt(); + tx.begin(); + + let deployment = DeployFunction { + function_code: "".to_string().try_into().unwrap(), + account_credit: 0, + }; + let result = deploy::execute(hrt, &mut tx, &source, deployment); + assert!(result.is_ok()); + let receipt = result.unwrap(); + assert!(matches!( + receipt, + DeployFunctionReceipt { + address: NewAddress::SmartFunction(..), + } + )); + } + } } diff --git a/crates/jstz_proto/src/executor/withdraw.rs b/crates/jstz_proto/src/executor/withdraw.rs index 6c54a6a3b..bd05505ac 100644 --- a/crates/jstz_proto/src/executor/withdraw.rs +++ b/crates/jstz_proto/src/executor/withdraw.rs @@ -12,8 +12,8 @@ use tezos_crypto_rs::hash::ContractKt1Hash; use crate::{ context::{ - account::{Account, Amount}, new_account::NewAddress, + new_account::{Account, Amount}, }, Error, Result, }; @@ -92,7 +92,7 @@ mod test { use tezos_smart_rollup_mock::MockHost; use crate::{ - context::{account::Account, new_account::NewAddress}, + context::{new_account::Account, new_account::NewAddress}, executor::withdraw::execute_withdraw, Error, }; diff --git a/crates/jstz_proto/src/operation.rs b/crates/jstz_proto/src/operation.rs index 4e5c9c149..2d663ba58 100644 --- a/crates/jstz_proto/src/operation.rs +++ b/crates/jstz_proto/src/operation.rs @@ -1,7 +1,7 @@ use crate::{ context::{ - account::{Account, Amount, Nonce, ParsedCode}, new_account::NewAddress, + new_account::{Account, Amount, Nonce, ParsedCode}, }, Error, Result, }; @@ -244,7 +244,7 @@ pub mod openapi { #[cfg(test)] mod test { use super::{DeployFunction, RunFunction}; - use crate::{context::account::ParsedCode, operation::Content}; + use crate::{context::new_account::ParsedCode, operation::Content}; use http::{HeaderMap, Method, Uri}; use jstz_core::BinEncodable; use serde_json::json;