Repository: stellar/rs-soroban-env
Pull request #388 (2022-08-29 21:19:25)

Add vec_push_front and vec_push_back
### What

Resolves https://github.com/stellar/rs-soroban-env/issues/341.
Also renamed `vec_push` --> `vec_push_back` and `vec_pop` --> `vec_pop_back` and re-indexed to group the "push" and "pop" methods together. I know we discourage renaming/reindexing host functions in general as they are likely go through more iterations. But this change seems like an obvious one that improves clarity. Plus the recent work by @graydon on wasm test file restructuring makes it easy to recompile and update the `.wasm` files so they do not go stale. 
I will make the follow up change to update the SDK side once this is merged.

### 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 0652a2a6..d2c46dc5 100644
--- a/soroban-env-common/src/env.rs
+++ b/soroban-env-common/src/env.rs
@@ -225,37 +225,42 @@ macro_rules! call_macro_with_all_host_functions {
                 {"2", fn vec_del(v:Object, i:RawVal) -> Object}
                 /// Returns length of the vector.
                 {"3", fn vec_len(v:Object) -> RawVal}
+                /// Push a value to the front of a vector.
+                {"4", fn vec_push_front(v:Object, x:RawVal) -> Object}
+                /// Removes the first element from the vector and returns the new vector.
+                /// Traps if original vector is empty.
+                {"5", fn vec_pop_front(v:Object) -> Object}
                 /// Appends an element to the back of the vector.
-                {"4", fn vec_push(v:Object, x:RawVal) -> Object}
+                {"6", fn vec_push_back(v:Object, x:RawVal) -> Object}
                 /// Removes the last element from the vector and returns the new vector.
                 /// Traps if original vector is empty.
-                {"5", fn vec_pop(v:Object) -> Object}
+                {"7", fn vec_pop_back(v:Object) -> Object}
                 /// Return the first element in the vector. Traps if the vector is empty
-                {"6", fn vec_front(v:Object) -> RawVal}
+                {"8", fn vec_front(v:Object) -> RawVal}
                 /// Return the last element in the vector. Traps if the vector is empty
-                {"7", fn vec_back(v:Object) -> RawVal}
+                {"9", fn vec_back(v:Object) -> RawVal}
                 /// Inserts an element at index `i` within the vector, shifting all elements after it to the right.
                 /// Traps if the index is out of bound
-                {"8", fn vec_insert(v:Object, i:RawVal, x:RawVal) -> Object}
+                {"A", fn vec_insert(v:Object, i:RawVal, x:RawVal) -> Object}
                 /// Clone the vector `v1`, then moves all the elements of vector `v2` into it.
                 /// Return the new vector. Traps if number of elements in the vector overflows a u32.
-                {"9", fn vec_append(v1:Object, v2:Object) -> Object}
+                {"B", fn vec_append(v1:Object, v2:Object) -> Object}
                 /// Copy the elements from `start` index until `end` index, exclusive, in the vector and create a new vector from it.
                 /// Return the new vector. Traps if the index is out of bound.
-                {"A", fn vec_slice(v:Object, start:RawVal, end:RawVal) -> Object}
+                {"C", fn vec_slice(v:Object, start:RawVal, end:RawVal) -> Object}
                 /// Get the index of the first occurrence of a given element in the vector.
                 /// Returns the u32 index of the value if it's there. Otherwise, it returns `ScStatic::Void`.
-                {"B", fn vec_first_index_of(v:Object, x:RawVal) -> RawVal}
+                {"D", fn vec_first_index_of(v:Object, x:RawVal) -> RawVal}
                 /// Get the index of the last occurrence of a given element in the vector.
                 /// Returns the u32 index of the value if it's there. Otherwise, it returns `ScStatic::Void`.
-                {"C", fn vec_last_index_of(v:Object, x:RawVal) -> RawVal}
+                {"E", fn vec_last_index_of(v:Object, x:RawVal) -> RawVal}
                 /// Binary search a sorted vector for a given element.
                 /// If it exists, the high-32 bits of the return value is 0x0001 and the low-32 bits
                 /// contain the u32 index of the element.
                 /// If it does not exist, the high-32 bits of the return value is 0x0000 and the low-32 bits
                 /// contain the u32 index at which the element would need to be inserted into the vector to
                 /// maintain sorted order.
-                {"D", fn vec_binary_search(v:Object, x:RawVal) -> u64}
+                {"F", fn vec_binary_search(v:Object, x:RawVal) -> u64}
             }
 
             mod ledger "l" {
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index 88ca0b8a..15138bc3 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: 8,
+    interface_version: 9,
 );
diff --git a/soroban-env-common/src/tuple.rs b/soroban-env-common/src/tuple.rs
index 1d355c5b..6a724f3c 100644
--- a/soroban-env-common/src/tuple.rs
+++ b/soroban-env-common/src/tuple.rs
@@ -39,7 +39,7 @@ macro_rules! impl_for_tuple {
             fn into_val(self, env: &E) -> RawVal {
                 let env = env.clone();
                 let vec = env.vec_new($count.into());
-                $(let vec = env.vec_push(vec, self.$idx.into_val(&env));)*
+                $(let vec = env.vec_push_back(vec, self.$idx.into_val(&env));)*
                 vec.to_raw()
             }
         }
@@ -51,7 +51,7 @@ macro_rules! impl_for_tuple {
             fn into_val(self, env: &E) -> RawVal {
                 let env = env.clone();
                 let vec = env.vec_new($count.into());
-                $(let vec = env.vec_push(vec, (&self.$idx).into_val(&env));)*
+                $(let vec = env.vec_push_back(vec, (&self.$idx).into_val(&env));)*
                 vec.to_raw()
             }
         }
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 6f793e25..e88d8d6e 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -1116,7 +1116,7 @@ impl CheckedEnv for Host {
             let cap = self.usize_to_u32(hm.len(), "host map too large")?;
             let mut vec = self.vec_new(cap.into())?;
             for k in hm.keys()? {
-                vec = self.vec_push(vec, k.to_raw())?;
+                vec = self.vec_push_back(vec, k.to_raw())?;
             }
             Ok(vec)
         })
@@ -1127,7 +1127,7 @@ impl CheckedEnv for Host {
             let cap = self.usize_to_u32(hm.len(), "host map too large")?;
             let mut vec = self.vec_new(cap.into())?;
             for k in hm.values()? {
-                vec = self.vec_push(vec, k.to_raw())?;
+                vec = self.vec_push_back(vec, k.to_raw())?;
             }
             Ok(vec)
         })
@@ -1178,7 +1178,25 @@ impl CheckedEnv for Host {
         self.usize_to_rawval_u32(len)
     }
 
-    fn vec_push(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
+    fn vec_push_front(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
+        let x = self.associate_raw_val(x);
+        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.push_front(x)?;
+            Ok(vnew)
+        })?;
+        Ok(self.add_host_object(vnew)?.into())
+    }
+
+    fn vec_pop_front(&self, v: Object) -> Result<Object, HostError> {
+        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.pop_front().map(|_| vnew)
+        })?;
+        Ok(self.add_host_object(vnew)?.into())
+    }
+
+    fn vec_push_back(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
@@ -1188,7 +1206,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_pop(&self, v: Object) -> Result<Object, HostError> {
+    fn vec_pop_back(&self, v: Object) -> Result<Object, HostError> {
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
             vnew.pop_back().map(|_| vnew)
diff --git a/soroban-env-host/src/host/metered_vector.rs b/soroban-env-host/src/host/metered_vector.rs
index 06973c31..f2169f30 100644
--- a/soroban-env-host/src/host/metered_vector.rs
+++ b/soroban-env-host/src/host/metered_vector.rs
@@ -68,13 +68,27 @@ impl<A: Clone> MeteredVector<A> {
         self.vec.len()
     }
 
+    // Time: O(log n) worst case, O(1) ammortized
+    pub fn push_front(&mut self, value: A) -> Result<(), HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        Ok(self.vec.push_front(value))
+    }
+
+    // Time: O(log n) worst case, O(1) ammortized
+    pub fn pop_front(&mut self) -> Result<A, HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        self.vec
+            .pop_front()
+            .ok_or_else(|| ScHostObjErrorCode::VecIndexOutOfBound.into())
+    }
+
     // Time: O(log n) worst case, O(1) ammortized
     pub fn push_back(&mut self, value: A) -> Result<(), HostError> {
         self.charge_immut_access(self.len() as u64)?;
         Ok(self.vec.push_back(value))
     }
 
-    // Time: O(1)
+    // Time: O(log n) worst case, O(1) ammortized
     pub fn pop_back(&mut self) -> Result<A, HostError> {
         self.charge_immut_access(self.len() as u64)?;
         self.vec
diff --git a/soroban-env-host/src/native_contract/base_types.rs b/soroban-env-host/src/native_contract/base_types.rs
index 053e954f..760b7709 100644
--- a/soroban-env-host/src/native_contract/base_types.rs
+++ b/soroban-env-host/src/native_contract/base_types.rs
@@ -328,7 +328,7 @@ impl Vec {
         HostError: From<<T as TryIntoVal<Host, RawVal>>::Error>,
     {
         let rv = x.try_into_val(&self.0.env)?;
-        self.0.val = self.0.env.vec_push(self.0.val, rv)?;
+        self.0.val = self.0.env.vec_push_back(self.0.val, rv)?;
         Ok(())
     }
 }


Test patch:
diff --git a/soroban-env-host/src/test/binary.rs b/soroban-env-host/src/test/binary.rs
index b46f9824..b48e9a69 100644
--- a/soroban-env-host/src/test/binary.rs
+++ b/soroban-env-host/src/test/binary.rs
@@ -218,7 +218,7 @@ fn invoke_memcpy() -> Result<(), HostError> {
     {
         let obj0 = host.test_bin_obj(&[1, 2, 3, 4])?;
         let mut args = host.vec_new(RawVal::from_void())?;
-        args = host.vec_push(args, obj0.to_raw())?;
+        args = host.vec_push_back(args, obj0.to_raw())?;
         let obj: Object = host
             .call(
                 id_obj.to_object(),
diff --git a/soroban-env-host/src/test/contract_event.rs b/soroban-env-host/src/test/contract_event.rs
index 8dfaf3ce..b8d4a0d9 100644
--- a/soroban-env-host/src/test/contract_event.rs
+++ b/soroban-env-host/src/test/contract_event.rs
@@ -16,8 +16,8 @@ impl ContractFunctionSet for EmptyContractWithEvents {
         let mut data = host.map_new();
         data = host.map_put(data, 1_u32.into(), 2_u32.into());
         let mut topics = host.vec_new(().into());
-        topics = host.vec_push(topics, 0u32.into());
-        topics = host.vec_push(topics, 1u32.into());
+        topics = host.vec_push_back(topics, 0u32.into());
+        topics = host.vec_push_back(topics, 1u32.into());
         Some(host.contract_event(topics, data.to_raw()))
     }
 }
diff --git a/soroban-env-host/src/test/invocation.rs b/soroban-env-host/src/test/invocation.rs
index 859d6291..c80a85ec 100644
--- a/soroban-env-host/src/test/invocation.rs
+++ b/soroban-env-host/src/test/invocation.rs
@@ -93,7 +93,7 @@ fn invoke_cross_contract_indirect() -> Result<(), HostError> {
     let id1_obj = host.test_bin_obj(&dummy_id1)?;
     let sym = Symbol::from_str("add_with");
     let args = host.test_vec_obj::<i32>(&[5, 6])?;
-    let args = host.vec_push(args.val, id1_obj.into())?;
+    let args = host.vec_push_back(args.val, id1_obj.into())?;
     // try call
     let val = host.call(id0_obj.to_object(), sym.into(), args.clone().into())?;
     let exp: RawVal = 11i32.into();
@@ -117,7 +117,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
     let id1_obj = host.test_bin_obj(&dummy_id1)?;
     let sym = Symbol::from_str("add_with");
     let args = host.test_vec_obj::<i32>(&[i32::MAX, 1])?;
-    let args = host.vec_push(args.val, id1_obj.into())?;
+    let args = host.vec_push_back(args.val, id1_obj.into())?;
     // try call -- add will trap, and add_with will trap, but we will get a status
     let status = host.try_call(id0_obj.to_object(), sym.into(), args.clone().into())?;
     let exp: Status = ScVmErrorCode::TrapUnreachable.into();
diff --git a/soroban-env-host/src/test/vec.rs b/soroban-env-host/src/test/vec.rs
index 2f88c839..ac8885f1 100644
--- a/soroban-env-host/src/test/vec.rs
+++ b/soroban-env-host/src/test/vec.rs
@@ -88,14 +88,14 @@ fn vec_push_pop_and_len() -> Result<(), HostError> {
     let l =
         unsafe { <u32 as RawValConvertible>::unchecked_from_val(host.vec_len(obj.to_object())?) };
     assert_eq!(l, 0);
-    let obj1 = host.vec_push(obj.to_object(), 1u32.into())?;
-    let obj2 = host.vec_push(obj1, 2u32.into())?;
+    let obj1 = host.vec_push_back(obj.to_object(), 1u32.into())?;
+    let obj2 = host.vec_push_back(obj1, 2u32.into())?;
     let l = unsafe { <u32 as RawValConvertible>::unchecked_from_val(host.vec_len(obj2)?) };
     assert_eq!(l, 2);
-    let obj3 = host.vec_pop(obj2)?;
+    let obj3 = host.vec_pop_back(obj2)?;
     let l = unsafe { <u32 as RawValConvertible>::unchecked_from_val(host.vec_len(obj3)?) };
     assert_eq!(l, 1);
-    let obj4 = host.vec_pop(obj3)?;
+    let obj4 = host.vec_pop_back(obj3)?;
     let l = unsafe { <u32 as RawValConvertible>::unchecked_from_val(host.vec_len(obj4)?) };
     assert_eq!(l, 0);
     Ok(())
@@ -105,7 +105,27 @@ fn vec_push_pop_and_len() -> Result<(), HostError> {
 fn vec_pop_empty_vec() -> Result<(), HostError> {
     let host = Host::default();
     let obj = host.test_vec_obj::<u32>(&[])?;
-    let res = host.vec_pop(obj.to_object());
+    let res = host.vec_pop_back(obj.to_object());
+    let code = ScHostObjErrorCode::VecIndexOutOfBound;
+    assert!(HostError::result_matches_err_status(res, code));
+    Ok(())
+}
+
+#[test]
+fn vec_push_pop_front() -> Result<(), HostError> {
+    let host = Host::default();
+    let obj = host.test_vec_obj::<u32>(&[])?;
+    let mut vec = host.vec_push_front(obj.to_object(), 1u32.into())?;
+    vec = host.vec_push_front(vec, 2u32.into())?;
+    vec = host.vec_push_front(vec, 3u32.into())?;
+    let mut vec_ref = host.test_vec_obj::<u32>(&[3, 2, 1])?;
+    assert_eq!(host.obj_cmp(vec.to_raw(), vec_ref.val.to_raw())?, 0);
+    vec = host.vec_pop_front(vec)?;
+    vec_ref = host.test_vec_obj::<u32>(&[2, 1])?;
+    assert_eq!(host.obj_cmp(vec.to_raw(), vec_ref.val.to_raw())?, 0);
+    vec = host.vec_pop_front(vec)?;
+    vec = host.vec_pop_front(vec)?;
+    let res = host.vec_pop_front(vec);
     let code = ScHostObjErrorCode::VecIndexOutOfBound;
     assert!(HostError::result_matches_err_status(res, code));
     Ok(())
diff --git a/soroban-env-host/tests/integration.rs b/soroban-env-host/tests/integration.rs
index 3dea2cb7..4e3fedc2 100644
--- a/soroban-env-host/tests/integration.rs
+++ b/soroban-env-host/tests/integration.rs
@@ -6,12 +6,12 @@ fn vec_as_seen_by_user() -> Result<(), ()> {
     let int1 = host.obj_from_i64(5).in_env(&host);
 
     let vec1a = host.vec_new(RawVal::from_void()).in_env(&host);
-    let vec1b = host.vec_push(vec1a.val, *int1.as_ref()).in_env(&host);
+    let vec1b = host.vec_push_back(vec1a.val, *int1.as_ref()).in_env(&host);
 
     assert_ne!(vec1a.as_raw().get_payload(), vec1b.as_raw().get_payload());
 
     let vec2a = host.vec_new(RawVal::from_void()).in_env(&host);
-    let vec2b = host.vec_push(vec2a.val, *int1.as_ref()).in_env(&host);
+    let vec2b = host.vec_push_back(vec2a.val, *int1.as_ref()).in_env(&host);
 
     assert_ne!(vec2a.as_raw().get_payload(), vec2b.as_raw().get_payload());
     assert_ne!(vec1b.as_raw().get_payload(), vec2b.as_raw().get_payload());