diff --git a/Cargo.lock b/Cargo.lock index 58d68e312..fb9a1bc70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,7 +1141,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-xdr" version = "0.0.12" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=154e07e#154e07ebbb0ad307475fd665d5a0dcf169a9596f" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=763b104#763b104d0eb0147c0be94620254c223677fd4b7b" dependencies = [ "base64", "crate-git-revision", diff --git a/Cargo.toml b/Cargo.toml index 2531697d6..0240b7795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ soroban-native-sdk-macros = { version = "0.0.12", path = "soroban-native-sdk-mac [workspace.dependencies.stellar-xdr] version = "0.0.12" git = "https://github.com/stellar/rs-stellar-xdr" -rev = "154e07e" +rev = "763b104" default-features = false [workspace.dependencies.wasmi] diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index 1c3bb11f5..dc2ac17fe 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -57,10 +57,10 @@ }, { "export": "3", - "name": "get_current_contract", + "name": "get_current_contract_id", "args": [], "return": "Object", - "docs": "Get the contractID `Bytes` of the contract which invoked the running contract. Traps if the running contract was not invoked by a contract." + "docs": "Gets the 32-byte identifer of the current contract. Traps if the running contract was not invoked by a contract." }, { "export": "4", @@ -85,20 +85,13 @@ }, { "export": "7", - "name": "get_ledger_network_passphrase", - "args": [], - "return": "Object", - "docs": "Return the network passphrase of the current ledger as `Bytes`." - }, - { - "export": "8", "name": "get_current_call_stack", "args": [], "return": "Object", "docs": "Returns the full call stack from the first contract call to the current one as a vector of vectors, where the inside vector contains the contract id as Hash, and a function as a Symbol." }, { - "export": "9", + "export": "8", "name": "fail_with_status", "args": [ { @@ -110,7 +103,7 @@ "docs": "Causes the currently executing contract to fail immediately with a provided status code, which must be of error-type `ScStatusType::ContractError`. Does not actually return." }, { - "export": "a", + "export": "9", "name": "log_fmt_values", "args": [ { @@ -126,7 +119,7 @@ "docs": "Record a debug event. Fmt must be a Bytes. Args must be a Vec. Void is returned." }, { - "export": "b", + "export": "a", "name": "get_invoker_type", "args": [], "return": "u64", @@ -134,18 +127,18 @@ }, { "export": "c", - "name": "get_invoking_account", + "name": "get_ledger_network_id", "args": [], "return": "Object", - "docs": "Get the AccountID object type of the account which invoked the running contract. Traps if the running contract was not invoked by an account." + "docs": "Return the network id (sha256 hash of network passphrase) of the current ledger as `Bytes`. The value is always 32 bytes in length." }, { "export": "d", - "name": "get_ledger_network_id", + "name": "get_current_contract_address", "args": [], "return": "Object", - "docs": "Return the network id (sha256 hash of network passphrase) of the current ledger as `Bytes`. The value is always 32 bytes in length." - } + "docs": "Get the Address object for the current contract." + } ] }, { @@ -1110,62 +1103,6 @@ } ] }, - { - "name": "hash", - "export": "h", - "functions": [ - { - "export": "_", - "name": "hash_from_bytes", - "args": [ - { - "name": "x", - "type": "Object" - } - ], - "return": "Object" - }, - { - "export": "0", - "name": "hash_to_bytes", - "args": [ - { - "name": "x", - "type": "Object" - } - ], - "return": "Object" - } - ] - }, - { - "name": "key", - "export": "k", - "functions": [ - { - "export": "_", - "name": "public_key_from_bytes", - "args": [ - { - "name": "x", - "type": "Object" - } - ], - "return": "Object" - }, - { - "export": "0", - "name": "public_key_to_bytes", - "args": [ - { - "name": "x", - "type": "Object" - } - ], - "return": "Object" - } - ] - }, { "name": "crypto", "export": "c", @@ -1208,67 +1145,19 @@ "functions": [ { "export": "_", - "name": "account_get_low_threshold", + "name": "require_auth", "args": [ { - "name": "a", - "type": "Object" - } - ], - "return": "RawVal", - "docs": "Get the low threshold for the account with ID `a` (`a` is `AccountId`). Traps if no such account exists." - }, - { - "export": "0", - "name": "account_get_medium_threshold", - "args": [ - { - "name": "a", - "type": "Object" - } - ], - "return": "RawVal", - "docs": "Get the medium threshold for the account with ID `a` (`a` is `AccountId`). Traps if no such account exists." - }, - { - "export": "1", - "name": "account_get_high_threshold", - "args": [ - { - "name": "a", - "type": "Object" - } - ], - "return": "RawVal", - "docs": "Get the high threshold for the account with ID `a` (`a` is `AccountId`). Traps if no such account exists." - }, - { - "export": "2", - "name": "account_get_signer_weight", - "args": [ - { - "name": "a", + "name": "address", "type": "Object" }, { - "name": "s", - "type": "Object" - } - ], - "return": "RawVal", - "docs": "Get the signer weight for the signer with ed25519 public key `s` (`s` is `Bytes`) on the account with ID `a` (`a` is `AccountId`). Returns the master weight if the signer is the master, and returns 0 if no such signer exists. Traps if no such account exists." - }, - { - "export": "3", - "name": "account_exists", - "args": [ - { - "name": "a", + "name": "args", "type": "Object" } ], "return": "RawVal", - "docs": "Given an ID `a` (`a` is `AccountId`) of an account, check if it exists. Returns (SCStatic) TRUE/FALSE." + "docs": "Checks if the address has authorized the invocation of the current contract function with the provided arguments. Traps if the invocation hasn't been authorized." } ] }, diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs index 737da31de..4a6541dec 100644 --- a/soroban-env-common/src/meta.rs +++ b/soroban-env-common/src/meta.rs @@ -58,5 +58,5 @@ pub const ENV_META_V0_SECTION_NAME: &'static str = "contractenvmetav0"; soroban_env_macros::generate_env_meta_consts!( - interface_version: 27, + interface_version: 28, ); diff --git a/soroban-env-host/src/auth.rs b/soroban-env-host/src/auth.rs new file mode 100644 index 000000000..a6e4b8524 --- /dev/null +++ b/soroban-env-host/src/auth.rs @@ -0,0 +1,940 @@ +use std::collections::HashMap; + +use soroban_env_common::xdr::{ + ContractAuth, ContractDataEntry, HashIdPreimage, HashIdPreimageContractAuth, LedgerEntry, + LedgerEntryData, LedgerEntryExt, ScAddress, ScObject, ScVal, +}; +use soroban_env_common::{RawVal, Symbol}; + +use crate::budget::Budget; +use crate::host::metered_clone::MeteredClone; +use crate::host::Frame; +use crate::native_contract::account_contract::{ + check_account_authentication, check_account_contract_auth, +}; +use crate::{Host, HostError}; + +use super::xdr; +use super::xdr::{Hash, ScUnknownErrorCode, ScVec}; + +// Authorization manager encapsulates host-based authentication & authorization +// framework. +// This supports enforcing authentication & authorization of the contract +// invocation trees as well as recording the authorization requirements in +// simulated environments (such as tests or preflight). +#[derive(Clone)] +pub(crate) struct AuthorizationManager { + // Mode of operation of this AuthorizationManager. This can't be changed; in + // order to switch the mode a new instance of AuthorizationManager has to + // be created. + mode: AuthorizationMode, + // Per-address trackers of authorized invocations. + // Every tracker takes care about a single rooted invocation tree for some + // address. There can be multiple trackers per address. + trackers: Vec, + // Current call stack consisting only of the contract invocations (i.e. not + // the host functions). + call_stack: Vec, + budget: Budget, +} + +// The authorization payload recorded for an address in the recording +// authorization mode. +#[derive(Eq, PartialEq, Debug)] +pub struct RecordedAuthPayload { + pub address: Option, + pub nonce: Option, + pub invocation: xdr::AuthorizedInvocation, +} + +// Snapshot of `AuthorizationManager` to use when performing the callstack +// rollbacks. +#[derive(Clone)] +pub struct AuthorizationManagerSnapshot { + // AuthorizationTracker has some immutable parts, but it is safer to just + // rollback everything. If this is an issue, then the AuthorizationTracker should + // probably be separated into 'mutable' and 'immutable' parts. + trackers: Vec, + tracker_by_address_handle: Option>, +} + +#[derive(Clone)] +enum AuthorizationMode { + Enforcing, + Recording(RecordingAuthInfo), +} + +// Additional AuthorizationManager fields needed only for the recording mode. +#[derive(Clone)] +struct RecordingAuthInfo { + // Maps the `Address` object identifiers to the respective tracker indices + // in `trackers` + // This allows to disambiguate between the addresses that have the same + // value, but are specified as two different objects (e.g. as two different + // contract function inputs). + tracker_by_address_handle: HashMap, +} + +// Stores all the authorizations that are authorized by an address. +// In the enforcing mode this performs authentication and makes sure that only +// pre-authorized invocations can happen on behalf of the `address`. +// In the recording mode this will record the invocations that are authorized +// on behalf of the address. +#[derive(Clone)] +struct AuthorizationTracker { + // Tracked address. If `None`, then lazily set to the transaction source + // account's (i.e. invoker's) address is used. + address: Option, + // Root of the authorized invocation tree. + // The authorized invocation tree only contains the contract invocations + // that explicitly require authorization on behalf of the address. + root_authorized_invocation: AuthorizedInvocation, + // Call stack that tracks the current walk in the tree of authorized + // invocations. + // This is set to `None` if the invocation didn't require authorization on + // behalf of the address. + // When not `None` this is an index of the authorized invocation in the + // parent's `sub_invocations` vector or 0 for the + // `root_authorized_invocation`. + invocation_id_in_call_stack: Vec>, + // Arguments representing the signature(s) made by the address to authorize + // the invocations tracked here. + signature_args: Vec, + // Indicates whether this tracker is still valid. If invalidated once, this + // can't be used to authorize anything anymore + is_valid: bool, + // Indicates whether this is a tracker for the transaction invoker. + is_invoker: bool, + // Indicates whether authentication has already succesfully happened. + authenticated: bool, + // Indicates whether nonce still needs to be verified and consumed. + need_nonce: bool, + // The value of nonce authorized by the address. Must match the stored + // nonce value. + nonce: Option, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct ContractInvocation { + pub(crate) contract_id: Hash, + pub(crate) function_name: Symbol, +} + +// A single node in the authorized invocation tree. +// This represents an invocation and all it's authorized sub-invocations. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct AuthorizedInvocation { + pub(crate) contract_id: Hash, + pub(crate) function_name: Symbol, + pub(crate) args: ScVec, + pub(crate) sub_invocations: Vec, + // Indicates that this invocation has been already used in the + // enforcing mode. Exhausted authorizations can't be reused. + // In the recording mode this is immediately set to `true` (as the + // authorizations are recorded when the actually happen). + is_exhausted: bool, +} + +impl AuthorizedInvocation { + fn from_xdr(xdr_invocation: xdr::AuthorizedInvocation) -> Result { + let sub_invocations_xdr = xdr_invocation.sub_invocations.to_vec(); + let sub_invocations = sub_invocations_xdr + .into_iter() + .map(|i| AuthorizedInvocation::from_xdr(i)) + .collect::, _>>()?; + Ok(Self { + contract_id: xdr_invocation.contract_id.clone(), + function_name: Symbol::try_from(xdr_invocation.function_name)?, + args: xdr_invocation.args.clone(), + sub_invocations, + is_exhausted: false, + }) + } + + fn to_xdr(&self, budget: &Budget) -> Result { + Ok(xdr::AuthorizedInvocation { + contract_id: self.contract_id.metered_clone(budget)?, + // This ideally should be infallible + function_name: self + .function_name + .to_string() + .try_into() + .map_err(|_| HostError::from(ScUnknownErrorCode::General))?, + args: self.args.metered_clone(budget)?, + sub_invocations: self + .sub_invocations + .iter() + .map(|i| i.to_xdr(budget)) + .collect::, HostError>>()? + .try_into() + .map_err(|_| HostError::from(ScUnknownErrorCode::General))?, + }) + } + + fn new_recording(contract_id: &Hash, function_name: Symbol, args: ScVec) -> Self { + Self { + contract_id: contract_id.clone(), + function_name, + args, + sub_invocations: vec![], + is_exhausted: true, + } + } + + // Non-metered conversion should only be used for the recording preflight + // runs. + fn to_xdr_non_metered(&self) -> Result { + Ok(xdr::AuthorizedInvocation { + contract_id: self.contract_id.clone(), + // This ideally should be infallible + function_name: self + .function_name + .to_string() + .try_into() + .map_err(|_| HostError::from(ScUnknownErrorCode::General))?, + args: self.args.clone(), + sub_invocations: self + .sub_invocations + .iter() + .map(|i| i.to_xdr_non_metered()) + .collect::, HostError>>()? + .try_into() + .map_err(|_| HostError::from(ScUnknownErrorCode::General))?, + }) + } + + // Walks a path in the tree defined by `invocation_id_in_call_stack` and + // returns the last visited authorized node. + fn last_authorized_invocation_mut( + &mut self, + invocation_id_in_call_stack: &Vec>, + call_stack_id: usize, + ) -> Option<&mut AuthorizedInvocation> { + // Start walking the stack from `call_stack_id`. We trust the callers to + // hold the invariant that `invocation_id_in_call_stack[call_stack_id - 1]` + // corresponds to this invocation tree, so that the next non-`None` child + // corresponds to the child of the current tree. + for i in call_stack_id..invocation_id_in_call_stack.len() { + if let Some(id) = invocation_id_in_call_stack[i] { + // We trust the caller to have the correct sub-invocation + // indices. + return self.sub_invocations[id] + .last_authorized_invocation_mut(invocation_id_in_call_stack, i + 1); + } + // Skip `None` invocations as they don't require authorization. + } + Some(self) + } +} + +impl Default for AuthorizationManager { + fn default() -> Self { + Self::new_enforcing_without_authorizations(Budget::default()) + } +} + +impl AuthorizationManager { + // Creates a new enforcing `AuthorizationManager` from the given + // authorization entries. + // This should be created once per top-level contract invocation. + pub(crate) fn new_enforcing( + host: &Host, + auth_entries: Vec, + ) -> Result { + let mut trackers = vec![]; + for auth_entry in auth_entries { + trackers.push(AuthorizationTracker::from_authorization_entry( + host, auth_entry, + )?); + } + Ok(Self { + mode: AuthorizationMode::Enforcing, + call_stack: vec![], + budget: host.budget_cloned(), + trackers, + }) + } + + // Creates a new enforcing `AuthorizationManager` that doesn't allow any + // authorizations. + // This is useful as a safe default mode. + pub(crate) fn new_enforcing_without_authorizations(budget: Budget) -> Self { + Self { + mode: AuthorizationMode::Enforcing, + call_stack: vec![], + budget, + trackers: vec![], + } + } + + // Creates a new recording `AuthorizationManager`. + // All the authorization requirements will be recorded and can then be + // retrieved using `get_recorded_signature_payloads`. + pub(crate) fn new_recording(budget: Budget) -> Self { + Self { + mode: AuthorizationMode::Recording(RecordingAuthInfo { + tracker_by_address_handle: Default::default(), + }), + call_stack: vec![], + budget, + trackers: vec![], + } + } + + // Require the `address` to have authorized the current contract invocation + // with provided args and within the current context (i.e. the current + // authorized call stack and for the current network). + // In the recording mode this stores the auth requirement instead of + // verifying it. + pub(crate) fn require_auth( + &mut self, + host: &Host, + address_obj_handle: u32, + address: ScAddress, + args: ScVec, + ) -> Result<(), HostError> { + if let ScAddress::Contract(contract_addr) = &address { + // For now we give a blanket approval of the invoker contract to any + // calls it made, but never to the deeper calls. It's possible + // to eventually add a capability to pre-authorize arbitrary call + // stacks on behalf of the contract. + if let Ok(invoker_contract) = host.get_invoking_contract_internal() { + if &invoker_contract == contract_addr { + return Ok(()); + } + } + } + + if let Some(curr_invocation) = self.call_stack.last() { + match &mut self.mode { + AuthorizationMode::Enforcing => { + // Iterate all the trackers and try to find one that + // fullfills the authorization requirement. + for tracker in &mut self.trackers { + let address_matches = if let Some(addr) = &tracker.address { + addr == &address + } else { + // Lazily fill the address for the invoker trackers, + // so that it's possible to create the auth manager + // without knowing the invoker beforehand and also + // to not keep calling into `source_account` function. + let source_addr = ScAddress::Account(host.source_account()?); + let source_matches = source_addr == address; + tracker.address = Some(source_addr); + source_matches + }; + // If address doesn't match, just skip the tracker. + if address_matches { + match tracker.maybe_authorize_invocation( + host, + &curr_invocation.contract_id, + curr_invocation.function_name, + &args, + ) { + // If tracker doesn't have a matching invocation, + // just skip it (there could still be another + // tracker that matches it). + Ok(false) => continue, + // Found a matching authorization. + Ok(true) => return Ok(()), + // Found a matching authorization, but another + // requirement hasn't been fullfilled (for + // example, incorrect authentication or nonce). + Err(e) => return Err(e), + } + } + } + // No matching tracker found, hence the invocation isn't + // authorized. + Err(host.err_general("invocation is not authorized")) + } + AuthorizationMode::Recording(recording_info) => { + if let Some(tracker_id) = recording_info + .tracker_by_address_handle + .get(&address_obj_handle) + { + let tracker = &mut self.trackers[*tracker_id]; + // The recording invariant is that trackers are created + // with the first authorized invocation, which means + // that when their stack no longer has authorized + // invocation, then we've popped frames past its root + // and hence need to create a new tracker. + if !tracker.has_authorized_invocations_in_stack() { + recording_info + .tracker_by_address_handle + .remove(&address_obj_handle); + } else { + return self.trackers[*tracker_id].record_invocation( + host, + &curr_invocation.contract_id, + curr_invocation.function_name, + args, + ); + } + } + // If a tracker for the new tree doesn't exist yet, create + // it and initialize with the current invocation. + self.trackers.push(AuthorizationTracker::new_recording( + host, + address, + &curr_invocation.contract_id, + curr_invocation.function_name, + args, + self.call_stack.len(), + )?); + recording_info + .tracker_by_address_handle + .insert(address_obj_handle, self.trackers.len() - 1); + Ok(()) + } + } + } else { + // This would be a bug + Err(ScUnknownErrorCode::General.into()) + } + } + + // Returns a snapshot of `AuthorizationManager` to use for rollback. + pub(crate) fn snapshot(&self) -> AuthorizationManagerSnapshot { + let tracker_by_address_handle = match &self.mode { + AuthorizationMode::Enforcing => None, + AuthorizationMode::Recording(recording_info) => { + Some(recording_info.tracker_by_address_handle.clone()) + } + }; + AuthorizationManagerSnapshot { + trackers: self.trackers.clone(), + tracker_by_address_handle, + } + } + + // Rolls back this `AuthorizationManager` to the snapshot state. + pub(crate) fn rollback(&mut self, snapshot: AuthorizationManagerSnapshot) { + self.trackers = snapshot.trackers; + if let Some(tracker_by_address_handle) = snapshot.tracker_by_address_handle { + match &mut self.mode { + AuthorizationMode::Recording(recording_info) => { + recording_info.tracker_by_address_handle = tracker_by_address_handle; + } + AuthorizationMode::Enforcing => (), + } + } + } + + // Records a new call stack frame. + // This should be called for every `Host` `push_frame`. + pub(crate) fn push_frame(&mut self, frame: &Frame) -> Result<(), HostError> { + let (contract_id, function_name) = match frame { + #[cfg(feature = "vm")] + Frame::ContractVM(vm, fn_name) => { + (vm.contract_id.metered_clone(&self.budget)?, fn_name.clone()) + } + // Just skip the host function stack frames for now. + // We could also make this included into the authorized stack to + // generalize all the host function invocations. + Frame::HostFunction(_) => return Ok(()), + Frame::Token(id, fn_name) => (id.metered_clone(&self.budget)?, fn_name.clone()), + #[cfg(any(test, feature = "testutils"))] + Frame::TestContract(tc) => (tc.id.clone(), tc.func.clone()), + }; + self.call_stack.push(ContractInvocation { + contract_id, + function_name, + }); + for tracker in &mut self.trackers { + tracker.push_frame(); + } + Ok(()) + } + + // Pops a call stack frame. + // This should be called for every `Host` `pop_frame`. + pub(crate) fn pop_frame(&mut self) { + // Currently we don't push host function call frames, hence this may be + // called with empty stack. We trust the Host to keep things correct, + // i.e. that only host function frames are ignored this way. + // Eventually we may want to also authorize host fns via this, so this + // won't be needed. + if self.call_stack.is_empty() { + return; + } + self.call_stack.pop(); + for tracker in &mut self.trackers { + tracker.pop_frame(); + } + } + + // Returns the recorded per-address authorization payloads that would cover the + // top-level contract function invocation in the enforcing mode. + // Should only be called in the recording mode. + pub(crate) fn get_recorded_auth_payloads(&self) -> Result, HostError> { + match &self.mode { + AuthorizationMode::Enforcing => return Err(ScUnknownErrorCode::General.into()), + AuthorizationMode::Recording(_recording_info) => Ok(self + .trackers + .iter() + .map(|tracker| tracker.get_recorded_auth_payload()) + .collect::, HostError>>()?), + } + } + + // Returns a 'reset' instance of `AuthorizationManager` that has the same + // mode, but no data. + #[cfg(any(test, feature = "testutils"))] + pub(crate) fn reset(&mut self) { + *self = match self.mode { + AuthorizationMode::Enforcing => { + AuthorizationManager::new_enforcing_without_authorizations(self.budget.clone()) + } + AuthorizationMode::Recording(_) => { + AuthorizationManager::new_recording(self.budget.clone()) + } + } + } + + // Verify that the top-level authorization has happened for the given + // address and invocation arguments. + // This also keeps track of verifications that already happened. + #[cfg(any(test, feature = "testutils"))] + pub(crate) fn verify_top_authorization( + &mut self, + address: &ScAddress, + contract_id: &Hash, + function_name: &Symbol, + args: &ScVec, + ) -> bool { + match &mut self.mode { + AuthorizationMode::Enforcing => { + panic!("verifying the authorization is only available for recording-mode auth") + } + AuthorizationMode::Recording(_) => { + for tracker in &mut self.trackers { + if tracker.verify_top_authorization(address, contract_id, function_name, args) { + return true; + } + } + return false; + } + } + } +} + +impl AuthorizationTracker { + fn from_authorization_entry(host: &Host, auth_entry: ContractAuth) -> Result { + let is_invoker = auth_entry.address_with_nonce.is_none(); + let (address, nonce) = if let Some(address_with_nonce) = auth_entry.address_with_nonce { + ( + Some(address_with_nonce.address), + Some(address_with_nonce.nonce), + ) + } else { + (None, None) + }; + Ok(Self { + address, + root_authorized_invocation: AuthorizedInvocation::from_xdr(auth_entry.root_invocation)?, + signature_args: host.scvals_to_rawvals(auth_entry.signature_args.0.as_slice())?, + authenticated: false, + need_nonce: !is_invoker, + is_invoker, + nonce, + invocation_id_in_call_stack: vec![], + is_valid: true, + }) + } + + fn new_recording( + host: &Host, + address: ScAddress, + contract_id: &Hash, + function_name: Symbol, + args: ScVec, + current_stack_len: usize, + ) -> Result { + if current_stack_len == 0 { + // This would be a bug. + return Err(host.err_general("unexpected empty stack in recording auth")); + } + // If the invoker account is known, set it to `None`, so that the final + // recorded payload wouldn't contain the address. This makes it easier + // to use more optimal payload when only invoker auth is used. + let is_invoker = if let Ok(source_acc) = host.source_account() { + ScAddress::Account(source_acc) == address + } else { + false + }; + let mut nonce = None; + + let address = if is_invoker { + None + } else { + nonce = Some(host.read_and_consume_nonce(&contract_id, &address)?); + Some(address) + }; + let is_invoker = address.is_none(); + // Create the stack of `None` leading to the current invocation to + // represent invocations that didn't need authorization on behalf of + // the tracked address. + let mut invocation_id_in_call_stack = vec![None; current_stack_len - 1]; + // Add the id for the current(root) invocation. + invocation_id_in_call_stack.push(Some(0)); + Ok(Self { + address, + root_authorized_invocation: AuthorizedInvocation::new_recording( + contract_id, + function_name, + args, + ), + invocation_id_in_call_stack, + signature_args: Default::default(), + is_valid: true, + authenticated: true, + need_nonce: !is_invoker, + is_invoker, + nonce, + }) + } + + // Tries to find and enforce the provided invocation with this tracker and + // lazily performs authentication when needed. + // This is needed for the enforcing mode only. + // This assumes that the address matching is correctly performed before + // calling this. + // Returns true/false based on whether the invocation is found in the + // tracker. Returns error if invocation has been found, but the tracker + // itself is not valid (failed authentication or nonce check). + fn maybe_authorize_invocation( + &mut self, + host: &Host, + contract_id: &Hash, + function_name: Symbol, + args: &ScVec, + ) -> Result { + if !self.is_valid { + return Ok(false); + } + let frame_is_already_authorized = match self.invocation_id_in_call_stack.last() { + Some(Some(_)) => true, + _ => false, + }; + if frame_is_already_authorized + || !self.maybe_push_matching_invocation_frame(contract_id, function_name, args) + { + // The call isn't found in the currently tracked tree or is already + // authorized in it. + // That doesn't necessarily mean it's unauthorized (it can be + // authorized in a different tracker). + return Ok(false); + } + if !self.authenticated { + let authenticate_res = self + .authenticate(host) + .and_then(|_| self.verify_nonce(host)); + if let Some(err) = authenticate_res.err() { + self.is_valid = false; + return Err(err); + } + } + Ok(true) + } + + // Records the invocation in this tracker. + // This is needed for the recording mode only. + // This assumes that the address matching is correctly performed before + // calling this. + fn record_invocation( + &mut self, + host: &Host, + contract_id: &Hash, + function_name: Symbol, + args: ScVec, + ) -> Result<(), HostError> { + let frame_is_already_authorized = match self.invocation_id_in_call_stack.last() { + Some(Some(_)) => true, + _ => false, + }; + if frame_is_already_authorized { + return Err(host.err_general("duplicate authorizations are not allowed")); + } + if let Some(curr_invocation) = self.last_authorized_invocation_mut() { + curr_invocation + .sub_invocations + .push(AuthorizedInvocation::new_recording( + contract_id, + function_name, + args, + )); + *self.invocation_id_in_call_stack.last_mut().unwrap() = + Some(curr_invocation.sub_invocations.len() - 1); + } else { + // This would be a bug + return Err(host.err_general("unexpected missing authorized invocation")); + } + Ok(()) + } + + // Build the authorization payload from the invocations recorded in this + // tracker. + fn get_recorded_auth_payload(&self) -> Result { + Ok(RecordedAuthPayload { + address: self.address.clone(), + invocation: self.root_authorized_invocation.to_xdr_non_metered()?, + nonce: self.nonce, + }) + } + + // Checks if there is at least one authorized invocation in the current call + // stack. + fn has_authorized_invocations_in_stack(&self) -> bool { + self.invocation_id_in_call_stack.iter().any(|i| i.is_some()) + } + + fn invocation_to_xdr(&self, budget: &Budget) -> Result { + self.root_authorized_invocation.to_xdr(budget) + } + + fn push_frame(&mut self) { + self.invocation_id_in_call_stack.push(None); + } + + fn pop_frame(&mut self) { + self.invocation_id_in_call_stack.pop(); + } + + // Consumes nonce if the nonce check is still needed. + // Returns nonce if it has been consumed. + // Note, that for the invoker nonce is never needed. + fn maybe_consume_nonce(&mut self, host: &Host) -> Result, HostError> { + if !self.need_nonce { + return Ok(None); + } + self.need_nonce = false; + if let Some(addr) = &self.address { + Ok(Some(host.read_and_consume_nonce( + &self.root_authorized_invocation.contract_id, + addr, + )?)) + } else { + Ok(None) + } + } + + // Walks a path in the tree defined by `invocation_id_in_call_stack` and + // returns the last visited authorized node. + fn last_authorized_invocation_mut(&mut self) -> Option<&mut AuthorizedInvocation> { + for i in 0..self.invocation_id_in_call_stack.len() { + if self.invocation_id_in_call_stack[i].is_some() { + return self + .root_authorized_invocation + .last_authorized_invocation_mut(&self.invocation_id_in_call_stack, i + 1); + } + } + None + } + + // Tries to match the provided invocation to the authorized sub-invocation + // of the current tree and push it to the call stack. + // Returns `true` if the match has been found. + fn maybe_push_matching_invocation_frame( + &mut self, + contract_id: &Hash, + function_name: Symbol, + args: &ScVec, + ) -> bool { + let mut frame_index = None; + if let Some(curr_invocation) = self.last_authorized_invocation_mut() { + for i in 0..curr_invocation.sub_invocations.len() { + let sub_invocation = &mut curr_invocation.sub_invocations[i]; + if !sub_invocation.is_exhausted + && &sub_invocation.contract_id == contract_id + && sub_invocation.function_name == function_name + && &sub_invocation.args == args + { + frame_index = Some(i); + sub_invocation.is_exhausted = true; + } + } + } else { + if !self.root_authorized_invocation.is_exhausted + && &self.root_authorized_invocation.contract_id == contract_id + && self.root_authorized_invocation.function_name == function_name + && &self.root_authorized_invocation.args == args + { + frame_index = Some(0); + self.root_authorized_invocation.is_exhausted = true; + } + } + if frame_index.is_some() { + *self.invocation_id_in_call_stack.last_mut().unwrap() = frame_index; + } + frame_index.is_some() + } + + fn verify_nonce(&mut self, host: &Host) -> Result<(), HostError> { + let nonce_is_correct = if let Some(nonce) = self.maybe_consume_nonce(host)? { + if let Some(tracker_nonce) = self.nonce { + tracker_nonce == nonce + } else { + // If the nonce isn't set in the tracker, but is required, then + // it's incorrect. + false + } + } else { + // Nonce is either already checked or not needed in the first place. + true + }; + if nonce_is_correct { + Ok(()) + } else { + Err(ScUnknownErrorCode::General.into()) + } + } + + // Computes the payload that has to be signed in order to authenticate + // the authorized invocation tree corresponding to this tracker. + fn get_signature_payload(&self, host: &Host) -> Result<[u8; 32], HostError> { + let payload_preimage = HashIdPreimage::ContractAuth(HashIdPreimageContractAuth { + network_id: Hash( + host.with_ledger_info(|li| li.network_id.metered_clone(host.budget_ref()))?, + ), + invocation: self.invocation_to_xdr(host.budget_ref())?, + }); + + host.metered_hash_xdr(&payload_preimage) + } + + fn authenticate(&self, host: &Host) -> Result<(), HostError> { + if self.is_invoker { + return Ok(()); + } + if let Some(address) = &self.address { + // TODO: there should also be a mode where a dummy payload is used + // instead (for enforcing mode preflight). + let payload = self.get_signature_payload(host)?; + match address { + ScAddress::Account(acc) => { + check_account_authentication(host, &acc, &payload, &self.signature_args)?; + } + ScAddress::Contract(acc_contract) => { + check_account_contract_auth( + host, + acc_contract, + &payload, + &self.signature_args, + &self.root_authorized_invocation, + )?; + } + } + } else { + return Err(host.err_general("missing address to authenticate")); + } + Ok(()) + } + + // Checks whether the provided top-level authorized invocation happened + // for this tracker. + // This also makes sure double verification of the same invocation is not + // possible. + #[cfg(any(test, feature = "testutils"))] + fn verify_top_authorization( + &mut self, + address: &ScAddress, + contract_id: &Hash, + function_name: &Symbol, + args: &ScVec, + ) -> bool { + // The recording invariant is that every recorded authorization is + // immediately exhausted, so during the verification we revert the + // 'exhausted' flags back to 'false' values in order to prevent + // verifying the same authorization twice. + if !self.root_authorized_invocation.is_exhausted { + return false; + } + let is_matching = self.address.as_ref().unwrap() == address + && &self.root_authorized_invocation.contract_id == contract_id + && &self.root_authorized_invocation.function_name == function_name + && &self.root_authorized_invocation.args == args; + if is_matching { + self.root_authorized_invocation.is_exhausted = false; + } + is_matching + } +} + +impl Host { + #[cfg(test)] + pub(crate) fn read_nonce( + &self, + contract_id: &Hash, + address: &ScAddress, + ) -> Result { + let nonce_key_scval = ScVal::Object(Some(ScObject::NonceKey( + address.metered_clone(self.budget_ref())?, + ))); + let nonce_key = self.storage_key_for_contract( + contract_id.metered_clone(self.budget_ref())?, + nonce_key_scval, + ); + let curr_nonce: u64 = + if self.with_mut_storage(|storage| storage.has(&nonce_key, self.budget_ref()))? { + let sc_val = self.with_mut_storage(|storage| { + match storage.get(&nonce_key, self.budget_ref())?.data { + LedgerEntryData::ContractData(ContractDataEntry { val, .. }) => Ok(val), + _ => Err(self.err_general("unexpected missing nonce entry")), + } + })?; + match sc_val { + ScVal::Object(Some(ScObject::U64(val))) => val, + _ => { + return Err(self.err_general("unexpected nonce entry type")); + } + } + } else { + 0 + }; + Ok(curr_nonce) + } + + fn read_and_consume_nonce( + &self, + contract_id: &Hash, + address: &ScAddress, + ) -> Result { + let nonce_key_scval = ScVal::Object(Some(ScObject::NonceKey( + address.metered_clone(self.budget_ref())?, + ))); + let nonce_key = self.storage_key_for_contract( + contract_id.metered_clone(self.budget_ref())?, + nonce_key_scval.clone(), + ); + let curr_nonce: u64 = + if self.with_mut_storage(|storage| storage.has(&nonce_key, self.budget_ref()))? { + let sc_val = self.with_mut_storage(|storage| { + match storage.get(&nonce_key, self.budget_ref())?.data { + LedgerEntryData::ContractData(ContractDataEntry { val, .. }) => Ok(val), + _ => Err(self.err_general("unexpected missing nonce entry")), + } + })?; + match sc_val { + ScVal::Object(Some(ScObject::U64(val))) => val, + _ => { + return Err(self.err_general("unexpected nonce entry type")); + } + } + } else { + 0 + }; + let data = LedgerEntryData::ContractData(ContractDataEntry { + contract_id: contract_id.metered_clone(self.budget_ref())?, + key: nonce_key_scval, + val: ScVal::Object(Some(ScObject::U64(curr_nonce + 1))), + }); + let entry = LedgerEntry { + last_modified_ledger_seq: 0, + data, + ext: LedgerEntryExt::V0, + }; + self.with_mut_storage(|storage| storage.put(&nonce_key, &entry, self.budget_ref()))?; + Ok(curr_nonce) + } +} diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index e885e5736..739cd72bb 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -6,22 +6,25 @@ use core::cmp::Ordering; use core::fmt::Debug; use std::rc::Rc; -use sha2::{Digest, Sha256}; use soroban_env_common::{ xdr::{ AccountId, Asset, ContractCodeEntry, ContractDataEntry, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0, ContractId, CreateContractArgs, ExtensionPoint, Hash, HashIdPreimage, HostFunction, HostFunctionType, InstallContractCodeArgs, Int128Parts, - LedgerEntryData, LedgerKey, LedgerKeyContractCode, ScContractCode, ScHostContextErrorCode, - ScHostFnErrorCode, ScHostObjErrorCode, ScHostStorageErrorCode, ScHostValErrorCode, ScMap, - ScMapEntry, ScObject, ScStatusType, ScVal, ScVec, ThresholdIndexes, + LedgerEntryData, LedgerKey, LedgerKeyContractCode, ScAddress, ScContractCode, + ScHostContextErrorCode, ScHostFnErrorCode, ScHostObjErrorCode, ScHostStorageErrorCode, + ScHostValErrorCode, ScMap, ScMapEntry, ScObject, ScStatusType, ScVal, ScVec, }, Convert, InvokerType, Status, TryFromVal, TryIntoVal, VmCaller, VmCallerEnv, }; use crate::budget::{AsBudget, Budget, CostType}; use crate::events::{DebugError, DebugEvent, Events}; -use crate::storage::{Storage, StorageMap}; +use crate::storage::Storage; +use crate::{ + auth::{AuthorizationManager, AuthorizationManagerSnapshot, RecordedAuthPayload}, + storage::StorageMap, +}; use crate::host_object::{HostMap, HostObject, HostObjectType, HostVec}; #[cfg(feature = "vm")] @@ -55,6 +58,7 @@ use crate::Compare; pub(crate) struct RollbackPoint { storage: StorageMap, objects: usize, + auth: Option, } #[cfg(any(test, feature = "testutils"))] @@ -111,12 +115,12 @@ struct VmSlice { len: u32, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct LedgerInfo { pub protocol_version: u32, pub sequence_number: u32, pub timestamp: u64, - pub network_passphrase: Vec, + pub network_id: [u8; 32], pub base_reserve: u32, } @@ -133,12 +137,21 @@ pub(crate) struct HostImpl { // actually wants their clones to be metered by "the same" total budget pub(crate) budget: Budget, events: RefCell, + authorization_manager: RefCell, // Note: we're not going to charge metering for testutils because it's out of the scope // of what users will be charged for in production -- it's scaffolding for testing a contract, // but shouldn't be charged to the contract itself (and will never be compiled-in to // production hosts) #[cfg(any(test, feature = "testutils"))] contracts: RefCell>>, + // Store a copy of the `AuthorizationManager` for the last host function + // invocation. In order to emulate the production behavior in tests, we reset + // authorization manager after every invocation (as it's not meant to be + // shared between invocations). + // This enables test-only functions like `verify_top_authorization` + // that allow checking if the authorization has been recorded. + #[cfg(any(test, feature = "testutils"))] + previous_authorization_manager: RefCell>, } // Host is a newtype on Rc so we can impl Env for it below. #[derive(Default, Clone)] @@ -195,10 +208,15 @@ impl Host { objects: Default::default(), storage: RefCell::new(storage), context: Default::default(), - budget, + budget: budget.clone(), events: Default::default(), + authorization_manager: RefCell::new( + AuthorizationManager::new_enforcing_without_authorizations(budget), + ), #[cfg(any(test, feature = "testutils"))] contracts: Default::default(), + #[cfg(any(test, feature = "testutils"))] + previous_authorization_manager: RefCell::new(None), })) } @@ -219,6 +237,20 @@ impl Host { } } + pub fn switch_to_recording_auth(&self) { + *self.0.authorization_manager.borrow_mut() = + AuthorizationManager::new_recording(self.budget_cloned()); + } + + pub fn set_authorization_entries( + &self, + auth_entries: Vec, + ) -> Result<(), HostError> { + let new_auth_manager = AuthorizationManager::new_enforcing(self, auth_entries)?; + *self.0.authorization_manager.borrow_mut() = new_auth_manager; + Ok(()) + } + pub fn set_ledger_info(&self, info: LedgerInfo) { *self.0.ledger.borrow_mut() = Some(info) } @@ -304,7 +336,7 @@ impl Host { ) -> Result<(), HostError> { let ce = ContractEvent { ext: ExtensionPoint::V0, - contract_id: self.get_current_contract_id().ok(), + contract_id: self.get_current_contract_id_internal().ok(), type_, body: ContractEventBody::V0(ContractEventV0 { topics, data }), }; @@ -342,10 +374,21 @@ impl Host { /// operation fails, it can be used to roll the [`Host`] back to the state /// it had before its associated [`Frame`] was pushed. fn push_frame(&self, frame: Frame) -> Result { + // This is a bit hacky, as it relies on re-borrow to occur only during + // the account contract invocations. Instead we should probably call it + // in more explicitly different fashion and check if we're calling it + // instead of a borrow check. + let mut auth_snapshot = None; + if let Ok(mut auth_manager) = self.0.authorization_manager.try_borrow_mut() { + auth_manager.push_frame(&frame)?; + auth_snapshot = Some(auth_manager.snapshot()); + } + self.0.context.borrow_mut().push(frame); Ok(RollbackPoint { objects: self.0.objects.borrow().len(), storage: self.0.storage.borrow().map.clone(), + auth: auth_snapshot, }) } @@ -358,9 +401,33 @@ impl Host { .borrow_mut() .pop() .expect("unmatched host frame push/pop"); + // This is a bit hacky, as it relies on re-borrow to occur only doing + // the account contract invocations. Instead we should probably call it + // in more explicitly different fashion and check if we're calling it + // instead of a borrow check. + if let Ok(mut auth_manager) = self.0.authorization_manager.try_borrow_mut() { + auth_manager.pop_frame(); + } + #[cfg(any(test, feature = "testutils"))] + { + // Empty call stack in tests means that some contract function call + // has been finished and hence the authorization manager can be reset. + // In non-test scenarios, there should be no need to ever reset + // the authorization manager as the host instance shouldn't be + // shared between the contract invocations. + if self.0.context.borrow().is_empty() { + *self.0.previous_authorization_manager.borrow_mut() = + Some(self.0.authorization_manager.borrow().clone()); + self.0.authorization_manager.borrow_mut().reset(); + } + } + if let Some(rp) = orp { self.0.objects.borrow_mut().truncate(rp.objects); self.0.storage.borrow_mut().map = rp.storage; + if let Some(auth_rp) = rp.auth { + self.0.authorization_manager.borrow_mut().rollback(auth_rp); + } } Ok(()) } @@ -451,7 +518,7 @@ impl Host { /// Returns [`Hash`] contract ID from the VM frame at the top of the context /// stack, or a [`HostError`] if the context stack is empty or has a non-VM /// frame at its top. - fn get_current_contract_id(&self) -> Result { + pub(crate) fn get_current_contract_id_internal(&self) -> Result { self.with_current_frame(|frame| match frame { #[cfg(feature = "vm")] Frame::ContractVM(vm, _) => vm.contract_id.metered_clone(&self.0.budget), @@ -552,55 +619,56 @@ impl Host { self.charge_budget(CostType::ValXdrConv, 1)?; match ob { None => Err(self.err_status(ScHostObjErrorCode::UnknownReference)), - Some(ho) => match ho { - HostObject::Vec(vv) => { - // Here covers the cost of space allocating and maneuvering needed to go - // from one structure to the other. The actual conversion work (heavy lifting) - // is covered by `from_host_val`, which is recursive. - self.charge_budget(CostType::ScVecFromHostVec, vv.len() as u64)?; - let sv = vv.iter().map(|e| self.from_host_val(*e)).collect::, - HostError, - >>( - )?; - Ok(ScObject::Vec(ScVec(self.map_err(sv.try_into())?))) - } - HostObject::Map(mm) => { - // Here covers the cost of space allocating and maneuvering needed to go - // from one structure to the other. The actual conversion work (heavy lifting) - // is covered by `from_host_val`, which is recursive. - self.charge_budget(CostType::ScMapFromHostMap, mm.len() as u64)?; - let mut mv = Vec::new(); - for (k, v) in mm.iter(self)? { - let key = self.from_host_val(*k)?; - let val = self.from_host_val(*v)?; - mv.push(ScMapEntry { key, val }); + Some(ho) => { + match ho { + HostObject::Vec(vv) => { + // Here covers the cost of space allocating and maneuvering needed to go + // from one structure to the other. The actual conversion work (heavy lifting) + // is covered by `from_host_val`, which is recursive. + self.charge_budget(CostType::ScVecFromHostVec, vv.len() as u64)?; + let sv = vv + .iter() + .map(|e| self.from_host_val(*e)) + .collect::, HostError>>()?; + Ok(ScObject::Vec(ScVec(self.map_err(sv.try_into())?))) + } + HostObject::Map(mm) => { + // Here covers the cost of space allocating and maneuvering needed to go + // from one structure to the other. The actual conversion work (heavy lifting) + // is covered by `from_host_val`, which is recursive. + self.charge_budget(CostType::ScMapFromHostMap, mm.len() as u64)?; + let mut mv = Vec::new(); + for (k, v) in mm.iter(self)? { + let key = self.from_host_val(*k)?; + let val = self.from_host_val(*v)?; + mv.push(ScMapEntry { key, val }); + } + Ok(ScObject::Map(ScMap(self.map_err(mv.try_into())?))) + } + HostObject::U64(u) => Ok(ScObject::U64(*u)), + HostObject::I64(i) => Ok(ScObject::I64(*i)), + HostObject::U128(u) => Ok(ScObject::U128(Int128Parts { + lo: *u as u64, + hi: (*u >> 64) as u64, + })), + HostObject::I128(u) => { + let u = *u as u128; + Ok(ScObject::I128(Int128Parts { + lo: u as u64, + hi: (u >> 64) as u64, + })) + } + HostObject::Bytes(b) => Ok(ScObject::Bytes( + self.map_err(b.metered_clone(&self.0.budget)?.try_into())?, + )), + HostObject::ContractCode(cc) => { + Ok(ScObject::ContractCode(cc.metered_clone(&self.0.budget)?)) + } + HostObject::Address(addr) => { + Ok(ScObject::Address(addr.metered_clone(&self.0.budget)?)) } - Ok(ScObject::Map(ScMap(self.map_err(mv.try_into())?))) - } - HostObject::U64(u) => Ok(ScObject::U64(*u)), - HostObject::I64(i) => Ok(ScObject::I64(*i)), - HostObject::U128(u) => Ok(ScObject::U128(Int128Parts { - lo: *u as u64, - hi: (*u >> 64) as u64, - })), - HostObject::I128(u) => { - let u = *u as u128; - Ok(ScObject::I128(Int128Parts { - lo: u as u64, - hi: (u >> 64) as u64, - })) - } - HostObject::Bytes(b) => Ok(ScObject::Bytes( - self.map_err(b.metered_clone(&self.0.budget)?.try_into())?, - )), - HostObject::ContractCode(cc) => { - Ok(ScObject::ContractCode(cc.metered_clone(&self.0.budget)?)) - } - HostObject::AccountId(aid) => { - Ok(ScObject::AccountId(aid.metered_clone(&self.0.budget)?)) } - }, + } } }) } @@ -637,9 +705,10 @@ impl Host { self.add_host_object::>(b.as_vec().metered_clone(&self.0.budget)?.into()) } ScObject::ContractCode(cc) => self.add_host_object(cc.metered_clone(&self.0.budget)?), - ScObject::AccountId(account_id) => { - self.add_host_object(account_id.metered_clone(&self.0.budget)?) + ScObject::NonceKey(_) => { + Err(self.err_general("nonce keys aren't allowed to be used directly")) } + ScObject::Address(addr) => self.add_host_object(addr.metered_clone(&self.0.budget)?), } } @@ -741,10 +810,10 @@ impl Host { Ok(id_obj) } - pub fn get_contract_id_from_asset(&self, asset: Asset) -> Result { + pub(crate) fn get_contract_id_from_asset(&self, asset: Asset) -> Result { let id_preimage = self.id_preimage_from_asset(asset)?; let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?; - Ok(self.add_host_object(id_arr.to_vec())?) + Ok(Hash(id_arr)) } // Notes on metering: this is covered by the called components. @@ -778,17 +847,25 @@ impl Host { } } - // Notes on metering: this is covered by the called components. fn call_n( &self, - contract: Object, + id: Object, func: Symbol, args: &[RawVal], allow_reentry: bool, ) -> Result { - // Get contract ID - let id = self.hash_from_obj_input("contract", contract)?; + let id = self.hash_from_obj_input("contract", id)?; + self.call_n_internal(&id, func, args, allow_reentry) + } + // Notes on metering: this is covered by the called components. + pub(crate) fn call_n_internal( + &self, + id: &Hash, + func: Symbol, + args: &[RawVal], + allow_reentry: bool, + ) -> Result { if !allow_reentry { for f in self.0.context.borrow().iter() { let exist_id = match f { @@ -799,7 +876,7 @@ impl Host { Frame::TestContract(tc) => &tc.id, Frame::HostFunction(_) => continue, }; - if id == *exist_id { + if id == exist_id { return Err(self.err_status_msg( // TODO: proper error code ScHostContextErrorCode::UnknownError, @@ -964,6 +1041,25 @@ impl Host { self.with_mut_storage(|storage| storage.put(&key, &val, self.as_budget())) } + #[cfg(any(test, feature = "testutils"))] + pub fn verify_top_authorization( + &self, + address: Object, + contract_id: Hash, + function_name: Symbol, + args: Object, + ) -> Result { + let address = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?; + let args = self.call_args_to_scvec(args)?; + Ok(self + .0 + .previous_authorization_manager + .borrow_mut() + .as_mut() + .ok_or(self.err_general("previous invocation is missing - no auth to verify"))? + .verify_top_authorization(&address, &contract_id, &function_name, &args)) + } + /// Records a `System` contract event. `topics` is expected to be a `SCVec` /// length <= 4 that cannot contain `Vec`, `Map`, or `Bytes` with length > 32 /// On success, returns an `SCStatus::Ok`. @@ -1038,6 +1134,46 @@ impl Host { .verify(payload, &sig) .map_err(|_| self.err_general("Failed ED25519 verification")) } + + pub(crate) fn get_invoking_contract_internal(&self) -> Result { + let frames = self.0.context.borrow(); + // the previous frame must exist and must be a contract + let hash = match frames.as_slice() { + [.., f2, _] => match f2 { + #[cfg(feature = "vm")] + Frame::ContractVM(vm, _) => Ok(vm.contract_id.metered_clone(&self.0.budget)?), + Frame::HostFunction(_) => Err(self.err_general("invoker is not a contract")), + Frame::Token(id, _) => Ok(id.clone()), + #[cfg(any(test, feature = "testutils"))] + Frame::TestContract(tc) => Ok(tc.id.clone()), // no metering + }, + _ => Err(self.err_general("no frames to derive the invoker from")), + }?; + Ok(hash) + } + + // Returns the recorded per-address authorization payloads that would cover the + // top-level contract function invocation in the enforcing mode. + // This should only be called in the recording authorization mode, i.e. only + // if `switch_to_recording_auth` has been called. + pub fn get_recorded_auth_payloads(&self) -> Result, HostError> { + #[cfg(not(any(test, feature = "testutils")))] + { + self.0 + .authorization_manager + .borrow() + .get_recorded_auth_payloads() + } + #[cfg(any(test, feature = "testutils"))] + { + self.0 + .previous_authorization_manager + .borrow() + .as_ref() + .ok_or(self.err_general("previous invocation is missing - no payloads recorded"))? + .get_recorded_auth_payloads() + } + } } // Notes on metering: these are called from the guest and thus charged on the VM instructions. @@ -1249,30 +1385,12 @@ impl VmCallerEnv for Host { Ok(st as u64) } - // Notes on metering: covered by the components - fn get_invoking_account(&self, vmcaller: &mut VmCaller) -> Result { - if self.get_invoker_type(vmcaller)? != InvokerType::Account as u64 { - return Err(self.err_general("invoker is not an account")); - } - self.source_account().map(|aid| self.add_host_object(aid))? - } - // Notes on metering: covered by the components fn get_invoking_contract(&self, _vmcaller: &mut VmCaller) -> Result { - let frames = self.0.context.borrow(); - // the previous frame must exist and must be a contract - let hash = match frames.as_slice() { - [.., f2, _] => match f2 { - #[cfg(feature = "vm")] - Frame::ContractVM(vm, _) => Ok(vm.contract_id.metered_clone(&self.0.budget)?), - Frame::HostFunction(_) => Err(self.err_general("invoker is not a contract")), - Frame::Token(id, _) => Ok(id.clone()), - #[cfg(any(test, feature = "testutils"))] - Frame::TestContract(tc) => Ok(tc.id.clone()), // no metering - }, - _ => Err(self.err_general("no frames to derive the invoker from")), - }?; - self.add_host_object(>::from(hash.0)) + let invoking_contract_hash = self.get_invoking_contract_internal()?; + Ok(self + .add_host_object(>::from(invoking_contract_hash.0))? + .into()) } // Metered: covered by `visit` and `metered_cmp`. @@ -1307,9 +1425,15 @@ impl VmCallerEnv for Host { } // Notes on metering: covered by the components. - fn get_current_contract(&self, _vmcaller: &mut VmCaller) -> Result { - let hash: Hash = self.get_current_contract_id()?; - self.add_host_object(>::from(hash.0)) + fn get_current_contract_address( + &self, + _vmcaller: &mut VmCaller, + ) -> Result { + Ok(self + .add_host_object(ScAddress::Contract( + self.get_current_contract_id_internal()?, + ))? + .into()) } // Notes on metering: covered by `add_host_object`. @@ -1706,7 +1830,7 @@ impl VmCallerEnv for Host { ) -> Result { let key = self.contract_data_key_from_rawval(k)?; let data = LedgerEntryData::ContractData(ContractDataEntry { - contract_id: self.get_current_contract_id()?, + contract_id: self.get_current_contract_id_internal()?, key: self.from_host_val(k)?, val: self.from_host_val(v)?, }); @@ -1773,7 +1897,7 @@ impl VmCallerEnv for Host { wasm_hash: Object, salt: Object, ) -> Result { - let contract_id = self.get_current_contract_id()?; + let contract_id = self.get_current_contract_id_internal()?; let salt = self.uint256_from_obj_input("salt", salt)?; let code = ScContractCode::WasmRef(self.hash_from_obj_input("wasm_hash", wasm_hash)?); @@ -2086,38 +2210,6 @@ impl VmCallerEnv for Host { Ok(self.add_host_object(vnew)?.into()) } - fn hash_from_bytes( - &self, - _vmcaller: &mut VmCaller, - x: Object, - ) -> Result { - todo!() - } - - fn hash_to_bytes( - &self, - _vmcaller: &mut VmCaller, - x: Object, - ) -> Result { - todo!() - } - - fn public_key_from_bytes( - &self, - _vmcaller: &mut VmCaller, - x: Object, - ) -> Result { - todo!() - } - - fn public_key_to_bytes( - &self, - _vmcaller: &mut VmCaller, - x: Object, - ) -> Result { - todo!() - } - // Notes on metering: covered by components. fn compute_hash_sha256( &self, @@ -2144,65 +2236,6 @@ impl VmCallerEnv for Host { Ok(res?.into()) } - // Notes on metering: covered by components. - fn account_get_low_threshold( - &self, - _vmcaller: &mut VmCaller, - a: Object, - ) -> Result { - let threshold = - self.load_account(self.to_account_id(a)?)?.thresholds.0[ThresholdIndexes::Low as usize]; - let threshold = Into::::into(threshold); - Ok(threshold.into()) - } - - // Notes on metering: covered by components. - fn account_get_medium_threshold( - &self, - _vmcaller: &mut VmCaller, - a: Object, - ) -> Result { - let threshold = - self.load_account(self.to_account_id(a)?)?.thresholds.0[ThresholdIndexes::Med as usize]; - let threshold = Into::::into(threshold); - Ok(threshold.into()) - } - - // Notes on metering: covered by components. - fn account_get_high_threshold( - &self, - _vmcaller: &mut VmCaller, - a: Object, - ) -> Result { - let threshold = self.load_account(self.to_account_id(a)?)?.thresholds.0 - [ThresholdIndexes::High as usize]; - let threshold = Into::::into(threshold); - Ok(threshold.into()) - } - - // Notes on metering: covered by components. - fn account_exists( - &self, - _vmcaller: &mut VmCaller, - a: Object, - ) -> Result { - Ok(self.has_account(self.to_account_id(a)?)?.into()) - } - - // Notes on metering: some covered. The for loop and comparisons are free (for now). - fn account_get_signer_weight( - &self, - _vmcaller: &mut VmCaller, - a: Object, - s: Object, - ) -> Result { - let target_signer = self.to_u256(s)?; - - let ae = self.load_account(self.to_account_id(a)?)?; - let weight = self.get_signer_weight_from_account(target_signer, &ae)?; - Ok((weight as u32).into()) - } - fn get_ledger_version(&self, _vmcaller: &mut VmCaller) -> Result { self.with_ledger_info(|li| Ok(li.protocol_version.into())) } @@ -2215,26 +2248,9 @@ impl VmCallerEnv for Host { self.with_ledger_info(|li| Ok(self.add_host_object(li.timestamp)?.into())) } - fn get_ledger_network_passphrase( - &self, - _vmcaller: &mut VmCaller, - ) -> Result { - Ok(self - .with_ledger_info(|li| self.add_host_object(li.network_passphrase.clone()))? - .into()) - } - fn get_ledger_network_id(&self, _vmcaller: &mut VmCaller) -> Result { Ok(self - .with_ledger_info(|li| { - let hash = Sha256::digest(li.network_passphrase.clone()) - .as_slice() - .to_vec(); - if hash.len() != 32 { - return Err(self.err_general("incorrect hash size")); - } - self.add_host_object(hash) - })? + .with_ledger_info(|li| self.add_host_object(li.network_id.to_vec()))? .into()) } @@ -2281,6 +2297,35 @@ impl VmCallerEnv for Host { fn dummy0(&self, vmcaller: &mut VmCaller) -> Result { Ok(().into()) } + + fn require_auth( + &self, + vmcaller: &mut VmCaller, + address: Object, + args: Object, + ) -> Result { + let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?; + + Ok(self + .0 + .authorization_manager + .borrow_mut() + .require_auth( + self, + address.get_handle(), + addr, + self.call_args_to_scvec(args)?, + )? + .into()) + } + + fn get_current_contract_id( + &self, + vmcaller: &mut VmCaller, + ) -> Result { + let id = self.get_current_contract_id_internal()?; + Ok(self.add_host_object(id.0.to_vec())?) + } } #[cfg(any(test, feature = "testutils"))] diff --git a/soroban-env-host/src/host/comparison.rs b/soroban-env-host/src/host/comparison.rs index 8476e388c..f462fb966 100644 --- a/soroban-env-host/src/host/comparison.rs +++ b/soroban-env-host/src/host/comparison.rs @@ -6,8 +6,8 @@ use soroban_env_common::{ DataEntry, Hash, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyClaimableBalance, LedgerKeyConfigSetting, LedgerKeyContractCode, LedgerKeyData, LedgerKeyLiquidityPool, LedgerKeyOffer, LedgerKeyTrustLine, LiquidityPoolEntry, OfferEntry, - PublicKey, ScContractCode, ScMap, ScObject, ScVal, ScVec, TrustLineAsset, TrustLineEntry, - Uint256, + PublicKey, ScAddress, ScContractCode, ScMap, ScObject, ScVal, ScVec, TrustLineAsset, + TrustLineEntry, Uint256, }, Compare, }; @@ -31,7 +31,7 @@ fn host_obj_discriminant(ho: &HostObject) -> usize { HostObject::I128(_) => 5, HostObject::Bytes(_) => 6, HostObject::ContractCode(_) => 7, - HostObject::AccountId(_) => 8, + HostObject::Address(_) => 8, } } @@ -49,7 +49,7 @@ impl Compare for Host { (Map(a), Map(b)) => self.compare(a, b), (Bytes(a), Bytes(b)) => self.as_budget().compare(&a.as_slice(), &b.as_slice()), (ContractCode(a), ContractCode(b)) => self.as_budget().compare(a, b), - (AccountId(a), AccountId(b)) => self.as_budget().compare(a, b), + (Address(a), Address(b)) => self.as_budget().compare(a, b), // List out at least one side of all the remaining cases here so // we don't accidentally forget to update this when/if a new @@ -62,7 +62,7 @@ impl Compare for Host { | (Map(_), _) | (Bytes(_), _) | (ContractCode(_), _) - | (AccountId(_), _) => { + | (Address(_), _) => { let a = host_obj_discriminant(a); let b = host_obj_discriminant(b); Ok(a.cmp(&b)) @@ -80,6 +80,15 @@ impl Compare<&[u8]> for Budget { } } +impl Compare<[u8; N]> for Budget { + type Error = HostError; + + fn compare(&self, a: &[u8; N], b: &[u8; N]) -> Result { + self.charge(CostType::BytesCmp, min(a.len(), b.len()) as u64)?; + Ok(a.cmp(b)) + } +} + // Apparently we can't do a blanket T:Ord impl because there are Ord derivations // that also go through &T and Option that conflict with our impls above // (patches welcome from someone who understands trait-system workarounds @@ -139,6 +148,7 @@ impl_compare_fixed_size_ord_type!(Hash); impl_compare_fixed_size_ord_type!(Uint256); impl_compare_fixed_size_ord_type!(ScContractCode); impl_compare_fixed_size_ord_type!(AccountId); +impl_compare_fixed_size_ord_type!(ScAddress); impl_compare_fixed_size_ord_type!(PublicKey); impl_compare_fixed_size_ord_type!(TrustLineAsset); @@ -211,7 +221,8 @@ impl Compare for Budget { | (I128(_), _) | (Bytes(_), _) | (ContractCode(_), _) - | (AccountId(_), _) => Ok(a.cmp(b)), + | (Address(_), _) + | (NonceKey(_), _) => Ok(a.cmp(b)), }, (Object(_), _) | (U63(_), _) diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs index b003693fb..dee2164aa 100644 --- a/soroban-env-host/src/host/conversion.rs +++ b/soroban-env-host/src/host/conversion.rs @@ -11,7 +11,8 @@ use crate::{ }; use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH}; use sha2::{Digest, Sha256}; -use soroban_env_common::xdr::AccountId; +use soroban_env_common::xdr::{self, AccountId, ScObject}; +use soroban_env_common::TryFromVal; impl Host { // Notes on metering: free @@ -56,12 +57,6 @@ impl Host { } } - pub(crate) fn to_account_id(&self, a: Object) -> Result { - self.visit_obj(a, |account_id: &AccountId| { - Ok(account_id.metered_clone(&self.0.budget)?) - }) - } - pub(crate) fn to_u256_from_account( &self, account_id: &AccountId, @@ -178,6 +173,14 @@ impl Host { self.visit_obj(k, |bytes: &Vec| self.ed25519_pub_key_from_bytes(bytes)) } + pub(crate) fn account_id_from_bytes(&self, k: Object) -> Result { + self.visit_obj(k, |bytes: &Vec| { + Ok(AccountId(xdr::PublicKey::PublicKeyTypeEd25519( + self.fixed_length_bytes_from_slice("account_id", bytes.as_slice())?, + ))) + }) + } + pub fn sha256_hash_from_bytes_input(&self, x: Object) -> Result, HostError> { self.visit_obj(x, |bytes: &Vec| { self.charge_budget(CostType::ComputeSha256Hash, bytes.len() as u64)?; @@ -194,20 +197,42 @@ impl Host { // Notes on metering: covered by components. pub fn storage_key_from_rawval(&self, k: RawVal) -> Result { Ok(LedgerKey::ContractData(LedgerKeyContractData { - contract_id: self.get_current_contract_id()?, + contract_id: self.get_current_contract_id_internal()?, key: self.from_host_val(k)?, })) } + pub(crate) fn storage_key_for_contract(&self, contract_id: Hash, key: ScVal) -> LedgerKey { + LedgerKey::ContractData(LedgerKeyContractData { contract_id, key }) + } + + pub fn storage_key_from_scval(&self, key: ScVal) -> Result { + Ok(LedgerKey::ContractData(LedgerKeyContractData { + contract_id: self.get_current_contract_id_internal()?, + key, + })) + } + // Notes on metering: covered by components. pub fn contract_data_key_from_rawval(&self, k: RawVal) -> Result { - if self.from_host_val(k)? == ScVal::Static(ScStatic::LedgerKeyContractCode) { - return Err(self.err_status_msg( - ScHostFnErrorCode::InputArgsInvalid, - "cannot update contract code", - )); - } - self.storage_key_from_rawval(k) + let key_scval = self.from_host_val(k)?; + match &key_scval { + ScVal::Static(ScStatic::LedgerKeyContractCode) => { + return Err(self.err_status_msg( + ScHostFnErrorCode::InputArgsInvalid, + "cannot update contract code", + )); + } + ScVal::Object(Some(ScObject::NonceKey(_))) => { + return Err(self.err_status_msg( + ScHostFnErrorCode::InputArgsInvalid, + "cannot access internal nonce", + )); + } + _ => (), + }; + + self.storage_key_from_scval(key_scval) } fn event_topic_from_rawval(&self, topic: RawVal) -> Result { @@ -270,6 +295,22 @@ impl Host { }) } + // Metering: free? + pub(crate) fn call_args_to_scvec(&self, args: Object) -> Result { + self.visit_obj(args, |hv: &HostVec| { + Ok(ScVec( + hv.iter() + .map(|v| { + ScVal::try_from_val(self, v) + .map_err(|_| self.err_general("couldn't convert RawVal")) + }) + .collect::, HostError>>()? + .try_into() + .map_err(|_| self.err_general("too many args"))?, + )) + }) + } + pub(crate) fn scvals_to_rawvals(&self, sc_vals: &[ScVal]) -> Result, HostError> { sc_vals .iter() diff --git a/soroban-env-host/src/host/data_helper.rs b/soroban-env-host/src/host/data_helper.rs index 24d1a6c2e..2f3826ac2 100644 --- a/soroban-env-host/src/host/data_helper.rs +++ b/soroban-env-host/src/host/data_helper.rs @@ -173,12 +173,6 @@ impl Host { }) } - // notes on metering: covered by `has`. - pub fn has_account(&self, account_id: AccountId) -> Result { - let acc = self.to_account_key(account_id); - self.with_mut_storage(|storage| storage.has(&acc, self.as_budget())) - } - pub(crate) fn to_account_key(&self, account_id: AccountId) -> LedgerKey { LedgerKey::Account(LedgerKeyAccount { account_id }) } diff --git a/soroban-env-host/src/host/metered_clone.rs b/soroban-env-host/src/host/metered_clone.rs index 779fbb5bb..e3f620acc 100644 --- a/soroban-env-host/src/host/metered_clone.rs +++ b/soroban-env-host/src/host/metered_clone.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use soroban_env_common::{ - xdr::{BytesM, LedgerEntry, LedgerKey, ScMap, ScObject, ScVal}, + xdr::{BytesM, LedgerEntry, LedgerKey, ScAddress, ScMap, ScObject, ScVal}, RawVal, }; @@ -78,6 +78,7 @@ impl MeteredClone for AccessType {} impl MeteredClone for AccountId {} impl MeteredClone for ScContractCode {} impl MeteredClone for Uint256 {} +impl MeteredClone for ScAddress {} impl MeteredClone for [u8; N] {} // TODO: this isn't correct: these two have substructure to account for; @@ -104,7 +105,8 @@ impl MeteredClone for ScVal { | ScObject::U128(_) | ScObject::I128(_) | ScObject::ContractCode(_) - | ScObject::AccountId(_) => Ok(()), + | ScObject::Address(_) + | ScObject::NonceKey(_) => Ok(()), } } ScVal::Object(None) diff --git a/soroban-env-host/src/host_object.rs b/soroban-env-host/src/host_object.rs index 1ab9d065f..1f98e462d 100644 --- a/soroban-env-host/src/host_object.rs +++ b/soroban-env-host/src/host_object.rs @@ -20,7 +20,7 @@ pub(crate) enum HostObject { I128(i128), Bytes(Vec), ContractCode(xdr::ScContractCode), - AccountId(xdr::AccountId), + Address(xdr::ScAddress), } pub(crate) trait HostObjectType: Sized { @@ -59,4 +59,4 @@ declare_host_object_type!(u128, U128, U128); declare_host_object_type!(i128, I128, I128); declare_host_object_type!(Vec, Bytes, Bytes); declare_host_object_type!(xdr::ScContractCode, ContractCode, ContractCode); -declare_host_object_type!(xdr::AccountId, AccountId, AccountId); +declare_host_object_type!(xdr::ScAddress, Address, Address); diff --git a/soroban-env-host/src/lib.rs b/soroban-env-host/src/lib.rs index b9eb186c6..17bcbef9e 100644 --- a/soroban-env-host/src/lib.rs +++ b/soroban-env-host/src/lib.rs @@ -35,6 +35,7 @@ pub(crate) mod host_object; mod native_contract; +pub mod auth; #[cfg(feature = "vm")] pub mod vm; #[cfg(feature = "vm")] diff --git a/soroban-env-host/src/native_contract.rs b/soroban-env-host/src/native_contract.rs index 20c65c70e..e4964cd57 100644 --- a/soroban-env-host/src/native_contract.rs +++ b/soroban-env-host/src/native_contract.rs @@ -1,5 +1,5 @@ pub(crate) mod base_types; -mod invoker; +pub(crate) mod contract_error; pub(crate) mod token; use crate::host::{Host, HostError}; @@ -11,5 +11,7 @@ pub trait NativeContract { pub use token::Token; +pub(crate) mod account_contract; + #[cfg(test)] pub(crate) mod testutils; diff --git a/soroban-env-host/src/native_contract/account_contract.rs b/soroban-env-host/src/native_contract/account_contract.rs new file mode 100644 index 000000000..7433a4748 --- /dev/null +++ b/soroban-env-host/src/native_contract/account_contract.rs @@ -0,0 +1,182 @@ +use crate::auth::AuthorizedInvocation; +// This is a built-in account 'contract'. This is not actually a contract, as +// it doesn't need to be directly invoked. But semantically this is analagous +// to a generic smart wallet contract that supports authentication and blanket +// context authorization. +use crate::host::metered_clone::MeteredClone; +use crate::host::Host; +use crate::native_contract::{ + base_types::{BytesN, Map}, + contract_error::ContractError, +}; +use crate::{err, HostError}; +use core::cmp::Ordering; +use soroban_env_common::xdr::{Hash, ThresholdIndexes, Uint256}; +use soroban_env_common::{Env, EnvBase, RawVal, Symbol, TryFromVal, TryIntoVal}; + +use crate::native_contract::base_types::Vec as HostVec; + +const MAX_ACCOUNT_SIGNATURES: u32 = 20; + +use soroban_env_common::xdr::AccountId; +use soroban_native_sdk_macros::contracttype; + +#[derive(Clone)] +#[contracttype] +pub struct AuthorizationContext { + pub contract: BytesN<32>, + pub fn_name: Symbol, + pub args: HostVec, +} + +#[derive(Clone)] +#[contracttype] +pub struct AccountEd25519Signature { + pub public_key: BytesN<32>, + pub signature: BytesN<64>, +} + +impl AuthorizationContext { + fn from_invocation(host: &Host, invocation: &AuthorizedInvocation) -> Result { + let args = + HostVec::try_from_val(host, &host.scvals_to_rawvals(invocation.args.0.as_slice())?)?; + Ok(Self { + contract: BytesN::try_from_val( + host, + &host.bytes_new_from_slice(invocation.contract_id.0.as_slice())?, + )?, + fn_name: invocation.function_name, + args, + }) + } +} + +fn invocation_tree_to_auth_contexts( + host: &Host, + invocation: &AuthorizedInvocation, + out_contexts: &mut HostVec, +) -> Result<(), HostError> { + out_contexts.push(&AuthorizationContext::from_invocation(host, invocation)?)?; + for sub_invocation in &invocation.sub_invocations { + invocation_tree_to_auth_contexts(host, sub_invocation, out_contexts)?; + } + Ok(()) +} + +pub(crate) fn check_account_contract_auth( + host: &Host, + account_contract: &Hash, + signature_payload: &[u8; 32], + signature_args: &Vec, + invocation: &AuthorizedInvocation, +) -> Result<(), HostError> { + let payload_obj = host.bytes_new_from_slice(signature_payload)?; + let signature_args_vec = HostVec::try_from_val(host, signature_args)?; + let mut auth_context_vec = HostVec::new(host)?; + invocation_tree_to_auth_contexts(host, invocation, &mut auth_context_vec)?; + Ok(host + .call_n_internal( + account_contract, + Symbol::from_str("check_auth"), + &[ + payload_obj.into(), + signature_args_vec.into(), + auth_context_vec.into(), + ], + // Allow reentry for this function in order to do wallet admin ops + // within the auth framework. Maybe there is a more elegant way + // around this. + // TODO: check if there are security concerns about this. + true, + )? + .try_into()?) +} + +pub(crate) fn check_account_authentication( + host: &Host, + account_id: &AccountId, + payload: &[u8], + signature_args: &Vec, +) -> Result<(), HostError> { + if signature_args.len() != 1 { + return Err(err!( + host, + ContractError::AuthenticationError, + "incorrect number of signature args: {} != 1", + signature_args.len() as u32 + )); + } + let sigs: HostVec = signature_args[0].try_into_val(host).map_err(|_| { + host.err_status_msg( + ContractError::AuthenticationError, + "incompatible signature format", + ) + })?; + + // Check if there is too many signatures: there shouldn't be more + // signatures then the amount of account signers. + if sigs.len()? > MAX_ACCOUNT_SIGNATURES { + return Err(err!( + host, + ContractError::AuthenticationError, + "too many account signers: {} > {}", + sigs.len()?, + MAX_ACCOUNT_SIGNATURES + )); + } + let payload_obj = host.bytes_new_from_slice(payload)?; + let account = host.load_account(account_id.metered_clone(host.budget_ref())?)?; + let mut prev_pk: Option> = None; + let mut weight = 0u32; + for i in 0..sigs.len()? { + let sig: AccountEd25519Signature = sigs.get(i)?; + // Cannot take multiple signatures from the same key + if let Some(prev) = prev_pk { + if prev.compare(&sig.public_key)? != Ordering::Less { + return Err(err!( + host, + ContractError::AuthenticationError, + "public keys are not ordered: {} >= {}", + prev, + sig.public_key + )); + } + } + + host.verify_sig_ed25519( + payload_obj.clone(), + sig.public_key.clone().into(), + sig.signature.into(), + )?; + + let signer_weight = + host.get_signer_weight_from_account(Uint256(sig.public_key.to_array()?), &account)?; + // 0 weight indicates that signer doesn't belong to this account. Treat + // this as an error to indicate a bug in signatures, even if another + // signers would have enough weight. + if signer_weight == 0 { + return Err(err!( + host, + ContractError::AuthenticationError, + "signer '{}' does not belong to account", + sig.public_key + )); + } + // Overflow isn't possible here as + // 255 * MAX_ACCOUNT_SIGNATURES is < u32::MAX. + weight += signer_weight as u32; + prev_pk = Some(sig.public_key); + } + let threshold = account.thresholds.0[ThresholdIndexes::Med as usize]; + if weight < threshold as u32 { + Err(err!( + host, + ContractError::AuthenticationError, + "signature weight is lower than threshold: {} < {}", + weight, + threshold as u32 + )) + } else { + Ok(()) + } +} diff --git a/soroban-env-host/src/native_contract/base_types.rs b/soroban-env-host/src/native_contract/base_types.rs index 99971f8f6..da754e709 100644 --- a/soroban-env-host/src/native_contract/base_types.rs +++ b/soroban-env-host/src/native_contract/base_types.rs @@ -2,7 +2,7 @@ use crate::budget::CostType; use crate::host::{Host, HostError}; use core::cmp::Ordering; -use soroban_env_common::xdr::{AccountId, ScObjectType}; +use soroban_env_common::xdr::{AccountId, ScAddress, ScObjectType}; use soroban_env_common::{Compare, ConversionError, Env, EnvBase, Object, RawVal, TryFromVal}; #[derive(Clone)] @@ -149,6 +149,12 @@ impl TryFromVal> for RawVal { } } +impl Into for BytesN { + fn into(self) -> RawVal { + self.object.into() + } +} + impl From> for Object { fn from(bytes: BytesN) -> Self { bytes.object @@ -323,12 +329,38 @@ impl TryFromVal for RawVal { } } +impl Into for Vec { + fn into(self) -> RawVal { + self.object.into() + } +} + impl From for Object { fn from(vec: Vec) -> Self { vec.object } } +impl TryFromVal> for Vec { + type Error = HostError; + + fn try_from_val(env: &Host, vals: &std::vec::Vec) -> Result { + let mut v = Vec::new(env)?; + for rv in vals { + v.push_raw(rv.clone())? + } + Ok(v) + } +} + +impl TryFromVal> for Vec { + type Error = HostError; + + fn try_from_val(env: &Host, vals: &&std::vec::Vec) -> Result { + Vec::try_from_val(env, *vals) + } +} + impl Vec { pub fn new(env: &Host) -> Result { let vec = env.vec_new(().into())?; @@ -366,29 +398,76 @@ impl Vec { } } -impl TryFromVal for AccountId { +#[derive(Clone)] +pub struct Address { + host: Host, + object: Object, +} + +impl TryFromVal for Address { type Error = HostError; - fn try_from_val(env: &Host, val: &Object) -> Result { - let val = *val; - env.visit_obj(val, |acc: &AccountId| Ok(acc.clone())) + fn try_from_val(env: &Host, obj: &Object) -> Result { + if obj.is_obj_type(ScObjectType::Address) { + Ok(Address { + host: env.clone(), + object: obj.clone(), + }) + } else { + Err(ConversionError.into()) + } } } -impl TryFromVal for AccountId { +impl TryFromVal for Address { type Error = HostError; fn try_from_val(env: &Host, val: &RawVal) -> Result { let val = *val; let obj: Object = val.try_into()?; - AccountId::try_from_val(env, &obj) + Address::try_from_val(env, &obj) + } +} + +impl TryFromVal for RawVal { + type Error = HostError; + + fn try_from_val(_env: &Host, val: &Address) -> Result { + Ok(val.object.into()) } } -impl TryFromVal for RawVal { +impl Compare
for Host { type Error = HostError; - fn try_from_val(env: &Host, val: &AccountId) -> Result { - Ok(env.add_host_object(val.clone())?.to_raw()) + fn compare(&self, a: &Address, b: &Address) -> Result { + self.compare(&a.object, &b.object) + } +} + +impl From
for Object { + fn from(a: Address) -> Self { + a.object + } +} + +impl Address { + pub(crate) fn from_account(env: &Host, account_id: &AccountId) -> Result { + Address::try_from_val( + env, + &env.add_host_object(ScAddress::Account(account_id.clone()))?, + ) + } + + pub(crate) fn to_sc_address(&self) -> Result { + self.host + .visit_obj(self.object, |addr: &ScAddress| Ok(addr.clone())) + } + + pub(crate) fn authorize(&self, args: Vec) -> Result<(), HostError> { + Ok(self + .host + .require_auth(self.object, args.into())? + .try_into()?) } } diff --git a/soroban-env-host/src/native_contract/contract_error.rs b/soroban-env-host/src/native_contract/contract_error.rs new file mode 100644 index 000000000..5cfc9e95e --- /dev/null +++ b/soroban-env-host/src/native_contract/contract_error.rs @@ -0,0 +1,30 @@ +use num_derive::FromPrimitive; +use soroban_env_common::Status; + +// Use the same error for all the built-in contract error. +// In theory we could have a separate enum for each built-in contract, but it's +// not clear how to distinguish them if multiple built-in contracts are involved. +#[derive(Debug, FromPrimitive, PartialEq, Eq)] +pub enum ContractError { + InternalError = 1, + OperationNotSupportedError = 2, + AlreadyInitializedError = 3, + + UnauthorizedError = 4, + AuthenticationError = 5, + AccountMissingError = 6, + AccountIsNotClassic = 7, + + NegativeAmountError = 8, + AllowanceError = 9, + BalanceError = 10, + BalanceDeauthorizedError = 11, + OverflowError = 12, + TrustlineMissingError = 13, +} + +impl From for Status { + fn from(err: ContractError) -> Self { + Status::from_contract_error(err as u32) + } +} diff --git a/soroban-env-host/src/native_contract/invoker.rs b/soroban-env-host/src/native_contract/invoker.rs deleted file mode 100644 index 15599b6e9..000000000 --- a/soroban-env-host/src/native_contract/invoker.rs +++ /dev/null @@ -1,27 +0,0 @@ -use soroban_env_common::{xdr::AccountId, Env, InvokerType, TryFromVal, TryIntoVal}; -use soroban_native_sdk_macros::contracttype; - -use crate::{Host, HostError}; - -use super::base_types::BytesN; - -#[derive(Clone)] -#[contracttype] -pub enum Invoker { - Account(AccountId), - Contract(BytesN<32>), -} - -pub fn invoker(env: &Host) -> Result { - let invoker_type: InvokerType = Host::get_invoker_type(&env)?.try_into()?; - Ok(match invoker_type { - InvokerType::Account => Invoker::Account(AccountId::try_from_val( - env, - &Host::get_invoking_account(&env)?, - )?), - InvokerType::Contract => Invoker::Contract(BytesN::<32>::try_from_val( - env, - &Host::get_invoking_contract(&env)?, - )?), - }) -} diff --git a/soroban-env-host/src/native_contract/testutils.rs b/soroban-env-host/src/native_contract/testutils.rs index f16702876..a1396372d 100644 --- a/soroban-env-host/src/native_contract/testutils.rs +++ b/soroban-env-host/src/native_contract/testutils.rs @@ -1,22 +1,18 @@ -use crate::{ - native_contract::token::public_types::{ - AccountSignatures, Ed25519Signature, Signature, SignaturePayload, SignaturePayloadV0, - }, - test::util::generate_bytes_array, - Host, HostError, -}; +use crate::{Host, HostError, LedgerInfo}; use ed25519_dalek::{Keypair, Signer}; use rand::thread_rng; -use soroban_env_common::{ - xdr::{AccountId, PublicKey, Uint256}, - Env, +use soroban_env_common::xdr::{ + AccountId, AddressWithNonce, AuthorizedInvocation, ContractAuth, Hash, HashIdPreimage, + HashIdPreimageContractAuth, PublicKey, ScAddress, ScVec, Uint256, }; -use soroban_env_common::{EnvBase, RawVal, Symbol, TryFromVal, TryIntoVal}; +use soroban_env_common::{EnvBase, RawVal, TryFromVal, TryIntoVal}; + +use crate::native_contract::base_types::BytesN; -use crate::native_contract::base_types::{Bytes, BytesN}; +pub(crate) use crate::native_contract::base_types::Vec as HostVec; -use crate::native_contract::token::public_types::{self, Identifier}; -pub(crate) use public_types::Vec as HostVec; +use super::account_contract::AccountEd25519Signature; +use super::base_types::Address; impl HostVec { pub(crate) fn from_array(host: &Host, vals: &[RawVal]) -> Result { @@ -42,34 +38,43 @@ pub(crate) fn generate_keypair() -> Keypair { Keypair::generate(&mut thread_rng()) } -pub(crate) fn generate_bytes(host: &Host) -> BytesN<32> { - BytesN::<32>::try_from_val( +pub(crate) fn keypair_to_account_id(key: &Keypair) -> AccountId { + AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( + key.public.to_bytes(), + ))) +} + +pub(crate) fn account_to_address(host: &Host, account_id: AccountId) -> Address { + Address::try_from_val( host, - &host.bytes_new_from_slice(&generate_bytes_array()).unwrap(), + &host + .add_host_object(ScAddress::Account(account_id)) + .unwrap(), ) .unwrap() } -pub(crate) fn signer_to_id_bytes(host: &Host, key: &Keypair) -> BytesN<32> { - BytesN::<32>::try_from_val( +pub(crate) fn contract_id_to_address(host: &Host, contract_id: [u8; 32]) -> Address { + Address::try_from_val( host, - &host.bytes_new_from_slice(&key.public.to_bytes()).unwrap(), + &host + .add_host_object(ScAddress::Contract(Hash(contract_id))) + .unwrap(), ) .unwrap() } -pub(crate) fn signer_to_account_id(host: &Host, key: &Keypair) -> AccountId { - let account_id_bytes = signer_to_id_bytes(host, key); - AccountId(PublicKey::PublicKeyTypeEd25519( - host.to_u256(account_id_bytes.into()).unwrap(), - )) -} - pub(crate) enum TestSigner<'a> { - ContractInvoker, - AccountInvoker, - Ed25519(&'a Keypair), + AccountInvoker(AccountId), + ContractInvoker(Hash), Account(AccountSigner<'a>), + #[allow(dead_code)] + AccountContract(AccountContractSigner<'a>), +} + +pub(crate) struct AccountContractSigner<'a> { + pub(crate) id: Hash, + pub(crate) sign: Box HostVec + 'a>, } pub(crate) struct AccountSigner<'a> { @@ -78,7 +83,17 @@ pub(crate) struct AccountSigner<'a> { } impl<'a> TestSigner<'a> { - pub(crate) fn account(account_id: &AccountId, mut signers: Vec<&'a Keypair>) -> Self { + pub(crate) fn account(kp: &'a Keypair) -> Self { + TestSigner::Account(AccountSigner { + account_id: keypair_to_account_id(kp), + signers: vec![kp], + }) + } + + pub(crate) fn account_with_multisig( + account_id: &AccountId, + mut signers: Vec<&'a Keypair>, + ) -> Self { signers.sort_by_key(|k| k.public.as_bytes()); TestSigner::Account(AccountSigner { account_id: account_id.clone(), @@ -86,62 +101,123 @@ impl<'a> TestSigner<'a> { }) } - pub(crate) fn get_identifier(&self, host: &Host) -> Identifier { + pub(crate) fn account_id(&self) -> AccountId { match self { - TestSigner::ContractInvoker => { - // Use stub id to make this work in wrapped contract calls. - // The id shouldn't be used anywhere else. - Identifier::Contract(BytesN::<32>::from_slice(host, &[0; 32]).unwrap()) - } - TestSigner::AccountInvoker => { - // Use stub id to make this work in wrapped contract calls. - // The id shouldn't be used anywhere else. - Identifier::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32])))) - } - TestSigner::Ed25519(key) => Identifier::Ed25519(signer_to_id_bytes(host, key)), - TestSigner::Account(acc_signer) => Identifier::Account(acc_signer.account_id.clone()), + TestSigner::AccountInvoker(acc_id) => acc_id.clone(), + TestSigner::Account(AccountSigner { + account_id, + signers: _, + }) => account_id.clone(), + TestSigner::AccountContract(_) => panic!("not supported"), + TestSigner::ContractInvoker(_) => panic!("not supported"), } } + + fn sign(&self, host: &Host, payload: &[u8]) -> ScVec { + let signature_args = match self { + TestSigner::AccountInvoker(_) => host_vec![host], + TestSigner::Account(account_signer) => { + let mut signatures = HostVec::new(&host).unwrap(); + for key in &account_signer.signers { + signatures + .push(&sign_payload_for_account(host, key, payload)) + .unwrap(); + } + host_vec![host, signatures] + } + TestSigner::AccountContract(signer) => (signer.sign)(payload), + TestSigner::ContractInvoker(_) => host_vec![host], + }; + host.call_args_to_scvec(signature_args.into()).unwrap() + } + + pub(crate) fn address(&self, host: &Host) -> Address { + let sc_address = match self { + TestSigner::AccountInvoker(acc_id) => ScAddress::Account(acc_id.clone()), + TestSigner::Account(acc) => ScAddress::Account(acc.account_id.clone()), + TestSigner::AccountContract(signer) => ScAddress::Contract(signer.id.clone()), + TestSigner::ContractInvoker(contract_id) => ScAddress::Contract(contract_id.clone()), + }; + Address::try_from_val(host, &host.add_host_object(sc_address).unwrap()).unwrap() + } } -pub(crate) fn sign_args( +pub(crate) fn authorize_single_invocation_with_nonce( host: &Host, signer: &TestSigner, - fn_name: &str, contract_id: &BytesN<32>, + function_name: &str, args: HostVec, -) -> Signature { - let msg = SignaturePayload::V0(SignaturePayloadV0 { - name: Symbol::from_str(fn_name), - contract: contract_id.clone(), - network: Bytes::try_from_val(host, &host.get_ledger_network_passphrase().unwrap()).unwrap(), - args, - }); - let msg_bin = host - .serialize_to_bytes(msg.try_into_val(host).unwrap()) - .unwrap(); - let msg_bytes = Bytes::try_from_val(host, &msg_bin).unwrap().to_vec(); - let payload = &msg_bytes[..]; - - match signer { - TestSigner::ContractInvoker => Signature::Invoker, - TestSigner::AccountInvoker => Signature::Invoker, - TestSigner::Ed25519(key) => Signature::Ed25519(sign_payload(host, key, payload)), - TestSigner::Account(account_signer) => Signature::Account(AccountSignatures { - account_id: account_signer.account_id.clone(), - signatures: { - let mut signatures = HostVec::new(&host).unwrap(); - for key in &account_signer.signers { - signatures.push(&sign_payload(host, key, payload)).unwrap(); - } - signatures - }, + nonce: Option, +) { + let sc_address = signer.address(host).to_sc_address().unwrap(); + let address_with_nonce = match signer { + TestSigner::AccountInvoker(_) => None, + TestSigner::Account(_) | TestSigner::AccountContract(_) => Some(AddressWithNonce { + address: sc_address.clone(), + nonce: nonce.unwrap(), }), - } + TestSigner::ContractInvoker(_) => { + // Nothing need to be authorized for contract invoker here. + return; + } + }; + + let root_invocation = AuthorizedInvocation { + contract_id: contract_id.to_vec().try_into().unwrap(), + function_name: function_name.try_into().unwrap(), + args: host.call_args_to_scvec(args.into()).unwrap(), + sub_invocations: Default::default(), + }; + + let signature_payload_preimage = HashIdPreimage::ContractAuth(HashIdPreimageContractAuth { + network_id: host + .with_ledger_info(|li: &LedgerInfo| Ok(li.network_id.clone())) + .unwrap() + .try_into() + .unwrap(), + invocation: root_invocation.clone(), + }); + let signature_payload = host.metered_hash_xdr(&signature_payload_preimage).unwrap(); + let signature_args = signer.sign(host, &signature_payload); + let auth_entry = ContractAuth { + address_with_nonce, + root_invocation, + signature_args, + }; + + host.set_authorization_entries(vec![auth_entry]).unwrap(); } -fn sign_payload(host: &Host, signer: &Keypair, payload: &[u8]) -> Ed25519Signature { - Ed25519Signature { +pub(crate) fn authorize_single_invocation( + host: &Host, + signer: &TestSigner, + contract_id: &BytesN<32>, + function_name: &str, + args: HostVec, +) { + let nonce = match signer { + TestSigner::AccountInvoker(_) => None, + TestSigner::Account(_) | TestSigner::AccountContract(_) => Some( + host.read_nonce( + &Hash(contract_id.to_vec().clone().try_into().unwrap()), + &signer.address(host).to_sc_address().unwrap(), + ) + .unwrap(), + ), + TestSigner::ContractInvoker(_) => { + return; + } + }; + authorize_single_invocation_with_nonce(host, signer, contract_id, function_name, args, nonce); +} + +fn sign_payload_for_account( + host: &Host, + signer: &Keypair, + payload: &[u8], +) -> AccountEd25519Signature { + AccountEd25519Signature { public_key: BytesN::<32>::try_from_val( host, &host @@ -158,3 +234,18 @@ fn sign_payload(host: &Host, signer: &Keypair, payload: &[u8]) -> Ed25519Signatu .unwrap(), } } + +#[allow(dead_code)] +pub(crate) fn sign_payload_for_ed25519( + host: &Host, + signer: &Keypair, + payload: &[u8], +) -> BytesN<64> { + BytesN::<64>::try_from_val( + host, + &host + .bytes_new_from_slice(&signer.sign(payload).to_bytes()) + .unwrap(), + ) + .unwrap() +} diff --git a/soroban-env-host/src/native_contract/token.rs b/soroban-env-host/src/native_contract/token.rs index 5b423cffe..b2503921f 100644 --- a/soroban-env-host/src/native_contract/token.rs +++ b/soroban-env-host/src/native_contract/token.rs @@ -2,11 +2,8 @@ mod admin; mod allowance; mod balance; mod contract; -mod cryptography; -pub(crate) mod error; mod event; mod metadata; -mod nonce; pub(crate) mod public_types; mod storage_types; diff --git a/soroban-env-host/src/native_contract/token/admin.rs b/soroban-env-host/src/native_contract/token/admin.rs index deab351f7..ab3018a0c 100644 --- a/soroban-env-host/src/native_contract/token/admin.rs +++ b/soroban-env-host/src/native_contract/token/admin.rs @@ -1,36 +1,33 @@ use crate::host::Host; -use crate::native_contract::token::public_types::{Identifier, Signature}; +use crate::native_contract::base_types::Address; +use crate::native_contract::contract_error::ContractError; use crate::native_contract::token::storage_types::DataKey; use crate::{err, HostError}; use soroban_env_common::{Compare, Env, TryIntoVal}; -use super::error::ContractError; - // Metering: covered by components -fn read_administrator(e: &Host) -> Result { +fn read_administrator(e: &Host) -> Result { let key = DataKey::Admin; let rv = e.get_contract_data(key.try_into_val(e)?)?; Ok(rv.try_into_val(e)?) } // Metering: covered by components -pub fn write_administrator(e: &Host, id: Identifier) -> Result<(), HostError> { +pub fn write_administrator(e: &Host, id: Address) -> Result<(), HostError> { let key = DataKey::Admin; e.put_contract_data(key.try_into_val(e)?, id.try_into_val(e)?)?; Ok(()) } // Metering: *mostly* covered by components. -pub fn check_admin(e: &Host, auth: &Signature) -> Result<(), HostError> { +pub fn check_admin(e: &Host, addr: &Address) -> Result<(), HostError> { let admin = read_administrator(e)?; - let id = auth.get_identifier(e)?; - - if e.compare(&id, &admin)? != core::cmp::Ordering::Equal { + if e.compare(&admin, addr)? != core::cmp::Ordering::Equal { Err(err!( e, ContractError::UnauthorizedError, - "identifer '{}' is not an admin ('{}')", - id, + "address '{}' is not an admin ('{}')", + addr.clone(), admin )) } else { diff --git a/soroban-env-host/src/native_contract/token/allowance.rs b/soroban-env-host/src/native_contract/token/allowance.rs index eb317e3e9..93cd3cef3 100644 --- a/soroban-env-host/src/native_contract/token/allowance.rs +++ b/soroban-env-host/src/native_contract/token/allowance.rs @@ -1,13 +1,12 @@ use crate::host::Host; -use crate::native_contract::token::public_types::Identifier; +use crate::native_contract::base_types::Address; +use crate::native_contract::contract_error::ContractError; use crate::native_contract::token::storage_types::{AllowanceDataKey, DataKey}; use crate::{err, HostError}; use soroban_env_common::{Env, TryIntoVal}; -use super::error::ContractError; - // Metering: covered by components -pub fn read_allowance(e: &Host, from: Identifier, spender: Identifier) -> Result { +pub fn read_allowance(e: &Host, from: Address, spender: Address) -> Result { let key = DataKey::Allowance(AllowanceDataKey { from, spender }); if let Ok(allowance) = e.get_contract_data(key.try_into_val(e)?) { Ok(allowance.try_into_val(e)?) @@ -19,8 +18,8 @@ pub fn read_allowance(e: &Host, from: Identifier, spender: Identifier) -> Result // Metering: covered by components pub fn write_allowance( e: &Host, - from: Identifier, - spender: Identifier, + from: Address, + spender: Address, amount: i128, ) -> Result<(), HostError> { let key = DataKey::Allowance(AllowanceDataKey { from, spender }); @@ -31,8 +30,8 @@ pub fn write_allowance( // Metering: covered by components pub fn spend_allowance( e: &Host, - from: Identifier, - spender: Identifier, + from: Address, + spender: Address, amount: i128, ) -> Result<(), HostError> { let allowance = read_allowance(e, from.clone(), spender.clone())?; diff --git a/soroban-env-host/src/native_contract/token/balance.rs b/soroban-env-host/src/native_contract/token/balance.rs index 67787b486..f4ff6cc5a 100644 --- a/soroban-env-host/src/native_contract/token/balance.rs +++ b/soroban-env-host/src/native_contract/token/balance.rs @@ -1,16 +1,18 @@ use crate::budget::AsBudget; use crate::host::Host; +use crate::native_contract::base_types::Address; +use crate::native_contract::contract_error::ContractError; use crate::native_contract::token::metadata::read_metadata; -use crate::native_contract::token::public_types::{Identifier, Metadata}; +use crate::native_contract::token::public_types::Metadata; use crate::native_contract::token::storage_types::DataKey; use crate::{err, HostError}; use soroban_env_common::xdr::{ AccountEntry, AccountEntryExt, AccountEntryExtensionV1Ext, AccountFlags, AccountId, - LedgerEntryData, TrustLineAsset, TrustLineEntry, TrustLineEntryExt, TrustLineFlags, + LedgerEntryData, ScAddress, TrustLineAsset, TrustLineEntry, TrustLineEntryExt, TrustLineFlags, }; use soroban_env_common::{Env, TryIntoVal}; -use super::error::ContractError; +use super::public_types::BytesN; use super::storage_types::BalanceValue; /// This module handles all balance and authorization related logic for both @@ -24,11 +26,11 @@ use super::storage_types::BalanceValue; /// by the issuer/admin before it's allowed to hold a balance. // Metering: *mostly* covered by components. Not sure about `try_into_val`. -pub fn read_balance(e: &Host, id: Identifier) -> Result { - match id { - Identifier::Account(acc_id) => Ok(get_classic_balance(e, acc_id)?.0.into()), - Identifier::Contract(_) | Identifier::Ed25519(_) => { - let key = DataKey::Balance(id); +pub fn read_balance(e: &Host, addr: Address) -> Result { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => Ok(get_classic_balance(e, acc_id)?.0.into()), + ScAddress::Contract(_) => { + let key = DataKey::Balance(addr); if let Ok(raw_balance) = e.get_contract_data(key.try_into_val(e)?) { let balance: BalanceValue = raw_balance.try_into_val(e)?; Ok(balance.amount) @@ -40,20 +42,20 @@ pub fn read_balance(e: &Host, id: Identifier) -> Result { } // Metering: *mostly* covered by components. -pub fn get_spendable_balance(e: &Host, id: Identifier) -> Result { - match id { - Identifier::Account(acc_id) => Ok(get_classic_balance(e, acc_id)?.1.into()), - Identifier::Contract(_) | Identifier::Ed25519(_) => read_balance(e, id), +pub fn get_spendable_balance(e: &Host, addr: Address) -> Result { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => Ok(get_classic_balance(e, acc_id)?.1.into()), + ScAddress::Contract(_) => read_balance(e, addr), } } fn write_balance_and_auth( e: &Host, - id: Identifier, + addr: Address, amount: i128, authorized: bool, ) -> Result<(), HostError> { - let key = DataKey::Balance(id); + let key = DataKey::Balance(addr); e.put_contract_data( key.try_into_val(e)?, BalanceValue { amount, authorized }.try_into_val(e)?, @@ -62,16 +64,16 @@ fn write_balance_and_auth( } // Metering: covered by components. -pub fn receive_balance(e: &Host, id: Identifier, amount: i128) -> Result<(), HostError> { - if !is_authorized(e, id.clone())? { +pub fn receive_balance(e: &Host, addr: Address, amount: i128) -> Result<(), HostError> { + if !is_authorized(e, addr.clone())? { return Err(e.err_status_msg( ContractError::BalanceDeauthorizedError, "balance is deauthorized", )); } - match id { - Identifier::Account(acc_id) => { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => { let i64_amount = i64::try_from(amount).map_err(|_| { e.err_status_msg( ContractError::OverflowError, @@ -80,15 +82,14 @@ pub fn receive_balance(e: &Host, id: Identifier, amount: i128) -> Result<(), Hos })?; Ok(transfer_classic_balance(e, acc_id, i64_amount)?) } - Identifier::Contract(_) | Identifier::Ed25519(_) => { - let balance = read_balance(e, id.clone())?; - + ScAddress::Contract(_) => { + let balance = read_balance(e, addr.clone())?; let new_balance = balance .checked_add(amount) .ok_or_else(|| e.err_status(ContractError::OverflowError))?; // balance passed the authorization check at the top of this function, so write true. - write_balance_and_auth(e, id, new_balance, true) + write_balance_and_auth(e, addr, new_balance, true) } } } @@ -96,11 +97,11 @@ pub fn receive_balance(e: &Host, id: Identifier, amount: i128) -> Result<(), Hos // TODO: Metering analysis pub fn spend_balance_no_authorization_check( e: &Host, - id: Identifier, + addr: Address, amount: i128, ) -> Result<(), HostError> { - match id { - Identifier::Account(acc_id) => { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => { let i64_amount = i64::try_from(amount).map_err(|_| { e.err_status_msg( ContractError::OverflowError, @@ -109,10 +110,10 @@ pub fn spend_balance_no_authorization_check( })?; transfer_classic_balance(e, acc_id, -(i64_amount as i64)) } - Identifier::Contract(_) | Identifier::Ed25519(_) => { + ScAddress::Contract(_) => { // If a balance exists, calculate new amount and write the existing authorized state as is because // this can be used to clawback when deauthorized. - let key = DataKey::Balance(id.clone()); + let key = DataKey::Balance(addr.clone()); if let Ok(raw_balance) = e.get_contract_data(key.try_into_val(e)?) { let balance: BalanceValue = raw_balance.try_into_val(e)?; if balance.amount < amount { @@ -128,7 +129,7 @@ pub fn spend_balance_no_authorization_check( .amount .checked_sub(amount) .ok_or_else(|| e.err_status(ContractError::OverflowError))?; - write_balance_and_auth(e, id, new_balance, balance.authorized)? + write_balance_and_auth(e, addr, new_balance, balance.authorized)? } } else if amount > 0 { return Err(err!( @@ -144,23 +145,23 @@ pub fn spend_balance_no_authorization_check( } // Metering: covered by components. -pub fn spend_balance(e: &Host, id: Identifier, amount: i128) -> Result<(), HostError> { - if !is_authorized(e, id.clone())? { +pub fn spend_balance(e: &Host, addr: Address, amount: i128) -> Result<(), HostError> { + if !is_authorized(e, addr.clone())? { return Err(e.err_status_msg( ContractError::BalanceDeauthorizedError, "balance is deauthorized", )); } - spend_balance_no_authorization_check(e, id, amount) + spend_balance_no_authorization_check(e, addr, amount) } // Metering: *mostly* covered by components. Not sure about `try_into_val`. -pub fn is_authorized(e: &Host, id: Identifier) -> Result { - match id { - Identifier::Account(acc_id) => is_account_authorized(e, acc_id), - Identifier::Contract(_) | Identifier::Ed25519(_) => { - let key = DataKey::Balance(id); +pub fn is_authorized(e: &Host, addr: Address) -> Result { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => is_account_authorized(e, acc_id), + ScAddress::Contract(_) => { + let key = DataKey::Balance(addr); if let Ok(raw_balance) = e.get_contract_data(key.try_into_val(e)?) { let balance: BalanceValue = raw_balance.try_into_val(e)?; Ok(balance.authorized) @@ -172,25 +173,25 @@ pub fn is_authorized(e: &Host, id: Identifier) -> Result { } // Metering: *mostly* covered by components. Not sure about `try_into_val`. -pub fn write_authorization(e: &Host, id: Identifier, authorize: bool) -> Result<(), HostError> { - match id { - Identifier::Account(acc_id) => set_authorization(e, acc_id, authorize), - Identifier::Contract(_) | Identifier::Ed25519(_) => { - let key = DataKey::Balance(id.clone()); +pub fn write_authorization(e: &Host, addr: Address, authorize: bool) -> Result<(), HostError> { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => set_authorization(e, acc_id, authorize), + ScAddress::Contract(_) => { + let key = DataKey::Balance(addr.clone()); if let Ok(raw_balance) = e.get_contract_data(key.try_into_val(e)?) { let balance: BalanceValue = raw_balance.try_into_val(e)?; - write_balance_and_auth(e, id, balance.amount, authorize) + write_balance_and_auth(e, addr, balance.amount, authorize) } else { // Balance does not exist, so write a 0 amount along with the authorization flag. // No need to check auth_required because this function can only be called by the admin. - write_balance_and_auth(e, id, 0, authorize) + write_balance_and_auth(e, addr, 0, authorize) } } } } // TODO: Metering analysis -pub fn check_clawbackable(e: &Host, id: Identifier) -> Result<(), HostError> { +pub fn check_clawbackable(e: &Host, addr: Address) -> Result<(), HostError> { let validate_trustline = |asset: TrustLineAsset, issuer: AccountId, account: AccountId| -> Result<(), HostError> { if issuer == account { @@ -208,26 +209,32 @@ pub fn check_clawbackable(e: &Host, id: Identifier) -> Result<(), HostError> { Ok(()) }; - match id { - Identifier::Account(acc_id) => match read_metadata(e)? { + match addr.to_sc_address()? { + ScAddress::Account(acc_id) => match read_metadata(e)? { Metadata::Native => { return Err(e.err_status_msg( ContractError::OperationNotSupportedError, "cannot clawback native asset", )) } - Metadata::AlphaNum4(asset) => validate_trustline( - e.create_asset_4(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - acc_id, - ), - Metadata::AlphaNum12(asset) => validate_trustline( - e.create_asset_12(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - acc_id, - ), + Metadata::AlphaNum4(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + validate_trustline( + e.create_asset_4(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + acc_id, + ) + } + Metadata::AlphaNum12(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + validate_trustline( + e.create_asset_12(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + acc_id, + ) + } }, - Identifier::Contract(_) | Identifier::Ed25519(_) => { + ScAddress::Contract(_) => { // TODO: Non-account balances are always clawbackable for now if admin is set. Revisit this. Ok(()) } @@ -247,16 +254,22 @@ pub fn transfer_classic_balance(e: &Host, to_key: AccountId, amount: i64) -> Res match read_metadata(e)? { Metadata::Native => transfer_account_balance(e, to_key, amount)?, - Metadata::AlphaNum4(asset) => transfer_trustline_balance_safe( - e.create_asset_4(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - )?, - Metadata::AlphaNum12(asset) => transfer_trustline_balance_safe( - e.create_asset_12(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - )?, + Metadata::AlphaNum4(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + transfer_trustline_balance_safe( + e.create_asset_4(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + )? + } + Metadata::AlphaNum12(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + transfer_trustline_balance_safe( + e.create_asset_12(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + )? + } }; Ok(()) } @@ -277,16 +290,23 @@ fn get_classic_balance(e: &Host, to_key: AccountId) -> Result<(i64, i64), HostEr match read_metadata(e)? { Metadata::Native => get_account_balance(e, to_key), - Metadata::AlphaNum4(asset) => get_trustline_balance_safe( - e.create_asset_4(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), - Metadata::AlphaNum12(asset) => get_trustline_balance_safe( - e.create_asset_12(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), + Metadata::AlphaNum4(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + get_trustline_balance_safe( + e.create_asset_4(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + ) + } + + Metadata::AlphaNum12(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + get_trustline_balance_safe( + e.create_asset_12(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + ) + } } } @@ -476,7 +496,7 @@ fn get_min_max_trustline_balance(e: &Host, tl: &TrustLineEntry) -> Result<(i64, } // TODO: Metering analysis -fn is_account_authorized(e: &Host, to_key: AccountId) -> Result { +fn is_account_authorized(e: &Host, account_id: AccountId) -> Result { let is_trustline_authorized_safe = |asset: TrustLineAsset, issuer: AccountId, to: AccountId| -> Result { if issuer == to { @@ -487,16 +507,22 @@ fn is_account_authorized(e: &Host, to_key: AccountId) -> Result match read_metadata(e)? { Metadata::Native => Ok(true), - Metadata::AlphaNum4(asset) => is_trustline_authorized_safe( - e.create_asset_4(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), - Metadata::AlphaNum12(asset) => is_trustline_authorized_safe( - e.create_asset_12(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), + Metadata::AlphaNum4(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + is_trustline_authorized_safe( + e.create_asset_4(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + account_id, + ) + } + Metadata::AlphaNum12(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + is_trustline_authorized_safe( + e.create_asset_12(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + account_id, + ) + } } } @@ -560,16 +586,22 @@ fn set_authorization(e: &Host, to_key: AccountId, authorize: bool) -> Result<(), "expected trustline asset", )) } - Metadata::AlphaNum4(asset) => set_trustline_authorization_safe( - e.create_asset_4(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), - Metadata::AlphaNum12(asset) => set_trustline_authorization_safe( - e.create_asset_12(asset.asset_code.to_array()?, asset.issuer.clone()), - asset.issuer, - to_key, - ), + Metadata::AlphaNum4(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + set_trustline_authorization_safe( + e.create_asset_4(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + ) + } + Metadata::AlphaNum12(asset) => { + let issuer_account_id = e.account_id_from_bytes(asset.issuer.into())?; + set_trustline_authorization_safe( + e.create_asset_12(asset.asset_code.to_array()?, issuer_account_id.clone()), + issuer_account_id, + to_key, + ) + } } } @@ -604,8 +636,8 @@ fn set_trustline_authorization( }) } -fn is_issuer_auth_required(e: &Host, issuer_id: AccountId) -> Result { - let issuer_acc = e.load_account(issuer_id.clone())?; +fn is_issuer_auth_required(e: &Host, issuer_id: BytesN<32>) -> Result { + let issuer_acc = e.load_account(e.account_id_from_bytes(issuer_id.into())?)?; Ok(issuer_acc.flags & (AccountFlags::RequiredFlag as u32) != 0) } diff --git a/soroban-env-host/src/native_contract/token/contract.rs b/soroban-env-host/src/native_contract/token/contract.rs index 283287faf..8203d82bc 100644 --- a/soroban-env-host/src/native_contract/token/contract.rs +++ b/soroban-env-host/src/native_contract/token/contract.rs @@ -1,30 +1,25 @@ -use core::cmp::Ordering; - -use crate::host::metered_clone::MeteredClone; use crate::host::Host; -use crate::native_contract::base_types::{Bytes, BytesN, Vec}; +use crate::native_contract::base_types::{Address, Bytes, BytesN, Vec}; +use crate::native_contract::contract_error::ContractError; use crate::native_contract::token::admin::{check_admin, write_administrator}; use crate::native_contract::token::allowance::{read_allowance, spend_allowance, write_allowance}; use crate::native_contract::token::balance::{ is_authorized, read_balance, receive_balance, spend_balance, write_authorization, }; -use crate::native_contract::token::cryptography::check_auth; use crate::native_contract::token::event; use crate::native_contract::token::metadata::{ has_metadata, read_name, read_symbol, write_metadata, }; -use crate::native_contract::token::nonce::read_nonce; -use crate::native_contract::token::public_types::{Identifier, Metadata, Signature}; +use crate::native_contract::token::public_types::Metadata; use crate::{err, HostError}; use soroban_env_common::xdr::Asset; -use soroban_env_common::{Compare, Env, EnvBase, Symbol, TryFromVal, TryIntoVal}; +use soroban_env_common::{EnvBase, TryFromVal, TryIntoVal}; use soroban_native_sdk_macros::contractimpl; use super::balance::{ check_clawbackable, get_spendable_balance, spend_balance_no_authorization_check, }; -use super::error::ContractError; use super::metadata::read_metadata; use super::public_types::{AlphaNum12Metadata, AlphaNum4Metadata}; @@ -39,89 +34,41 @@ pub trait TokenTrait { /// (clawback, set_auth, mint, set_admin) will always fail fn init_asset(e: &Host, asset_bytes: Bytes) -> Result<(), HostError>; - fn nonce(e: &Host, id: Identifier) -> Result; + fn allowance(e: &Host, from: Address, spender: Address) -> Result; - fn allowance(e: &Host, from: Identifier, spender: Identifier) -> Result; + fn incr_allow(e: &Host, from: Address, spender: Address, amount: i128) + -> Result<(), HostError>; - fn incr_allow( - e: &Host, - from: Signature, - nonce: i128, - spender: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn decr_allow(e: &Host, from: Address, spender: Address, amount: i128) + -> Result<(), HostError>; - fn decr_allow( - e: &Host, - from: Signature, - nonce: i128, - spender: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn balance(e: &Host, addr: Address) -> Result; - fn balance(e: &Host, id: Identifier) -> Result; + fn spendable(e: &Host, addr: Address) -> Result; - fn spendable(e: &Host, id: Identifier) -> Result; + fn authorized(e: &Host, addr: Address) -> Result; - fn authorized(e: &Host, id: Identifier) -> Result; - - fn xfer( - e: &Host, - from: Signature, - nonce: i128, - to: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn xfer(e: &Host, from: Address, to: Address, amount: i128) -> Result<(), HostError>; fn xfer_from( e: &Host, - spender: Signature, - nonce: i128, - from: Identifier, - to: Identifier, + spender: Address, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError>; - fn burn(e: &Host, from: Signature, nonce: i128, amount: i128) -> Result<(), HostError>; + fn burn(e: &Host, from: Address, amount: i128) -> Result<(), HostError>; - fn burn_from( - e: &Host, - spender: Signature, - nonce: i128, - from: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn burn_from(e: &Host, spender: Address, from: Address, amount: i128) -> Result<(), HostError>; - fn set_auth( - e: &Host, - admin: Signature, - nonce: i128, - id: Identifier, - authorize: bool, - ) -> Result<(), HostError>; + fn set_auth(e: &Host, admin: Address, addr: Address, authorize: bool) -> Result<(), HostError>; - fn mint( - e: &Host, - admin: Signature, - nonce: i128, - to: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn mint(e: &Host, admin: Address, to: Address, amount: i128) -> Result<(), HostError>; - fn clawback( - e: &Host, - admin: Signature, - nonce: i128, - from: Identifier, - amount: i128, - ) -> Result<(), HostError>; + fn clawback(e: &Host, admin: Address, from: Address, amount: i128) -> Result<(), HostError>; - fn set_admin( - e: &Host, - admin: Signature, - nonce: i128, - new_admin: Identifier, - ) -> Result<(), HostError>; + fn set_admin(e: &Host, admin: Address, new_admin: Address) -> Result<(), HostError>; fn decimals(e: &Host) -> Result; @@ -168,16 +115,15 @@ impl TokenTrait for Token { let asset: Asset = e.metered_from_xdr_obj(asset_bytes.into())?; - let curr_contract_id = BytesN::<32>::try_from_val(e, &e.get_current_contract()?)?; - let expected_contract_id = - BytesN::<32>::try_from_val(e, &e.get_contract_id_from_asset(asset.clone())?)?; - if e.compare(&curr_contract_id, &expected_contract_id)? != Ordering::Equal { + let curr_contract_id = e.get_current_contract_id_internal()?; + let expected_contract_id = e.get_contract_id_from_asset(asset.clone())?; + if curr_contract_id != expected_contract_id { return Err(err!( e, ContractError::InternalError, "bad id for asset contract: '{}' expected, got '{}'", - expected_contract_id, - curr_contract_id + expected_contract_id.0, + curr_contract_id.0 )); } match asset { @@ -186,10 +132,7 @@ impl TokenTrait for Token { //No admin for the Native token } Asset::CreditAlphanum4(asset4) => { - write_administrator( - &e, - Identifier::Account(asset4.issuer.metered_clone(e.budget_ref())?), - )?; + write_administrator(&e, Address::from_account(e, &asset4.issuer)?)?; write_metadata( &e, Metadata::AlphaNum4(AlphaNum4Metadata { @@ -197,15 +140,15 @@ impl TokenTrait for Token { e, &e.bytes_new_from_slice(&asset4.asset_code.0)?, )?, - issuer: asset4.issuer, + issuer: BytesN::<32>::try_from_val( + e, + &e.bytes_new_from_slice(&e.to_u256_from_account(&asset4.issuer)?.0)?, + )?, }), )?; } Asset::CreditAlphanum12(asset12) => { - write_administrator( - &e, - Identifier::Account(asset12.issuer.metered_clone(e.budget_ref())?), - )?; + write_administrator(&e, Address::from_account(e, &asset12.issuer)?)?; write_metadata( &e, Metadata::AlphaNum12(AlphaNum12Metadata { @@ -213,7 +156,10 @@ impl TokenTrait for Token { e, &e.bytes_new_from_slice(&asset12.asset_code.0)?, )?, - issuer: asset12.issuer, + issuer: BytesN::<32>::try_from_val( + e, + &e.bytes_new_from_slice(&e.to_u256_from_account(&asset12.issuer)?.0)?, + )?, }), )?; } @@ -221,250 +167,183 @@ impl TokenTrait for Token { Ok(()) } - fn nonce(e: &Host, id: Identifier) -> Result { - read_nonce(e, id) - } - - fn allowance(e: &Host, from: Identifier, spender: Identifier) -> Result { - read_allowance(&e, from, spender) + fn allowance(e: &Host, from: Address, spender: Address) -> Result { + read_allowance(e, from, spender) } // Metering: covered by components fn incr_allow( e: &Host, - from: Signature, - nonce: i128, - spender: Identifier, + from: Address, + spender: Address, amount: i128, ) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - let from_id = from.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&from.get_identifier(&e)?)?; - args.push(&nonce.clone())?; - args.push(&spender.clone())?; + args.push(&from)?; + args.push(&spender)?; args.push(&amount)?; - check_auth(&e, from, nonce, Symbol::from_str("incr_allow"), args)?; - - let allowance = read_allowance(&e, from_id.clone(), spender.clone())?; + from.authorize(args)?; + let allowance = read_allowance(&e, from.clone(), spender.clone())?; let new_allowance = allowance .checked_add(amount) .ok_or_else(|| e.err_status(ContractError::OverflowError))?; - - write_allowance(&e, from_id.clone(), spender.clone(), new_allowance)?; - event::incr_allow(e, from_id, spender, amount)?; + write_allowance(&e, from.clone(), spender.clone(), new_allowance)?; + event::incr_allow(e, from, spender, amount)?; Ok(()) } fn decr_allow( e: &Host, - from: Signature, - nonce: i128, - spender: Identifier, + from: Address, + spender: Address, amount: i128, ) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - let from_id = from.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&from.get_identifier(&e)?)?; - args.push(&nonce.clone())?; - args.push(&spender.clone())?; + args.push(&from)?; + args.push(&spender)?; args.push(&amount)?; - check_auth(&e, from, nonce, Symbol::from_str("decr_allow"), args)?; - - let allowance = read_allowance(&e, from_id.clone(), spender.clone())?; - + from.authorize(args)?; + let allowance = read_allowance(&e, from.clone(), spender.clone())?; if amount >= allowance { - write_allowance(&e, from_id.clone(), spender.clone(), 0)?; + write_allowance(&e, from.clone(), spender.clone(), 0)?; } else { - write_allowance(&e, from_id.clone(), spender.clone(), allowance - amount)?; + write_allowance(&e, from.clone(), spender.clone(), allowance - amount)?; } - event::decr_allow(e, from_id, spender, amount)?; + event::decr_allow(e, from, spender, amount)?; Ok(()) } // Metering: covered by components - fn balance(e: &Host, id: Identifier) -> Result { - read_balance(e, id) + fn balance(e: &Host, addr: Address) -> Result { + read_balance(e, addr) } - fn spendable(e: &Host, id: Identifier) -> Result { - get_spendable_balance(e, id) + fn spendable(e: &Host, addr: Address) -> Result { + get_spendable_balance(e, addr) } // Metering: covered by components - fn authorized(e: &Host, id: Identifier) -> Result { - is_authorized(&e, id) + fn authorized(e: &Host, addr: Address) -> Result { + is_authorized(&e, addr) } // Metering: covered by components - fn xfer( - e: &Host, - from: Signature, - nonce: i128, - to: Identifier, - amount: i128, - ) -> Result<(), HostError> { + fn xfer(e: &Host, from: Address, to: Address, amount: i128) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - let from_id = from.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&from.get_identifier(&e)?)?; - args.push(&nonce.clone())?; - args.push(&to.clone())?; + args.push(&from)?; + args.push(&to)?; args.push(&amount)?; - check_auth(&e, from, nonce, Symbol::from_str("xfer"), args)?; - spend_balance(&e, from_id.clone(), amount)?; - receive_balance(&e, to.clone(), amount)?; - event::transfer(e, from_id, to, amount)?; + from.authorize(args)?; + spend_balance(e, from.clone(), amount)?; + receive_balance(e, to.clone(), amount)?; + event::transfer(e, from, to, amount)?; Ok(()) } // Metering: covered by components fn xfer_from( e: &Host, - spender: Signature, - nonce: i128, - from: Identifier, - to: Identifier, + spender: Address, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - let spender_id = spender.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&spender.get_identifier(&e)?)?; - args.push(&nonce.clone())?; - args.push(&from.clone())?; - args.push(&to.clone())?; + args.push(&spender)?; + args.push(&from)?; + args.push(&to)?; args.push(&amount)?; - check_auth(&e, spender, nonce, Symbol::from_str("xfer_from"), args)?; - spend_allowance(&e, from.clone(), spender_id, amount)?; - spend_balance(&e, from.clone(), amount)?; - receive_balance(&e, to.clone(), amount)?; + spender.authorize(args)?; + spend_allowance(e, from.clone(), spender, amount)?; + spend_balance(e, from.clone(), amount)?; + receive_balance(e, to.clone(), amount)?; event::transfer(e, from, to, amount)?; Ok(()) } // Metering: covered by components - fn burn(e: &Host, from: Signature, nonce: i128, amount: i128) -> Result<(), HostError> { + fn burn(e: &Host, from: Address, amount: i128) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; check_non_native(e)?; - let from_id = from.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&from.get_identifier(&e)?)?; - args.push(&nonce.clone())?; + args.push(&from)?; args.push(&amount)?; - check_auth(&e, from, nonce, Symbol::from_str("burn"), args)?; - spend_balance(&e, from_id.clone(), amount)?; - event::burn(e, from_id, amount)?; + from.authorize(args)?; + spend_balance(&e, from.clone(), amount)?; + event::burn(e, from, amount)?; Ok(()) } // Metering: covered by components - fn burn_from( - e: &Host, - spender: Signature, - nonce: i128, - from: Identifier, - amount: i128, - ) -> Result<(), HostError> { + fn burn_from(e: &Host, spender: Address, from: Address, amount: i128) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; check_non_native(e)?; - let spender_id = spender.get_identifier(&e)?; let mut args = Vec::new(e)?; - args.push(&spender.get_identifier(&e)?)?; - args.push(&nonce.clone())?; - args.push(&from.clone())?; + args.push(&spender)?; + args.push(&from)?; args.push(&amount)?; - check_auth(&e, spender, nonce, Symbol::from_str("burn_from"), args)?; - spend_allowance(&e, from.clone(), spender_id, amount)?; + spender.authorize(args)?; + spend_allowance(&e, from.clone(), spender, amount)?; spend_balance(&e, from.clone(), amount)?; event::burn(e, from, amount)?; Ok(()) } // Metering: covered by components - fn clawback( - e: &Host, - admin: Signature, - nonce: i128, - from: Identifier, - amount: i128, - ) -> Result<(), HostError> { + fn clawback(e: &Host, admin: Address, from: Address, amount: i128) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - check_admin(&e, &admin)?; + check_admin(e, &admin)?; check_clawbackable(&e, from.clone())?; let mut args = Vec::new(e)?; - let admin_id = admin.get_identifier(&e)?; - args.push(&admin_id.clone())?; - args.push(&nonce.clone())?; - args.push(&from.clone())?; + args.push(&admin)?; + args.push(&from)?; args.push(&amount)?; - check_auth(&e, admin, nonce, Symbol::from_str("clawback"), args)?; - // admin can clawback a deauthorized balance - spend_balance_no_authorization_check(&e, from.clone(), amount)?; - event::clawback(e, admin_id, from, amount)?; + admin.authorize(args)?; + spend_balance_no_authorization_check(e, from.clone(), amount.clone())?; + event::clawback(e, admin, from, amount)?; Ok(()) } // Metering: covered by components - fn set_auth( - e: &Host, - admin: Signature, - nonce: i128, - id: Identifier, - authorize: bool, - ) -> Result<(), HostError> { - check_admin(&e, &admin)?; + fn set_auth(e: &Host, admin: Address, addr: Address, authorize: bool) -> Result<(), HostError> { + check_admin(e, &admin)?; let mut args = Vec::new(e)?; - let admin_id = admin.get_identifier(&e)?; - args.push(&admin_id.clone())?; - args.push(&nonce.clone())?; - args.push(&id.clone())?; + args.push(&admin)?; + args.push(&addr)?; args.push(&authorize)?; - check_auth(&e, admin, nonce, Symbol::from_str("set_auth"), args)?; - write_authorization(&e, id.clone(), authorize)?; - event::set_auth(e, admin_id, id, authorize)?; + admin.authorize(args)?; + write_authorization(e, addr.clone(), authorize)?; + event::set_auth(e, admin, addr, authorize)?; Ok(()) } // Metering: covered by components - fn mint( - e: &Host, - admin: Signature, - nonce: i128, - to: Identifier, - amount: i128, - ) -> Result<(), HostError> { + fn mint(e: &Host, admin: Address, to: Address, amount: i128) -> Result<(), HostError> { check_nonnegative_amount(e, amount)?; - check_admin(&e, &admin)?; + check_admin(e, &admin)?; let mut args = Vec::new(e)?; - let admin_id = admin.get_identifier(&e)?; - args.push(&admin_id.clone())?; - args.push(&nonce.clone())?; - args.push(&to.clone())?; + args.push(&admin)?; + args.push(&to)?; args.push(&amount)?; - check_auth(&e, admin, nonce, Symbol::from_str("mint"), args)?; - receive_balance(&e, to.clone(), amount)?; - event::mint(e, admin_id, to, amount)?; + admin.authorize(args)?; + receive_balance(e, to.clone(), amount)?; + event::mint(e, admin, to, amount)?; Ok(()) } // Metering: covered by components - fn set_admin( - e: &Host, - admin: Signature, - nonce: i128, - new_admin: Identifier, - ) -> Result<(), HostError> { - check_admin(&e, &admin)?; + fn set_admin(e: &Host, admin: Address, new_admin: Address) -> Result<(), HostError> { + check_admin(e, &admin)?; let mut args = Vec::new(e)?; - let admin_id = admin.get_identifier(&e)?; - args.push(&admin_id.clone())?; - args.push(&nonce.clone())?; - args.push(&new_admin.clone())?; - check_auth(&e, admin, nonce, Symbol::from_str("set_admin"), args)?; - write_administrator(&e, new_admin.clone())?; - event::set_admin(e, admin_id, new_admin)?; + args.push(&admin)?; + args.push(&new_admin)?; + admin.authorize(args)?; + write_administrator(e, new_admin.clone())?; + event::set_admin(e, admin, new_admin)?; Ok(()) } diff --git a/soroban-env-host/src/native_contract/token/cryptography.rs b/soroban-env-host/src/native_contract/token/cryptography.rs deleted file mode 100644 index af88eee60..000000000 --- a/soroban-env-host/src/native_contract/token/cryptography.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::host::Host; -use crate::native_contract::base_types::{Bytes, BytesN, Vec}; -use crate::native_contract::token::nonce::read_and_increment_nonce; -use crate::native_contract::token::public_types::{ - AccountSignatures, Ed25519Signature, Identifier, Signature, SignaturePayload, - SignaturePayloadV0, -}; -use crate::{err, HostError}; -use core::cmp::Ordering; -use soroban_env_common::xdr::{ThresholdIndexes, Uint256}; -use soroban_env_common::{Env, InvokerType, Symbol, TryFromVal, TryIntoVal}; - -use super::error::ContractError; - -const MAX_ACCOUNT_SIGNATURES: u32 = 20; - -// Metering: covered by components -fn check_ed25519_auth( - e: &Host, - auth: Ed25519Signature, - name: Symbol, - args: Vec, -) -> Result<(), HostError> { - let msg = SignaturePayloadV0 { - name, - contract: BytesN::<32>::try_from_val(e, &e.get_current_contract()?)?, - network: Bytes::try_from_val(e, &e.get_ledger_network_passphrase()?)?, - args, - }; - let msg_bin = e.serialize_to_bytes(SignaturePayload::V0(msg).try_into_val(e)?)?; - - e.verify_sig_ed25519(msg_bin, auth.public_key.into(), auth.signature.into())?; - Ok(()) -} - -// Metering: *mostly* covered by components. -fn check_account_auth( - e: &Host, - auth: AccountSignatures, - name: Symbol, - args: Vec, -) -> Result<(), HostError> { - let msg = SignaturePayloadV0 { - name, - contract: BytesN::<32>::try_from_val(e, &e.get_current_contract()?)?, - network: Bytes::try_from_val(e, &e.get_ledger_network_passphrase()?)?, - args, - }; - let msg_bin = e.serialize_to_bytes(SignaturePayload::V0(msg).try_into_val(e)?)?; - - let mut weight = 0u32; - let sigs = &auth.signatures; - // Check if there is too many signatures: there shouldn't be more - // signatures then the amount of account signers. - if sigs.len()? > MAX_ACCOUNT_SIGNATURES { - return Err(err!( - e, - ContractError::AuthenticationError, - "too many account signers: {} > {}", - sigs.len()?, - MAX_ACCOUNT_SIGNATURES - )); - } - let account = e.load_account(auth.account_id)?; - let mut prev_pk: Option> = None; - for i in 0..sigs.len()? { - let sig: Ed25519Signature = sigs.get(i)?; - // Cannot take multiple signatures from the same key - if let Some(prev) = prev_pk { - if prev.compare(&sig.public_key)? != Ordering::Less { - return Err(err!( - e, - ContractError::AuthenticationError, - "public keys are not ordered: {} > {}", - prev, - sig.public_key - )); - } - } - - e.verify_sig_ed25519( - msg_bin.clone(), - sig.public_key.clone().into(), - sig.signature.into(), - )?; - - let signer_weight = - e.get_signer_weight_from_account(Uint256(sig.public_key.to_array()?), &account)?; - // 0 weight indicates that signer doesn't belong to this account. Treat - // this as an error to indicate a bug in signatures, even if another - // signers would have enough weight. - if signer_weight == 0 { - return Err(err!( - e, - ContractError::AuthenticationError, - "signer '{}' does not belong to account", - sig.public_key - )); - } - // Overflow isn't possible here as - // 255 * MAX_ACCOUNT_SIGNATURES is < u32::MAX. - weight += signer_weight as u32; - prev_pk = Some(sig.public_key); - } - let threshold = account.thresholds.0[ThresholdIndexes::Med as usize]; - if weight < threshold as u32 { - Err(err!( - e, - ContractError::AuthenticationError, - "signature weight is lower than threshold: {} < {}", - weight, - threshold as u32 - )) - } else { - Ok(()) - } -} - -// Metering: *mostly* covered by components. -pub fn check_auth( - e: &Host, - auth: Signature, - nonce: i128, - function: Symbol, - args: Vec, -) -> Result<(), HostError> { - match auth { - Signature::Invoker => { - if nonce != 0 { - Err(err!( - e, - ContractError::NonceError, - "non-zero invoker nonce: {}", - nonce - )) - } else { - let invoker_type: InvokerType = Host::get_invoker_type(&e)?.try_into()?; - match invoker_type { - InvokerType::Account => e.get_invoking_account()?, - InvokerType::Contract => e.get_invoking_contract()?, - }; - Ok(()) - } - } - Signature::Ed25519(kea) => { - let stored_nonce = - read_and_increment_nonce(e, Identifier::Ed25519(kea.public_key.clone()))?; - if nonce != stored_nonce { - Err(err!( - e, - ContractError::NonceError, - "incorrect nonce: expected {}, got {}", - stored_nonce, - nonce - )) - } else { - check_ed25519_auth(e, kea, function, args)?; - Ok(()) - } - } - Signature::Account(kaa) => { - let stored_nonce = - read_and_increment_nonce(e, Identifier::Account(kaa.account_id.clone()))?; - if nonce != stored_nonce { - Err(err!( - e, - ContractError::NonceError, - "incorrect nonce: expected {}, got {}", - stored_nonce, - nonce - )) - } else { - check_account_auth(e, kaa, function, args)?; - Ok(()) - } - } - } -} diff --git a/soroban-env-host/src/native_contract/token/error.rs b/soroban-env-host/src/native_contract/token/error.rs deleted file mode 100644 index 789388736..000000000 --- a/soroban-env-host/src/native_contract/token/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use num_derive::FromPrimitive; -use soroban_env_common::Status; - -#[derive(Debug, FromPrimitive, PartialEq, Eq)] -pub enum ContractError { - InternalError = 1, - OperationNotSupportedError = 2, - AlreadyInitializedError = 3, - - UnauthorizedError = 4, - AuthenticationError = 5, - NonceError = 6, - SignatureError = 7, - AccountMissingError = 8, - - NegativeAmountError = 9, - AllowanceError = 10, - BalanceError = 11, - BalanceDeauthorizedError = 12, - OverflowError = 13, - TrustlineMissingError = 14, -} - -impl From for Status { - fn from(err: ContractError) -> Self { - Status::from_contract_error(err as u32) - } -} diff --git a/soroban-env-host/src/native_contract/token/event.rs b/soroban-env-host/src/native_contract/token/event.rs index c687b46d6..54d3cf7dd 100644 --- a/soroban-env-host/src/native_contract/token/event.rs +++ b/soroban-env-host/src/native_contract/token/event.rs @@ -1,13 +1,12 @@ -use crate::host::Host; use crate::native_contract::base_types::Vec; -use crate::native_contract::token::public_types::Identifier; use crate::HostError; +use crate::{host::Host, native_contract::base_types::Address}; use soroban_env_common::{Env, Symbol, TryIntoVal}; pub(crate) fn incr_allow( e: &Host, - from: Identifier, - to: Identifier, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError> { let mut topics = Vec::new(e)?; @@ -20,8 +19,8 @@ pub(crate) fn incr_allow( pub(crate) fn decr_allow( e: &Host, - from: Identifier, - to: Identifier, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError> { let mut topics = Vec::new(e)?; @@ -34,8 +33,8 @@ pub(crate) fn decr_allow( pub(crate) fn transfer( e: &Host, - from: Identifier, - to: Identifier, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError> { let mut topics = Vec::new(e)?; @@ -46,12 +45,7 @@ pub(crate) fn transfer( Ok(()) } -pub(crate) fn mint( - e: &Host, - admin: Identifier, - to: Identifier, - amount: i128, -) -> Result<(), HostError> { +pub(crate) fn mint(e: &Host, admin: Address, to: Address, amount: i128) -> Result<(), HostError> { let mut topics = Vec::new(e)?; topics.push(&Symbol::from_str("mint"))?; topics.push(&admin)?; @@ -62,8 +56,8 @@ pub(crate) fn mint( pub(crate) fn clawback( e: &Host, - admin: Identifier, - from: Identifier, + admin: Address, + from: Address, amount: i128, ) -> Result<(), HostError> { let mut topics = Vec::new(e)?; @@ -76,8 +70,8 @@ pub(crate) fn clawback( pub(crate) fn set_auth( e: &Host, - admin: Identifier, - id: Identifier, + admin: Address, + id: Address, authorize: bool, ) -> Result<(), HostError> { let mut topics = Vec::new(e)?; @@ -88,11 +82,7 @@ pub(crate) fn set_auth( Ok(()) } -pub(crate) fn set_admin( - e: &Host, - admin: Identifier, - new_admin: Identifier, -) -> Result<(), HostError> { +pub(crate) fn set_admin(e: &Host, admin: Address, new_admin: Address) -> Result<(), HostError> { let mut topics = Vec::new(e)?; topics.push(&Symbol::from_str("set_admin"))?; topics.push(&admin)?; @@ -100,7 +90,7 @@ pub(crate) fn set_admin( Ok(()) } -pub(crate) fn burn(e: &Host, from: Identifier, amount: i128) -> Result<(), HostError> { +pub(crate) fn burn(e: &Host, from: Address, amount: i128) -> Result<(), HostError> { let mut topics = Vec::new(e)?; topics.push(&Symbol::from_str("burn"))?; topics.push(&from)?; diff --git a/soroban-env-host/src/native_contract/token/metadata.rs b/soroban-env-host/src/native_contract/token/metadata.rs index 9ef1b89ee..67bda8bf3 100644 --- a/soroban-env-host/src/native_contract/token/metadata.rs +++ b/soroban-env-host/src/native_contract/token/metadata.rs @@ -32,21 +32,13 @@ pub fn read_name(e: &Host) -> Result { Metadata::AlphaNum4(asset) => { let mut res: Bytes = asset.asset_code.into(); res.push(b':')?; - let issuer_id = e.to_u256_from_account(&asset.issuer)?; - res.append(Bytes::try_from_val( - e, - &e.bytes_new_from_slice(&issuer_id.0)?, - )?)?; + res.append(asset.issuer.into())?; Ok(res) } Metadata::AlphaNum12(asset) => { let mut res: Bytes = asset.asset_code.into(); res.push(b':')?; - let issuer_id = e.to_u256_from_account(&asset.issuer)?; - res.append(Bytes::try_from_val( - e, - &e.bytes_new_from_slice(&issuer_id.0)?, - )?)?; + res.append(asset.issuer.into())?; Ok(res) } } diff --git a/soroban-env-host/src/native_contract/token/nonce.rs b/soroban-env-host/src/native_contract/token/nonce.rs deleted file mode 100644 index a851934ab..000000000 --- a/soroban-env-host/src/native_contract/token/nonce.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::native_contract::token::public_types::Identifier; -use crate::native_contract::token::storage_types::DataKey; -use crate::{host::Host, HostError}; -use soroban_env_common::{Env, RawVal, TryIntoVal}; - -use super::error::ContractError; - -// Metering: covered by components -pub fn read_nonce(e: &Host, id: Identifier) -> Result { - let key = DataKey::Nonce(id); - if let Ok(nonce) = e.get_contract_data(key.try_into_val(e)?) { - Ok(nonce.try_into_val(e)?) - } else { - Ok(0) - } -} - -// Metering: covered by components -pub fn read_and_increment_nonce(e: &Host, id: Identifier) -> Result { - let key = DataKey::Nonce(id.clone()); - let key_raw: RawVal = key.try_into_val(e)?; - let old_nonce: i128 = read_nonce(e, id)?; - let new_nonce = old_nonce - .checked_add(1) - .ok_or_else(|| e.err_status(ContractError::OverflowError))?; - let new_nonce_raw: RawVal = new_nonce.try_into_val(e)?; - e.put_contract_data(key_raw, new_nonce_raw)?; - Ok(old_nonce) -} diff --git a/soroban-env-host/src/native_contract/token/public_types.rs b/soroban-env-host/src/native_contract/token/public_types.rs index 0b74f3a7b..9db1bbd08 100644 --- a/soroban-env-host/src/native_contract/token/public_types.rs +++ b/soroban-env-host/src/native_contract/token/public_types.rs @@ -1,100 +1,19 @@ -use crate::host::Host; -pub(crate) use crate::native_contract::base_types::{Bytes, BytesN, Map, Vec}; -use crate::native_contract::invoker::{invoker, Invoker}; -use crate::HostError; -use soroban_env_common::xdr::AccountId; -use soroban_env_common::{Symbol, TryIntoVal}; +pub(crate) use crate::native_contract::base_types::{BytesN, Map}; +use soroban_env_common::TryIntoVal; use soroban_native_sdk_macros::contracttype; -use super::error::ContractError; - -#[derive(Clone)] -#[contracttype] -pub struct Ed25519Signature { - pub public_key: BytesN<32>, - pub signature: BytesN<64>, -} - -#[derive(Clone)] -#[contracttype] -pub struct AccountSignatures { - pub account_id: AccountId, - pub signatures: Vec, -} - -#[derive(Clone)] -#[contracttype] -pub enum Signature { - Invoker, - Ed25519(Ed25519Signature), - Account(AccountSignatures), -} - -impl Signature { - pub fn get_identifier(&self, env: &Host) -> Result { - Ok(match self { - Signature::Invoker => match invoker(env)? { - Invoker::Account(a) => Identifier::Account(a), - Invoker::Contract(c) => Identifier::Contract(c), - }, - Signature::Ed25519(kea) => Identifier::Ed25519(kea.public_key.clone()), - Signature::Account(kaa) => Identifier::Account(kaa.account_id.clone()), - }) - } - - pub fn get_account_id(&self, env: &Host) -> Result { - match self { - Signature::Account(acc) => Ok(acc.account_id.clone()), - Signature::Invoker => match invoker(env)? { - Invoker::Account(a) => Ok(a), - Invoker::Contract(_) => Err(env.err_status_msg( - ContractError::SignatureError, - "signature doesn't belong to account", - )), - }, - _ => Err(env.err_status_msg( - ContractError::SignatureError, - "signature doesn't belong to account", - )), - } - } -} - -#[derive(Clone)] -#[contracttype] -pub enum Identifier { - Contract(BytesN<32>), - Ed25519(BytesN<32>), - Account(AccountId), -} - -#[derive(Clone)] -#[contracttype] -pub struct SignaturePayloadV0 { - pub network: Bytes, - pub contract: BytesN<32>, - pub name: Symbol, - pub args: Vec, -} - -#[derive(Clone)] -#[contracttype] -pub enum SignaturePayload { - V0(SignaturePayloadV0), -} - #[derive(Clone)] #[contracttype] pub struct AlphaNum4Metadata { pub asset_code: BytesN<4>, - pub issuer: AccountId, + pub issuer: BytesN<32>, } #[derive(Clone)] #[contracttype] pub struct AlphaNum12Metadata { pub asset_code: BytesN<12>, - pub issuer: AccountId, + pub issuer: BytesN<32>, } #[derive(Clone)] diff --git a/soroban-env-host/src/native_contract/token/storage_types.rs b/soroban-env-host/src/native_contract/token/storage_types.rs index a49626cac..a4fbcf9a4 100644 --- a/soroban-env-host/src/native_contract/token/storage_types.rs +++ b/soroban-env-host/src/native_contract/token/storage_types.rs @@ -1,12 +1,11 @@ -use crate::native_contract::base_types::Map; -use crate::native_contract::token::public_types::Identifier; +use crate::native_contract::base_types::{Address, Map}; use soroban_env_common::TryIntoVal; use soroban_native_sdk_macros::contracttype; #[contracttype] pub struct AllowanceDataKey { - pub from: Identifier, - pub spender: Identifier, + pub from: Address, + pub spender: Address, } #[contracttype] @@ -18,8 +17,7 @@ pub struct BalanceValue { #[contracttype] pub enum DataKey { Allowance(AllowanceDataKey), - Balance(Identifier), - Nonce(Identifier), + Balance(Address), Admin, Metadata, } diff --git a/soroban-env-host/src/native_contract/token/test_token.rs b/soroban-env-host/src/native_contract/token/test_token.rs index 3314b1fa1..77720d135 100644 --- a/soroban-env-host/src/native_contract/token/test_token.rs +++ b/soroban-env-host/src/native_contract/token/test_token.rs @@ -1,6 +1,9 @@ use crate::{ host_vec, - native_contract::testutils::{sign_args, HostVec, TestSigner}, + native_contract::{ + base_types::Address, + testutils::{authorize_single_invocation, HostVec, TestSigner}, + }, Host, HostError, }; use soroban_env_common::{ @@ -11,8 +14,6 @@ use soroban_env_common::{Symbol, TryFromVal, TryIntoVal}; use crate::native_contract::base_types::{Bytes, BytesN}; -use crate::native_contract::token::public_types::Identifier; - pub(crate) struct TestToken<'a> { pub(crate) id: BytesN<32>, host: &'a Host, @@ -34,112 +35,89 @@ impl<'a> TestToken<'a> { } } - pub(crate) fn nonce(&self, id: Identifier) -> Result { + pub(crate) fn allowance(&self, from: Address, spender: Address) -> Result { Ok(self .host .call( self.id.clone().into(), - Symbol::from_str("nonce").into(), - host_vec![self.host, id].into(), + Symbol::from_str("allowance"), + host_vec![self.host, from, spender].into(), )? .try_into_val(self.host)?) } - pub(crate) fn allowance( + fn call_with_single_signer( &self, - from: Identifier, - spender: Identifier, - ) -> Result { + signer: &TestSigner, + function_name: &str, + args: HostVec, + ) -> Result<(), HostError> { + authorize_single_invocation(self.host, signer, &self.id, function_name, args.clone()); Ok(self .host .call( self.id.clone().into(), - Symbol::from_str("allowance").into(), - host_vec![self.host, from, spender].into(), + Symbol::from_str(function_name), + args.into(), )? - .try_into_val(self.host)?) + .try_into()?) } pub(crate) fn incr_allow( &self, from: &TestSigner, - nonce: i128, - spender: Identifier, + spender: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( from, "incr_allow", - &self.id, - host_vec![ - self.host, - from.get_identifier(self.host), - nonce.clone(), - spender.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("incr_allow").into(), - host_vec![self.host, signature, nonce, spender, amount].into(), - )? - .try_into()?) + host_vec![self.host, from.address(self.host), spender, amount], + ) } pub(crate) fn decr_allow( &self, from: &TestSigner, - nonce: i128, - spender: Identifier, + spender: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( from, "decr_allow", - &self.id, - host_vec![ - self.host, - from.get_identifier(self.host), - nonce.clone(), - spender.clone(), - amount.clone() - ], - ); + host_vec![self.host, from.address(self.host), spender, amount], + ) + } + pub(crate) fn balance(&self, addr: Address) -> Result { Ok(self .host .call( self.id.clone().into(), - Symbol::from_str("decr_allow").into(), - host_vec![self.host, signature, nonce, spender, amount].into(), + Symbol::from_str("balance"), + host_vec![self.host, addr].into(), )? - .try_into()?) + .try_into_val(self.host)?) } - pub(crate) fn balance(&self, id: Identifier) -> Result { + pub(crate) fn spendable(&self, addr: Address) -> Result { Ok(self .host .call( self.id.clone().into(), - Symbol::from_str("balance").into(), - host_vec![self.host, id].into(), + Symbol::from_str("spendable"), + host_vec![self.host, addr].into(), )? .try_into_val(self.host)?) } - pub(crate) fn spendable(&self, id: Identifier) -> Result { + pub(crate) fn authorized(&self, addr: Address) -> Result { Ok(self .host .call( self.id.clone().into(), - Symbol::from_str("spendable").into(), - host_vec![self.host, id].into(), + Symbol::from_str("authorized"), + host_vec![self.host, addr].into(), )? .try_into_val(self.host)?) } @@ -147,258 +125,100 @@ impl<'a> TestToken<'a> { pub(crate) fn xfer( &self, from: &TestSigner, - nonce: i128, - to: Identifier, + to: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( from, "xfer", - &self.id, - host_vec![ - self.host, - from.get_identifier(self.host), - nonce.clone(), - to.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("xfer").into(), - host_vec![self.host, signature, nonce, to, amount].into(), - )? - .try_into()?) + host_vec![self.host, from.address(self.host), to, amount], + ) } pub(crate) fn xfer_from( &self, spender: &TestSigner, - nonce: i128, - from: Identifier, - to: Identifier, + from: Address, + to: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( spender, "xfer_from", - &self.id, - host_vec![ - self.host, - spender.get_identifier(self.host), - nonce.clone(), - from.clone(), - to.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("xfer_from").into(), - host_vec![self.host, signature, nonce, from, to, amount].into(), - )? - .try_into()?) + host_vec![self.host, spender.address(self.host), from, to, amount], + ) } - pub(crate) fn burn( - &self, - from: &TestSigner, - nonce: i128, - amount: i128, - ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + pub(crate) fn burn(&self, from: &TestSigner, amount: i128) -> Result<(), HostError> { + self.call_with_single_signer( from, "burn", - &self.id, - host_vec![ - self.host, - from.get_identifier(self.host), - nonce.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("burn").into(), - host_vec![self.host, signature, nonce, amount].into(), - )? - .try_into()?) + host_vec![self.host, from.address(self.host), amount], + ) } pub(crate) fn burn_from( &self, spender: &TestSigner, - nonce: i128, - from: Identifier, + from: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( spender, "burn_from", - &self.id, - host_vec![ - self.host, - spender.get_identifier(self.host), - nonce.clone(), - from.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("burn_from").into(), - host_vec![self.host, signature, nonce, from, amount].into(), - )? - .try_into()?) + host_vec![self.host, spender.address(self.host), from, amount], + ) } pub(crate) fn set_auth( &self, admin: &TestSigner, - nonce: i128, - id: Identifier, + addr: Address, authorize: bool, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( admin, "set_auth", - &self.id, - host_vec![ - self.host, - admin.get_identifier(self.host), - nonce.clone(), - id.clone(), - authorize - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("set_auth").into(), - host_vec![self.host, signature, nonce, id, authorize].into(), - )? - .try_into()?) - } - - pub(crate) fn authorized(&self, id: Identifier) -> Result { - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("authorized").into(), - host_vec![self.host, id].into(), - )? - .try_into_val(self.host)?) + host_vec![self.host, admin.address(self.host), addr, authorize], + ) } pub(crate) fn mint( &self, admin: &TestSigner, - nonce: i128, - to: Identifier, + to: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( admin, "mint", - &self.id, - host_vec![ - self.host, - admin.get_identifier(self.host), - nonce.clone(), - to.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("mint").into(), - host_vec![self.host, signature, nonce, to, amount].into(), - )? - .try_into()?) + host_vec![self.host, admin.address(self.host), to, amount], + ) } pub(crate) fn clawback( &self, admin: &TestSigner, - nonce: i128, - from: Identifier, + from: Address, amount: i128, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( admin, "clawback", - &self.id, - host_vec![ - self.host, - admin.get_identifier(self.host), - nonce.clone(), - from.clone(), - amount.clone() - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("clawback").into(), - host_vec![self.host, signature, nonce, from, amount].into(), - )? - .try_into()?) + host_vec![self.host, admin.address(self.host), from, amount], + ) } pub(crate) fn set_admin( &self, admin: &TestSigner, - nonce: i128, - new_admin: Identifier, + new_admin: Address, ) -> Result<(), HostError> { - let signature = sign_args( - self.host, + self.call_with_single_signer( admin, "set_admin", - &self.id, - host_vec![ - self.host, - admin.get_identifier(self.host), - nonce.clone(), - new_admin.clone(), - ], - ); - - Ok(self - .host - .call( - self.id.clone().into(), - Symbol::from_str("set_admin").into(), - host_vec![self.host, signature, nonce, new_admin].into(), - )? - .try_into()?) + host_vec![self.host, admin.address(self.host), new_admin], + ) } pub(crate) fn decimals(&self) -> Result { @@ -406,7 +226,7 @@ impl<'a> TestToken<'a> { .host .call( self.id.clone().into(), - Symbol::from_str("decimals").into(), + Symbol::from_str("decimals"), host_vec![self.host].into(), )? .try_into()?) @@ -417,7 +237,7 @@ impl<'a> TestToken<'a> { .host .call( self.id.clone().into(), - Symbol::from_str("name").into(), + Symbol::from_str("name"), host_vec![self.host].into(), )? .try_into_val(self.host)?) @@ -428,7 +248,7 @@ impl<'a> TestToken<'a> { .host .call( self.id.clone().into(), - Symbol::from_str("symbol").into(), + Symbol::from_str("symbol"), host_vec![self.host].into(), )? .try_into_val(self.host)?) diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs index eaa6aebff..144a06869 100644 --- a/soroban-env-host/src/test.rs +++ b/soroban-env-host/src/test.rs @@ -1,6 +1,5 @@ pub(crate) mod util; -mod account; mod basic; mod bytes; mod crypto; diff --git a/soroban-env-host/src/test/account.rs b/soroban-env-host/src/test/account.rs deleted file mode 100644 index 508cc95ee..000000000 --- a/soroban-env-host/src/test/account.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::rc::Rc; - -use soroban_env_common::Env; - -use crate::{ - budget::Budget, - host::metered_map::MeteredOrdMap, - storage::{AccessType, Footprint, Storage}, - xdr, Host, HostError, RawVal, -}; - -#[test] -fn check_account_exists() -> Result<(), HostError> { - let acc_id0 = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256([0; 32]))); - let acc_id1 = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256([1; 32]))); // declared, but not in storage - let acc_id2 = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256([2; 32]))); // not declared - let (lk0, le0) = Host::test_account_ledger_key_entry_pair(acc_id0.clone()); - let (lk1, _le1) = Host::test_account_ledger_key_entry_pair(acc_id1.clone()); - let (_lk2, _le2) = Host::test_account_ledger_key_entry_pair(acc_id2.clone()); - - let budget = Budget::default(); - let mut footprint = Footprint::default(); - footprint.record_access(&lk0, AccessType::ReadOnly, &budget)?; - footprint.record_access(&lk1, AccessType::ReadOnly, &budget)?; - - let mut map = Vec::new(); - map.push((Rc::new(lk0), Some(Rc::new(le0)))); - let storage = Storage::with_enforcing_footprint_and_map( - footprint, - MeteredOrdMap::from_map(map, &budget)?, - ); - - let host = Host::with_storage_and_budget(storage, budget.clone()); - let obj0 = host.add_host_object(acc_id0)?; - let obj1 = host.add_host_object(acc_id1)?; - let obj2 = host.add_host_object(acc_id2)?; - // declared and exists - assert_eq!( - host.account_exists(obj0)?.get_payload(), - RawVal::from_bool(true).get_payload() - ); - // declared but does not exist - assert_eq!( - host.account_exists(obj1)?.get_payload(), - RawVal::from_bool(false).get_payload() - ); - // not declared - assert!(HostError::result_matches_err_status( - host.account_exists(obj2), - xdr::ScHostStorageErrorCode::AccessToUnknownEntry - )); - Ok(()) -} diff --git a/soroban-env-host/src/test/complex.rs b/soroban-env-host/src/test/complex.rs index a082247f8..fd31d83de 100644 --- a/soroban-env-host/src/test/complex.rs +++ b/soroban-env-host/src/test/complex.rs @@ -26,7 +26,7 @@ fn run_complex() -> Result<(), HostError> { protocol_version: 21, sequence_number: 1234, timestamp: 1234, - network_passphrase: "hello".as_bytes().to_vec(), + network_id: [7; 32], base_reserve: 1, }; let id: Hash = [0; 32].into(); @@ -34,8 +34,7 @@ fn run_complex() -> Result<(), HostError> { // Run 1: record footprint, emulating "preflight". let foot = { let store = Storage::with_recording_footprint(Rc::new(EmptySnap)); - let budget = Budget::default(); - let host = Host::with_storage_and_budget(store, budget); + let host = Host::with_storage_and_budget(store, Budget::default()); host.set_ledger_info(info.clone()); { let vm = Vm::new(&host, id.clone(), COMPLEX)?; @@ -49,8 +48,7 @@ fn run_complex() -> Result<(), HostError> { // Run 2: enforce preflight footprint, with empty map -- contract should only write. { let store = Storage::with_enforcing_footprint_and_map(foot, MeteredOrdMap::default()); - let budget = Budget::default(); - let host = Host::with_storage_and_budget(store, budget); + let host = Host::with_storage_and_budget(store, Budget::default()); host.set_ledger_info(info); let vm = Vm::new(&host, id, COMPLEX)?; let args: ScVec = host.test_scvec::(&[])?; diff --git a/soroban-env-host/src/test/ledger.rs b/soroban-env-host/src/test/ledger.rs index bfbc68452..32f2ceb40 100644 --- a/soroban-env-host/src/test/ledger.rs +++ b/soroban-env-host/src/test/ledger.rs @@ -7,35 +7,6 @@ use crate::{ Host, HostError, LedgerInfo, }; -#[test] -fn ledger_network_passphrase() -> Result<(), HostError> { - let budget = Budget::default(); - let storage = Storage::with_enforcing_footprint_and_map( - Footprint::default(), - MeteredOrdMap::new(&budget)?, - ); - - let host = Host::with_storage_and_budget(storage, budget); - host.set_ledger_info(LedgerInfo { - protocol_version: 0, - sequence_number: 0, - timestamp: 0, - network_passphrase: "Public Global Stellar Network ; September 2015" - .as_bytes() - .to_vec(), - base_reserve: 0, - }); - let obj = host.get_ledger_network_passphrase()?; - let np = host.visit_obj(obj, |np: &Vec| Ok(np.clone()))?; - assert_eq!( - np, - "Public Global Stellar Network ; September 2015" - .as_bytes() - .to_vec(), - ); - Ok(()) -} - #[test] fn ledger_network_id() -> Result<(), HostError> { let budget = Budget::default(); @@ -44,22 +15,16 @@ fn ledger_network_id() -> Result<(), HostError> { MeteredOrdMap::new(&budget)?, ); - let host = Host::with_storage_and_budget(storage, budget); + let host = Host::with_storage_and_budget(storage, budget.clone()); host.set_ledger_info(LedgerInfo { protocol_version: 0, sequence_number: 0, timestamp: 0, - network_passphrase: "Public Global Stellar Network ; September 2015" - .as_bytes() - .to_vec(), + network_id: [7; 32], base_reserve: 0, }); let obj = host.get_ledger_network_id()?; let np = host.visit_obj(obj, |np: &Vec| Ok(np.clone()))?; - assert_eq!( - np, - bytes_lit::bytes!(0x7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979) - .to_vec(), - ); + assert_eq!(np, vec![7; 32],); Ok(()) } diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs index d8f4dd547..a942d515f 100644 --- a/soroban-env-host/src/test/lifecycle.rs +++ b/soroban-env-host/src/test/lifecycle.rs @@ -56,15 +56,14 @@ fn get_bytes_from_sc_val(val: ScVal) -> Vec { } fn test_host() -> Host { - let network_passphrase = generate_bytes_array().to_vec(); let budget = Budget::default(); let storage = Storage::with_enforcing_footprint_and_map( Footprint::default(), MeteredOrdMap::new(&budget).unwrap(), ); - let host = Host::with_storage_and_budget(storage, budget); + let host = Host::with_storage_and_budget(storage, budget.clone()); host.set_ledger_info(LedgerInfo { - network_passphrase, + network_id: generate_bytes_array(), ..Default::default() }); diff --git a/soroban-env-host/src/test/token.rs b/soroban-env-host/src/test/token.rs index 52b487575..d3d2bfa8c 100644 --- a/soroban-env-host/src/test/token.rs +++ b/soroban-env-host/src/test/token.rs @@ -1,33 +1,34 @@ -use std::{convert::TryInto, rc::Rc}; +use std::convert::TryInto; use crate::{ - budget::{AsBudget, Budget}, + auth::RecordedAuthPayload, + budget::AsBudget, host::{Frame, TestContractFrame}, host_vec, native_contract::{ + base_types::Address, + contract_error::ContractError, testutils::{ - generate_bytes, generate_keypair, sign_args, signer_to_account_id, signer_to_id_bytes, - AccountSigner, HostVec, TestSigner, - }, - token::{ - error::ContractError, - public_types::{Ed25519Signature, Identifier, Signature}, - test_token::TestToken, + account_to_address, authorize_single_invocation, + authorize_single_invocation_with_nonce, contract_id_to_address, generate_keypair, + keypair_to_account_id, AccountSigner, HostVec, TestSigner, }, + token::test_token::TestToken, }, - storage::{test_storage::MockSnapshotSource, Storage}, + test::util::generate_bytes_array, Host, HostError, LedgerInfo, }; use ed25519_dalek::Keypair; use soroban_env_common::{ + xdr::{self, AccountFlags, ScVal, ScVec, Uint256}, xdr::{ AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext, - AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountFlags, AccountId, AlphaNum12, - AlphaNum4, Asset, AssetCode12, AssetCode4, Hash, HostFunctionType, LedgerEntryData, - LedgerKey, Liabilities, PublicKey, ScStatusType, SequenceNumber, SignerKey, Thresholds, + AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountId, AlphaNum12, AlphaNum4, + Asset, AssetCode12, AssetCode4, Hash, HostFunctionType, LedgerEntryData, LedgerKey, + Liabilities, PublicKey, ScStatusType, SequenceNumber, SignerKey, Thresholds, TrustLineEntry, TrustLineEntryExt, TrustLineEntryV1, TrustLineEntryV1Ext, TrustLineFlags, }, - RawVal, + EnvBase, RawVal, }; use soroban_env_common::{Env, Symbol, TryFromVal, TryIntoVal}; @@ -35,7 +36,6 @@ use crate::native_contract::base_types::BytesN; struct TokenTest { host: Host, - admin_key: Keypair, issuer_key: Keypair, user_key: Keypair, user_key_2: Keypair, @@ -46,19 +46,16 @@ struct TokenTest { impl TokenTest { fn setup() -> Self { - let snapshot_source = Rc::::new(MockSnapshotSource::new()); - let storage = Storage::with_recording_footprint(snapshot_source); - let host = Host::with_storage_and_budget(storage, Budget::default()); + let host = Host::test_host_with_recording_footprint(); host.set_ledger_info(LedgerInfo { protocol_version: 20, sequence_number: 123, timestamp: 123456, - network_passphrase: vec![1, 2, 3, 4], + network_id: [5; 32], base_reserve: 5_000_000, }); Self { host, - admin_key: generate_keypair(), issuer_key: generate_keypair(), user_key: generate_keypair(), user_key_2: generate_keypair(), @@ -68,9 +65,16 @@ impl TokenTest { } } - fn default_token_with_admin_id(&self, new_admin: Identifier) -> TestToken { - let issuer_id = signer_to_account_id(&self.host, &self.issuer_key); - self.create_classic_account( + fn default_token_with_admin_id(&self, new_admin: &Address) -> TestToken { + let token = self.default_token(); + let issuer = TestSigner::account(&self.issuer_key); + token.set_admin(&issuer, new_admin.clone()).unwrap(); + token + } + + fn default_token(&self) -> TestToken { + let issuer_id = keypair_to_account_id(&self.issuer_key); + self.create_account( &issuer_id, vec![(&self.issuer_key, 100)], 10_000_000, @@ -78,7 +82,7 @@ impl TokenTest { [1, 0, 0, 0], None, None, - 0, + AccountFlags::RevocableFlag as u32, ); let asset = Asset::CreditAlphanum4(AlphaNum4 { @@ -86,27 +90,38 @@ impl TokenTest { issuer: issuer_id.clone(), }); - let token = TestToken::new_from_asset(&self.host, asset.clone()); - - if let Identifier::Account(new_admin_id) = new_admin.clone() { - if new_admin_id == issuer_id { - return token; - } - } + TestToken::new_from_asset(&self.host, asset) + } - let issuer = TestSigner::account(&issuer_id, vec![&self.issuer_key]); - token - .set_admin( - &issuer, - token.nonce(issuer.get_identifier(&self.host)).unwrap(), - new_admin, - ) - .unwrap(); - token + fn create_default_account(&self, user: &TestSigner) { + let signers = match user { + TestSigner::AccountInvoker(_) => vec![], + TestSigner::Account(acc_signer) => acc_signer.signers.iter().map(|s| (*s, 1)).collect(), + TestSigner::AccountContract(_) | TestSigner::ContractInvoker(_) => unreachable!(), + }; + self.create_account( + &user.account_id(), + signers, + 0, + 1, + [1, 0, 0, 0], + None, + None, + 0, + ); } - fn default_token(&self, admin: &TestSigner) -> TestToken { - self.default_token_with_admin_id(admin.get_identifier(&self.host)) + fn create_default_trustline(&self, user: &TestSigner) { + self.create_trustline( + &user.account_id(), + &keypair_to_account_id(&self.issuer_key), + &self.asset_code, + 0, + i64::MAX, + TrustLineFlags::AuthorizedFlag as u32 + | TrustLineFlags::TrustlineClawbackEnabledFlag as u32, + None, + ); } fn get_native_balance(&self, account_id: &AccountId) -> i64 { @@ -114,7 +129,7 @@ impl TokenTest { account.balance } - fn get_classic_trustline_balance(&self, key: &LedgerKey) -> i64 { + fn get_trustline_balance(&self, key: &LedgerKey) -> i64 { self.host .with_mut_storage(|s| match s.get(key, self.host.as_budget()).unwrap().data { LedgerEntryData::Trustline(trustline) => Ok(trustline.balance), @@ -123,7 +138,7 @@ impl TokenTest { .unwrap() } - fn create_classic_account( + fn create_account( &self, account_id: &AccountId, signers: Vec<(&Keypair, u32)>, @@ -144,11 +159,7 @@ impl TokenTest { let mut acc_signers = vec![]; for (signer, weight) in signers { acc_signers.push(soroban_env_common::xdr::Signer { - key: SignerKey::Ed25519( - self.host - .to_u256(signer_to_id_bytes(&self.host, signer).into()) - .unwrap(), - ), + key: SignerKey::Ed25519(Uint256(signer.public.to_bytes())), weight, }); } @@ -197,7 +208,7 @@ impl TokenTest { .unwrap(); } - fn create_classic_trustline( + fn create_trustline( &self, account_id: &AccountId, issuer: &AccountId, @@ -305,8 +316,8 @@ fn to_contract_err(e: HostError) -> ContractError { fn test_native_token_smart_roundtrip() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + test.create_account( &account_id, vec![(&test.user_key, 100)], 100_000_000, @@ -317,40 +328,33 @@ fn test_native_token_smart_roundtrip() { 0, ); let token = TestToken::new_from_asset(&test.host, Asset::Native); - let expected_token_id = BytesN::<32>::try_from_val( - &test.host, - &test.host.get_contract_id_from_asset(Asset::Native).unwrap(), - ) - .unwrap(); + let expected_token_id = test.host.get_contract_id_from_asset(Asset::Native).unwrap(); - assert_eq!(token.id.to_vec(), expected_token_id.to_vec()); + assert_eq!(token.id.to_vec(), expected_token_id.0.to_vec()); assert_eq!(token.symbol().unwrap().to_vec(), b"native".to_vec()); assert_eq!(token.decimals().unwrap(), 7); assert_eq!(token.name().unwrap().to_vec(), b"native".to_vec()); - let user = TestSigner::account(&account_id, vec![&test.user_key]); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); // Also can't set a new admin (and there is no admin in the first place). - assert!(token - .set_admin(&user, 0, user.get_identifier(&test.host)) - .is_err()); + assert!(token.set_admin(&user, user.address(&test.host)).is_err()); assert_eq!(test.get_native_balance(&account_id), 100_000_000); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), + token.balance(user.address(&test.host)).unwrap(), 100_000_000 ); - assert_eq!(token.nonce(user.get_identifier(&test.host)).unwrap(), 0); } -fn test_classic_asset_init(asset_code: &[u8]) { +fn test_asset_init(asset_code: &[u8]) { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let issuer_id = signer_to_account_id(&test.host, &test.admin_key); + let account_id = keypair_to_account_id(&test.user_key); + let issuer_id = keypair_to_account_id(&test.issuer_key); - test.create_classic_account( + test.create_account( &account_id, vec![(&test.user_key, 100)], 10_000_000, @@ -361,7 +365,7 @@ fn test_classic_asset_init(asset_code: &[u8]) { 0, ); - let trustline_key = test.create_classic_trustline( + let trustline_key = test.create_trustline( &account_id, &issuer_id, asset_code, @@ -386,12 +390,8 @@ fn test_classic_asset_init(asset_code: &[u8]) { }) }; let token = TestToken::new_from_asset(&test.host, asset.clone()); - let expected_token_id = BytesN::<32>::try_from_val( - &test.host, - &test.host.get_contract_id_from_asset(asset).unwrap(), - ) - .unwrap(); - assert_eq!(token.id.to_vec(), expected_token_id.to_vec()); + let expected_token_id = test.host.get_contract_id_from_asset(asset).unwrap(); + assert_eq!(token.id.to_vec(), expected_token_id.0.to_vec()); assert_eq!(token.symbol().unwrap().to_vec(), asset_code.to_vec()); assert_eq!(token.decimals().unwrap(), 7); @@ -400,72 +400,57 @@ fn test_classic_asset_init(asset_code: &[u8]) { [ asset_code.to_vec(), b":".to_vec(), - test.admin_key.public.to_bytes().to_vec() + test.issuer_key.public.to_bytes().to_vec() ] .concat() .to_vec() ); - let user = TestSigner::account(&account_id, vec![&test.user_key]); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); - assert_eq!( - test.get_classic_trustline_balance(&trustline_key), - 10_000_000 - ); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 10_000_000 - ); - assert_eq!(token.nonce(user.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(test.get_trustline_balance(&trustline_key), 10_000_000); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 10_000_000); } #[test] -fn test_classic_asset4_smart_init() { - test_classic_asset_init(&[0, 'a' as u8, 'b' as u8, 255]); +fn test_asset4_smart_init() { + test_asset_init(&[0, 'a' as u8, 'b' as u8, 255]); } #[test] -fn test_classic_asset12_smart_init() { - test_classic_asset_init(&[255, 0, 0, 127, b'a', b'b', b'c', 1, 2, 3, 4, 5]); +fn test_asset12_smart_init() { + test_asset_init(&[255, 0, 0, 127, b'a', b'b', b'c', 1, 2, 3, 4, 5]); } #[test] fn test_direct_transfer() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + + let user = TestSigner::account(&test.user_key); + let user_2 = TestSigner::account(&test.user_key_2); + test.create_default_account(&user); + test.create_default_account(&user_2); + test.create_default_trustline(&user); + test.create_default_trustline(&user_2); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), + token.balance(user.address(&test.host)).unwrap(), 100_000_000 ); - assert_eq!(token.balance(user_2.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user_2.address(&test.host)).unwrap(), 0); // Transfer some balance from user 1 to user 2. token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 9_999_999, - ) + .xfer(&user, user_2.address(&test.host), 9_999_999) .unwrap(); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 90_000_001); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 90_000_001 - ); - assert_eq!( - token.balance(user_2.get_identifier(&test.host)).unwrap(), + token.balance(user_2.address(&test.host)).unwrap(), 9_999_999 ); @@ -473,12 +458,7 @@ fn test_direct_transfer() { assert_eq!( to_contract_err( token - .xfer( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 10_000_000, - ) + .xfer(&user_2, user.address(&test.host), 10_000_000,) .err() .unwrap() ), @@ -487,19 +467,11 @@ fn test_direct_transfer() { // Transfer some balance back from user 2 to user 1. token - .xfer( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 999_999, - ) + .xfer(&user_2, user.address(&test.host), 999_999) .unwrap(); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 91_000_000); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 91_000_000 - ); - assert_eq!( - token.balance(user_2.get_identifier(&test.host)).unwrap(), + token.balance(user_2.address(&test.host)).unwrap(), 9_000_000 ); } @@ -507,51 +479,42 @@ fn test_direct_transfer() { #[test] fn test_transfer_with_allowance() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + + let user = TestSigner::account(&test.user_key); + let user_2 = TestSigner::account(&test.user_key_2); + let user_3 = TestSigner::account(&test.user_key_3); + test.create_default_account(&user); + test.create_default_account(&user_2); + test.create_default_account(&user_3); + test.create_default_trustline(&user); + test.create_default_trustline(&user_2); + test.create_default_trustline(&user_3); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); - let user_3 = TestSigner::Ed25519(&test.user_key_3); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), + token.balance(user.address(&test.host)).unwrap(), 100_000_000 ); - assert_eq!(token.balance(user_2.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user_2.address(&test.host)).unwrap(), 0); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_3.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_3.address(&test.host)) .unwrap(), 0 ); // Allow 10_000_000 units of token to be transferred from user by user 3. token - .incr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_3.get_identifier(&test.host), - 10_000_000, - ) + .incr_allow(&user, user_3.address(&test.host), 10_000_000) .unwrap(); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_3.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_3.address(&test.host)) .unwrap(), 10_000_000 ); @@ -560,27 +523,20 @@ fn test_transfer_with_allowance() { token .xfer_from( &user_3, - token.nonce(user_3.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - user_2.get_identifier(&test.host), + user.address(&test.host), + user_2.address(&test.host), 6_000_000, ) .unwrap(); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 94_000_000); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 94_000_000 - ); - assert_eq!( - token.balance(user_2.get_identifier(&test.host)).unwrap(), + token.balance(user_2.address(&test.host)).unwrap(), 6_000_000 ); - assert_eq!(token.balance(user_3.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user_3.address(&test.host)).unwrap(), 0); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_3.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_3.address(&test.host)) .unwrap(), 4_000_000 ); @@ -591,9 +547,8 @@ fn test_transfer_with_allowance() { token .xfer_from( &user_3, - token.nonce(user_3.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - user_3.get_identifier(&test.host), + user.address(&test.host), + user_3.address(&test.host), 4_000_001, ) .err() @@ -601,65 +556,43 @@ fn test_transfer_with_allowance() { ), ContractError::AllowanceError ); - // Decrease allow by more than what's left. This will set the allowance to 0 token - .decr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_3.get_identifier(&test.host), - 10_000_000, - ) + .decr_allow(&user, user_3.address(&test.host), 10_000_000) .unwrap(); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_3.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_3.address(&test.host)) .unwrap(), 0 ); token - .incr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_3.get_identifier(&test.host), - 4_000_000, - ) + .incr_allow(&user, user_3.address(&test.host), 4_000_000) .unwrap(); - // Transfer the remaining allowance to user 3. token .xfer_from( &user_3, - token.nonce(user_3.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - user_3.get_identifier(&test.host), + user.address(&test.host), + user_3.address(&test.host), 4_000_000, ) .unwrap(); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 90_000_000); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 90_000_000 - ); - assert_eq!( - token.balance(user_2.get_identifier(&test.host)).unwrap(), + token.balance(user_2.address(&test.host)).unwrap(), 6_000_000 ); assert_eq!( - token.balance(user_3.get_identifier(&test.host)).unwrap(), + token.balance(user_3.address(&test.host)).unwrap(), 4_000_000 ); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_3.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_3.address(&test.host)) .unwrap(), 0 ); @@ -670,9 +603,8 @@ fn test_transfer_with_allowance() { token .xfer_from( &user_3, - token.nonce(user_3.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - user_3.get_identifier(&test.host), + user.address(&test.host), + user_3.address(&test.host), 1, ) .err() @@ -685,76 +617,53 @@ fn test_transfer_with_allowance() { #[test] fn test_burn() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); + let user = TestSigner::account(&test.user_key); + let user_2 = TestSigner::account(&test.user_key_2); + test.create_default_account(&user); + test.create_default_account(&user_2); + test.create_default_trustline(&user); + test.create_default_trustline(&user_2); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), + token.balance(user.address(&test.host)).unwrap(), 100_000_000 ); - assert_eq!(token.balance(user_2.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user_2.address(&test.host)).unwrap(), 0); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_2.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_2.address(&test.host)) .unwrap(), 0 ); // Allow 10_000_000 units of token to be transferred from user by user 3. token - .incr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 10_000_000, - ) + .incr_allow(&user, user_2.address(&test.host), 10_000_000) .unwrap(); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_2.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_2.address(&test.host)) .unwrap(), 10_000_000 ); // Burn 5_000_000 of allowance from user. token - .burn_from( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 6_000_000, - ) + .burn_from(&user_2, user.address(&test.host), 6_000_000) .unwrap(); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 94_000_000 - ); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 94_000_000); - assert_eq!(token.balance(user_2.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user_2.address(&test.host)).unwrap(), 0); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_2.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_2.address(&test.host)) .unwrap(), 4_000_000 ); @@ -763,12 +672,7 @@ fn test_burn() { assert_eq!( to_contract_err( token - .burn_from( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 4_000_001, - ) + .burn_from(&user_2, user.address(&test.host), 4_000_001,) .err() .unwrap() ), @@ -777,104 +681,55 @@ fn test_burn() { // Burn the remaining allowance to user 3. token - .burn_from( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 4_000_000, - ) + .burn_from(&user_2, user.address(&test.host), 4_000_000) .unwrap(); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 90_000_000 - ); - assert_eq!(token.balance(user_2.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 90_000_000); + assert_eq!(token.balance(user_2.address(&test.host)).unwrap(), 0); assert_eq!( token - .allowance( - user.get_identifier(&test.host), - user_2.get_identifier(&test.host) - ) + .allowance(user.address(&test.host), user_2.address(&test.host)) .unwrap(), 0 ); // Now call burn - token - .burn( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - 45_000_000, - ) - .unwrap(); + token.burn(&user, 45_000_000).unwrap(); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 45_000_000 - ); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 45_000_000); // Deauthorize the balance of `user` and then try to burn. token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - false, - ) + .set_auth(&admin, user.address(&test.host), false) .unwrap(); // Can't burn while deauthorized assert_eq!( - to_contract_err( - token - .burn( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - 100, - ) - .err() - .unwrap() - ), + to_contract_err(token.burn(&user, 100,).err().unwrap()), ContractError::BalanceDeauthorizedError ); // Authorize the balance of `user` and then burn. token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - true, - ) + .set_auth(&admin, user.address(&test.host), true) .unwrap(); - token - .burn( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - 1_000_000, - ) - .unwrap(); + token.burn(&user, 1_000_000).unwrap(); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 44_000_000 - ); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 44_000_000); } #[test] fn test_cannot_burn_native() { let test = TokenTest::setup(); let token = TestToken::new_from_asset(&test.host, Asset::Native); - let user_acc = signer_to_account_id(&test.host, &test.user_key); - let user_id = Identifier::Account(user_acc.clone()); + let user_acc_id = keypair_to_account_id(&test.user_key); - let user_signer = TestSigner::Ed25519(&test.user_key); - let user2_signer = TestSigner::Ed25519(&test.user_key_2); + let user = TestSigner::account_with_multisig(&user_acc_id, vec![&test.user_key]); + let user2 = TestSigner::account(&test.user_key_2); - test.create_classic_account( - &user_acc, + test.create_account( + &user_acc_id, vec![(&test.user_key, 100)], 100_000_000, 1, @@ -884,42 +739,24 @@ fn test_cannot_burn_native() { 0, ); - assert_eq!(token.balance(user_id.clone()).unwrap(), 100_000_000); + assert_eq!( + token.balance(user.address(&test.host)).unwrap(), + 100_000_000 + ); assert_eq!( - to_contract_err( - token - .burn( - &user_signer, - token.nonce(user_signer.get_identifier(&test.host)).unwrap(), - 1, - ) - .err() - .unwrap() - ), + to_contract_err(token.burn(&user, 1,).err().unwrap()), ContractError::OperationNotSupportedError ); token - .incr_allow( - &user_signer, - token.nonce(user_signer.get_identifier(&test.host)).unwrap(), - user2_signer.get_identifier(&test.host), - 100, - ) + .incr_allow(&user, user2.address(&test.host), 100) .unwrap(); assert_eq!( to_contract_err( token - .burn_from( - &user2_signer, - token - .nonce(user2_signer.get_identifier(&test.host)) - .unwrap(), - user_signer.get_identifier(&test.host), - 1, - ) + .burn_from(&user2, user.address(&test.host), 1,) .err() .unwrap() ), @@ -930,51 +767,36 @@ fn test_cannot_burn_native() { #[test] fn test_token_authorization() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + + let user = TestSigner::account(&test.user_key); + let user_2 = TestSigner::account(&test.user_key_2); + test.create_default_account(&user); + test.create_default_account(&user_2); + test.create_default_trustline(&user); + test.create_default_trustline(&user_2); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 200_000_000, - ) + .mint(&admin, user_2.address(&test.host), 200_000_000) .unwrap(); - assert!(token.authorized(user.get_identifier(&test.host)).unwrap()); + assert!(token.authorized(user.address(&test.host)).unwrap()); // Deauthorize the balance of `user`. token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - false, - ) + .set_auth(&admin, user.address(&test.host), false) .unwrap(); - assert!(!token.authorized(user.get_identifier(&test.host)).unwrap()); + assert!(!token.authorized(user.address(&test.host)).unwrap()); // Make sure neither outgoing nor incoming balance transfers are possible. assert_eq!( to_contract_err( token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 1 - ) + .xfer(&user, user_2.address(&test.host), 1) .err() .unwrap() ), @@ -983,12 +805,7 @@ fn test_token_authorization() { assert_eq!( to_contract_err( token - .xfer( - &user_2, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 1 - ) + .xfer(&user_2, user.address(&test.host), 1) .err() .unwrap() ), @@ -997,122 +814,79 @@ fn test_token_authorization() { // Authorize the balance of `user`. token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - true, - ) + .set_auth(&admin, user.address(&test.host), true) .unwrap(); - assert!(token.authorized(user.get_identifier(&test.host)).unwrap()); + assert!(token.authorized(user.address(&test.host)).unwrap()); // Make sure balance transfers are possible now. - token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 1, - ) - .unwrap(); - token - .xfer( - &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 1, - ) - .unwrap(); + token.xfer(&user, user_2.address(&test.host), 1).unwrap(); + token.xfer(&user_2, user.address(&test.host), 1).unwrap(); } #[test] fn test_clawback() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + + let user = TestSigner::account(&test.user_key); + test.create_default_trustline(&user); - let user = TestSigner::Ed25519(&test.user_key); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), + token.balance(user.address(&test.host)).unwrap(), 100_000_000 ); token - .clawback( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 40_000_000, - ) + .clawback(&admin, user.address(&test.host), 40_000_000) .unwrap(); - assert_eq!( - token.balance(user.get_identifier(&test.host)).unwrap(), - 60_000_000 - ); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 60_000_000); // Can't clawback more than the balance assert_eq!( to_contract_err( token - .clawback( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 60_000_001, - ) + .clawback(&admin, user.address(&test.host), 60_000_001,) .err() .unwrap() ), ContractError::BalanceError ); - // clawback everything else + // Clawback everything else token - .clawback( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 60_000_000, - ) + .clawback(&admin, user.address(&test.host), 60_000_000) .unwrap(); - assert_eq!(token.balance(user.get_identifier(&test.host)).unwrap(), 0); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 0); } #[test] fn test_set_admin() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); - let new_admin = TestSigner::Ed25519(&test.user_key); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + let new_admin = TestSigner::account(&test.user_key); + let user = TestSigner::account(&test.user_key_2); + test.create_default_account(&new_admin); + test.create_default_trustline(&new_admin); + test.create_default_account(&user); + test.create_default_trustline(&user); // Give admin rights to the new admin. token - .set_admin( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - ) + .set_admin(&admin, new_admin.address(&test.host)) .unwrap(); // Make sure admin functions are unavailable to the old admin. assert_eq!( to_contract_err( token - .set_admin( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - ) + .set_admin(&admin, new_admin.address(&test.host),) .err() .unwrap() ), @@ -1121,12 +895,7 @@ fn test_set_admin() { assert_eq!( to_contract_err( token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - 1 - ) + .mint(&admin, new_admin.address(&test.host), 1) .err() .unwrap() ), @@ -1135,12 +904,7 @@ fn test_set_admin() { assert_eq!( to_contract_err( token - .clawback( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - 1 - ) + .clawback(&admin, new_admin.address(&test.host), 1) .err() .unwrap() ), @@ -1149,12 +913,7 @@ fn test_set_admin() { assert_eq!( to_contract_err( token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - false - ) + .set_auth(&admin, new_admin.address(&test.host), false,) .err() .unwrap() ), @@ -1163,12 +922,7 @@ fn test_set_admin() { assert_eq!( to_contract_err( token - .set_auth( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - true - ) + .set_auth(&admin, new_admin.address(&test.host), true) .err() .unwrap() ), @@ -1176,67 +930,34 @@ fn test_set_admin() { ); // The admin functions are now available to the new admin. + token.mint(&new_admin, user.address(&test.host), 1).unwrap(); token - .mint( - &new_admin, - token.nonce(new_admin.get_identifier(&test.host)).unwrap(), - admin.get_identifier(&test.host), - 1, - ) - .unwrap(); - token - .clawback( - &new_admin, - token.nonce(new_admin.get_identifier(&test.host)).unwrap(), - admin.get_identifier(&test.host), - 1, - ) + .clawback(&new_admin, user.address(&test.host), 1) .unwrap(); token - .set_auth( - &new_admin, - token.nonce(new_admin.get_identifier(&test.host)).unwrap(), - admin.get_identifier(&test.host), - false, - ) + .set_auth(&new_admin, user.address(&test.host), false) .unwrap(); token - .set_auth( - &new_admin, - token.nonce(new_admin.get_identifier(&test.host)).unwrap(), - admin.get_identifier(&test.host), - true, - ) + .set_auth(&new_admin, user.address(&test.host), true) .unwrap(); // Return the admin rights to the old admin token - .set_admin( - &new_admin, - token.nonce(new_admin.get_identifier(&test.host)).unwrap(), - admin.get_identifier(&test.host), - ) + .set_admin(&new_admin, admin.address(&test.host)) .unwrap(); // Make sure old admin can now perform admin operations - token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - new_admin.get_identifier(&test.host), - 1, - ) - .unwrap(); + token.mint(&admin, user.address(&test.host), 1).unwrap(); } #[test] fn test_account_spendable_balance() { let test = TokenTest::setup(); let token = TestToken::new_from_asset(&test.host, Asset::Native); - let user_acc = signer_to_account_id(&test.host, &test.user_key); - let user_id = Identifier::Account(user_acc.clone()); + let user_acc_id = keypair_to_account_id(&test.user_key); + let user_addr = account_to_address(&test.host, user_acc_id.clone()); - test.create_classic_account( - &user_acc, + test.create_account( + &user_acc_id, vec![(&test.user_key, 100)], 100_000_000, 1, @@ -1246,22 +967,26 @@ fn test_account_spendable_balance() { 0, ); - assert_eq!(token.balance(user_id.clone()).unwrap(), 100_000_000); + assert_eq!(token.balance(user_addr.clone()).unwrap(), 100_000_000); // base reserve = 5_000_000 // signer + account = 3 base reserves - assert_eq!(token.spendable(user_id.clone()).unwrap(), 85_000_000); + assert_eq!(token.spendable(user_addr.clone()).unwrap(), 85_000_000); } #[test] fn test_trustline_auth() { let test = TokenTest::setup(); // the admin is the issuer_key - let admin_acc = signer_to_account_id(&test.host, &test.issuer_key); - let user_acc = signer_to_account_id(&test.host, &test.user_key); + let admin_acc_id = keypair_to_account_id(&test.issuer_key); + let user_acc_id = keypair_to_account_id(&test.user_key); - test.create_classic_account( - &admin_acc, - vec![(&test.admin_key, 100)], + let admin = TestSigner::account_with_multisig(&admin_acc_id, vec![&test.issuer_key]); + let user = TestSigner::account_with_multisig(&user_acc_id, vec![&test.user_key]); + let token = test.default_token_with_admin_id(&admin.address(&test.host)); + + test.create_account( + &admin_acc_id, + vec![(&test.issuer_key, 100)], 10_000_000, 1, [1, 0, 0, 0], @@ -1269,8 +994,8 @@ fn test_trustline_auth() { None, 0, ); - test.create_classic_account( - &user_acc, + test.create_account( + &user_acc_id, vec![(&test.user_key, 100)], 10_000_000, 1, @@ -1280,16 +1005,10 @@ fn test_trustline_auth() { 0, ); - let admin_id = Identifier::Account(admin_acc.clone()); - let user_id = Identifier::Account(user_acc.clone()); - - let acc_invoker = TestSigner::AccountInvoker; - let token = test.default_token_with_admin_id(admin_id.clone()); - // create a trustline for user_acc so the issuer can mint into it - test.create_classic_trustline( - &user_acc, - &admin_acc, + test.create_trustline( + &user_acc_id, + &admin_acc_id, &test.asset_code, 0, 10000, @@ -1297,39 +1016,36 @@ fn test_trustline_auth() { Some((0, 0)), ); - //mint to user_id - test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 0, user_id.clone(), 1000) - }) - .unwrap(); + //mint some token to the user + token.mint(&admin, user.address(&test.host), 1000).unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 1000); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 1000); // transfer 1 back to the issuer (which gets burned) - test.run_from_account(user_acc.clone(), || { - token.xfer(&acc_invoker, 0, admin_id.clone(), 1) - }) - .unwrap(); + token.xfer(&user, admin.address(&test.host), 1).unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 999); - assert_eq!(token.balance(admin_id.clone()).unwrap(), i64::MAX.into()); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 999); + assert_eq!( + token.balance(admin.address(&test.host)).unwrap(), + i64::MAX.into() + ); - // try to deauthorize trustline, but fail because RevocableFlag is not set on the issuer + // try to deauthorize trustline, but fail because RevocableFlag is not set + // on the issuer assert_eq!( to_contract_err( - test.run_from_account(admin_acc.clone(), || { - token.set_auth(&acc_invoker, 0, user_id.clone(), false) - }) - .err() - .unwrap() + token + .set_auth(&admin, user.address(&test.host), false) + .err() + .unwrap() ), ContractError::OperationNotSupportedError ); // Add RevocableFlag to the issuer - test.create_classic_account( - &admin_acc, - vec![(&test.admin_key, 100)], + test.create_account( + &admin_acc_id, + vec![(&test.issuer_key, 100)], 10_000_000, 1, [1, 0, 0, 0], @@ -1339,19 +1055,18 @@ fn test_trustline_auth() { ); // trustline should be deauthorized now. - test.run_from_account(admin_acc.clone(), || { - token.set_auth(&acc_invoker, 0, user_id.clone(), false) - }) - .unwrap(); + + token + .set_auth(&admin, user.address(&test.host), false) + .unwrap(); // transfer should fail from deauthorized trustline assert_eq!( to_contract_err( - test.run_from_account(user_acc.clone(), || { - token.xfer(&acc_invoker, 0, admin_id.clone(), 1) - }) - .err() - .unwrap() + token + .xfer(&user, admin.address(&test.host), 1) + .err() + .unwrap() ), ContractError::BalanceDeauthorizedError ); @@ -1359,55 +1074,51 @@ fn test_trustline_auth() { // mint should also fail for the same reason assert_eq!( to_contract_err( - test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 0, user_id.clone(), 1000) - }) - .err() - .unwrap() + token + .mint(&admin, user.address(&test.host), 1000) + .err() + .unwrap() ), ContractError::BalanceDeauthorizedError ); // Now authorize trustline - test.run_from_account(admin_acc.clone(), || { - token.set_auth(&acc_invoker, 0, user_id.clone(), true) - }) - .unwrap(); - - test.run_from_account(user_acc.clone(), || { - token.incr_allow(&acc_invoker, 0, admin_id.clone(), 500) - }) - .unwrap(); + token + .set_auth(&admin, user.address(&test.host), true) + .unwrap(); - test.run_from_account(admin_acc.clone(), || { - token.xfer_from(&acc_invoker, 0, user_id.clone(), admin_id, 500) - }) - .unwrap(); - - test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 0, user_id.clone(), 1) - }) - .unwrap(); + // Balance operations are possible now. + token + .incr_allow(&user, admin.address(&test.host), 500) + .unwrap(); + token + .xfer_from( + &admin, + user.address(&test.host), + admin.address(&test.host), + 500, + ) + .unwrap(); + token.mint(&admin, user.address(&test.host), 1).unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 500); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 500); // try to clawback assert_eq!( to_contract_err( - test.run_from_account(admin_acc.clone(), || { - token.clawback(&acc_invoker, 0, user_id.clone(), 10) - }) - .err() - .unwrap() + token + .clawback(&admin, user.address(&test.host), 10) + .err() + .unwrap() ), ContractError::BalanceError ); // set TrustlineClawbackEnabledFlag on trustline // Also add selling liabilities to test spendable balance - test.create_classic_trustline( - &user_acc, - &admin_acc, + test.create_trustline( + &user_acc_id, + &admin_acc_id, &test.asset_code, 500, 10000, @@ -1415,24 +1126,23 @@ fn test_trustline_auth() { Some((0, 10)), ); - test.run_from_account(admin_acc.clone(), || { - token.clawback(&acc_invoker, 0, user_id.clone(), 10) - }) - .unwrap(); + token + .clawback(&admin, user.address(&test.host), 10) + .unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 490); - assert_eq!(token.spendable(user_id.clone()).unwrap(), 480); + assert_eq!(token.balance(user.address(&test.host)).unwrap(), 490); + assert_eq!(token.spendable(user.address(&test.host)).unwrap(), 480); } #[test] fn test_account_invoker_auth_with_issuer_admin() { let test = TokenTest::setup(); - let admin_acc = signer_to_account_id(&test.host, &test.issuer_key); - let user_acc = signer_to_account_id(&test.host, &test.user_key); + let admin_acc = keypair_to_account_id(&test.issuer_key); + let user_acc = keypair_to_account_id(&test.user_key); - test.create_classic_account( + test.create_account( &admin_acc, - vec![(&test.admin_key, 100)], + vec![(&test.issuer_key, 100)], 10_000_000, 1, [1, 0, 0, 0], @@ -1440,7 +1150,7 @@ fn test_account_invoker_auth_with_issuer_admin() { None, 0, ); - test.create_classic_account( + test.create_account( &user_acc, vec![(&test.user_key, 100)], 10_000_000, @@ -1451,14 +1161,11 @@ fn test_account_invoker_auth_with_issuer_admin() { 0, ); - let admin_id = Identifier::Account(admin_acc.clone()); - let user_id = Identifier::Account(user_acc.clone()); - - let acc_invoker = TestSigner::AccountInvoker; - let token = test.default_token_with_admin_id(admin_id.clone()); - + let admin_address = account_to_address(&test.host, admin_acc.clone()); + let user_address = account_to_address(&test.host, user_acc.clone()); + let token = test.default_token_with_admin_id(&admin_address); // create a trustline for user_acc so the issuer can mint into it - test.create_classic_trustline( + test.create_trustline( &user_acc, &admin_acc, &test.asset_code, @@ -1470,63 +1177,94 @@ fn test_account_invoker_auth_with_issuer_admin() { // Admin invoker can perform admin operation. test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 0, user_id.clone(), 1000) + token.mint( + &TestSigner::AccountInvoker(admin_acc.clone()), + user_address.clone(), + 1000, + ) }) .unwrap(); - // Non-zero nonce is not allowed for invoker. - assert_eq!( - to_contract_err( - test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 1, user_id.clone(), 1000) - }) - .err() - .unwrap() - ), - ContractError::NonceError - ); - - // Make another succesful call with 0 nonce. + // Make another succesful call. test.run_from_account(admin_acc.clone(), || { - token.mint(&acc_invoker, 0, admin_id.clone(), 2000) + token.mint( + &TestSigner::AccountInvoker(admin_acc.clone()), + admin_address.clone(), + 2000, + ) }) .unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 1000); - assert_eq!(token.balance(admin_id.clone()).unwrap(), i64::MAX.into()); + assert_eq!(token.balance(user_address.clone()).unwrap(), 1000); + assert_eq!( + token.balance(admin_address.clone()).unwrap(), + i64::MAX.into() + ); - // // User invoker can't perform admin operation. - // test.host.set_source_account(user_acc.clone()); + // User invoker can't perform admin operation. assert_eq!( to_contract_err( test.run_from_account(user_acc.clone(), || { - token.mint(&acc_invoker, 0, user_id.clone(), 1000) + token.mint( + &TestSigner::AccountInvoker(user_acc.clone()), + user_address.clone(), + 1000, + ) }) .err() .unwrap() ), ContractError::UnauthorizedError ); + // Invoke a transaction with non-matching address - this will fail in host + // due to invoker mismatching with admin. + assert!(test + .run_from_account(user_acc.clone(), || { + token.mint( + &TestSigner::AccountInvoker(admin_acc.clone()), + user_address.clone(), + 1000, + ) + }) + .is_err()); // Perform transfers based on the invoker id. test.run_from_account(user_acc.clone(), || { - token.xfer(&acc_invoker, 0, admin_id.clone(), 500) + token.xfer( + &TestSigner::AccountInvoker(user_acc.clone()), + admin_address.clone(), + 500, + ) }) .unwrap(); test.run_from_account(admin_acc.clone(), || { - token.xfer(&acc_invoker, 0, user_id.clone(), 800) + token.xfer( + &TestSigner::AccountInvoker(admin_acc.clone()), + user_address.clone(), + 800, + ) }) .unwrap(); - assert_eq!(token.balance(user_id.clone()).unwrap(), 1300); - assert_eq!(token.balance(admin_id.clone()).unwrap(), i64::MAX.into()); + assert_eq!(token.balance(user_address.clone()).unwrap(), 1300); + assert_eq!( + token.balance(admin_address.clone()).unwrap(), + i64::MAX.into() + ); // Contract invoker can't perform unauthorized admin operation. + let contract_id = generate_bytes_array(); + let contract_invoker = TestSigner::ContractInvoker(Hash(contract_id.clone())); + let contract_id_bytes = BytesN::<32>::try_from_val( + &test.host, + &test.host.bytes_new_from_slice(&contract_id).unwrap(), + ) + .unwrap(); assert_eq!( to_contract_err( - test.run_from_contract(&generate_bytes(&test.host), || { - token.mint(&TestSigner::ContractInvoker, 0, user_id.clone(), 1000) + test.run_from_contract(&contract_id_bytes, || { + token.mint(&contract_invoker, user_address.clone(), 1000) }) .err() .unwrap() @@ -1538,46 +1276,48 @@ fn test_account_invoker_auth_with_issuer_admin() { #[test] fn test_contract_invoker_auth() { let test = TokenTest::setup(); - let contract_invoker = TestSigner::ContractInvoker; - let admin_contract_id_bytes = generate_bytes(&test.host); - let user_contract_id_bytes = generate_bytes(&test.host); - let admin_contract_id = Identifier::Contract(admin_contract_id_bytes.clone()); - let user_contract_id = Identifier::Contract(user_contract_id_bytes.clone()); - - let token = test.default_token_with_admin_id(admin_contract_id.clone()); + let admin_contract_id = generate_bytes_array(); + let user_contract_id = generate_bytes_array(); + let admin_contract_invoker = TestSigner::ContractInvoker(Hash(admin_contract_id.clone())); + let user_contract_invoker = TestSigner::ContractInvoker(Hash(user_contract_id.clone())); + let admin_contract_address = contract_id_to_address(&test.host, admin_contract_id.clone()); + let user_contract_address = contract_id_to_address(&test.host, user_contract_id.clone()); + let admin_contract_id_bytes = BytesN::<32>::try_from_val( + &test.host, + &test.host.bytes_new_from_slice(&admin_contract_id).unwrap(), + ) + .unwrap(); + let user_contract_id_bytes = BytesN::<32>::try_from_val( + &test.host, + &test.host.bytes_new_from_slice(&user_contract_id).unwrap(), + ) + .unwrap(); + let token = test.default_token_with_admin_id(&admin_contract_address); test.run_from_contract(&admin_contract_id_bytes, || { - token.mint(&contract_invoker, 0, user_contract_id.clone(), 1000) + token.mint(&admin_contract_invoker, user_contract_address.clone(), 1000) }) .unwrap(); - // Non-zero nonce is not allowed for invoker. - assert_eq!( - to_contract_err( - test.run_from_contract(&admin_contract_id_bytes, || { - token.mint(&contract_invoker, 1, user_contract_id.clone(), 1000) - }) - .err() - .unwrap() - ), - ContractError::NonceError - ); - - // Make another succesful call with 0 nonce. + // Make another succesful call test.run_from_contract(&admin_contract_id_bytes, || { - token.mint(&contract_invoker, 0, admin_contract_id.clone(), 2000) + token.mint( + &admin_contract_invoker, + admin_contract_address.clone(), + 2000, + ) }) .unwrap(); - assert_eq!(token.balance(user_contract_id.clone()).unwrap(), 1000); - assert_eq!(token.balance(admin_contract_id.clone()).unwrap(), 2000); + assert_eq!(token.balance(user_contract_address.clone()).unwrap(), 1000); + assert_eq!(token.balance(admin_contract_address.clone()).unwrap(), 2000); // User contract invoker can't perform admin operation. assert_eq!( to_contract_err( test.run_from_contract(&user_contract_id_bytes, || { - token.mint(&contract_invoker, 0, user_contract_id.clone(), 1000) + token.mint(&user_contract_invoker, user_contract_address.clone(), 1000) }) .err() .unwrap() @@ -1585,26 +1325,34 @@ fn test_contract_invoker_auth() { ContractError::UnauthorizedError ); + // Also don't allow an incorrect contract invoker (not a contract error, should + // be some auth error) + assert!(test + .run_from_contract(&user_contract_id_bytes, || { + token.mint(&admin_contract_invoker, user_contract_address.clone(), 1000) + }) + .is_err()); + // Perform transfers based on the invoker id. test.run_from_contract(&user_contract_id_bytes, || { - token.xfer(&contract_invoker, 0, admin_contract_id.clone(), 500) + token.xfer(&user_contract_invoker, admin_contract_address.clone(), 500) }) .unwrap(); test.run_from_contract(&admin_contract_id_bytes, || { - token.xfer(&contract_invoker, 0, user_contract_id.clone(), 800) + token.xfer(&admin_contract_invoker, user_contract_address.clone(), 800) }) .unwrap(); - assert_eq!(token.balance(user_contract_id.clone()).unwrap(), 1300); - assert_eq!(token.balance(admin_contract_id.clone()).unwrap(), 1700); + assert_eq!(token.balance(user_contract_address.clone()).unwrap(), 1300); + assert_eq!(token.balance(admin_contract_address.clone()).unwrap(), 1700); // Account invoker can't perform unauthorized admin operation. - let acc_invoker = TestSigner::AccountInvoker; + let acc_invoker = TestSigner::AccountInvoker(keypair_to_account_id(&test.issuer_key)); assert_eq!( to_contract_err( - test.run_from_account(signer_to_account_id(&test.host, &test.admin_key), || { - token.mint(&acc_invoker, 0, user_contract_id.clone(), 1000) + test.run_from_account(keypair_to_account_id(&test.issuer_key), || { + token.mint(&acc_invoker, user_contract_address.clone(), 1000) }) .err() .unwrap() @@ -1614,227 +1362,192 @@ fn test_contract_invoker_auth() { } #[test] -fn test_auth_rejected_with_incorrect_nonce() { +fn test_auth_rejected_for_incorrect_nonce() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + let user = TestSigner::account(&test.user_key); + test.create_default_account(&user); + test.create_default_trustline(&user); - token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, + let args = host_vec![ + &test.host, + admin.address(&test.host), + user.address(&test.host), + 100_i128 + ]; + + // Incorrect value of nonce + authorize_single_invocation_with_nonce( + &test.host, + &admin, + &token.id, + "mint", + args.clone(), + Some(1), + ); + assert!(test + .host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.clone().into(), ) - .unwrap(); + .is_err()); - // Bump user's nonce and approve some amount to cover xfer_from below. - token - .incr_allow(&user, 0, user_2.get_identifier(&test.host), 1000) + // Correct call to bump nonce. + authorize_single_invocation_with_nonce( + &test.host, + &admin, + &token.id, + "mint", + args.clone(), + Some(0), + ); + test.host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.clone().into(), + ) .unwrap(); - assert_eq!( - to_contract_err( - token - .xfer(&user, 2, user_2.get_identifier(&test.host), 1000) - .err() - .unwrap() - ), - ContractError::NonceError + // Repeat the previous nonce + authorize_single_invocation_with_nonce( + &test.host, + &admin, + &token.id, + "mint", + args.clone(), + Some(0), ); + assert!(test + .host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.clone().into(), + ) + .is_err()); - assert_eq!( - to_contract_err( - token - .incr_allow(&user, 2, user_2.get_identifier(&test.host), 1000) - .err() - .unwrap() - ), - ContractError::NonceError - ); - assert_eq!( - to_contract_err( - token - .xfer_from( - &user_2, - 1, - user.get_identifier(&test.host), - user_2.get_identifier(&test.host), - 100 - ) - .err() - .unwrap() - ), - ContractError::NonceError - ); - assert_eq!( - to_contract_err( - token - .mint(&admin, 2, user.get_identifier(&test.host), 10_000_000,) - .err() - .unwrap() - ), - ContractError::NonceError - ); - assert_eq!( - to_contract_err( - token - .clawback(&admin, 2, user.get_identifier(&test.host), 10_000_000,) - .err() - .unwrap() - ), - ContractError::NonceError - ); - assert_eq!( - to_contract_err( - token - .set_admin(&admin, 2, user.get_identifier(&test.host)) - .err() - .unwrap() - ), - ContractError::NonceError + // Correct call with bumped nonce. + authorize_single_invocation_with_nonce( + &test.host, + &admin, + &token.id, + "mint", + args.clone(), + Some(1), ); + test.host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.clone().into(), + ) + .unwrap(); } #[test] -fn test_auth_rejected_with_incorrect_signer() { +fn test_auth_rejected_for_incorrect_payload() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); - let user = TestSigner::Ed25519(&test.user_key); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + let user = TestSigner::account(&test.user_key); + test.create_default_account(&user); + test.create_default_trustline(&user); - let nonce = 0; - let amount = 1000; - let user_signature = sign_args( + let args = host_vec![ &test.host, - &user, - "mint", - &token.id, - host_vec![ - &test.host, - admin.get_identifier(&test.host), - nonce.clone(), - user.get_identifier(&test.host), - amount.clone(), - ], - ); - // Replace public key in the user signature to imitate admin signature. - let signature = Signature::Ed25519(Ed25519Signature { - public_key: match admin.get_identifier(&test.host) { - Identifier::Ed25519(id) => id, - _ => unreachable!(), - }, - signature: match user_signature { - Signature::Ed25519(signature) => signature.signature, - _ => unreachable!(), - }, - }); + admin.address(&test.host), + user.address(&test.host), + 100_i128 + ]; + // Incorrect signer. + authorize_single_invocation(&test.host, &user, &token.id, "mint", args.clone()); assert!(test .host .call( token.id.clone().into(), Symbol::from_str("mint").into(), - host_vec![ - &test.host, - signature, - nonce, - user.get_identifier(&test.host), - amount - ] - .into(), + args.clone().into(), ) .is_err()); -} -#[test] -fn test_auth_rejected_for_incorrect_function_name() { - let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); - let user = TestSigner::Ed25519(&test.user_key); + // Incorrect function. + authorize_single_invocation(&test.host, &admin, &token.id, "burn", args.clone()); + assert!(test + .host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.clone().into(), + ) + .is_err()); - let nonce = 0; - let amount = 1000; - let signature = sign_args( + // Incorrect argument values. + authorize_single_invocation( &test.host, &admin, - "clawback", &token.id, + "mint", host_vec![ &test.host, - admin.get_identifier(&test.host), - nonce.clone(), - user.get_identifier(&test.host), - amount.clone(), + admin.address(&test.host), + user.address(&test.host), + 1_i128 ], ); - assert!(test .host .call( token.id.clone().into(), Symbol::from_str("mint").into(), - host_vec![ - &test.host, - signature, - nonce, - user.get_identifier(&test.host), - amount - ] - .into(), + args.clone().into(), ) .is_err()); -} -#[test] -fn test_auth_rejected_for_incorrect_function_args() { - let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); - let user = TestSigner::Ed25519(&test.user_key); - - let nonce = 0; - let signature = sign_args( + // Incorrect argument order. + authorize_single_invocation( &test.host, &admin, - "mint", &token.id, + "mint", host_vec![ &test.host, - admin.get_identifier(&test.host), - nonce.clone(), - user.get_identifier(&test.host), - 1000, + user.address(&test.host), + admin.address(&test.host), + 100_i128 ], ); - assert!(test .host .call( token.id.clone().into(), Symbol::from_str("mint").into(), - host_vec![ - &test.host, - signature, - nonce, - user.get_identifier(&test.host), - // call with 1000000 amount instead of 1000 that was signed. - 1_000_000, - ] - .into(), + args.clone().into(), ) .is_err()); + + // Correct signer and payload result in success. + authorize_single_invocation(&test.host, &admin, &token.id, "mint", args.clone()); + + test.host + .call( + token.id.clone().into(), + Symbol::from_str("mint").into(), + args.into(), + ) + .unwrap(); } #[test] fn test_classic_account_multisig_auth() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + test.create_account( &account_id, vec![ (&test.user_key_2, u32::MAX), @@ -1849,16 +1562,14 @@ fn test_classic_account_multisig_auth() { None, 0, ); - - let account_ident = Identifier::Account(account_id.clone()); let token = TestToken::new_from_asset(&test.host, Asset::Native); + let receiver = TestSigner::account(&test.user_key).address(&test.host); // Success: account weight (60) + 40 = 100 token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key, &test.user_key_3]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig(&account_id, vec![&test.user_key, &test.user_key_3]), + receiver.clone(), 100, ) .unwrap(); @@ -1866,9 +1577,8 @@ fn test_classic_account_multisig_auth() { // Success: 1 high weight signer (u32::MAX) token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key_2]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig(&account_id, vec![&test.user_key_2]), + receiver.clone(), 100, ) .unwrap(); @@ -1876,9 +1586,11 @@ fn test_classic_account_multisig_auth() { // Success: 60 + 59 > 100, no account signature token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key_3, &test.user_key_4]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig( + &account_id, + vec![&test.user_key_3, &test.user_key_4], + ), + receiver.clone(), 100, ) .unwrap(); @@ -1886,12 +1598,11 @@ fn test_classic_account_multisig_auth() { // Success: 40 + 60 + 59 > 100 token .xfer( - &TestSigner::account( + &TestSigner::account_with_multisig( &account_id, vec![&test.user_key, &test.user_key_3, &test.user_key_4], ), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + receiver.clone(), 100, ) .unwrap(); @@ -1899,7 +1610,7 @@ fn test_classic_account_multisig_auth() { // Success: all signers token .xfer( - &TestSigner::account( + &TestSigner::account_with_multisig( &account_id, vec![ &test.user_key, @@ -1908,8 +1619,7 @@ fn test_classic_account_multisig_auth() { &test.user_key_4, ], ), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + receiver.clone(), 100, ) .unwrap(); @@ -1919,9 +1629,8 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig(&account_id, vec![&test.user_key]), + receiver.clone(), 100, ) .err() @@ -1935,9 +1644,11 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key, &test.user_key_4]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig( + &account_id, + vec![&test.user_key, &test.user_key_4] + ), + receiver.clone(), 100, ) .err() @@ -1951,9 +1662,11 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key_3, &test.user_key_3]), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig( + &account_id, + vec![&test.user_key_3, &test.user_key_3] + ), + receiver.clone(), 100, ) .err() @@ -1967,12 +1680,11 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account( + &TestSigner::account_with_multisig( &account_id, vec![&test.user_key_3, &test.user_key_4, &test.user_key_3], ), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + receiver.clone(), 100, ) .err() @@ -1986,9 +1698,11 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account(&account_id, vec![&test.user_key_3, &test.admin_key],), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig( + &account_id, + vec![&test.user_key_3, &test.issuer_key], + ), + receiver.clone(), 100, ) .err() @@ -2002,12 +1716,11 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account( + &TestSigner::account_with_multisig( &account_id, - vec![&test.user_key_3, &test.user_key_4, &test.admin_key], + vec![&test.user_key_3, &test.user_key_4, &test.issuer_key], ), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + receiver.clone(), 100, ) .err() @@ -2026,9 +1739,8 @@ fn test_classic_account_multisig_auth() { to_contract_err( token .xfer( - &TestSigner::account(&account_id, too_many_sigs,), - token.nonce(account_ident.clone()).unwrap(), - account_ident.clone(), + &TestSigner::account_with_multisig(&account_id, too_many_sigs,), + receiver.clone(), 100, ) .err() @@ -2054,8 +1766,7 @@ fn test_classic_account_multisig_auth() { account_id: account_id, signers: out_of_order_signers, }), - token.nonce(account_ident.clone()).unwrap(), - account_ident, + receiver.clone(), 100, ) .err() @@ -2068,29 +1779,24 @@ fn test_classic_account_multisig_auth() { #[test] fn test_negative_amounts_are_not_allowed() { let test = TokenTest::setup(); - let admin = TestSigner::Ed25519(&test.admin_key); - let token = test.default_token(&admin); + let admin = TestSigner::account(&test.issuer_key); + let token = test.default_token(); + + let user = TestSigner::account(&test.user_key); + let user_2 = TestSigner::account(&test.user_key_2); + test.create_default_account(&user); + test.create_default_account(&user_2); + test.create_default_trustline(&user); + test.create_default_trustline(&user_2); - let user = TestSigner::Ed25519(&test.user_key); - let user_2 = TestSigner::Ed25519(&test.user_key_2); token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 100_000_000, - ) + .mint(&admin, user.address(&test.host), 100_000_000) .unwrap(); assert_eq!( to_contract_err( token - .mint( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - -1, - ) + .mint(&admin, user.address(&test.host), -1,) .err() .unwrap() ), @@ -2100,12 +1806,7 @@ fn test_negative_amounts_are_not_allowed() { assert_eq!( to_contract_err( token - .clawback( - &admin, - token.nonce(admin.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - -1, - ) + .clawback(&admin, user.address(&test.host), -1,) .err() .unwrap() ), @@ -2115,12 +1816,7 @@ fn test_negative_amounts_are_not_allowed() { assert_eq!( to_contract_err( token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - -1, - ) + .xfer(&user, user_2.address(&test.host), -1) .err() .unwrap() ), @@ -2130,12 +1826,7 @@ fn test_negative_amounts_are_not_allowed() { assert_eq!( to_contract_err( token - .incr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - -1, - ) + .incr_allow(&user, user_2.address(&test.host), -1) .err() .unwrap() ), @@ -2145,12 +1836,7 @@ fn test_negative_amounts_are_not_allowed() { assert_eq!( to_contract_err( token - .decr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - -1, - ) + .decr_allow(&user, user_2.address(&test.host), -1) .err() .unwrap() ), @@ -2159,12 +1845,7 @@ fn test_negative_amounts_are_not_allowed() { // Approve some balance before doing the negative xfer_from. token - .incr_allow( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user_2.get_identifier(&test.host), - 10_000, - ) + .incr_allow(&user, user_2.address(&test.host), 10_000) .unwrap(); assert_eq!( @@ -2172,9 +1853,8 @@ fn test_negative_amounts_are_not_allowed() { token .xfer_from( &user_2, - token.nonce(user_2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - user_2.get_identifier(&test.host), + user.address(&test.host), + user_2.address(&test.host), -1, ) .err() @@ -2195,9 +1875,10 @@ fn test_native_token_classic_balance_boundaries( let token = TestToken::new_from_asset(&test.host, Asset::Native); let new_balance_key = generate_keypair(); - let new_balance_acc = signer_to_account_id(&test.host, &new_balance_key); - let new_balance_signer = TestSigner::account(&new_balance_acc, vec![&new_balance_key]); - test.create_classic_account( + let new_balance_acc = keypair_to_account_id(&new_balance_key); + let new_balance_signer = + TestSigner::account_with_multisig(&new_balance_acc, vec![&new_balance_key]); + test.create_account( &new_balance_acc, vec![(&new_balance_key, 100)], 10_000_000, @@ -2214,8 +1895,7 @@ fn test_native_token_classic_balance_boundaries( token .xfer( &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - new_balance_signer.get_identifier(&test.host), + new_balance_signer.address(&test.host), (init_balance - expected_min_balance + 1).into(), ) .err() @@ -2228,20 +1908,17 @@ fn test_native_token_classic_balance_boundaries( token .xfer( &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - new_balance_signer.get_identifier(&test.host), + new_balance_signer.address(&test.host), (init_balance - expected_min_balance).into(), ) .unwrap(); + assert_eq!(test.get_native_balance(account_id), expected_min_balance); // now transfer back token .xfer( &new_balance_signer, - token - .nonce(new_balance_signer.get_identifier(&test.host)) - .unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (init_balance - expected_min_balance).into(), ) .unwrap(); @@ -2251,9 +1928,10 @@ fn test_native_token_classic_balance_boundaries( // given limited XLM supply, but that's the only way to // cover max_balance. let large_balance_key = generate_keypair(); - let large_balance_acc = signer_to_account_id(&test.host, &large_balance_key); - let large_balance_signer = TestSigner::account(&large_balance_acc, vec![&large_balance_key]); - test.create_classic_account( + let large_balance_acc = keypair_to_account_id(&large_balance_key); + let large_balance_signer = + TestSigner::account_with_multisig(&large_balance_acc, vec![&large_balance_key]); + test.create_account( &large_balance_acc, vec![(&large_balance_key, 100)], i64::MAX, @@ -2271,10 +1949,7 @@ fn test_native_token_classic_balance_boundaries( token .xfer( &large_balance_signer, - token - .nonce(large_balance_signer.get_identifier(&test.host)) - .unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (expected_max_balance - init_balance + 1).into(), ) .err() @@ -2288,10 +1963,7 @@ fn test_native_token_classic_balance_boundaries( token .xfer( &large_balance_signer, - token - .nonce(large_balance_signer.get_identifier(&test.host)) - .unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (expected_max_balance - init_balance).into(), ) .unwrap(); @@ -2303,10 +1975,10 @@ fn test_native_token_classic_balance_boundaries( fn test_native_token_classic_balance_boundaries_simple() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); // Account with no liabilities/sponsorships. - test.create_classic_account( + test.create_account( &account_id, vec![(&test.user_key, 100)], 100_000_000, @@ -2331,9 +2003,9 @@ fn test_native_token_classic_balance_boundaries_simple() { #[test] fn test_native_token_classic_balance_boundaries_with_liabilities() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); + test.create_account( &account_id, vec![(&test.user_key, 100)], 1_000_000_000, @@ -2360,9 +2032,9 @@ fn test_native_token_classic_balance_boundaries_with_liabilities() { #[test] fn test_native_token_classic_balance_boundaries_with_sponsorships() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); + test.create_account( &account_id, vec![(&test.user_key, 100)], 100_000_000, @@ -2388,9 +2060,9 @@ fn test_native_token_classic_balance_boundaries_with_sponsorships() { #[test] fn test_native_token_classic_balance_boundaries_with_sponsorships_and_liabilities() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); + test.create_account( &account_id, vec![(&test.user_key, 100)], 1_000_000_000, @@ -2417,9 +2089,9 @@ fn test_native_token_classic_balance_boundaries_with_sponsorships_and_liabilitie #[test] fn test_native_token_classic_balance_boundaries_with_large_values() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); + test.create_account( &account_id, vec![(&test.user_key, 100)], i64::MAX - i64::MAX / 4, @@ -2450,9 +2122,9 @@ fn test_wrapped_asset_classic_balance_boundaries( limit: i64, ) { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account_with_multisig(&account_id, vec![&test.user_key]); + test.create_account( &account_id, vec![(&test.user_key, 100)], 10_000_000, @@ -2463,9 +2135,9 @@ fn test_wrapped_asset_classic_balance_boundaries( 0, ); - let account_id2 = signer_to_account_id(&test.host, &test.user_key_2); - let user2 = TestSigner::account(&account_id2, vec![&test.user_key_2]); - test.create_classic_account( + let account_id2 = keypair_to_account_id(&test.user_key_2); + let user2 = TestSigner::account_with_multisig(&account_id2, vec![&test.user_key_2]); + test.create_account( &account_id2, vec![(&test.user_key_2, 100)], 10_000_000, @@ -2476,11 +2148,11 @@ fn test_wrapped_asset_classic_balance_boundaries( 0, ); - let issuer_id = signer_to_account_id(&test.host, &test.admin_key); - let issuer = TestSigner::account(&issuer_id, vec![&test.admin_key]); - test.create_classic_account( + let issuer_id = keypair_to_account_id(&test.issuer_key); + let issuer = TestSigner::account_with_multisig(&issuer_id, vec![&test.issuer_key]); + test.create_account( &issuer_id, - vec![(&test.admin_key, 100)], + vec![(&test.issuer_key, 100)], 10_000_000, 0, [1, 0, 0, 0], @@ -2489,7 +2161,7 @@ fn test_wrapped_asset_classic_balance_boundaries( 0, ); - let trustline_key = test.create_classic_trustline( + let trustline_key = test.create_trustline( &account_id, &issuer_id, &[255; 12], @@ -2499,7 +2171,7 @@ fn test_wrapped_asset_classic_balance_boundaries( liabilities, ); - let trustline_key2 = test.create_classic_trustline( + let trustline_key2 = test.create_trustline( &account_id2, &issuer_id, &[255; 12], @@ -2524,8 +2196,7 @@ fn test_wrapped_asset_classic_balance_boundaries( token .xfer( &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), + user2.address(&test.host), (init_balance - expected_min_balance + 1).into(), ) .err() @@ -2538,19 +2209,17 @@ fn test_wrapped_asset_classic_balance_boundaries( token .xfer( &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user2.get_identifier(&test.host), + user2.address(&test.host), (init_balance - expected_min_balance).into(), ) .unwrap(); - assert_eq!( - test.get_classic_trustline_balance(&trustline_key), + test.get_trustline_balance(&trustline_key), expected_min_balance ); assert_eq!( - test.get_classic_trustline_balance(&trustline_key2), + test.get_trustline_balance(&trustline_key2), init_balance - expected_min_balance ); @@ -2558,21 +2227,19 @@ fn test_wrapped_asset_classic_balance_boundaries( token .xfer( &user2, - token.nonce(user2.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (init_balance - expected_min_balance).into(), ) .unwrap(); - // Mint a balancethat would exceed - // expected_max_balance shouldn't be possible. + // Minting amount that would exceed expected_max_balance + // shouldn't be possible. assert_eq!( to_contract_err( token .mint( &issuer, - token.nonce(issuer.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (expected_max_balance - init_balance + 1).into(), ) .err() @@ -2586,17 +2253,16 @@ fn test_wrapped_asset_classic_balance_boundaries( token .mint( &issuer, - token.nonce(issuer.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), + user.address(&test.host), (expected_max_balance - init_balance).into(), ) .unwrap(); assert_eq!( - test.get_classic_trustline_balance(&trustline_key), + test.get_trustline_balance(&trustline_key), expected_max_balance ); - assert_eq!(test.get_classic_trustline_balance(&trustline_key2), 0); + assert_eq!(test.get_trustline_balance(&trustline_key2), 0); } #[test] @@ -2645,9 +2311,9 @@ fn test_asset_token_classic_balance_boundaries_large_values() { #[test] fn test_classic_transfers_not_possible_for_unauthorized_asset() { let test = TokenTest::setup(); - let account_id = signer_to_account_id(&test.host, &test.user_key); - let user = TestSigner::account(&account_id, vec![&test.user_key]); - test.create_classic_account( + let account_id = keypair_to_account_id(&test.user_key); + let user = TestSigner::account(&test.user_key); + test.create_account( &account_id, vec![(&test.user_key, 100)], 10_000_000, @@ -2658,9 +2324,9 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { 0, ); - let issuer_id = signer_to_account_id(&test.host, &test.admin_key); + let issuer_id = keypair_to_account_id(&test.issuer_key); - let trustline_key = test.create_classic_trustline( + let trustline_key = test.create_trustline( &account_id, &issuer_id, &[255; 4], @@ -2670,10 +2336,7 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { None, ); - assert_eq!( - test.get_classic_trustline_balance(&trustline_key), - 100_000_000 - ); + assert_eq!(test.get_trustline_balance(&trustline_key), 100_000_000); let token = TestToken::new_from_asset( &test.host, @@ -2686,17 +2349,10 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { ); // Authorized to xfer - token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 1.into(), - ) - .unwrap(); + token.xfer(&user, user.address(&test.host), 1_i128).unwrap(); // Override the trustline authorization flag. - let trustline_key = test.create_classic_trustline( + let trustline_key = test.create_trustline( &account_id, &issuer_id, &[255; 4], @@ -2710,12 +2366,7 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { assert_eq!( to_contract_err( token - .xfer( - &user, - token.nonce(user.get_identifier(&test.host)).unwrap(), - user.get_identifier(&test.host), - 1.into(), - ) + .xfer(&user, user.address(&test.host), 1_i128,) .err() .unwrap() ), @@ -2723,61 +2374,261 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { ); // Trustline balance stays the same. - assert_eq!( - test.get_classic_trustline_balance(&trustline_key), - 100_000_000 - ); + assert_eq!(test.get_trustline_balance(&trustline_key), 100_000_000); } +#[cfg(feature = "vm")] +fn simple_account_sign_fn<'a>( + host: &'a Host, + kp: &'a Keypair, +) -> Box HostVec + 'a> { + use crate::native_contract::testutils::sign_payload_for_ed25519; + Box::new(|payload: &[u8]| -> HostVec { + let signature = sign_payload_for_ed25519(host, kp, payload); + host_vec![host, signature] + }) +} + +#[cfg(feature = "vm")] #[test] -fn test_non_account_auth_required() { +fn test_custom_account_auth() { + use crate::native_contract::testutils::AccountContractSigner; + use soroban_test_wasms::SIMPLE_ACCOUNT_CONTRACT; + let test = TokenTest::setup(); + let admin_kp = generate_keypair(); + let account_contract_id_obj = test + .host + .register_test_contract_wasm(SIMPLE_ACCOUNT_CONTRACT) + .unwrap(); + let account_contract_id = test + .host + .hash_from_obj_input("account_contract_id", account_contract_id_obj) + .unwrap(); - // the admin is the issuer_key - let admin_acc = signer_to_account_id(&test.host, &test.issuer_key); - let user = TestSigner::Ed25519(&test.user_key); + let admin = TestSigner::AccountContract(AccountContractSigner { + id: account_contract_id.clone(), + sign: simple_account_sign_fn(&test.host, &admin_kp), + }); - let admin_id = Identifier::Account(admin_acc.clone()); - let user_id = user.get_identifier(&test.host); + let admin_public_key = BytesN::<32>::try_from_val( + &test.host, + &test + .host + .bytes_new_from_slice(admin_kp.public.as_bytes().as_slice()) + .unwrap(), + ) + .unwrap(); + // Initialize the admin account + test.host + .call( + account_contract_id_obj.clone(), + Symbol::from_str("init"), + host_vec![&test.host, admin_public_key.clone()].into(), + ) + .unwrap(); - let acc_invoker = TestSigner::AccountInvoker; - let token = test.default_token_with_admin_id(admin_id.clone()); + let token = test.default_token_with_admin_id(&admin.address(&test.host)); + let user = TestSigner::account(&test.user_key); + let user_address = user.address(&test.host); + test.create_default_account(&user); + test.create_default_trustline(&user); + + token.mint(&admin, user_address.clone(), 100).unwrap(); + assert_eq!(token.balance(user_address.clone()).unwrap(), 100); + + // Create a signer for the new admin, but not yet set its key as the account + // owner. + let new_admin_kp = generate_keypair(); + let new_admin = TestSigner::AccountContract(AccountContractSigner { + id: account_contract_id.clone(), + sign: simple_account_sign_fn(&test.host, &new_admin_kp), + }); + let new_admin_public_key = BytesN::<32>::try_from_val( + &test.host, + &test + .host + .bytes_new_from_slice(new_admin_kp.public.as_bytes().as_slice()) + .unwrap(), + ) + .unwrap(); + // The new signer can't authorize admin ops. + assert!(token.mint(&new_admin, user_address.clone(), 100).is_err()); - test.create_classic_account( - &admin_acc, - vec![(&test.admin_key, 100)], - 10_000_000, - 1, - [1, 0, 0, 0], - None, - None, - AccountFlags::RequiredFlag as u32, + // Authorize the 'set_owner' invocation using the current owner signature. + authorize_single_invocation( + &test.host, + &admin, + &account_contract_id_obj.try_into_val(&test.host).unwrap(), + "set_owner", + host_vec![&test.host, new_admin_public_key.clone()], ); - // user_id is deauthorized by default because the issuer has AUTH_REQUIRED set, - assert!(!token.authorized(user_id.clone()).unwrap()); + // Change the owner of the account. + test.host + .call( + account_contract_id_obj.clone(), + Symbol::from_str("set_owner"), + host_vec![&test.host, new_admin_public_key].into(), + ) + .unwrap(); - // xfer to user_id will fail because the issuer has the AUTH_REQUIRED flag set - assert_eq!( - to_contract_err( - test.run_from_account(admin_acc.clone(), || { - token.xfer(&acc_invoker, 0, user_id.clone(), 1) - }) - .err() - .unwrap() - ), - ContractError::BalanceDeauthorizedError + // Now the token ops should work with the signatures from the new admin + // account owner. + token.mint(&new_admin, user_address.clone(), 100).unwrap(); + assert_eq!(token.balance(user_address.clone()).unwrap(), 200); + + // And they shouldn't work with the old owner signatures. + assert!(token.mint(&admin, user_address.clone(), 100).is_err()); +} + +#[test] +fn test_recording_auth_for_token() { + let test = TokenTest::setup(); + + let token = test.default_token(); + + let admin = TestSigner::account(&test.issuer_key); + let user = TestSigner::account(&test.user_key); + test.create_default_account(&user); + test.create_default_trustline(&user); + test.host.switch_to_recording_auth(); + + let args = host_vec![ + &test.host, + admin.address(&test.host), + user.address(&test.host), + 100_i128 + ]; + test.host + .call( + token.id.clone().into(), + Symbol::from_str("mint"), + args.clone().into(), + ) + .unwrap(); + let recorded_payloads = test.host.get_recorded_auth_payloads().unwrap(); + + assert_eq!( + recorded_payloads, + vec![RecordedAuthPayload { + address: Some(admin.address(&test.host).to_sc_address().unwrap()), + nonce: Some(0), + invocation: xdr::AuthorizedInvocation { + contract_id: Hash(token.id.to_array().unwrap()), + function_name: "mint".try_into().unwrap(), + args: ScVec( + vec![ + ScVal::try_from_val( + &test.host, + &RawVal::try_from_val(&test.host, &admin.address(&test.host)).unwrap() + ) + .unwrap(), + ScVal::try_from_val( + &test.host, + &RawVal::try_from_val(&test.host, &user.address(&test.host)).unwrap() + ) + .unwrap(), + ScVal::try_from_val( + &test.host, + &RawVal::try_from_val(&test.host, &100_i128).unwrap() + ) + .unwrap() + ] + .try_into() + .unwrap() + ), + sub_invocations: Default::default(), + } + }] ); - // authorize user_id - test.run_from_account(admin_acc.clone(), || { - token.set_auth(&acc_invoker, 0, user_id.clone(), true) - }) - .unwrap(); + // Incorrect address + assert!(!test + .host + .verify_top_authorization( + user.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint"), + args.clone().into() + ) + .unwrap()); - // user_id can now hold a balance - test.run_from_account(admin_acc.clone(), || { - token.xfer(&acc_invoker, 0, user_id.clone(), 1) - }) - .unwrap(); + // Incorrect contract + assert!(!test + .host + .verify_top_authorization( + user.address(&test.host).into(), + Hash([1; 32]), + Symbol::from_str("mint"), + args.clone().into() + ) + .unwrap()); + + // Incorrect function + assert!(!test + .host + .verify_top_authorization( + admin.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint2"), + args.clone().into() + ) + .unwrap()); + + // Incorrect args + assert!(!test + .host + .verify_top_authorization( + admin.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint2"), + host_vec![ + &test.host, + admin.address(&test.host), + user.address(&test.host), + 101_i128 + ] + .into() + ) + .unwrap()); + + // Incorrect args order + assert!(!test + .host + .verify_top_authorization( + admin.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint2"), + host_vec![ + &test.host, + admin.address(&test.host), + 100_i128, + user.address(&test.host), + ] + .into() + ) + .unwrap()); + + // Correct args + assert!(test + .host + .verify_top_authorization( + admin.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint"), + args.clone().into() + ) + .unwrap()); + + // Correct args again, but the verification is exhausted now + assert!(!test + .host + .verify_top_authorization( + admin.address(&test.host).into(), + Hash(token.id.to_array().unwrap()), + Symbol::from_str("mint"), + args.clone().into() + ) + .unwrap()); } diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs index 09198f377..63e3af46d 100644 --- a/soroban-env-host/src/test/util.rs +++ b/soroban-env-host/src/test/util.rs @@ -13,7 +13,7 @@ use soroban_env_common::{ use crate::{ budget::{Budget, CostType}, storage::{test_storage::MockSnapshotSource, Storage}, - xdr, Host, HostError, LedgerInfo, + xdr, Host, HostError, }; // Test utilities for the host, used in various tests in sub-modules. @@ -167,15 +167,3 @@ impl Host { Ok(id_obj.try_into()?) } } - -impl Default for LedgerInfo { - fn default() -> Self { - Self { - protocol_version: Default::default(), - sequence_number: Default::default(), - timestamp: Default::default(), - network_passphrase: vec![0; 32], - base_reserve: Default::default(), - } - } -} diff --git a/soroban-test-wasms/src/lib.rs b/soroban-test-wasms/src/lib.rs index 401a6bc21..04a522c41 100644 --- a/soroban-test-wasms/src/lib.rs +++ b/soroban-test-wasms/src/lib.rs @@ -60,3 +60,5 @@ pub const FANNKUCH: &'static [u8] = include_bytes!("../wasm-workspace/opt/example_fannkuch.wasm").as_slice(); pub const COMPLEX: &'static [u8] = include_bytes!("../wasm-workspace/opt/example_complex.wasm").as_slice(); +pub const SIMPLE_ACCOUNT_CONTRACT: &'static [u8] = + include_bytes!("../wasm-workspace/opt/example_simple_account.wasm").as_slice(); diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock index e2ad0fd63..15d061886 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.lock +++ b/soroban-test-wasms/wasm-workspace/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.19.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -34,9 +34,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cfg-if" @@ -115,9 +115,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", "num-integer", @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ "cc", "cxxbridge-flags", @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" dependencies = [ "cc", "codespan-reporting", @@ -214,15 +214,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote", @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -387,6 +387,14 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "example_simple_account" +version = "0.0.0" +dependencies = [ + "soroban-account", + "soroban-sdk", +] + [[package]] name = "example_vec" version = "0.0.0" @@ -434,9 +442,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "hashbrown" @@ -485,9 +493,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", @@ -526,9 +534,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libm" @@ -568,9 +576,9 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -630,9 +638,9 @@ dependencies = [ [[package]] name = "object" -version = "0.30.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "memchr", ] @@ -774,18 +782,18 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] name = "serde" -version = "1.0.150" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -794,9 +802,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", @@ -805,9 +813,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef" +checksum = "368f2d60d049ea019a84dcd6687b0d1e0030fe663ae105039bdf967ed5e6a9a7" dependencies = [ "base64", "chrono", @@ -821,9 +829,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa" +checksum = "1ccadfacf6cf10faad22bbadf55986bdd0856edfb5d9210aa1dcf1f516e84e93" dependencies = [ "darling", "proc-macro2", @@ -852,7 +860,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.5", ] [[package]] @@ -861,9 +869,17 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "soroban-account" +version = "0.4.3" +dependencies = [ + "soroban-sdk", + "soroban-sdk-macros", +] + [[package]] name = "soroban-env-common" -version = "0.0.11" +version = "0.0.12" dependencies = [ "crate-git-revision", "serde", @@ -875,7 +891,7 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "0.0.11" +version = "0.0.12" dependencies = [ "soroban-env-common", "static_assertions", @@ -883,7 +899,7 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "0.0.11" +version = "0.0.12" dependencies = [ "backtrace", "curve25519-dalek", @@ -904,19 +920,21 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "0.0.11" +version = "0.0.12" dependencies = [ "itertools", "proc-macro2", "quote", + "serde", + "serde_json", "stellar-xdr", "syn", + "thiserror", ] [[package]] name = "soroban-ledger-snapshot" -version = "0.3.2" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=0b6e010#0b6e0104721eed8d5f13e20e9b19b1880feada0a" +version = "0.4.3" dependencies = [ "serde", "serde_json", @@ -926,7 +944,7 @@ dependencies = [ [[package]] name = "soroban-native-sdk-macros" -version = "0.0.11" +version = "0.0.12" dependencies = [ "itertools", "proc-macro2", @@ -936,8 +954,7 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "0.3.2" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=0b6e010#0b6e0104721eed8d5f13e20e9b19b1880feada0a" +version = "0.4.3" dependencies = [ "bytes-lit", "ed25519-dalek", @@ -951,8 +968,7 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "0.3.2" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=0b6e010#0b6e0104721eed8d5f13e20e9b19b1880feada0a" +version = "0.4.3" dependencies = [ "darling", "itertools", @@ -967,8 +983,7 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "0.3.2" -source = "git+https://github.com/stellar/rs-soroban-sdk?rev=0b6e010#0b6e0104721eed8d5f13e20e9b19b1880feada0a" +version = "0.4.3" dependencies = [ "base64", "darling", @@ -1031,8 +1046,7 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "0.0.11" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=dbf2aba#dbf2abab7cfde069f89ec5846a1c0c75ce4764ef" +version = "0.0.12" dependencies = [ "base64", "crate-git-revision", @@ -1055,9 +1069,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -1149,9 +1163,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "typenum" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-ident" @@ -1303,9 +1317,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/soroban-test-wasms/wasm-workspace/Cargo.toml b/soroban-test-wasms/wasm-workspace/Cargo.toml index 74c7ddf52..b26055f95 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.toml +++ b/soroban-test-wasms/wasm-workspace/Cargo.toml @@ -27,6 +27,7 @@ members = [ "hostile", "complex", "fannkuch", + "simple_account", ] [profile.release] opt-level = "z" @@ -46,11 +47,20 @@ soroban-env-guest = { path = "../../soroban-env-guest" } soroban-env-host = { path = "../../soroban-env-host" } [workspace.dependencies.soroban-sdk] -version = "0.3.2" +version = "0.4.3" git = "https://github.com/stellar/rs-soroban-sdk" -rev = "0b6e010" +rev = "63bef69c" + +[workspace.dependencies.soroban-account] +version = "0.4.3" +git = "https://github.com/stellar/rs-soroban-sdk" +rev = "63bef69c" # Uncomment the following to build against local SDK. This is useful when env # changes are breaking for SDK and require the respective SDK change -# [patch."https://github.com/stellar/rs-soroban-sdk"] -# soroban-sdk = { path = "../../../rs-soroban-sdk/soroban-sdk" } +[patch."https://github.com/stellar/rs-soroban-sdk"] +soroban-sdk = { path = "../../../rs-soroban-sdk/soroban-sdk" } +soroban-account = { path = "../../../rs-soroban-sdk/soroban-account" } + +[patch."https://github.com/stellar/rs-stellar-xdr"] +stellar-xdr = { path = "../../../rs-stellar-xdr/" } \ No newline at end of file diff --git a/soroban-test-wasms/wasm-workspace/complex/src/lib.rs b/soroban-test-wasms/wasm-workspace/complex/src/lib.rs index 333468cf5..77e4f3a14 100644 --- a/soroban-test-wasms/wasm-workspace/complex/src/lib.rs +++ b/soroban-test-wasms/wasm-workspace/complex/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -use soroban_sdk::{contractimpl, contracttype, Bytes, Env, Symbol, Vec}; +use soroban_sdk::{contractimpl, contracttype, Bytes, BytesN, Env, Symbol, Vec}; // This is a "complex" contract that uses a nontrivial amount of the host // interface from the guest: UDTs (thus maps), vectors, byte arrays and linear @@ -15,7 +15,7 @@ pub struct Contract; #[contracttype] struct MyLedger { - passphrase: Bytes, + network_id: BytesN<32>, version: u32, seq: u32, time: u64, @@ -26,18 +26,18 @@ impl Contract { pub fn go(e: Env) { let ledger = e.ledger(); let my_ledger = MyLedger { - passphrase: ledger.network_passphrase(), + network_id: ledger.network_id(), version: ledger.protocol_version(), seq: ledger.sequence(), time: ledger.timestamp(), }; let data = Symbol::from_str("data"); - let hash = e.crypto().sha256(&my_ledger.passphrase); + let hash = e.crypto().sha256(&my_ledger.network_id.clone().into()); let mut buf: [u8; 32] = [0; 32]; hash.copy_into_slice(&mut buf); let vec_with_half_hash = Vec::from_slice(&e, &[Bytes::from_slice(&e, &buf[0..16])]); e.events().publish((data,), hash); e.log_value(vec_with_half_hash); - e.storage().set(data, my_ledger); + e.storage().set(&data, &my_ledger); } } diff --git a/soroban-test-wasms/wasm-workspace/contract_data/src/lib.rs b/soroban-test-wasms/wasm-workspace/contract_data/src/lib.rs index a910cdf56..f25459af2 100644 --- a/soroban-test-wasms/wasm-workspace/contract_data/src/lib.rs +++ b/soroban-test-wasms/wasm-workspace/contract_data/src/lib.rs @@ -6,10 +6,10 @@ pub struct Contract; #[contractimpl] impl Contract { pub fn put(e: Env, key: Symbol, val: Symbol) { - e.storage().set(key, val) + e.storage().set(&key, &val) } pub fn del(e: Env, key: Symbol) { - e.storage().remove(key) + e.storage().remove(&key) } } diff --git a/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm b/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm index 5683454d7..20b62cf94 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_add_i32.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm b/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm index 8f3c161fb..f381d6fe4 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_complex.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm b/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm index 938011a7b..4e2ecbe3a 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_contract_data.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm index 1566daef2..ae78c44db 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_create_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm b/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm index e0e9488f2..6e869d7ca 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_fannkuch.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm b/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm index 100fd0b8b..3cc8793b4 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_fib.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm b/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm index 520599fe4..891671d0d 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_hostile.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm b/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm index 551f3afd7..a821fc252 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_invoke_contract.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm b/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm index 6d2d11a16..69b0ff641 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_linear_memory.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm b/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm new file mode 100644 index 000000000..b6072f311 Binary files /dev/null and b/soroban-test-wasms/wasm-workspace/opt/example_simple_account.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm b/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm index 57e262611..3ebbcb861 100644 Binary files a/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm and b/soroban-test-wasms/wasm-workspace/opt/example_vec.wasm differ diff --git a/soroban-test-wasms/wasm-workspace/simple_account/Cargo.toml b/soroban-test-wasms/wasm-workspace/simple_account/Cargo.toml new file mode 100644 index 000000000..4d3530e5c --- /dev/null +++ b/soroban-test-wasms/wasm-workspace/simple_account/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example_simple_account" +version = "0.0.0" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +rust-version = "1.65" + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } +soroban-account = { workspace = true } \ No newline at end of file diff --git a/soroban-test-wasms/wasm-workspace/simple_account/src/lib.rs b/soroban-test-wasms/wasm-workspace/simple_account/src/lib.rs new file mode 100644 index 000000000..0b246bcbf --- /dev/null +++ b/soroban-test-wasms/wasm-workspace/simple_account/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] + +struct SimpleAccount; + +use soroban_account::AuthorizationContext; +use soroban_sdk::{contractimpl, contracttype, BytesN, Env, IntoVal, Vec}; + +#[derive(Clone)] +#[contracttype] +pub enum DataKey { + Owner, +} + +#[contractimpl] +impl SimpleAccount { + pub fn init(env: Env, public_key: BytesN<32>) { + env.storage().set(&DataKey::Owner, &public_key); + } + + pub fn set_owner(env: Env, new_owner: BytesN<32>) { + env.current_contract_address() + .require_auth((new_owner.clone(),).into_val(&env)); + env.storage().set(&DataKey::Owner, &new_owner); + } + + pub fn check_auth( + env: Env, + signature_payload: BytesN<32>, + signature_args: Vec>, + _auth_context: Vec, + ) { + if signature_args.len() != 1 { + panic!("incorrect number of signature args"); + } + let public_key: BytesN<32> = env.storage().get(&DataKey::Owner).unwrap().unwrap(); + env.crypto().ed25519_verify( + &public_key, + &signature_payload.into(), + &signature_args.get(0).unwrap().unwrap(), + ); + } +}