Repository: stellar/rs-soroban-env
Pull request #383 (2022-08-28 23:28:20)

Add hostfn `account_exists`.
### What

Resolves https://github.com/stellar/rs-soroban-env/issues/262

### Why

[TODO: Why this change is being made. Include any context required to understand the why.]

### Known limitations

[TODO or N/A]



Patch:
diff --git a/soroban-env-common/src/env.rs b/soroban-env-common/src/env.rs
index 8fd43f36..03f22d5f 100644
--- a/soroban-env-common/src/env.rs
+++ b/soroban-env-common/src/env.rs
@@ -411,6 +411,9 @@ macro_rules! call_macro_with_all_host_functions {
                 /// master, and returns 0 if no such signer exists. Traps if no
                 /// such account exists.
                 {"2", fn account_get_signer_weight(a:Object, s:Object) -> RawVal}
+                /// Given an ed25519 public key `a` (`a` is binary) of an account,
+                /// check if it exists. Returns (SCStatic) TRUE/FALSE.
+                {"3", fn account_exists(a:Object) -> RawVal}
             }
         }
     };
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index 420d94cf..c970c732 100644
--- a/soroban-env-common/src/meta.rs
+++ b/soroban-env-common/src/meta.rs
@@ -56,5 +56,5 @@
 // implementations over a long period of time.
 
 soroban_env_macros::generate_env_meta_consts!(
-    interface_version: 6,
+    interface_version: 7,
 );
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index b3eeb3d9..964b1333 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -1905,6 +1905,11 @@ impl CheckedEnv for Host {
         Ok(threshold.into())
     }
 
+    // Notes on metering: covered by components.
+    fn account_exists(&self, a: Object) -> Result<RawVal, Self::Error> {
+        Ok(self.has_account(a)?.into())
+    }
+
     // Notes on metering: some covered. The for loop and comparisons are free (for now).
     fn account_get_signer_weight(&self, a: Object, s: Object) -> Result<RawVal, Self::Error> {
         use xdr::{Signer, SignerKey};
diff --git a/soroban-env-host/src/host/data_helper.rs b/soroban-env-host/src/host/data_helper.rs
index f1fd7790..1ae16a69 100644
--- a/soroban-env-host/src/host/data_helper.rs
+++ b/soroban-env-host/src/host/data_helper.rs
@@ -102,4 +102,12 @@ impl Host {
             _ => Err(self.err_general("not account")),
         })
     }
+
+    // notes on metering: covered by `has` and `to_u256`.
+    pub fn has_account(&self, a: Object) -> Result<bool, HostError> {
+        let acc = LedgerKey::Account(LedgerKeyAccount {
+            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(a)?)),
+        });
+        self.visit_storage(|storage| storage.has(&acc))
+    }
 }


Test patch:
diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs
index a821f0a5..8486c8c8 100644
--- a/soroban-env-host/src/test.rs
+++ b/soroban-env-host/src/test.rs
@@ -1,5 +1,6 @@
 mod util;
 
+mod account;
 mod basic;
 mod bigint;
 mod binary;
diff --git a/soroban-env-host/src/test/account.rs b/soroban-env-host/src/test/account.rs
new file mode 100644
index 00000000..494eb5fd
--- /dev/null
+++ b/soroban-env-host/src/test/account.rs
@@ -0,0 +1,57 @@
+use soroban_env_common::{CheckedEnv, EnvBase};
+
+use crate::{
+    budget::Budget,
+    host::metered_map::MeteredOrdMap,
+    storage::{AccessType, Footprint, Storage},
+    xdr, Host, HostError, RawVal,
+};
+
+#[test]
+fn check_account_exists() -> Result<(), HostError> {
+    let id0 = [0; 32];
+    let id1 = [1; 32]; // declared, but not in storage
+    let id2 = [2; 32]; // not declared
+    let acc_id0 = xdr::Uint256(id0.clone());
+    let acc_id1 = xdr::Uint256(id1.clone());
+    let acc_id2 = xdr::Uint256(id2.clone());
+    let (lk0, le0) = Host::test_account_ledger_key_entry_pair(acc_id0);
+    let (lk1, _le1) = Host::test_account_ledger_key_entry_pair(acc_id1);
+    let (_lk2, _le2) = Host::test_account_ledger_key_entry_pair(acc_id2);
+
+    let budget = Budget::default();
+    let mut footprint = Footprint::default();
+    footprint.record_access(&lk0, AccessType::ReadOnly).unwrap();
+    footprint.record_access(&lk1, AccessType::ReadOnly).unwrap();
+
+    let mut map = im_rc::OrdMap::default();
+    map.insert(lk0, Some(le0));
+    let storage = Storage::with_enforcing_footprint_and_map(
+        footprint,
+        MeteredOrdMap {
+            budget: budget.clone(),
+            map,
+        },
+    );
+
+    let host = Host::with_storage_and_budget(storage, budget.clone());
+    let obj0 = host.binary_new_from_slice(&id0);
+    let obj1 = host.binary_new_from_slice(&id1);
+    let obj2 = host.binary_new_from_slice(&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/util.rs b/soroban-env-host/src/test/util.rs
index e5b3ce79..68aaf5d2 100644
--- a/soroban-env-host/src/test/util.rs
+++ b/soroban-env-host/src/test/util.rs
@@ -4,9 +4,11 @@ use crate::{
     host_object::{HostObj, HostVal},
     im_rc::OrdMap,
     storage::{AccessType, Footprint, Storage},
+    xdr,
     xdr::{
-        ContractDataEntry, Hash, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey,
-        LedgerKeyContractData, ScContractCode, ScObject, ScStatic, ScVal, ScVec,
+        AccountEntry, AccountId, ContractDataEntry, Hash, LedgerEntry, LedgerEntryData,
+        LedgerEntryExt, LedgerKey, LedgerKeyContractData, PublicKey, ScContractCode, ScObject,
+        ScStatic, ScVal, ScVec, Uint256,
     },
     Host, HostError,
 };
@@ -95,6 +97,31 @@ impl Host {
         Storage::with_enforcing_footprint_and_map(footprint, MeteredOrdMap { budget, map })
     }
 
+    pub(crate) fn test_account_ledger_key_entry_pair(id: Uint256) -> (LedgerKey, LedgerEntry) {
+        let account_id = AccountId(PublicKey::PublicKeyTypeEd25519(id));
+        let lk = LedgerKey::Account(xdr::LedgerKeyAccount {
+            account_id: account_id.clone(),
+        });
+        let account_entry = AccountEntry {
+            account_id,
+            balance: 100,
+            seq_num: xdr::SequenceNumber(0),
+            num_sub_entries: 0,
+            inflation_dest: None,
+            flags: 0,
+            home_domain: Default::default(),
+            thresholds: xdr::Thresholds([0; 4]),
+            signers: Default::default(),
+            ext: xdr::AccountEntryExt::V0,
+        };
+        let le = LedgerEntry {
+            last_modified_ledger_seq: 0,
+            data: LedgerEntryData::Account(account_entry),
+            ext: LedgerEntryExt::V0,
+        };
+        (lk, le)
+    }
+
     pub(crate) fn test_scvec<T: AsScVal>(&self, vals: &[T]) -> Result<ScVec, HostError> {
         let v: Vec<ScVal> = vals.iter().map(|x| x.as_scval()).collect();
         self.map_err(v.try_into())