Repository: paritytech/ink (rust)

Issue:
Mis-aligned allocation in ink
I am trying to port the quickjs runtime into ink. It always crashes because it rely on the heap allocation alignment.

Looks like the ink's bump allocator doesn't obey Rust's alignment requirements.
Given the follow code:
```rust
let buf1 = Vec::<u8>::with_capacity(3);
let buf2 = Vec::<u64>::with_capacity(1);
info!("ptr to buf1 :{:?}", buf1.as_ptr());
info!("ptr to buf2 :{:?}", buf2.as_ptr());
info!("align of u64:{}", align_of::<u64>());
```
Runing in ink would get somthing like:
```
 ptr to buf1 :0x30022
 ptr to buf2 :0x30025
 align of u64:8
```
Where the ptr of buf2 is mis-aligned.

**Expected behavior**
It should be aligned as Rust's default allocator do. See [here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d360e8a505d68acb41aadd985de0e063) which meets the [Allocator requirements](https://doc.rust-lang.org/beta/core/alloc/trait.Allocator.html#required-methods).


Pull request description:
Align the start pointer in ink allocator
## Summary
Closes #1535 
- [ ] y/n | Does it introduce breaking changes?
- [ ] y/n | Is it dependant on the specific version of `cargo-contract` or `pallet-contracts`?

Fixes alignment rules on the ink bump allocator.

## Description
Before this PR, the allocator allocated types of different order in sequential order after each other without respecting the alignment and padding rules. While this saved up tiny bits of memory usage, it misaligned the types in memory which can potentially increase direct access.

This PR ensure the valid alignment of datatypes, by correctly aligning the end pointer of the linear memory buffer.

## Checklist before requesting a review
- [x] My code follows the style guidelines of this project
- [x] I have added an entry to `CHANGELOG.md`
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] Any dependent changes have been merged and published in downstream modules


Patch:
diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic
index 4831da94b3..61a6f3445e 100644
--- a/.config/cargo_spellcheck.dic
+++ b/.config/cargo_spellcheck.dic
@@ -25,6 +25,7 @@ Wasm32
 WebAssembly
 adjunctive
 bitvector
+bitmask
 bitwise
 callee
 codegen
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0d3ec8f515..10487cea4b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -391,7 +391,7 @@ jobs:
           # FIXME: remove me after CI image gets nonroot
           chown -R nonroot:nonroot ./crate-docs
 
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: ${{ github.job }}-data
           path: ./crate-docs
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index 371dfc7b43..d2883513d3 100644
--- a/.github/workflows/examples.yml
+++ b/.github/workflows/examples.yml
@@ -42,21 +42,19 @@ jobs:
 
       - name: Install toolchain
         id: toolchain
-        uses: actions-rs/toolchain@v1
+        uses: dtolnay/rust-toolchain@master
         with:
-          profile: minimal
           toolchain: ${{ matrix.toolchain }}
           components: rust-src
-          override: true
 
       - name: Install cargo-dylint
-        uses: baptiste0928/cargo-install@1cd874a5478fdca35d868ccc74640c5aabbb8f1b # v3.0.0
+        uses: baptiste0928/cargo-install@v3
         with:
           crate: cargo-dylint
           version: 1
 
       - name: Install dylint-link
-        uses: baptiste0928/cargo-install@1cd874a5478fdca35d868ccc74640c5aabbb8f1b # v3.0.0
+        uses: baptiste0928/cargo-install@v3
         with:
           crate: dylint-link
           version: 1
@@ -76,13 +74,14 @@ jobs:
             substrate-contracts-node -lruntime::contracts=debug  2>&1 | tee /tmp/contracts-node.log &
 
       - name: Rust Cache
-        uses: Swatinem/rust-cache@v2.7.1
+        uses: Swatinem/rust-cache@v2
 
       - name: Install `cargo-contract` `master`
-        uses: actions-rs/cargo@v1
+        uses: baptiste0928/cargo-install@v3
         with:
-            command: install
-            args: --git https://github.com/paritytech/cargo-contract.git
+            crate: cargo-contract
+            git: https://github.com/paritytech/cargo-contract.git
+            branch: master
 
       - name: Output versions
         run: |
diff --git a/.github/workflows/measurements.yml b/.github/workflows/measurements.yml
index 5cea238d4c..8efffe30b6 100644
--- a/.github/workflows/measurements.yml
+++ b/.github/workflows/measurements.yml
@@ -67,7 +67,7 @@ jobs:
           echo "PR_NUMBER=\"${PR_NUMBER}\"" >> ${ARTIFACTS_DIR}/context.out
           rm -rf ${MEASUREMENTS_DIR}
 
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@v4
         with:
           name: ${{ github.job }}-data
           path: ./artifacts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5580e8129..37aa5ad1e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Fixed
+- Fix alignment in allocator [#2100](https://github.com/paritytech/ink/pull/2100)
+
 ## Version 5.0.0-rc.1
 
 ### Added
@@ -14,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [Linter] Publish the linting crates on crates.io - [#2060](https://github.com/paritytech/ink/pull/2060)
 - [E2E] Added `create_call_builder` for testing existing contracts - [#2075](https://github.com/paritytech/ink/pull/2075)
 - `call_v2` cross-contract calls with additional limit parameters - [#2077](https://github.com/paritytech/ink/pull/2077)
+- `delegate_dependency` api calls - [#2076](https://github.com/paritytech/ink/pull/2076)
 
 ### Changed
 - `Mapping`: Reflect all possible failure cases in comments ‒ [#2079](https://github.com/paritytech/ink/pull/2079)
diff --git a/Cargo.lock b/Cargo.lock
index 93eb33830f..7a4a084425 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2626,7 +2626,6 @@ name = "ink"
 version = "5.0.0-rc.1"
 dependencies = [
  "derive_more",
- "ink-pallet-contracts-uapi",
  "ink_env 5.0.0-rc.1",
  "ink_ir",
  "ink_macro",
@@ -2634,6 +2633,7 @@ dependencies = [
  "ink_prelude 5.0.0-rc.1",
  "ink_primitives 5.0.0-rc.1",
  "ink_storage",
+ "pallet-contracts-uapi-next",
  "parity-scale-codec",
  "scale-info",
  "trybuild",
@@ -2647,7 +2647,7 @@ checksum = "dd3e608f5410d03e529145875eb736305e0d7cae4b989faf54f932eff31bc048"
 dependencies = [
  "bitflags 1.3.2",
  "paste",
- "polkavm-derive",
+ "polkavm-derive 0.4.0",
 ]
 
 [[package]]
@@ -2743,8 +2743,8 @@ version = "5.0.0-rc.1"
 dependencies = [
  "blake2",
  "derive_more",
- "ink-pallet-contracts-uapi",
  "ink_primitives 5.0.0-rc.1",
+ "pallet-contracts-uapi-next",
  "parity-scale-codec",
  "secp256k1",
  "sha2 0.10.8",
@@ -2776,13 +2776,13 @@ dependencies = [
  "const_env",
  "derive_more",
  "ink",
- "ink-pallet-contracts-uapi",
  "ink_allocator 5.0.0-rc.1",
  "ink_engine 5.0.0-rc.1",
  "ink_prelude 5.0.0-rc.1",
  "ink_primitives 5.0.0-rc.1",
  "ink_storage_traits 5.0.0-rc.1",
  "num-traits",
+ "pallet-contracts-uapi-next",
  "parity-scale-codec",
  "paste",
  "rlibc",
@@ -2946,13 +2946,13 @@ dependencies = [
  "cfg-if",
  "derive_more",
  "ink",
- "ink-pallet-contracts-uapi",
  "ink_env 5.0.0-rc.1",
  "ink_metadata 5.0.0-rc.1",
  "ink_prelude 5.0.0-rc.1",
  "ink_primitives 5.0.0-rc.1",
  "ink_storage_traits 5.0.0-rc.1",
  "itertools 0.12.1",
+ "pallet-contracts-uapi-next",
  "parity-scale-codec",
  "quickcheck",
  "quickcheck_macros",
@@ -3862,10 +3862,21 @@ dependencies = [
  "bitflags 1.3.2",
  "parity-scale-codec",
  "paste",
- "polkavm-derive",
+ "polkavm-derive 0.4.0",
  "scale-info",
 ]
 
+[[package]]
+name = "pallet-contracts-uapi-next"
+version = "6.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e941fe56abf3b3d127c80d0a82989323f240ad81e6222421a56f1a3142db1e16"
+dependencies = [
+ "bitflags 1.3.2",
+ "paste",
+ "polkavm-derive 0.5.0",
+]
+
 [[package]]
 name = "pallet-timestamp"
 version = "27.0.0"
@@ -4092,13 +4103,29 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fecd2caacfc4a7ee34243758dd7348859e6dec73f5e5df059890f5742ee46f0e"
 
+[[package]]
+name = "polkavm-common"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c"
+
 [[package]]
 name = "polkavm-derive"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db65a500d4adf574893c726ae365e37e4fbb7f2cbd403f6eaa1b665457456adc"
 dependencies = [
- "polkavm-derive-impl",
+ "polkavm-derive-impl 0.4.0",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "polkavm-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8"
+dependencies = [
+ "polkavm-derive-impl 0.5.0",
  "syn 2.0.48",
 ]
 
@@ -4108,7 +4135,19 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c99f4e7a9ff434ef9c885b874c99d824c3a5693bf5e3e8569bb1d2245a8c1b7f"
 dependencies = [
- "polkavm-common",
+ "polkavm-common 0.4.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
+[[package]]
+name = "polkavm-derive-impl"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d"
+dependencies = [
+ "polkavm-common 0.5.0",
  "proc-macro2",
  "quote",
  "syn 2.0.48",
diff --git a/Cargo.toml b/Cargo.toml
index e3f42283b4..efe7a19889 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,7 +84,7 @@ const_env = { version = "0.1"}
 
 # Substrate dependencies
 pallet-contracts = { version = "27.0.0", default-features = false }
-pallet-contracts-uapi = { package = "ink-pallet-contracts-uapi", version = "=6.0.0", default-features = false }
+pallet-contracts-uapi = { package = "pallet-contracts-uapi-next", version = "=6.0.1", default-features = false }
 sp-core = { version = "28.0.0", default-features = false }
 sp-keyring = { version = "31.0.0", default-features = false }
 sp-runtime = { version = "31.0.1", default-features = false }
diff --git a/RELEASES_CHECKLIST.md b/RELEASES_CHECKLIST.md
index 247525eb25..414429ffd1 100644
--- a/RELEASES_CHECKLIST.md
+++ b/RELEASES_CHECKLIST.md
@@ -58,7 +58,8 @@ in the future.
     - `ink_e2e` is dependent on the `cargo-contract` crates, which are dependent on the `ink` "core" crates.
     - Therefore, first release all `ink` crates except `ink_e2e`.
     - Release `cargo-contract` crates.
-    - Release `ink_e2e`
+    - Request update of `drink` client, which depends on the `cargo-contract` crates.
+    - Release `ink_e2e`.
 2. Do a dry run with `cargo release [new_version] -v --no-tag --no-push`
     - `[new_version]` should be the **exact** SemVer compatible version you are attempting
       to release e.g. `5.0.0`
diff --git a/crates/allocator/src/bump.rs b/crates/allocator/src/bump.rs
index a713cc5d04..78db75584f 100644
--- a/crates/allocator/src/bump.rs
+++ b/crates/allocator/src/bump.rs
@@ -14,9 +14,6 @@
 
 //! A simple bump allocator.
 //!
-//! Its goal to have a much smaller footprint than the admittedly more full-featured
-//! `wee_alloc` allocator which is currently being used by ink! smart contracts.
-//!
 //! The heap which is used by this allocator is built from pages of Wasm memory (each page
 //! is `64KiB`). We will request new pages of memory as needed until we run out of memory,
 //! at which point we will crash with an `OOM` error instead of freeing any memory.
@@ -174,9 +171,10 @@ impl InnerAlloc {
     /// Note: This implementation results in internal fragmentation when allocating across
     /// pages.
     fn alloc(&mut self, layout: Layout) -> Option<usize> {
-        let alloc_start = self.next;
+        let alloc_start = self.align_ptr(&layout);
+
+        let aligned_size = layout.size();
 
-        let aligned_size = layout.pad_to_align().size();
         let alloc_end = alloc_start.checked_add(aligned_size)?;
 
         if alloc_end > self.upper_limit {
@@ -194,6 +192,20 @@ impl InnerAlloc {
             Some(alloc_start)
         }
     }
+
+    /// Aligns the start pointer of the next allocation.
+    ///
+    /// We inductively calculate the start index
+    /// of a layout in the linear memory.
+    /// - Initially `self.next` is `0`` and aligned
+    /// - `layout.align() - 1` accounts for `0` as the first index.
+    /// - the binary with the inverse of the align creates a
+    /// bitmask that is used to zero out bits, ensuring alignment according to type
+    /// requirements and ensures that the next allocated pointer address is of the
+    /// power of 2.
+    fn align_ptr(&self, layout: &Layout) -> usize {
+        (self.next + layout.align() - 1) & !(layout.align() - 1)
+    }
 }
 
 /// Calculates the number of pages of memory needed for an allocation of `size` bytes.
@@ -329,6 +341,18 @@ mod tests {
         let expected_alloc_start = 2 * PAGE_SIZE + size_of::<u8>();
         assert_eq!(inner.next, expected_alloc_start);
     }
+
+    #[test]
+    fn correct_alloc_types() {
+        let mut inner = InnerAlloc::new();
+        let layout1 = Layout::for_value(&Vec::<u128>::with_capacity(3));
+        assert_eq!(inner.alloc(layout1), Some(0));
+        assert_eq!(inner.next, 24);
+
+        let layout2 = Layout::for_value(&Vec::<u128>::with_capacity(1));
+        assert_eq!(inner.alloc(layout2), Some(24));
+        assert_eq!(inner.next, 48);
+    }
 }
 
 #[cfg(all(test, feature = "ink-fuzz-tests"))]
@@ -351,7 +375,7 @@ mod fuzz_tests {
             Err(_) => return TestResult::discard(),
         };
 
-        let size = layout.pad_to_align().size();
+        let size = layout.size();
         assert_eq!(
             inner.alloc(layout),
             Some(0),
@@ -390,7 +414,7 @@ mod fuzz_tests {
             Err(_) => return TestResult::discard(),
         };
 
-        let size = layout.pad_to_align().size();
+        let size = layout.size();
         assert_eq!(
             inner.alloc(layout),
             Some(0),
@@ -459,7 +483,7 @@ mod fuzz_tests {
                 Err(_) => return TestResult::discard(),
             };
 
-            let size = layout.pad_to_align().size();
+            let size = layout.size();
 
             let current_page_limit = PAGE_SIZE * required_pages(inner.next).unwrap();
             let is_too_big_for_current_page = inner.next + size > current_page_limit;
diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs
index daac732e89..a532f593c8 100644
--- a/crates/env/src/api.rs
+++ b/crates/env/src/api.rs
@@ -823,3 +823,42 @@ where
         TypedEnvBackend::call_runtime::<E, _>(instance, call)
     })
 }
+
+/// Adds a new delegate dependency lock to the contract.
+///
+/// This guarantees that the code of the dependency cannot be removed without first
+/// calling [`unlock_delegate_dependency`]. It charges a fraction of the code
+/// deposit, see [`pallet_contracts::Config::CodeHashLockupDepositPercent`](https://docs.rs/pallet-contracts/latest/pallet_contracts/pallet/trait.Config.html#associatedtype.CodeHashLockupDepositPercent) for details.
+///
+/// # Errors
+///
+/// - If the supplied `code_hash` cannot be found on-chain.
+/// - If the `code_hash` is the same as the calling contract.
+/// - If the maximum number of delegate dependencies is reached.
+/// - If the delegate dependency already exists.
+pub fn lock_delegate_dependency<E>(code_hash: &E::Hash)
+where
+    E: Environment,
+{
+    <EnvInstance as OnInstance>::on_instance(|instance| {
+        instance.lock_delegate_dependency::<E>(code_hash)
+    })
+}
+
+/// Unlocks the delegate dependency from the contract.
+///
+/// This removes the lock and refunds the deposit from the call to
+/// [`lock_delegate_dependency`]. The code of the dependency can be removed if the
+/// reference count for the code hash is now zero.
+///
+/// # Errors
+///
+/// - If the delegate dependency does not exist.
+pub fn unlock_delegate_dependency<E>(code_hash: &E::Hash)
+where
+    E: Environment,
+{
+    <EnvInstance as OnInstance>::on_instance(|instance| {
+        instance.unlock_delegate_dependency::<E>(code_hash)
+    })
+}
diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs
index 43597082b3..55af65dc96 100644
--- a/crates/env/src/backend.rs
+++ b/crates/env/src/backend.rs
@@ -418,4 +418,24 @@ pub trait TypedEnvBackend: EnvBackend {
     where
         E: Environment,
         Call: scale::Encode;
+
+    /// Adds a new delegate dependency lock to the contract.
+    ///
+    /// # Note
+    ///
+    /// For more details visit:
+    /// [`lock_delegate_dependency`][`crate::lock_delegate_dependency`]
+    fn lock_delegate_dependency<E>(&mut self, code_hash: &E::Hash)
+    where
+        E: Environment;
+
+    /// Unlocks the delegate dependency from the contract.
+    ///
+    /// # Note
+    ///
+    /// For more details visit:
+    /// [`unlock_delegate_dependency`][`crate::unlock_delegate_dependency`].
+    fn unlock_delegate_dependency<E>(&mut self, code_hash: &E::Hash)
+    where
+        E: Environment;
 }
diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs
index 2e567004f2..92321ae6e9 100644
--- a/crates/env/src/engine/off_chain/impls.rs
+++ b/crates/env/src/engine/off_chain/impls.rs
@@ -560,4 +560,18 @@ impl TypedEnvBackend for EnvInstance {
     {
         unimplemented!("off-chain environment does not support `call_runtime`")
     }
+
+    fn lock_delegate_dependency<E>(&mut self, _code_hash: &E::Hash)
+    where
+        E: Environment,
+    {
+        unimplemented!("off-chain environment does not support delegate dependencies")
+    }
+
+    fn unlock_delegate_dependency<E>(&mut self, _code_hash: &E::Hash)
+    where
+        E: Environment,
+    {
+        unimplemented!("off-chain environment does not support delegate dependencies")
+    }
 }
diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs
index ce36767ab9..10f5af7d15 100644
--- a/crates/env/src/engine/on_chain/impls.rs
+++ b/crates/env/src/engine/on_chain/impls.rs
@@ -447,6 +447,7 @@ impl TypedEnvBackend for EnvInstance {
         };
         let output = &mut scope.take_rest();
         let flags = params.call_flags();
+        #[allow(deprecated)]
         let call_result = ext::call_v1(
             *flags,
             enc_callee,
@@ -570,6 +571,7 @@ impl TypedEnvBackend for EnvInstance {
         let salt = params.salt_bytes().as_ref();
         let out_return_value = &mut scoped.take_rest();
 
+        #[allow(deprecated)]
         let instantiate_result = ext::instantiate_v1(
             enc_code_hash,
             gas_limit,
@@ -660,4 +662,22 @@ impl TypedEnvBackend for EnvInstance {
         let enc_call = scope.take_encoded(call);
         ext::call_runtime(enc_call).map_err(Into::into)
     }
+
+    fn lock_delegate_dependency<E>(&mut self, code_hash: &E::Hash)
+    where
+        E: Environment,
+    {
+        let mut scope = self.scoped_buffer();
+        let enc_code_hash = scope.take_encoded(code_hash);
+        ext::lock_delegate_dependency(enc_code_hash)
+    }
+
+    fn unlock_delegate_dependency<E>(&mut self, code_hash: &E::Hash)
+    where
+        E: Environment,
+    {
+        let mut scope = self.scoped_buffer();
+        let enc_code_hash = scope.take_encoded(code_hash);
+        ext::unlock_delegate_dependency(enc_code_hash)
+    }
 }
diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs
index 7dc17992ae..a19f9ed7b8 100644
--- a/crates/ink/src/env_access.rs
+++ b/crates/ink/src/env_access.rs
@@ -1183,4 +1183,67 @@ where
     pub fn call_runtime<Call: scale::Encode>(self, call: &Call) -> Result<()> {
         ink_env::call_runtime::<E, _>(call)
     }
+
+    /// Adds a new delegate dependency lock for the given `code_hash` to prevent it from
+    /// being deleted.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn lock_delegate_dependency(&mut self, code_hash: Hash) {
+    ///     self.env().lock_delegate_dependency(&code_hash)
+    /// }
+    /// #    }
+    /// # }
+    /// ```
+    ///
+    /// # Note
+    ///
+    /// For more details visit: [`ink_env::lock_delegate_dependency`]
+    pub fn lock_delegate_dependency(self, code_hash: &E::Hash) {
+        ink_env::lock_delegate_dependency::<E>(code_hash)
+    }
+
+    /// Removes the delegate dependency lock from this contract for the given `code_hash`.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn unlock_delegate_dependency(&mut self, code_hash: Hash) {
+    ///     self.env().unlock_delegate_dependency(&code_hash)
+    /// }
+    /// #    }
+    /// # }
+    /// ```
+    ///
+    /// # Note
+    ///
+    /// For more details visit: [`ink_env::unlock_delegate_dependency`]
+    pub fn unlock_delegate_dependency(self, code_hash: &E::Hash) {
+        ink_env::unlock_delegate_dependency::<E>(code_hash)
+    }
 }


Test patch:
diff --git a/crates/e2e/src/backend.rs b/crates/e2e/src/backend.rs
index b3de975510..2f9b395ddd 100644
--- a/crates/e2e/src/backend.rs
+++ b/crates/e2e/src/backend.rs
@@ -16,6 +16,7 @@ use super::Keypair;
 use crate::{
     backend_calls::{
         InstantiateBuilder,
+        RemoveCodeBuilder,
         UploadBuilder,
     },
     builders::CreateBuilderPartial,
@@ -159,6 +160,29 @@ pub trait ContractsBackend<E: Environment> {
         UploadBuilder::new(self, contract_name, caller)
     }
 
+    /// Start building a remove code call.
+    ///
+    /// # Example
+    ///
+    /// ```ignore
+    /// let contract = client
+    ///     .remove_code(&ink_e2e::alice(), code_hash)
+    ///     // Submit the call for on-chain execution.
+    ///     .submit()
+    ///     .await
+    ///     .expect("remove failed");
+    /// ```
+    fn remove_code<'a>(
+        &'a mut self,
+        caller: &'a Keypair,
+        code_hash: E::Hash,
+    ) -> RemoveCodeBuilder<E, Self>
+    where
+        Self: Sized + BuilderClient<E>,
+    {
+        RemoveCodeBuilder::new(self, caller, code_hash)
+    }
+
     /// Start building a call using a builder pattern.
     ///
     /// # Example
@@ -193,8 +217,8 @@ pub trait ContractsBackend<E: Environment> {
 
 #[async_trait]
 pub trait BuilderClient<E: Environment>: ContractsBackend<E> {
-    /// Executes a bare `call` for the contract at `account_id`. This function does
-    /// perform a dry-run, and user is expected to provide the gas limit.
+    /// Executes a bare `call` for the contract at `account_id`. This function does not
+    /// perform a dry-run, and the user is expected to provide the gas limit.
     ///
     /// Use it when you want to have a more precise control over submitting extrinsic.
     ///
@@ -239,6 +263,13 @@ pub trait BuilderClient<E: Environment>: ContractsBackend<E> {
         storage_deposit_limit: Option<E::Balance>,
     ) -> Result<UploadResult<E, Self::EventLog>, Self::Error>;
 
+    /// Removes the code of the contract at `code_hash`.
+    async fn bare_remove_code(
+        &mut self,
+        caller: &Keypair,
+        code_hash: E::Hash,
+    ) -> Result<Self::EventLog, Self::Error>;
+
     /// Bare instantiate call. This function does not perform a dry-run,
     /// and user is expected to provide the gas limit.
     ///
diff --git a/crates/e2e/src/backend_calls.rs b/crates/e2e/src/backend_calls.rs
index ebf30f6c2b..dc192e59c6 100644
--- a/crates/e2e/src/backend_calls.rs
+++ b/crates/e2e/src/backend_calls.rs
@@ -400,6 +400,37 @@ where
     }
 }
 
+/// Allows to build an end-to-end remove code call using a builder pattern.
+pub struct RemoveCodeBuilder<'a, E, B>
+where
+    E: Environment,
+    B: BuilderClient<E>,
+{
+    client: &'a mut B,
+    caller: &'a Keypair,
+    code_hash: E::Hash,
+}
+
+impl<'a, E, B> RemoveCodeBuilder<'a, E, B>
+where
+    E: Environment,
+    B: BuilderClient<E>,
+{
+    /// Initialize a remove code builder with essential values.
+    pub fn new(client: &'a mut B, caller: &'a Keypair, code_hash: E::Hash) -> Self {
+        Self {
+            client,
+            caller,
+            code_hash,
+        }
+    }
+
+    /// Submit the remove code extrinsic.
+    pub async fn submit(&mut self) -> Result<B::EventLog, B::Error> {
+        B::bare_remove_code(self.client, self.caller, self.code_hash).await
+    }
+}
+
 fn calculate_weight(
     mut proof_size: u64,
     mut ref_time: u64,
diff --git a/crates/e2e/src/drink_client.rs b/crates/e2e/src/drink_client.rs
index 0fd3bb34e3..746b9ce154 100644
--- a/crates/e2e/src/drink_client.rs
+++ b/crates/e2e/src/drink_client.rs
@@ -208,7 +208,7 @@ where
 #[async_trait]
 impl<
         AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
-        Hash: Copy + From<[u8; 32]>,
+        Hash: Copy + Send + From<[u8; 32]>,
         Config: SandboxConfig,
         E: Environment<
                 AccountId = AccountId,
@@ -341,6 +341,14 @@ where
         })
     }
 
+    async fn bare_remove_code(
+        &mut self,
+        _caller: &Keypair,
+        _code_hash: E::Hash,
+    ) -> Result<Self::EventLog, Self::Error> {
+        unimplemented!("drink! sandbox does not yet support remove_code")
+    }
+
     async fn bare_call<Args: Sync + Encode + Clone, RetType: Send + Decode>(
         &mut self,
         caller: &Keypair,
@@ -417,7 +425,7 @@ where
 
 impl<
         AccountId: Clone + Send + Sync + From<[u8; 32]> + AsRef<[u8; 32]>,
-        Hash: Copy + From<[u8; 32]>,
+        Hash: Copy + Send + From<[u8; 32]>,
         Config: SandboxConfig,
         E: Environment<
                 AccountId = AccountId,
diff --git a/crates/e2e/src/error.rs b/crates/e2e/src/error.rs
index b41b7d4d9e..d3d2a29494 100644
--- a/crates/e2e/src/error.rs
+++ b/crates/e2e/src/error.rs
@@ -44,6 +44,9 @@ pub enum Error<DispatchError: fmt::Debug + fmt::Display> {
     /// The `call` extrinsic failed.
     #[error("Call extrinsic error: {0}")]
     CallExtrinsic(DispatchError),
+    /// The `remove_code` extrinsic failed.
+    #[error("Remove code extrinsic error: {0}")]
+    RemoveCodeExtrinsic(DispatchError),
     /// Error fetching account balance.
     #[error("Fetching account Balance error: {0}")]
     Balance(String),
diff --git a/crates/e2e/src/subxt_client.rs b/crates/e2e/src/subxt_client.rs
index 7c68a122d2..2439f34026 100644
--- a/crates/e2e/src/subxt_client.rs
+++ b/crates/e2e/src/subxt_client.rs
@@ -468,7 +468,7 @@ where
     E::AccountId: Debug + Send + Sync,
     E::Balance:
         Clone + Debug + Send + Sync + From<u128> + scale::HasCompact + serde::Serialize,
-    E::Hash: Debug + Send + scale::Encode,
+    E::Hash: Debug + Send + Sync + scale::Encode,
 {
     async fn bare_instantiate<Contract: Clone, Args: Send + Sync + Encode + Clone, R>(
         &mut self,
@@ -536,6 +536,30 @@ where
         Ok(ret)
     }
 
+    async fn bare_remove_code(
+        &mut self,
+        caller: &Keypair,
+        code_hash: E::Hash,
+    ) -> Result<Self::EventLog, Self::Error> {
+        let tx_events = self.api.remove_code(caller, code_hash).await;
+
+        for evt in tx_events.iter() {
+            let evt = evt.unwrap_or_else(|err| {
+                panic!("unable to unwrap event: {err:?}");
+            });
+
+            if is_extrinsic_failed_event(&evt) {
+                let metadata = self.api.client.metadata();
+                let dispatch_error =
+                    DispatchError::decode_from(evt.field_bytes(), metadata)
+                        .map_err(|e| Error::Decoding(e.to_string()))?;
+                return Err(Error::RemoveCodeExtrinsic(dispatch_error))
+            }
+        }
+
+        Ok(tx_events)
+    }
+
     async fn bare_call<Args: Sync + Encode + Clone, RetType: Send + Decode>(
         &mut self,
         caller: &Keypair,
@@ -571,7 +595,7 @@ where
             if is_extrinsic_failed_event(&evt) {
                 let metadata = self.api.client.metadata();
                 let dispatch_error =
-                    subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
+                    DispatchError::decode_from(evt.field_bytes(), metadata)
                         .map_err(|e| Error::Decoding(e.to_string()))?;
                 log_error(&format!("extrinsic for call failed: {dispatch_error}"));
                 return Err(Error::CallExtrinsic(dispatch_error))
@@ -660,13 +684,14 @@ where
     C::Address: From<sr25519::PublicKey>,
     C::Signature: From<sr25519::Signature>,
     C::Address: Send + Sync,
-    <<C as subxt::Config>::ExtrinsicParams as subxt::config::ExtrinsicParams<C>>::OtherParams: Default + Send + Sync,
+    <<C as subxt::Config>::ExtrinsicParams as ExtrinsicParams<C>>::OtherParams:
+        Default + Send + Sync,
 
     E: Environment,
     E::AccountId: Debug + Send + Sync,
     E::Balance:
         Clone + Debug + Send + Sync + From<u128> + scale::HasCompact + serde::Serialize,
-    E::Hash: Debug + Send + scale::Encode,
+    E::Hash: Debug + Send + Sync + scale::Encode,
 {
 }
 
diff --git a/crates/e2e/src/xts.rs b/crates/e2e/src/xts.rs
index 28e64f3ae7..458ea42572 100644
--- a/crates/e2e/src/xts.rs
+++ b/crates/e2e/src/xts.rs
@@ -144,6 +144,13 @@ pub enum Determinism {
     Relaxed,
 }
 
+/// A raw call to `pallet-contracts`'s `remove_code`.
+#[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
+#[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
+pub struct RemoveCode<E: Environment> {
+    code_hash: E::Hash,
+}
+
 /// A raw call to `pallet-contracts`'s `upload`.
 #[derive(Debug, scale::Encode, scale::Decode, scale_encode::EncodeAsType)]
 #[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")]
@@ -471,6 +478,25 @@ where
         self.submit_extrinsic(&call, signer).await
     }
 
+    /// Submits an extrinsic to remove the code at the given hash.
+    ///
+    /// Returns when the transaction is included in a block. The return value
+    /// contains all events that are associated with this transaction.
+    pub async fn remove_code(
+        &self,
+        signer: &Keypair,
+        code_hash: E::Hash,
+    ) -> ExtrinsicEvents<C> {
+        let call = subxt::tx::Payload::new(
+            "Contracts",
+            "remove_code",
+            RemoveCode::<E> { code_hash },
+        )
+        .unvalidated();
+
+        self.submit_extrinsic(&call, signer).await
+    }
+
     /// Dry runs a call of the contract at `contract` with the given parameters.
     pub async fn call_dry_run(
         &self,
diff --git a/integration-tests/static-buffer/lib.rs b/integration-tests/static-buffer/lib.rs
index c6b733535b..9f813f4655 100644
--- a/integration-tests/static-buffer/lib.rs
+++ b/integration-tests/static-buffer/lib.rs
@@ -2,6 +2,11 @@
 
 #[ink::contract]
 pub mod static_buffer {
+    use ink::prelude::{
+        string::String,
+        vec::Vec,
+    };
+
     #[allow(unused_imports)]
     use ink::env::BUFFER_SIZE;
     #[ink(storage)]
@@ -28,6 +33,19 @@ pub mod static_buffer {
         pub fn get_caller(&self) -> AccountId {
             self.env().caller()
         }
+
+        #[ink(message)]
+        pub fn buffer(&self) -> Result<(u64, u64), String> {
+            let buf1 = Vec::<u8>::with_capacity(3);
+            let buf2 = Vec::<u64>::with_capacity(1);
+            let ptr1 = buf1.as_ptr() as u64;
+            let ptr2 = buf2.as_ptr() as u64;
+            let align = core::mem::align_of::<Vec<u64>>() as u64;
+            let padding = ptr2
+                .checked_sub(ptr1)
+                .ok_or(String::from("Error during padding calculation"))?;
+            Ok((padding, align))
+        }
     }
 
     #[cfg(test)]
@@ -75,5 +93,32 @@ pub mod static_buffer {
 
             Ok(())
         }
+
+        #[ink_e2e::test]
+        async fn buffer<Client: E2EBackend>(mut client: Client) -> E2EResult<()> {
+            // given
+            let mut constructor = StaticBufferRef::new_default();
+
+            // when
+            let contract = client
+                .instantiate("static_buffer", &ink_e2e::bob(), &mut constructor)
+                .submit()
+                .await
+                .expect("instantiate failed");
+            let call_builder = contract.call_builder::<StaticBuffer>();
+
+            // then
+            let buffer_call = call_builder.buffer();
+            let buffer_call_res =
+                client.call(&ink_e2e::bob(), &buffer_call).submit().await?;
+            let value = buffer_call_res.return_value();
+            assert!(value.is_ok());
+            let value = value.unwrap();
+            let padding = value.0;
+            let align = value.1;
+            assert_eq!(padding, 8);
+            assert_eq!(align, 4);
+            Ok(())
+        }
     }
 }
diff --git a/integration-tests/upgradeable-contracts/delegator/.gitignore b/integration-tests/upgradeable-contracts/delegator/.gitignore
deleted file mode 100644
index bf910de10a..0000000000
--- a/integration-tests/upgradeable-contracts/delegator/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# Ignore build artifacts from the local tests sub-crate.
-/target/
-
-# Ignore backup files creates by cargo fmt.
-**/*.rs.bk
-
-# Remove Cargo.lock when creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
-Cargo.lock
\ No newline at end of file
diff --git a/integration-tests/upgradeable-contracts/delegator/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/Cargo.toml
index bf1cca71ed..fcac593edb 100644
--- a/integration-tests/upgradeable-contracts/delegator/Cargo.toml
+++ b/integration-tests/upgradeable-contracts/delegator/Cargo.toml
@@ -8,6 +8,7 @@ publish = false
 [dependencies]
 ink = { path = "../../../crates/ink", default-features = false }
 delegatee = { path = "delegatee", default-features = false, features = ["ink-as-dependency"] }
+delegatee2 = { path = "delegatee2", default-features = false, features = ["ink-as-dependency"] }
 
 [dev-dependencies]
 ink_e2e = { path = "../../../crates/e2e" }
diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore b/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore
deleted file mode 100644
index bf910de10a..0000000000
--- a/integration-tests/upgradeable-contracts/delegator/delegatee/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-# Ignore build artifacts from the local tests sub-crate.
-/target/
-
-# Ignore backup files creates by cargo fmt.
-**/*.rs.bk
-
-# Remove Cargo.lock when creating an executable, leave it for libraries
-# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
-Cargo.lock
\ No newline at end of file
diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee2/Cargo.toml b/integration-tests/upgradeable-contracts/delegator/delegatee2/Cargo.toml
new file mode 100644
index 0000000000..3a0a91ca0a
--- /dev/null
+++ b/integration-tests/upgradeable-contracts/delegator/delegatee2/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "delegatee2"
+version = "5.0.0-rc.1"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+publish = false
+
+[dependencies]
+ink = { path = "../../../../crates/ink", default-features = false }
+
+[lib]
+path = "lib.rs"
+
+[features]
+default = ["std"]
+std = [
+    "ink/std",
+]
+ink-as-dependency = []
+e2e-tests = []
diff --git a/integration-tests/upgradeable-contracts/delegator/delegatee2/lib.rs b/integration-tests/upgradeable-contracts/delegator/delegatee2/lib.rs
new file mode 100644
index 0000000000..447db46e00
--- /dev/null
+++ b/integration-tests/upgradeable-contracts/delegator/delegatee2/lib.rs
@@ -0,0 +1,37 @@
+#![cfg_attr(not(feature = "std"), no_std, no_main)]
+
+#[ink::contract]
+pub mod delegatee2 {
+    use ink::storage::{
+        traits::ManualKey,
+        Mapping,
+    };
+    #[ink(storage)]
+    pub struct Delegatee2 {
+        addresses: Mapping<AccountId, i32, ManualKey<0x23>>,
+        counter: i32,
+    }
+
+    impl Delegatee2 {
+        #[allow(clippy::new_without_default)]
+        #[ink(constructor)]
+        pub fn new() -> Self {
+            unreachable!(
+                "Constructors are not called when upgrading using `set_code_hash`."
+            )
+        }
+
+        /// Increments the current value.
+        #[ink(message)]
+        pub fn inc(&mut self) {
+            self.counter = self.counter.checked_add(3).unwrap();
+        }
+
+        /// Adds current value of counter to the `addresses`
+        #[ink(message)]
+        pub fn append_address_value(&mut self) {
+            let caller = self.env().caller();
+            self.addresses.insert(caller, &self.counter);
+        }
+    }
+}
diff --git a/integration-tests/upgradeable-contracts/delegator/lib.rs b/integration-tests/upgradeable-contracts/delegator/lib.rs
index 10c26dc62f..75b43bb04e 100644
--- a/integration-tests/upgradeable-contracts/delegator/lib.rs
+++ b/integration-tests/upgradeable-contracts/delegator/lib.rs
@@ -2,11 +2,6 @@
 
 #[ink::contract]
 pub mod delegator {
-    use ink::{
-        env::CallFlags,
-        storage::Mapping,
-    };
-
     use ink::{
         env::{
             call::{
@@ -14,40 +9,67 @@ pub mod delegator {
                 ExecutionInput,
                 Selector,
             },
+            CallFlags,
             DefaultEnvironment,
         },
-        storage::traits::ManualKey,
+        storage::{
+            traits::ManualKey,
+            Lazy,
+            Mapping,
+        },
     };
 
     #[ink(storage)]
     pub struct Delegator {
         addresses: Mapping<AccountId, i32, ManualKey<0x23>>,
         counter: i32,
+        delegate_to: Lazy<Hash>,
     }
 
     impl Delegator {
-        /// Creates a new delegator smart contract initialized with the given value.
+        /// Creates a new delegator smart contract with an initial value, and the hash of
+        /// the contract code to delegate to.
+        ///
+        /// Additionally, this code hash will be locked to prevent its deletion, since
+        /// this contract depends on it.
         #[ink(constructor)]
-        pub fn new(init_value: i32) -> Self {
+        pub fn new(init_value: i32, hash: Hash) -> Self {
             let v = Mapping::new();
+
+            // Initialize the hash of the contract to delegate to.
+            // Adds a delegate dependency lock, ensuring that the delegated to code cannot
+            // be removed.
+            let mut delegate_to = Lazy::new();
+            delegate_to.set(&hash);
+            Self::env().lock_delegate_dependency(&hash);
+
             Self {
                 addresses: v,
                 counter: init_value,
+                delegate_to,
             }
         }
 
-        /// Creates a new contract with default values.
-        #[ink(constructor)]
-        pub fn new_default() -> Self {
-            Self::new(Default::default())
+        /// Update the hash of the contract to delegate to.
+        /// - Unlocks the old delegate dependency, releasing the deposit and allowing old
+        ///   delegated to code to be removed.
+        /// - Adds a new delegate dependency lock, ensuring that the new delegated to code
+        ///   cannot be removed.
+        #[ink(message)]
+        pub fn update_delegate_to(&mut self, hash: Hash) {
+            if let Some(old_hash) = self.delegate_to.get() {
+                self.env().unlock_delegate_dependency(&old_hash)
+            }
+            self.env().lock_delegate_dependency(&hash);
+            self.delegate_to.set(&hash);
         }
 
         /// Increment the current value using delegate call.
         #[ink(message)]
-        pub fn inc_delegate(&mut self, hash: Hash) {
+        pub fn inc_delegate(&mut self) {
             let selector = ink::selector_bytes!("inc");
             let _ = build_call::<DefaultEnvironment>()
-                .delegate(hash)
+                .delegate(self.delegate_to())
                 // We specify `CallFlags::TAIL_CALL` to use the delegatee last memory frame
                 // as the end of the execution cycle.
                 // So any mutations to `Packed` types, made by delegatee,
@@ -66,10 +88,10 @@ pub mod delegator {
         /// Note that we don't need `CallFlags::TAIL_CALL` flag
         /// because `Mapping` updates the storage instantly on-demand.
         #[ink(message)]
-        pub fn add_entry_delegate(&mut self, hash: Hash) {
+        pub fn add_entry_delegate(&mut self) {
             let selector = ink::selector_bytes!("append_address_value");
             let _ = build_call::<DefaultEnvironment>()
-                .delegate(hash)
+                .delegate(self.delegate_to())
                 .exec_input(ExecutionInput::new(Selector::new(selector)))
                 .returns::<()>()
                 .try_invoke();
@@ -86,6 +108,12 @@ pub mod delegator {
         pub fn get_value(&self, address: AccountId) -> (AccountId, Option<i32>) {
             (self.env().caller(), self.addresses.get(address))
         }
+
+        fn delegate_to(&self) -> Hash {
+            self.delegate_to
+                .get()
+                .expect("delegate_to always has a value")
+        }
     }
 
     #[cfg(all(test, feature = "e2e-tests"))]
@@ -107,14 +135,6 @@ pub mod delegator {
                 .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000)
                 .await;
 
-            let mut constructor = DelegatorRef::new_default();
-            let contract = client
-                .instantiate("delegator", &origin, &mut constructor)
-                .submit()
-                .await
-                .expect("instantiate failed");
-            let mut call_builder = contract.call_builder::<Delegator>();
-
             let code_hash = client
                 .upload("delegatee", &origin)
                 .submit()
@@ -122,8 +142,16 @@ pub mod delegator {
                 .expect("upload `delegatee` failed")
                 .code_hash;
 
+            let mut constructor = DelegatorRef::new(0, code_hash);
+            let contract = client
+                .instantiate("delegator", &origin, &mut constructor)
+                .submit()
+                .await
+                .expect("instantiate failed");
+            let mut call_builder = contract.call_builder::<Delegator>();
+
             // when
-            let call_delegate = call_builder.inc_delegate(code_hash);
+            let call_delegate = call_builder.inc_delegate();
 
             let result = client.call(&origin, &call_delegate).submit().await;
             assert!(result.is_ok(), "delegate call failed.");
@@ -159,8 +187,15 @@ pub mod delegator {
                 .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000)
                 .await;
 
+            let code_hash = client
+                .upload("delegatee", &origin)
+                .submit()
+                .await
+                .expect("upload `delegatee` failed")
+                .code_hash;
+
             // given
-            let mut constructor = DelegatorRef::new(10);
+            let mut constructor = DelegatorRef::new(10, code_hash);
             let contract = client
                 .instantiate("delegator", &origin, &mut constructor)
                 .submit()
@@ -168,15 +203,8 @@ pub mod delegator {
                 .expect("instantiate failed");
             let mut call_builder = contract.call_builder::<Delegator>();
 
-            let code_hash = client
-                .upload("delegatee", &origin)
-                .submit()
-                .await
-                .expect("upload `delegatee` failed")
-                .code_hash;
-
             // when
-            let call_delegate = call_builder.add_entry_delegate(code_hash);
+            let call_delegate = call_builder.add_entry_delegate();
             let result = client.call(&origin, &call_delegate).submit().await;
             assert!(result.is_ok(), "delegate call failed.");
 
@@ -204,5 +232,57 @@ pub mod delegator {
 
             Ok(())
         }
+
+        #[ink_e2e::test]
+        async fn update_delegate<Client: E2EBackend>(
+            mut client: Client,
+        ) -> E2EResult<()> {
+            // given
+            let origin = client
+                .create_and_fund_account(&ink_e2e::alice(), 10_000_000_000_000)
+                .await;
+
+            let code_hash = client
+                .upload("delegatee", &origin)
+                .submit()
+                .await
+                .expect("upload `delegatee` failed")
+                .code_hash;
+
+            let code_hash2 = client
+                .upload("delegatee2", &origin)
+                .submit()
+                .await
+                .expect("upload `delegatee2` failed")
+                .code_hash;
+
+            let mut constructor = DelegatorRef::new(10, code_hash);
+            let contract = client
+                .instantiate("delegator", &origin, &mut constructor)
+                .submit()
+                .await
+                .expect("instantiate failed");
+            let mut call_builder = contract.call_builder::<Delegator>();
+
+            // when
+            let call_delegate = call_builder.update_delegate_to(code_hash2);
+            let result = client.call(&origin, &call_delegate).submit().await;
+            assert!(result.is_ok(), "update_delegate_to failed.");
+
+            // then
+
+            // remove the original delegatee code.
+            // should succeed because the delegate dependency has been removed.
+            let original_code_removed =
+                client.remove_code(&origin, code_hash).submit().await;
+            assert!(original_code_removed.is_ok());
+
+            // attempt to remove the new delegatee code.
+            // should fail because of the delegate dependency.
+            let new_code_removed = client.remove_code(&origin, code_hash2).submit().await;
+            assert!(new_code_removed.is_err());
+
+            Ok(())
+        }
     }
 }