From afe6d2679a8fa397757a3e0188fc211e7387ee0a Mon Sep 17 00:00:00 2001
From: Todd Nowacki <tmn@mystenlabs.com>
Date: Mon, 8 Jul 2024 14:56:21 -0700
Subject: [PATCH] [denylist v2] Added migration function. Additional tests. 
 Fix test_scenario. (#18523)

## Description

- Added functions to migrate v1 to v2
- Added more tests
- Fixed disable global pause
- Fixed test scenario caching

## Test plan

- new tests

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol:
- [ ] Nodes (Validators and Full nodes):
- [ ] Indexer:
- [ ] JSON-RPC:
- [ ] GraphQL:
- [ ] CLI:
- [ ] Rust SDK:
---
 .../deny_list_v1/coin_deny_and_undeny.exp     |  26 +-
 .../deny_list_v1/coin_deny_and_undeny.move    |   1 +
 .../coin_deny_multiple_per_module.exp         |  30 +-
 .../coin_deny_multiple_per_module.move        |   2 +
 .../tests/deny_list_v1/coin_deny_tto.exp      |  24 +-
 .../tests/deny_list_v1/coin_deny_tto.move     |   1 +
 .../tests/deny_list_v2/coin_global_pause.exp  |   2 +-
 .../sui-framework/docs/sui-framework/coin.md  |  37 ++
 .../docs/sui-framework/deny_list.md           |  72 ++-
 .../packages/sui-framework/sources/coin.move  |  20 +
 .../sui-framework/sources/deny_list.move      |  45 +-
 .../sui-framework/tests/coin_tests.move       | 431 ++++++++++++++----
 .../sui-framework/tests/config_tests.move     |  37 ++
 crates/sui-framework/published_api.txt        |  14 +-
 ..._populated_genesis_snapshot_matches-2.snap |  29 +-
 examples/move/coin/sources/regcoin.move       |  34 +-
 .../src/object_runtime/mod.rs                 |   6 +-
 .../src/object_runtime/object_store.rs        |  64 +--
 .../sui-move-natives/src/test_scenario.rs     |  19 +-
 19 files changed, 677 insertions(+), 217 deletions(-)

diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.exp b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.exp
index 40ef07305f8df..91c552bd035fd 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.exp
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.exp
@@ -3,15 +3,15 @@ processed 14 tasks
 init:
 A: object(0,0), B: object(0,1)
 
-task 1 'publish'. lines 12-34:
+task 1 'publish'. lines 12-35:
 created: object(1,0), object(1,1), object(1,2), object(1,3), object(1,4), object(1,5)
 mutated: object(0,0)
 gas summary: computation_cost: 1000000, storage_cost: 18316000,  storage_rebate: 0, non_refundable_storage_fee: 0
 
-task 2 'view-object'. lines 36-36:
+task 2 'view-object'. lines 37-37:
 1,0::regulated_coin
 
-task 3 'view-object'. lines 38-38:
+task 3 'view-object'. lines 39-39:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::Coin<test::regulated_coin::REGULATED_COIN> {
@@ -25,7 +25,7 @@ Contents: sui::coin::Coin<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 4 'view-object'. lines 40-40:
+task 4 'view-object'. lines 41-41:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::CoinMetadata<test::regulated_coin::REGULATED_COIN> {
@@ -88,7 +88,7 @@ Contents: sui::coin::CoinMetadata<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 5 'view-object'. lines 42-42:
+task 5 'view-object'. lines 43-43:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::DenyCap<test::regulated_coin::REGULATED_COIN> {
@@ -99,7 +99,7 @@ Contents: sui::coin::DenyCap<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 6 'view-object'. lines 44-44:
+task 6 'view-object'. lines 45-45:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::RegulatedCoinMetadata<test::regulated_coin::REGULATED_COIN> {
@@ -116,7 +116,7 @@ Contents: sui::coin::RegulatedCoinMetadata<test::regulated_coin::REGULATED_COIN>
     },
 }
 
-task 7 'view-object'. lines 46-48:
+task 7 'view-object'. lines 47-49:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::TreasuryCap<test::regulated_coin::REGULATED_COIN> {
@@ -130,27 +130,27 @@ Contents: sui::coin::TreasuryCap<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 8 'run'. lines 49-51:
+task 8 'run'. lines 50-52:
 created: object(8,0)
 mutated: object(0,0), object(1,1)
 gas summary: computation_cost: 1000000, storage_cost: 3936800,  storage_rebate: 2437776, non_refundable_storage_fee: 24624
 
-task 9 'run'. lines 52-54:
+task 9 'run'. lines 53-55:
 created: object(9,0), object(9,1)
 mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,3)
 gas summary: computation_cost: 1000000, storage_cost: 11415200,  storage_rebate: 2723688, non_refundable_storage_fee: 27512
 
-task 10 'transfer-object'. lines 55-57:
+task 10 'transfer-object'. lines 56-58:
 Error: Error checking transaction input objects: AddressDeniedForCoin { address: @B, coin_type: "object(1,0)::regulated_coin::REGULATED_COIN" }
 
-task 11 'run'. lines 58-60:
+task 11 'run'. lines 59-61:
 Error: Error checking transaction input objects: AddressDeniedForCoin { address: @B, coin_type: "object(1,0)::regulated_coin::REGULATED_COIN" }
 
-task 12 'run'. lines 61-63:
+task 12 'run'. lines 62-64:
 mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,3), object(9,1)
 deleted: object(9,0)
 gas summary: computation_cost: 1000000, storage_cost: 9522800,  storage_rebate: 11301048, non_refundable_storage_fee: 114152
 
-task 13 'transfer-object'. lines 64-64:
+task 13 'transfer-object'. lines 65-65:
 mutated: object(0,1), object(8,0)
 gas summary: computation_cost: 1000000, storage_cost: 2462400,  storage_rebate: 1459656, non_refundable_storage_fee: 14744
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.move b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.move
index 0fb3820b6d427..e3715c35dd842 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.move
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_and_undeny.move
@@ -10,6 +10,7 @@
 //# init --accounts A B --addresses test=0x0
 
 //# publish --sender A
+#[allow(deprecated_usage)]
 module test::regulated_coin {
     use sui::coin;
 
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.exp b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.exp
index a3643e00fc989..47d9728eb5466 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.exp
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.exp
@@ -3,15 +3,15 @@ processed 16 tasks
 init:
 A: object(0,0)
 
-task 1 'publish'. lines 9-54:
+task 1 'publish'. lines 9-56:
 created: object(1,0), object(1,1), object(1,2), object(1,3), object(1,4), object(1,5), object(1,6), object(1,7), object(1,8), object(1,9), object(1,10)
 mutated: object(0,0)
 gas summary: computation_cost: 1000000, storage_cost: 33082800,  storage_rebate: 0, non_refundable_storage_fee: 0
 
-task 2 'view-object'. lines 56-56:
+task 2 'view-object'. lines 58-58:
 1,0::{first_coin, second_coin}
 
-task 3 'view-object'. lines 58-58:
+task 3 'view-object'. lines 60-60:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::Coin<test::first_coin::FIRST_COIN> {
@@ -25,7 +25,7 @@ Contents: sui::coin::Coin<test::first_coin::FIRST_COIN> {
     },
 }
 
-task 4 'view-object'. lines 60-60:
+task 4 'view-object'. lines 62-62:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::Coin<test::second_coin::SECOND_COIN> {
@@ -39,7 +39,7 @@ Contents: sui::coin::Coin<test::second_coin::SECOND_COIN> {
     },
 }
 
-task 5 'view-object'. lines 62-62:
+task 5 'view-object'. lines 64-64:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::CoinMetadata<test::first_coin::FIRST_COIN> {
@@ -102,7 +102,7 @@ Contents: sui::coin::CoinMetadata<test::first_coin::FIRST_COIN> {
     },
 }
 
-task 6 'view-object'. lines 64-64:
+task 6 'view-object'. lines 66-66:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::CoinMetadata<test::second_coin::SECOND_COIN> {
@@ -165,7 +165,7 @@ Contents: sui::coin::CoinMetadata<test::second_coin::SECOND_COIN> {
     },
 }
 
-task 7 'view-object'. lines 66-66:
+task 7 'view-object'. lines 68-68:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::DenyCap<test::first_coin::FIRST_COIN> {
@@ -176,7 +176,7 @@ Contents: sui::coin::DenyCap<test::first_coin::FIRST_COIN> {
     },
 }
 
-task 8 'view-object'. lines 68-68:
+task 8 'view-object'. lines 70-70:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::DenyCap<test::second_coin::SECOND_COIN> {
@@ -187,7 +187,7 @@ Contents: sui::coin::DenyCap<test::second_coin::SECOND_COIN> {
     },
 }
 
-task 9 'view-object'. lines 70-70:
+task 9 'view-object'. lines 72-72:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::RegulatedCoinMetadata<test::first_coin::FIRST_COIN> {
@@ -204,7 +204,7 @@ Contents: sui::coin::RegulatedCoinMetadata<test::first_coin::FIRST_COIN> {
     },
 }
 
-task 10 'view-object'. lines 72-72:
+task 10 'view-object'. lines 74-74:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::RegulatedCoinMetadata<test::second_coin::SECOND_COIN> {
@@ -221,7 +221,7 @@ Contents: sui::coin::RegulatedCoinMetadata<test::second_coin::SECOND_COIN> {
     },
 }
 
-task 11 'view-object'. lines 74-74:
+task 11 'view-object'. lines 76-76:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::TreasuryCap<test::first_coin::FIRST_COIN> {
@@ -235,7 +235,7 @@ Contents: sui::coin::TreasuryCap<test::first_coin::FIRST_COIN> {
     },
 }
 
-task 12 'view-object'. lines 76-78:
+task 12 'view-object'. lines 78-80:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::TreasuryCap<test::second_coin::SECOND_COIN> {
@@ -249,14 +249,14 @@ Contents: sui::coin::TreasuryCap<test::second_coin::SECOND_COIN> {
     },
 }
 
-task 13 'run'. lines 79-81:
+task 13 'run'. lines 81-83:
 created: object(13,0), object(13,1)
 mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,5)
 gas summary: computation_cost: 1000000, storage_cost: 11293600,  storage_rebate: 2663496, non_refundable_storage_fee: 26904
 
-task 14 'transfer-object'. lines 82-84:
+task 14 'transfer-object'. lines 84-86:
 Error: Error checking transaction input objects: AddressDeniedForCoin { address: @A, coin_type: "object(1,0)::first_coin::FIRST_COIN" }
 
-task 15 'transfer-object'. lines 85-85:
+task 15 'transfer-object'. lines 87-87:
 mutated: object(0,0), object(1,2)
 gas summary: computation_cost: 1000000, storage_cost: 2416800,  storage_rebate: 2392632, non_refundable_storage_fee: 24168
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.move b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.move
index 5d305f035db0d..79f83ba766875 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.move
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_multiple_per_module.move
@@ -7,6 +7,7 @@
 //# init --accounts A --addresses test=0x0
 
 //# publish --sender A
+#[allow(deprecated_usage)]
 module test::first_coin {
     use sui::coin;
 
@@ -30,6 +31,7 @@ module test::first_coin {
     }
 }
 
+#[allow(deprecated_usage)]
 module test::second_coin {
     use sui::coin;
 
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.exp b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.exp
index 2e68efa1dcbad..ba8ba7fff47be 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.exp
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.exp
@@ -3,12 +3,12 @@ processed 13 tasks
 init:
 A: object(0,0)
 
-task 1 'publish'. lines 8-46:
+task 1 'publish'. lines 8-47:
 created: object(1,0), object(1,1), object(1,2), object(1,3), object(1,4), object(1,5), object(1,6)
 mutated: object(0,0)
 gas summary: computation_cost: 1000000, storage_cost: 21766400,  storage_rebate: 0, non_refundable_storage_fee: 0
 
-task 2 'view-object'. lines 48-48:
+task 2 'view-object'. lines 49-49:
 Owner: Account Address ( A )
 Version: 2
 Contents: test::regulated_coin::Wallet {
@@ -19,10 +19,10 @@ Contents: test::regulated_coin::Wallet {
     },
 }
 
-task 3 'view-object'. lines 50-50:
+task 3 'view-object'. lines 51-51:
 1,1::regulated_coin
 
-task 4 'view-object'. lines 52-52:
+task 4 'view-object'. lines 53-53:
 Owner: Account Address ( fake(1,0) )
 Version: 2
 Contents: sui::coin::Coin<test::regulated_coin::REGULATED_COIN> {
@@ -36,7 +36,7 @@ Contents: sui::coin::Coin<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 5 'view-object'. lines 54-54:
+task 5 'view-object'. lines 55-55:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::CoinMetadata<test::regulated_coin::REGULATED_COIN> {
@@ -99,7 +99,7 @@ Contents: sui::coin::CoinMetadata<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 6 'view-object'. lines 56-56:
+task 6 'view-object'. lines 57-57:
 Owner: Account Address ( A )
 Version: 2
 Contents: sui::coin::DenyCap<test::regulated_coin::REGULATED_COIN> {
@@ -110,7 +110,7 @@ Contents: sui::coin::DenyCap<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 7 'view-object'. lines 58-58:
+task 7 'view-object'. lines 59-59:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::RegulatedCoinMetadata<test::regulated_coin::REGULATED_COIN> {
@@ -127,7 +127,7 @@ Contents: sui::coin::RegulatedCoinMetadata<test::regulated_coin::REGULATED_COIN>
     },
 }
 
-task 8 'view-object'. lines 60-62:
+task 8 'view-object'. lines 61-63:
 Owner: Immutable
 Version: 2
 Contents: sui::coin::TreasuryCap<test::regulated_coin::REGULATED_COIN> {
@@ -141,19 +141,19 @@ Contents: sui::coin::TreasuryCap<test::regulated_coin::REGULATED_COIN> {
     },
 }
 
-task 9 'run'. lines 63-65:
+task 9 'run'. lines 64-66:
 created: object(9,0), object(9,1)
 mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,4)
 gas summary: computation_cost: 1000000, storage_cost: 11415200,  storage_rebate: 2723688, non_refundable_storage_fee: 27512
 
-task 10 'run'. lines 66-68:
+task 10 'run'. lines 67-69:
 Error: Error checking transaction input objects: AddressDeniedForCoin { address: @A, coin_type: "object(1,1)::regulated_coin::REGULATED_COIN" }
 
-task 11 'run'. lines 69-71:
+task 11 'run'. lines 70-72:
 mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,4), object(9,1)
 deleted: object(9,0)
 gas summary: computation_cost: 1000000, storage_cost: 9522800,  storage_rebate: 11301048, non_refundable_storage_fee: 114152
 
-task 12 'run'. lines 72-72:
+task 12 'run'. lines 73-73:
 mutated: object(0,0), object(1,0), object(1,2)
 gas summary: computation_cost: 1000000, storage_cost: 3807600,  storage_rebate: 3769524, non_refundable_storage_fee: 38076
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.move b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.move
index a9eb094f80d9a..19a0b477c3da4 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.move
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v1/coin_deny_tto.move
@@ -6,6 +6,7 @@
 //# init --accounts A --addresses test=0x0
 
 //# publish --sender A
+#[allow(deprecated_usage)]
 module test::regulated_coin {
     use sui::coin;
     use sui::coin::Coin;
diff --git a/crates/sui-adapter-transactional-tests/tests/deny_list_v2/coin_global_pause.exp b/crates/sui-adapter-transactional-tests/tests/deny_list_v2/coin_global_pause.exp
index f48eedc109581..3108d773a2d10 100644
--- a/crates/sui-adapter-transactional-tests/tests/deny_list_v2/coin_global_pause.exp
+++ b/crates/sui-adapter-transactional-tests/tests/deny_list_v2/coin_global_pause.exp
@@ -66,7 +66,7 @@ gas summary: computation_cost: 1000000, storage_cost: 988000,  storage_rebate: 9
 
 task 15 'run'. lines 113-115:
 mutated: 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(1,3), object(5,1)
-gas summary: computation_cost: 1000000, storage_cost: 6665200,  storage_rebate: 6591024, non_refundable_storage_fee: 66576
+gas summary: computation_cost: 1000000, storage_cost: 6657600,  storage_rebate: 6591024, non_refundable_storage_fee: 66576
 
 task 16 'run'. lines 116-118:
 mutated: object(0,0)
diff --git a/crates/sui-framework/docs/sui-framework/coin.md b/crates/sui-framework/docs/sui-framework/coin.md
index 33aed44e00283..94ff330171d86 100644
--- a/crates/sui-framework/docs/sui-framework/coin.md
+++ b/crates/sui-framework/docs/sui-framework/coin.md
@@ -33,6 +33,7 @@ tokens and coins. <code><a href="../sui-framework/coin.md#0x2_coin_Coin">Coin</a
 -  [Function `destroy_zero`](#0x2_coin_destroy_zero)
 -  [Function `create_currency`](#0x2_coin_create_currency)
 -  [Function `create_regulated_currency_v2`](#0x2_coin_create_regulated_currency_v2)
+-  [Function `migrate_regulated_currency_to_v2`](#0x2_coin_migrate_regulated_currency_to_v2)
 -  [Function `mint`](#0x2_coin_mint)
 -  [Function `mint_balance`](#0x2_coin_mint_balance)
 -  [Function `burn`](#0x2_coin_burn)
@@ -926,6 +927,42 @@ type, ensuring that there's only one <code><a href="../sui-framework/coin.md#0x2
 
 
 
+</details>
+
+<a name="0x2_coin_migrate_regulated_currency_to_v2"></a>
+
+## Function `migrate_regulated_currency_to_v2`
+
+
+
+<pre><code><b>public</b> <b>fun</b> <a href="../sui-framework/coin.md#0x2_coin_migrate_regulated_currency_to_v2">migrate_regulated_currency_to_v2</a>&lt;T&gt;(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, cap: <a href="../sui-framework/coin.md#0x2_coin_DenyCap">coin::DenyCap</a>&lt;T&gt;, allow_global_pause: bool, ctx: &<b>mut</b> <a href="../sui-framework/tx_context.md#0x2_tx_context_TxContext">tx_context::TxContext</a>): <a href="../sui-framework/coin.md#0x2_coin_DenyCapV2">coin::DenyCapV2</a>&lt;T&gt;
+</code></pre>
+
+
+
+<details>
+<summary>Implementation</summary>
+
+
+<pre><code><b>public</b> <b>fun</b> <a href="../sui-framework/coin.md#0x2_coin_migrate_regulated_currency_to_v2">migrate_regulated_currency_to_v2</a>&lt;T&gt;(
+    <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> DenyList,
+    cap: <a href="../sui-framework/coin.md#0x2_coin_DenyCap">DenyCap</a>&lt;T&gt;,
+    allow_global_pause: bool,
+    ctx: &<b>mut</b> TxContext,
+): <a href="../sui-framework/coin.md#0x2_coin_DenyCapV2">DenyCapV2</a>&lt;T&gt; {
+    <b>let</b> <a href="../sui-framework/coin.md#0x2_coin_DenyCap">DenyCap</a> { id } = cap;
+    <a href="../sui-framework/object.md#0x2_object_delete">object::delete</a>(id);
+    <b>let</b> ty = <a href="../move-stdlib/type_name.md#0x1_type_name_get_with_original_ids">type_name::get_with_original_ids</a>&lt;T&gt;().into_string().into_bytes();
+    <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>.migrate_v1_to_v2(<a href="../sui-framework/coin.md#0x2_coin_DENY_LIST_COIN_INDEX">DENY_LIST_COIN_INDEX</a>, ty, ctx);
+    <a href="../sui-framework/coin.md#0x2_coin_DenyCapV2">DenyCapV2</a> {
+        id: <a href="../sui-framework/object.md#0x2_object_new">object::new</a>(ctx),
+        allow_global_pause,
+    }
+}
+</code></pre>
+
+
+
 </details>
 
 <a name="0x2_coin_mint"></a>
diff --git a/crates/sui-framework/docs/sui-framework/deny_list.md b/crates/sui-framework/docs/sui-framework/deny_list.md
index b6f8d9ac5de91..464f885ff536f 100644
--- a/crates/sui-framework/docs/sui-framework/deny_list.md
+++ b/crates/sui-framework/docs/sui-framework/deny_list.md
@@ -23,6 +23,7 @@ list.
 -  [Function `v2_disable_global_pause`](#0x2_deny_list_v2_disable_global_pause)
 -  [Function `v2_is_global_pause_enabled_current_epoch`](#0x2_deny_list_v2_is_global_pause_enabled_current_epoch)
 -  [Function `v2_is_global_pause_enabled_next_epoch`](#0x2_deny_list_v2_is_global_pause_enabled_next_epoch)
+-  [Function `migrate_v1_to_v2`](#0x2_deny_list_migrate_v1_to_v2)
 -  [Function `add_per_type_config`](#0x2_deny_list_add_per_type_config)
 -  [Function `borrow_per_type_config_mut`](#0x2_deny_list_borrow_per_type_config_mut)
 -  [Function `borrow_per_type_config`](#0x2_deny_list_borrow_per_type_config)
@@ -532,13 +533,11 @@ meaningless to add them to the deny list.
 ) {
     <b>let</b> per_type_config = <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>.per_type_config_entry!(per_type_index, per_type_key, ctx);
     <b>let</b> setting_name = <a href="../sui-framework/deny_list.md#0x2_deny_list_GlobalPauseKey">GlobalPauseKey</a>();
-    <b>let</b> next_epoch_entry = per_type_config.entry!&lt;_, <a href="../sui-framework/deny_list.md#0x2_deny_list_GlobalPauseKey">GlobalPauseKey</a>, bool&gt;(
+    per_type_config.remove_for_next_epoch&lt;_, <a href="../sui-framework/deny_list.md#0x2_deny_list_GlobalPauseKey">GlobalPauseKey</a>, bool&gt;(
         &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">ConfigWriteCap</a>(),
         setting_name,
-        |_deny_list, _cap, _ctx| <b>false</b>,
         ctx,
     );
-    *next_epoch_entry = <b>false</b>;
 }
 </code></pre>
 
@@ -607,6 +606,57 @@ meaningless to add them to the deny list.
 
 
 
+</details>
+
+<a name="0x2_deny_list_migrate_v1_to_v2"></a>
+
+## Function `migrate_v1_to_v2`
+
+
+
+<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_migrate_v1_to_v2">migrate_v1_to_v2</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;, ctx: &<b>mut</b> <a href="../sui-framework/tx_context.md#0x2_tx_context_TxContext">tx_context::TxContext</a>)
+</code></pre>
+
+
+
+<details>
+<summary>Implementation</summary>
+
+
+<pre><code><b>public</b>(package) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_migrate_v1_to_v2">migrate_v1_to_v2</a>(
+    <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">DenyList</a>,
+    per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>,
+    per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;,
+    ctx: &<b>mut</b> TxContext,
+) {
+    <b>let</b> bag_entry: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_PerTypeList">PerTypeList</a> = &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>.lists[per_type_index];
+    <b>let</b> elements =
+        <b>if</b> (!bag_entry.denied_addresses.contains(per_type_key)) <a href="../move-stdlib/vector.md#0x1_vector">vector</a>[]
+        <b>else</b> bag_entry.denied_addresses.remove(per_type_key).into_keys();
+    elements.do_ref!(|addr| {
+        <b>let</b> addr = *addr;
+        <b>let</b> denied_count = &<b>mut</b> bag_entry.denied_count[addr];
+        *denied_count = *denied_count - 1;
+        <b>if</b> (*denied_count == 0) {
+            bag_entry.denied_count.remove(addr);
+        }
+    });
+    <b>let</b> per_type_config = <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>.per_type_config_entry!(per_type_index, per_type_key, ctx);
+    elements.do!(|addr|  {
+        <b>let</b> setting_name = <a href="../sui-framework/deny_list.md#0x2_deny_list_AddressKey">AddressKey</a>(addr);
+        <b>let</b> next_epoch_entry = per_type_config.entry!&lt;_,<a href="../sui-framework/deny_list.md#0x2_deny_list_AddressKey">AddressKey</a>, bool&gt;(
+            &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">ConfigWriteCap</a>(),
+            setting_name,
+            |_deny_list, _cap, _ctx| <b>true</b>,
+            ctx,
+        );
+        *next_epoch_entry = <b>true</b>;
+    });
+}
+</code></pre>
+
+
+
 </details>
 
 <a name="0x2_deny_list_add_per_type_config"></a>
@@ -615,7 +665,7 @@ meaningless to add them to the deny list.
 
 
 
-<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_add_per_type_config">add_per_type_config</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;, ctx: &<b>mut</b> <a href="../sui-framework/tx_context.md#0x2_tx_context_TxContext">tx_context::TxContext</a>)
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_add_per_type_config">add_per_type_config</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;, ctx: &<b>mut</b> <a href="../sui-framework/tx_context.md#0x2_tx_context_TxContext">tx_context::TxContext</a>)
 </code></pre>
 
 
@@ -624,7 +674,7 @@ meaningless to add them to the deny list.
 <summary>Implementation</summary>
 
 
-<pre><code><b>public</b>(package) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_add_per_type_config">add_per_type_config</a>(
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_add_per_type_config">add_per_type_config</a>(
     <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">DenyList</a>,
     per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>,
     per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;,
@@ -648,7 +698,7 @@ meaningless to add them to the deny list.
 
 
 
-<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config_mut">borrow_per_type_config_mut</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): &<b>mut</b> <a href="../sui-framework/config.md#0x2_config_Config">config::Config</a>&lt;<a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">deny_list::ConfigWriteCap</a>&gt;
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config_mut">borrow_per_type_config_mut</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): &<b>mut</b> <a href="../sui-framework/config.md#0x2_config_Config">config::Config</a>&lt;<a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">deny_list::ConfigWriteCap</a>&gt;
 </code></pre>
 
 
@@ -657,7 +707,7 @@ meaningless to add them to the deny list.
 <summary>Implementation</summary>
 
 
-<pre><code><b>public</b>(package) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config_mut">borrow_per_type_config_mut</a>(
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config_mut">borrow_per_type_config_mut</a>(
     <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<b>mut</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">DenyList</a>,
     per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>,
     per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;,
@@ -677,7 +727,7 @@ meaningless to add them to the deny list.
 
 
 
-<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config">borrow_per_type_config</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): &<a href="../sui-framework/config.md#0x2_config_Config">config::Config</a>&lt;<a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">deny_list::ConfigWriteCap</a>&gt;
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config">borrow_per_type_config</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): &<a href="../sui-framework/config.md#0x2_config_Config">config::Config</a>&lt;<a href="../sui-framework/deny_list.md#0x2_deny_list_ConfigWriteCap">deny_list::ConfigWriteCap</a>&gt;
 </code></pre>
 
 
@@ -686,7 +736,7 @@ meaningless to add them to the deny list.
 <summary>Implementation</summary>
 
 
-<pre><code><b>public</b>(package) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config">borrow_per_type_config</a>(
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_borrow_per_type_config">borrow_per_type_config</a>(
     <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">DenyList</a>,
     per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>,
     per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;,
@@ -706,7 +756,7 @@ meaningless to add them to the deny list.
 
 
 
-<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_per_type_exists">per_type_exists</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): bool
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_per_type_exists">per_type_exists</a>(<a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">deny_list::DenyList</a>, per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>, per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;): bool
 </code></pre>
 
 
@@ -715,7 +765,7 @@ meaningless to add them to the deny list.
 <summary>Implementation</summary>
 
 
-<pre><code><b>public</b>(package) <b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_per_type_exists">per_type_exists</a>(
+<pre><code><b>fun</b> <a href="../sui-framework/deny_list.md#0x2_deny_list_per_type_exists">per_type_exists</a>(
     <a href="../sui-framework/deny_list.md#0x2_deny_list">deny_list</a>: &<a href="../sui-framework/deny_list.md#0x2_deny_list_DenyList">DenyList</a>,
     per_type_index: <a href="../move-stdlib/u64.md#0x1_u64">u64</a>,
     per_type_key: <a href="../move-stdlib/vector.md#0x1_vector">vector</a>&lt;u8&gt;,
diff --git a/crates/sui-framework/packages/sui-framework/sources/coin.move b/crates/sui-framework/packages/sui-framework/sources/coin.move
index b4c93fe5d439d..8a3756e4690b1 100644
--- a/crates/sui-framework/packages/sui-framework/sources/coin.move
+++ b/crates/sui-framework/packages/sui-framework/sources/coin.move
@@ -267,6 +267,22 @@ module sui::coin {
         (treasury_cap, deny_cap, metadata)
     }
 
+    public fun migrate_regulated_currency_to_v2<T>(
+        deny_list: &mut DenyList,
+        cap: DenyCap<T>,
+        allow_global_pause: bool,
+        ctx: &mut TxContext,
+    ): DenyCapV2<T> {
+        let DenyCap { id } = cap;
+        object::delete(id);
+        let ty = type_name::get_with_original_ids<T>().into_string().into_bytes();
+        deny_list.migrate_v1_to_v2(DENY_LIST_COIN_INDEX, ty, ctx);
+        DenyCapV2 {
+            id: object::new(ctx),
+            allow_global_pause,
+        }
+    }
+
     /// Create a coin worth `value` and increase the total supply
     /// in `cap` accordingly.
     public fun mint<T>(
@@ -479,6 +495,7 @@ module sui::coin {
     /// This creates a new currency, via `create_currency`, but with an extra capability that
     /// allows for specific addresses to have their coins frozen. Those addresses cannot interact
     /// with the coin as input objects.
+    #[deprecated(note = b"For new coins, use `create_regulated_currency_v2`. To migrate existing regulated currencies, migrate with `migrate_regulated_currency_to_v2`")]
     public fun create_regulated_currency<T: drop>(
         witness: T,
         decimals: u8,
@@ -514,6 +531,7 @@ module sui::coin {
 
     /// Adds the given address to the deny list, preventing it
     /// from interacting with the specified coin type as an input to a transaction.
+    #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_add`")]
     public fun deny_list_add<T>(
        deny_list: &mut DenyList,
        _deny_cap: &mut DenyCap<T>,
@@ -531,6 +549,7 @@ module sui::coin {
 
     /// Removes an address from the deny list.
     /// Aborts with `ENotFrozen` if the address is not already in the list.
+    #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_remove`")]
     public fun deny_list_remove<T>(
        deny_list: &mut DenyList,
        _deny_cap: &mut DenyCap<T>,
@@ -548,6 +567,7 @@ module sui::coin {
 
     /// Returns true iff the given address is denied for the given coin type. It will
     /// return false if given a non-coin type.
+    #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_contains_next_epoch` or `deny_list_v2_contains_current_epoch`")]
     public fun deny_list_contains<T>(
        deny_list: &DenyList,
        addr: address,
diff --git a/crates/sui-framework/packages/sui-framework/sources/deny_list.move b/crates/sui-framework/packages/sui-framework/sources/deny_list.move
index 4c74cae67ceb9..12f94b566f58c 100644
--- a/crates/sui-framework/packages/sui-framework/sources/deny_list.move
+++ b/crates/sui-framework/packages/sui-framework/sources/deny_list.move
@@ -162,13 +162,11 @@ module sui::deny_list {
     ) {
         let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
         let setting_name = GlobalPauseKey();
-        let next_epoch_entry = per_type_config.entry!<_, GlobalPauseKey, bool>(
+        per_type_config.remove_for_next_epoch<_, GlobalPauseKey, bool>(
             &mut ConfigWriteCap(),
             setting_name,
-            |_deny_list, _cap, _ctx| false,
             ctx,
         );
-        *next_epoch_entry = false;
     }
 
     public(package) fun v2_is_global_pause_enabled_current_epoch(
@@ -200,7 +198,38 @@ module sui::deny_list {
     //    // TODO can read from the config directly once the ID is set
     // }
 
-    public(package) fun add_per_type_config(
+    public(package) fun migrate_v1_to_v2(
+        deny_list: &mut DenyList,
+        per_type_index: u64,
+        per_type_key: vector<u8>,
+        ctx: &mut TxContext,
+    ) {
+        let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index];
+        let elements =
+            if (!bag_entry.denied_addresses.contains(per_type_key)) vector[]
+            else bag_entry.denied_addresses.remove(per_type_key).into_keys();
+        elements.do_ref!(|addr| {
+            let addr = *addr;
+            let denied_count = &mut bag_entry.denied_count[addr];
+            *denied_count = *denied_count - 1;
+            if (*denied_count == 0) {
+                bag_entry.denied_count.remove(addr);
+            }
+        });
+        let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
+        elements.do!(|addr|  {
+            let setting_name = AddressKey(addr);
+            let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>(
+                &mut ConfigWriteCap(),
+                setting_name,
+                |_deny_list, _cap, _ctx| true,
+                ctx,
+            );
+            *next_epoch_entry = true;
+        });
+    }
+
+    fun add_per_type_config(
         deny_list: &mut DenyList,
         per_type_index: u64,
         per_type_key: vector<u8>,
@@ -213,7 +242,7 @@ module sui::deny_list {
         sui::event::emit(PerTypeConfigCreated { key, config_id });
     }
 
-    public(package) fun borrow_per_type_config_mut(
+    fun borrow_per_type_config_mut(
         deny_list: &mut DenyList,
         per_type_index: u64,
         per_type_key: vector<u8>,
@@ -222,7 +251,7 @@ module sui::deny_list {
         ofield::internal_borrow_mut(&mut deny_list.id, key)
     }
 
-    public(package) fun borrow_per_type_config(
+    fun borrow_per_type_config(
         deny_list: &DenyList,
         per_type_index: u64,
         per_type_key: vector<u8>,
@@ -231,7 +260,7 @@ module sui::deny_list {
         ofield::internal_borrow(&deny_list.id, key)
     }
 
-    public(package) fun per_type_exists(
+    fun per_type_exists(
         deny_list: &DenyList,
         per_type_index: u64,
         per_type_key: vector<u8>,
@@ -240,7 +269,7 @@ module sui::deny_list {
         ofield::exists_(&deny_list.id, key)
     }
 
-    public(package) macro fun per_type_config_entry(
+    macro fun per_type_config_entry(
         $deny_list: &mut DenyList,
         $per_type_index: u64,
         $per_type_key: vector<u8>,
diff --git a/crates/sui-framework/packages/sui-framework/tests/coin_tests.move b/crates/sui-framework/packages/sui-framework/tests/coin_tests.move
index f636ab4dea1a6..641070d931f4a 100644
--- a/crates/sui-framework/packages/sui-framework/tests/coin_tests.move
+++ b/crates/sui-framework/packages/sui-framework/tests/coin_tests.move
@@ -1,7 +1,7 @@
 // Copyright (c) Mysten Labs, Inc.
 // SPDX-License-Identifier: Apache-2.0
 
-#[test_only]
+#[test_only, allow(deprecated_usage)]
 module sui::coin_tests {
     use sui::coin::{Self, Coin};
     use sui::pay;
@@ -13,6 +13,41 @@ module sui::coin_tests {
 
     const TEST_ADDR: address = @0xA11CE;
 
+    fun assert_status(
+        deny_list: &deny_list::DenyList,
+        addr: address,
+        contains_current_epoch: bool,
+        contains_next_epoch: bool,
+        ctx: &TxContext,
+    ) {
+        use sui::coin::{
+            deny_list_v2_contains_next_epoch as contains_next_epoch,
+            deny_list_v2_contains_current_epoch as contains_current_epoch,
+        };
+        assert!(contains_current_epoch<COIN_TESTS>(deny_list, addr, ctx) == contains_current_epoch);
+        assert!(contains_next_epoch<COIN_TESTS>(deny_list, addr) == contains_next_epoch);
+    }
+
+    fun assert_global(
+        deny_list: &deny_list::DenyList,
+        paused_current_epoch: bool,
+        paused_next_epoch: bool,
+        ctx: &TxContext,
+    ) {
+        use sui::coin::{
+            deny_list_v2_is_global_pause_enabled_next_epoch as is_global_pause_enabled_next_epoch,
+            deny_list_v2_is_global_pause_enabled_current_epoch
+                as is_global_pause_enabled_current_epoch,
+        };
+        assert!(
+            is_global_pause_enabled_current_epoch<COIN_TESTS>(deny_list, ctx) ==
+            paused_current_epoch
+        );
+        assert!(
+            is_global_pause_enabled_next_epoch<COIN_TESTS>(deny_list) == paused_next_epoch
+        );
+    }
+
     #[test]
     fun coin_tests_metadata() {
         let mut scenario = test_scenario::begin(TEST_ADDR);
@@ -181,8 +216,6 @@ module sui::coin_tests {
     fun deny_list_v2() {
         use sui::coin::{
             deny_list_v2_add as add,
-            deny_list_v2_contains_next_epoch as contains_next_epoch,
-            deny_list_v2_contains_current_epoch as contains_current_epoch,
             deny_list_v2_remove as remove,
         };
         let mut scenario = test_scenario::begin(@0);
@@ -206,17 +239,14 @@ module sui::coin_tests {
         {
             // test freezing an address
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            remove(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            add(&mut deny_list, &mut deny_cap, @102, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @102));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @102, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            remove(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            add(&mut deny_list, &mut deny_cap, @102, ctx);
+            assert_status(&deny_list, @102, /* current */ false, /* next */ true, ctx);
             test_scenario::return_shared(deny_list);
         };
 
@@ -224,39 +254,33 @@ module sui::coin_tests {
         {
             // test freezing an address over multiple "transactions"
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @102));
-            assert!(contains_current_epoch<COIN_TESTS>(&deny_list, @102, scenario.ctx()));
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @200));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @200, scenario.ctx()));
-            add(&mut deny_list, &mut deny_cap, @200, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @200));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @200, scenario.ctx()));
-            remove(&mut deny_list, &mut deny_cap, @102, scenario.ctx());
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @102));
-            assert!(contains_current_epoch<COIN_TESTS>(&deny_list, @102, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @102, /* current */ true, /* next */ true, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
+            add(&mut deny_list, &mut deny_cap, @200, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ true, ctx);
+            remove(&mut deny_list, &mut deny_cap, @102, ctx);
+            assert_status(&deny_list, @102, /* current */ true, /* next */ false, ctx);
             test_scenario::return_shared(deny_list);
         };
 
         scenario.next_tx(TEST_ADDR);
         {
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @102));
-            assert!(contains_current_epoch<COIN_TESTS>(&deny_list, @102, scenario.ctx()));
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @200));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @200, scenario.ctx()));
-            remove(&mut deny_list, &mut deny_cap, @200, scenario.ctx());
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @200));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @200, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @102, /* current */ true, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ true, ctx);
+            remove(&mut deny_list, &mut deny_cap, @200, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
             test_scenario::return_shared(deny_list);
         };
 
         scenario.next_epoch(TEST_ADDR);
         {
             let deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @102));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @102, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @102, /* current */ false, /* next */ false, ctx);
             test_scenario::return_shared(deny_list);
         };
         transfer::public_freeze_object(deny_cap);
@@ -267,14 +291,9 @@ module sui::coin_tests {
     fun deny_list_v2_global_pause() {
         use sui::coin::{
             deny_list_v2_add as add,
-            deny_list_v2_contains_next_epoch as contains_next_epoch,
-            deny_list_v2_contains_current_epoch as contains_current_epoch,
             deny_list_v2_remove as remove,
             deny_list_v2_enable_global_pause as enable_global_pause,
             deny_list_v2_disable_global_pause as disable_global_pause,
-            deny_list_v2_is_global_pause_enabled_next_epoch as is_global_pause_enabled_next_epoch,
-            deny_list_v2_is_global_pause_enabled_current_epoch
-                as is_global_pause_enabled_current_epoch,
         };
         let mut scenario = test_scenario::begin(@0);
         deny_list::create_for_test(scenario.ctx());
@@ -297,63 +316,48 @@ module sui::coin_tests {
         {
             // global pause =/=> contains
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            assert!(!is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            enable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_global(&deny_list, /* current */ false, /* next */ false, ctx);
+            enable_global_pause(&mut deny_list, &mut deny_cap, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_global(&deny_list, /* current */ false, /* next */ true, ctx);
             // test double enable
-            enable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
-            assert!(!is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
+            enable_global_pause(&mut deny_list, &mut deny_cap, ctx);
+            assert_global(&deny_list, /* current */ false, /* next */ true, ctx);
             test_scenario::return_shared(deny_list);
         };
         scenario.next_epoch(TEST_ADDR);
         {
             // can still add/remove during global pause
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            remove(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ true, ctx);
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ true, ctx);
+            remove(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ true, ctx);
             test_scenario::return_shared(deny_list);
         };
         scenario.next_epoch(TEST_ADDR);
         {
             // global pause does not affect contains when disabled
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
-            disable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            assert!(!contains_current_epoch<COIN_TESTS>(&deny_list, @100, scenario.ctx()));
-            assert!(!is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ true, ctx);
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ true, ctx);
+            disable_global_pause(&mut deny_list, &mut deny_cap, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ false, ctx);
             // test double disable
-            disable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
-            assert!(!is_global_pause_enabled_next_epoch<COIN_TESTS>(&deny_list));
-            assert!(is_global_pause_enabled_current_epoch<COIN_TESTS>(&deny_list, scenario.ctx()));
+            disable_global_pause(&mut deny_list, &mut deny_cap, ctx);
+            assert_global(&deny_list, /* current */ true, /* next */ false, ctx);
             test_scenario::return_shared(deny_list);
         };
         transfer::public_freeze_object(deny_cap);
@@ -364,7 +368,6 @@ module sui::coin_tests {
     fun deny_list_v2_double_add() {
         use sui::coin::{
             deny_list_v2_add as add,
-            deny_list_v2_contains_next_epoch as contains_next_epoch,
             deny_list_v2_remove as remove,
         };
         let mut scenario = test_scenario::begin(@0);
@@ -384,16 +387,262 @@ module sui::coin_tests {
         );
         transfer::public_freeze_object(metadata);
         transfer::public_freeze_object(treasury);
+        scenario.next_tx(TEST_ADDR);
         {
             // test freezing an address
-            scenario.next_tx(TEST_ADDR);
             let mut deny_list: deny_list::DenyList = scenario.take_shared();
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(contains_next_epoch<COIN_TESTS>(&deny_list, @100));
-            remove(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
-            assert!(!contains_next_epoch<COIN_TESTS>(&deny_list, @100));
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            remove(&mut deny_list, &mut deny_cap, @100, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+        transfer::public_freeze_object(deny_cap);
+        scenario.end();
+    }
+
+    #[test, expected_failure(abort_code = sui::coin::EGlobalPauseNotAllowed)]
+    fun deny_list_v2_global_pause_not_allowed_enable() {
+        let mut scenario = test_scenario::begin(@0);
+        deny_list::create_for_test(scenario.ctx());
+        scenario.next_tx(TEST_ADDR);
+
+        let witness = COIN_TESTS {};
+        let (_treasury, mut deny_cap, _metadata) = coin::create_regulated_currency_v2(
+            witness,
+            6,
+            b"COIN_TESTS",
+            b"coin_name",
+            b"description",
+            option::some(url::new_unsafe_from_bytes(b"icon_url")),
+            /* allow_global_pause */ false ,
+            scenario.ctx(),
+        );
+        let mut deny_list: deny_list::DenyList = scenario.take_shared();
+        coin::deny_list_v2_enable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
+        abort 0
+    }
+
+    #[test, expected_failure(abort_code = sui::coin::EGlobalPauseNotAllowed)]
+    fun deny_list_v2_global_pause_not_allowed_disable() {
+        let mut scenario = test_scenario::begin(@0);
+        deny_list::create_for_test(scenario.ctx());
+        scenario.next_tx(TEST_ADDR);
+
+        let witness = COIN_TESTS {};
+        let (_treasury, mut deny_cap, _metadata) = coin::create_regulated_currency_v2(
+            witness,
+            6,
+            b"COIN_TESTS",
+            b"coin_name",
+            b"description",
+            option::some(url::new_unsafe_from_bytes(b"icon_url")),
+            /* allow_global_pause */ false ,
+            scenario.ctx(),
+        );
+        let mut deny_list: deny_list::DenyList = scenario.take_shared();
+        coin::deny_list_v2_disable_global_pause(&mut deny_list, &mut deny_cap, scenario.ctx());
+        abort 0
+    }
+
+
+    #[test]
+    fun migrate_regulated_currency_to_v2() {
+        let mut scenario = test_scenario::begin(@0);
+        deny_list::create_for_test(scenario.ctx());
+        scenario.next_tx(TEST_ADDR);
+
+        let witness = COIN_TESTS {};
+        let (treasury, mut deny_cap, metadata) = coin::create_regulated_currency(
+            witness,
+            6,
+            b"COIN_TESTS",
+            b"coin_name",
+            b"description",
+            option::some(url::new_unsafe_from_bytes(b"icon_url")),
+            scenario.ctx(),
+        );
+        let deny_cap_v2;
+        transfer::public_freeze_object(metadata);
+        transfer::public_freeze_object(treasury);
+        scenario.next_tx(TEST_ADDR);
+        {
+            // test freezing an address
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            coin::deny_list_add(&mut deny_list, &mut deny_cap, @100, ctx);
+            coin::deny_list_add(&mut deny_list, &mut deny_cap, @200, ctx);
+            coin::deny_list_add(&mut deny_list, &mut deny_cap, @300, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ false, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+        scenario.next_tx(TEST_ADDR);
+        {
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            assert!(coin::deny_list_contains<COIN_TESTS>(&deny_list, @100));
+            assert!(coin::deny_list_contains<COIN_TESTS>(&deny_list, @200));
+            assert!(coin::deny_list_contains<COIN_TESTS>(&deny_list, @300));
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ false, ctx);
+            deny_cap_v2 = coin::migrate_regulated_currency_to_v2(&mut deny_list, deny_cap, true, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ true, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ true, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            assert!(!coin::deny_list_contains<COIN_TESTS>(&deny_list, @100));
+            assert!(!coin::deny_list_contains<COIN_TESTS>(&deny_list, @200));
+            assert!(!coin::deny_list_contains<COIN_TESTS>(&deny_list, @300));
+            assert_status(&deny_list, @100, /* current */ true, /* next */ true, ctx);
+            assert_status(&deny_list, @200, /* current */ true, /* next */ true, ctx);
+            assert_status(&deny_list, @300, /* current */ true, /* next */ true, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+        transfer::public_freeze_object(deny_cap_v2);
+        scenario.end();
+    }
+
+    #[test, expected_failure(abort_code = sui::coin::EGlobalPauseNotAllowed)]
+    fun migrate_regulated_currency_to_v2_disallow_global_pause() {
+        let mut scenario = test_scenario::begin(@0);
+        deny_list::create_for_test(scenario.ctx());
+        scenario.next_tx(TEST_ADDR);
+
+        let witness = COIN_TESTS {};
+        let (treasury, mut deny_cap, metadata) = coin::create_regulated_currency(
+            witness,
+            6,
+            b"COIN_TESTS",
+            b"coin_name",
+            b"description",
+            option::some(url::new_unsafe_from_bytes(b"icon_url")),
+            scenario.ctx(),
+        );
+        let mut deny_cap_v2;
+        transfer::public_freeze_object(metadata);
+        transfer::public_freeze_object(treasury);
+        scenario.next_tx(TEST_ADDR);
+        {
+            // test freezing an address
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            coin::deny_list_add(&mut deny_list, &mut deny_cap, @100, scenario.ctx());
+            test_scenario::return_shared(deny_list);
+        };
+        scenario.next_tx(TEST_ADDR);
+        {
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            assert!(coin::deny_list_contains<COIN_TESTS>(&deny_list, @100));
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, scenario.ctx());
+            deny_cap_v2 = coin::migrate_regulated_currency_to_v2(&mut deny_list, deny_cap, false, scenario.ctx());
+            assert!(!coin::deny_list_contains<COIN_TESTS>(&deny_list, @100));
+            assert_status(&deny_list, @100, /* current */ false, /* next */ true, scenario.ctx());
+            test_scenario::return_shared(deny_list);
+        };
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let deny_list: deny_list::DenyList = scenario.take_shared();
+            assert!(!coin::deny_list_contains<COIN_TESTS>(&deny_list, @100));
+            assert_status(&deny_list, @100, /* current */ true, /* next */ true, scenario.ctx());
+            test_scenario::return_shared(deny_list);
+        };
+        scenario.next_tx(TEST_ADDR);
+        let mut deny_list: deny_list::DenyList = scenario.take_shared();
+        coin::deny_list_v2_enable_global_pause(&mut deny_list, &mut deny_cap_v2, scenario.ctx());
+        abort 0
+    }
+
+    #[test]
+    fun deny_list_v2_add_remove() {
+        use sui::coin::{
+            deny_list_v2_add as add,
+            deny_list_v2_remove as remove,
+        };
+        let mut scenario = test_scenario::begin(@0);
+        deny_list::create_for_test(scenario.ctx());
+        scenario.next_tx(TEST_ADDR);
+
+        let witness = COIN_TESTS {};
+        let (treasury, mut deny_cap, metadata) = coin::create_regulated_currency_v2(
+            witness,
+            6,
+            b"COIN_TESTS",
+            b"coin_name",
+            b"description",
+            option::some(url::new_unsafe_from_bytes(b"icon_url")),
+            /* allow_global_pause */ true,
+            scenario.ctx(),
+        );
+        transfer::public_freeze_object(metadata);
+        transfer::public_freeze_object(treasury);
+
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            add(&mut deny_list, &mut deny_cap, @300, ctx);
+            add(&mut deny_list, &mut deny_cap, @400, ctx);
+            add(&mut deny_list, &mut deny_cap, @500, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ true, ctx);
+            assert_status(&deny_list, @400, /* current */ false, /* next */ true, ctx);
+            assert_status(&deny_list, @500, /* current */ false, /* next */ true, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            add(&mut deny_list, &mut deny_cap, @200, ctx);
+            remove(&mut deny_list, &mut deny_cap, @300, ctx);
+            remove(&mut deny_list, &mut deny_cap, @400, ctx);
+            add(&mut deny_list, &mut deny_cap, @500, ctx);
+            remove(&mut deny_list, &mut deny_cap, @500, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ true, ctx);
+            assert_status(&deny_list, @300, /* current */ true, /* next */ false, ctx);
+            assert_status(&deny_list, @400, /* current */ true, /* next */ false, ctx);
+            assert_status(&deny_list, @500, /* current */ true, /* next */ false, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let mut deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            add(&mut deny_list, &mut deny_cap, @100, ctx);
+            remove(&mut deny_list, &mut deny_cap, @100, ctx);
+            remove(&mut deny_list, &mut deny_cap, @200, ctx);
+            remove(&mut deny_list, &mut deny_cap, @300, ctx);
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ true, /* next */ false, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @400, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @500, /* current */ false, /* next */ false, ctx);
+            test_scenario::return_shared(deny_list);
+        };
+
+        scenario.next_epoch(TEST_ADDR);
+        {
+            let deny_list: deny_list::DenyList = scenario.take_shared();
+            let ctx = scenario.ctx();
+            assert_status(&deny_list, @100, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @200, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @300, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @400, /* current */ false, /* next */ false, ctx);
+            assert_status(&deny_list, @500, /* current */ false, /* next */ false, ctx);
             test_scenario::return_shared(deny_list);
         };
         transfer::public_freeze_object(deny_cap);
diff --git a/crates/sui-framework/packages/sui-framework/tests/config_tests.move b/crates/sui-framework/packages/sui-framework/tests/config_tests.move
index 1759471c0c9c4..a7d5e0e115b7d 100644
--- a/crates/sui-framework/packages/sui-framework/tests/config_tests.move
+++ b/crates/sui-framework/packages/sui-framework/tests/config_tests.move
@@ -445,5 +445,42 @@ module sui::config_tests {
         ts.end();
     }
 
+    // tests
+    #[test]
+    fun add_remove_cache() {
+        let mut ts = ts::begin(SENDER);
+        config_create(&mut WriteCap(), ts.ctx());
+        ts.next_tx(SENDER);
+
+        let id = ts::most_recent_id_shared<Config<WriteCap>>().destroy_some();
+        let n = b"hello";
+
+        ts.next_tx(SENDER);
+        {
+            let mut config: Config<WriteCap> = ts.take_shared_by_id(id);
+            config.add_for_next_epoch<_, _, u8>(&mut WriteCap(), n, 0, ts.ctx());
+            ts::return_shared(config);
+        };
+
+        ts.next_tx(SENDER);
+        {
+            let mut config: Config<WriteCap> = ts.take_shared_by_id(id);
+            let removed_value =
+                config.remove_for_next_epoch<_, _, u8>(&mut WriteCap(), n, ts.ctx());
+            assert_eq!(removed_value, option::some(0));
+            ts::return_shared(config);
+        };
+
+        ts.next_epoch(SENDER);
+        {
+            let config: Config<WriteCap> = ts.take_shared_by_id(id);
+            assert!(config.read_setting_for_next_epoch<_, _, u8>(n).is_none());
+            assert!(config::read_setting<_, u8>(id, n, ts.ctx()).is_none());
+            ts::return_shared(config);
+        };
+
+        ts.end();
+    }
+
 
 }
diff --git a/crates/sui-framework/published_api.txt b/crates/sui-framework/published_api.txt
index cb367effd0fd6..6004165f7e9e1 100644
--- a/crates/sui-framework/published_api.txt
+++ b/crates/sui-framework/published_api.txt
@@ -1942,17 +1942,20 @@ v2_is_global_pause_enabled_current_epoch
 v2_is_global_pause_enabled_next_epoch
 	public(package) fun
 	0x2::deny_list
-add_per_type_config
+migrate_v1_to_v2
 	public(package) fun
 	0x2::deny_list
+add_per_type_config
+	fun
+	0x2::deny_list
 borrow_per_type_config_mut
-	public(package) fun
+	fun
 	0x2::deny_list
 borrow_per_type_config
-	public(package) fun
+	fun
 	0x2::deny_list
 per_type_exists
-	public(package) fun
+	fun
 	0x2::deny_list
 v1_add
 	public(package) fun
@@ -2053,6 +2056,9 @@ create_currency
 create_regulated_currency_v2
 	public fun
 	0x2::coin
+migrate_regulated_currency_to_v2
+	public fun
+	0x2::coin
 mint
 	public fun
 	0x2::coin
diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap
index abf55fb1a7b73..f6754250664f3 100644
--- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap
+++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap
@@ -240,13 +240,13 @@ validators:
         next_epoch_worker_address: ~
         extra_fields:
           id:
-            id: "0x21b304d72ac23a09a8a3dc9705c2a942c78990aec0215296f03788a6a94308ae"
+            id: "0x68e2fb2256436f2df5a9410d9e219b97eaa5765cabd2969c23d8083836a5ba79"
           size: 0
       voting_power: 10000
-      operation_cap_id: "0xa1f0041339a76d32ef942e1553a7e1a300c422ca15680e957e54ccad47bfa41f"
+      operation_cap_id: "0x9af5ab9c8057218b799125d1b5cbaeb044b585ff66bd3e944a28a23f2873333e"
       gas_price: 1000
       staking_pool:
-        id: "0x5211c5a5b401b521bd9f066bcc76aab910061729b7b50d80eaad3bfa756f922d"
+        id: "0x872082092168ee3579ef4abe820d2dc56b6b1ceab95f4c894bde019aa229dccb"
         activation_epoch: 0
         deactivation_epoch: ~
         sui_balance: 20000000000000000
@@ -254,14 +254,14 @@ validators:
           value: 0
         pool_token_balance: 20000000000000000
         exchange_rates:
-          id: "0xc307ed0e22e40d45fb78c21b77d60ee66c78d583da6e59805ac3d1ccd5fca271"
+          id: "0x7b6a8b50a9137072addd7c6751d40f60695ca6095f37620642c6c8d108319abb"
           size: 1
         pending_stake: 0
         pending_total_sui_withdraw: 0
         pending_pool_token_withdraw: 0
         extra_fields:
           id:
-            id: "0xdd00574d25ba64e3df2be234c1d30efe6cf6ea07769586006b7f0afc18b864c4"
+            id: "0x294aab97d986bc780637eab36f9d840a1dcc5c386cc5b1fe426a2057d6d918b5"
           size: 0
       commission_rate: 200
       next_epoch_stake: 20000000000000000
@@ -269,27 +269,27 @@ validators:
       next_epoch_commission_rate: 200
       extra_fields:
         id:
-          id: "0x5dc7eaf1371ed251a4d19f471334f4601731c7a89d38a620f2accbab7c719d7d"
+          id: "0xbe42bcfc164349cc63f5793998d8fdced1c3c270b44b39f20a7f6c07d9911533"
         size: 0
   pending_active_validators:
     contents:
-      id: "0xac590cacbff4d239b05f892c2c993859deb40430f5a790958d00c7aed637ca32"
+      id: "0x2669d3abbacd114f098a6d9beb9e58be32156043cc4a9106d9c3e6ec8ef465f0"
       size: 0
   pending_removals: []
   staking_pool_mappings:
-    id: "0x654e7f279fa342a5cc0c2f2fe71a5a7b802a3966c4db09c35e38af59f3b484af"
+    id: "0x8a232136668f3855addb49f6b94382511fa2d9b43e6bb1facfccca20b763fa2b"
     size: 1
   inactive_validators:
-    id: "0xb96c86d0086fb410de571c7bb906abb2d5bf7cca542fc1e31ecd048e3cf7990b"
+    id: "0xbadbf4e81386838963b4cd6dbc0f7ccf674483b29f2fb9cbdf8f777bc54bd411"
     size: 0
   validator_candidates:
-    id: "0x9777af49f7a691360a2d0bb5768b328b3daddf4a2463aad739ff77a0ce9e0c65"
+    id: "0x20466198424f3999e695e1de69ce4717a1e9ea2a35470c1c4d0dc4aff0a4e17e"
     size: 0
   at_risk_validators:
     contents: []
   extra_fields:
     id:
-      id: "0x1dbf050223b364ae9becd3499632eacb949d2bf4b5b255775b02141026020997"
+      id: "0x0ca591b7872ccc03715ea3acba3e36dcf7d92bf016bc7f6b270e94424508cb83"
     size: 0
 storage_fund:
   total_object_storage_rebates:
@@ -306,7 +306,7 @@ parameters:
   validator_low_stake_grace_period: 7
   extra_fields:
     id:
-      id: "0xa4e9bf86178b4516889a767d97a9f2e883af710b385aae15af595b73a64cc632"
+      id: "0x1790455a80f35cd81783a20e3cb078b7fc6f8d6feb4825c4d23577ea7a38682d"
     size: 0
 reference_gas_price: 1000
 validator_report_records:
@@ -320,7 +320,7 @@ stake_subsidy:
   stake_subsidy_decrease_rate: 1000
   extra_fields:
     id:
-      id: "0xe1a245ca09ef600aea7f1b78f4e159a40be155dd7f27102037408766ea64b1f7"
+      id: "0x6b15ef4e5abf6d83d7689d748c6e235749e9500ad456af501740a05fa8e86660"
     size: 0
 safe_mode: false
 safe_mode_storage_rewards:
@@ -332,5 +332,6 @@ safe_mode_non_refundable_storage_fee: 0
 epoch_start_timestamp_ms: 10
 extra_fields:
   id:
-    id: "0x9ffa3298e6c35ab222da78584d315f08aa2ef22dec02c065f273915a8becbc8b"
+    id: "0x3a28788f0c093f4750c2f1766056930b486fd6154073e4fb69358d0f3296e523"
   size: 0
+
diff --git a/examples/move/coin/sources/regcoin.move b/examples/move/coin/sources/regcoin.move
index e93c3ada9f727..e6198e89a2615 100644
--- a/examples/move/coin/sources/regcoin.move
+++ b/examples/move/coin/sources/regcoin.move
@@ -3,25 +3,43 @@
 
 //docs::#regulate
 module examples::regcoin {
-    use sui::coin::{Self, DenyCap};
+    use sui::coin::{Self, DenyCapV2};
     use sui::deny_list::{DenyList};
 
     public struct REGCOIN has drop {}
 
     fun init(witness: REGCOIN, ctx: &mut TxContext) {
-        let (treasury, deny_cap, metadata) = coin::create_regulated_currency(witness, 6, b"REGCOIN", b"", b"", option::none(), ctx);
+        let (treasury, deny_cap, metadata) = coin::create_regulated_currency_v2(
+            witness,
+            6,
+            b"REGCOIN",
+            b"",
+            b"",
+            option::none(),
+            false,
+            ctx,
+        );
         transfer::public_freeze_object(metadata);
         transfer::public_transfer(treasury, ctx.sender());
         transfer::public_transfer(deny_cap, ctx.sender())
     }
 
     //docs::/#regulate}
-
-    public fun add_addr_from_deny_list(denylist: &mut DenyList, denycap: &mut DenyCap<REGCOIN>, denyaddy: address, ctx: &mut TxContext) {
-        coin::deny_list_add(denylist, denycap, denyaddy, ctx);
+    public fun add_addr_from_deny_list(
+        denylist: &mut DenyList,
+        denycap: &mut DenyCapV2<REGCOIN>,
+        denyaddy: address,
+        ctx: &mut TxContext,
+    ) {
+        coin::deny_list_v2_add(denylist, denycap, denyaddy, ctx);
     }
 
-    public fun remove_addr_from_deny_list(denylist: &mut DenyList, denycap: &mut DenyCap<REGCOIN>, denyaddy: address, ctx: &mut TxContext){
-        coin::deny_list_remove(denylist, denycap, denyaddy, ctx);
+    public fun remove_addr_from_deny_list(
+        denylist: &mut DenyList,
+        denycap: &mut DenyCapV2<REGCOIN>,
+        denyaddy: address,
+        ctx: &mut TxContext,
+    ) {
+        coin::deny_list_v2_remove(denylist, denycap, denyaddy, ctx);
     }
-}
\ No newline at end of file
+}
diff --git a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs
index c1aad0c7e8bc3..e4dc5901a4a20 100644
--- a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs
+++ b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs
@@ -432,14 +432,14 @@ impl<'a> ObjectRuntime<'a> {
         }
     }
 
-    pub(super) fn config_setting_cache_insert(
+    pub(super) fn config_setting_cache_update(
         &mut self,
         config_id: ObjectID,
         name_df_id: ObjectID,
         setting_value_object_type: MoveObjectType,
-        value: Value,
+        value: Option<Value>,
     ) {
-        self.child_object_store.config_setting_cache_insert(
+        self.child_object_store.config_setting_cache_update(
             config_id,
             name_df_id,
             setting_value_object_type,
diff --git a/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs b/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs
index 4e3b79440fbf9..e0507223e417b 100644
--- a/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs
+++ b/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs
@@ -37,7 +37,7 @@ pub(crate) struct ActiveChildObject<'a> {
     pub(crate) owner: &'a ObjectID,
     pub(crate) ty: &'a Type,
     pub(crate) move_type: &'a MoveObjectType,
-    pub(crate) copied_value: Value,
+    pub(crate) copied_value: Option<Value>,
 }
 
 #[derive(Debug)]
@@ -679,20 +679,27 @@ impl<'a> ChildObjectStore<'a> {
 
     /// Used by test scenario to insert a config setting into the cache, which replicates the
     /// behavior of a config already being in the object store.
-    pub(super) fn config_setting_cache_insert(
+    pub(super) fn config_setting_cache_update(
         &mut self,
         config_id: ObjectID,
         name_df_id: ObjectID,
         setting_value_object_type: MoveObjectType,
-        value: Value,
+        value: Option<Value>,
     ) {
         let child_move_type = setting_value_object_type;
-        let setting = ConfigSetting {
-            config: config_id,
-            ty: child_move_type,
-            value,
-        };
-        self.config_setting_cache.insert(name_df_id, setting);
+        match value {
+            Some(value) => {
+                let setting = ConfigSetting {
+                    config: config_id,
+                    ty: child_move_type,
+                    value,
+                };
+                self.config_setting_cache.insert(name_df_id, setting);
+            }
+            None => {
+                self.config_setting_cache.remove(&name_df_id);
+            }
+        }
     }
 
     pub(super) fn cached_objects(&self) -> &BTreeMap<ObjectID, Option<Object>> {
@@ -722,26 +729,27 @@ impl<'a> ChildObjectStore<'a> {
     }
 
     pub(super) fn all_active_objects(&self) -> impl Iterator<Item = ActiveChildObject<'_>> {
-        self.store.iter().filter_map(|(id, child_object)| {
-            let child_exists = child_object.value.exists().unwrap();
-            if !child_exists {
-                None
+        self.store.iter().map(|(id, child_object)| {
+            let copied_child_value = if child_object.value.exists().unwrap() {
+                Some(
+                    child_object
+                        .value
+                        .borrow_global()
+                        .unwrap()
+                        .value_as::<StructRef>()
+                        .unwrap()
+                        .read_ref()
+                        .unwrap(),
+                )
             } else {
-                let copied_child_value = child_object
-                    .value
-                    .borrow_global()
-                    .unwrap()
-                    .value_as::<StructRef>()
-                    .unwrap()
-                    .read_ref()
-                    .unwrap();
-                Some(ActiveChildObject {
-                    id,
-                    owner: &child_object.owner,
-                    ty: &child_object.ty,
-                    move_type: &child_object.move_type,
-                    copied_value: copied_child_value,
-                })
+                None
+            };
+            ActiveChildObject {
+                id,
+                owner: &child_object.owner,
+                ty: &child_object.ty,
+                move_type: &child_object.move_type,
+                copied_value: copied_child_value,
             }
         })
     }
diff --git a/sui-execution/latest/sui-move-natives/src/test_scenario.rs b/sui-execution/latest/sui-move-natives/src/test_scenario.rs
index 4b32f532249d9..c76c78492a19c 100644
--- a/sui-execution/latest/sui-move-natives/src/test_scenario.rs
+++ b/sui-execution/latest/sui-move-natives/src/test_scenario.rs
@@ -137,8 +137,9 @@ pub fn end_transaction(
         }
     };
     let object_runtime_ref: &mut ObjectRuntime = context.extensions_mut().get_mut();
-    let all_active_child_objects = object_runtime_ref
+    let all_active_child_objects_with_values = object_runtime_ref
         .all_active_child_objects()
+        .filter(|child| child.copied_value.is_some())
         .map(|child| *child.id)
         .collect::<BTreeSet<_>>();
     let inventories = &mut object_runtime_ref.test_inventories;
@@ -152,7 +153,7 @@ pub fn end_transaction(
     for id in deleted_object_ids
         .iter()
         .chain(writes.keys())
-        .chain(&all_active_child_objects)
+        .chain(&all_active_child_objects_with_values)
     {
         for addr_inventory in inventories.address_inventories.values_mut() {
             for s in addr_inventory.values_mut() {
@@ -236,13 +237,13 @@ pub fn end_transaction(
         &mut all_wrapped,
         object_runtime_ref
             .all_active_child_objects()
-            .map(|child| (child.id, child.ty, child.copied_value)),
+            .filter_map(|child| Some((child.id, child.ty, child.copied_value?))),
     );
     // mark as "incorrect" if a shared/imm object was wrapped or is a child object
     incorrect_shared_or_imm_handling = incorrect_shared_or_imm_handling
-        || taken_shared_or_imm
-            .keys()
-            .any(|id| all_wrapped.contains(id) || all_active_child_objects.contains(id));
+        || taken_shared_or_imm.keys().any(|id| {
+            all_wrapped.contains(id) || all_active_child_objects_with_values.contains(id)
+        });
     // if incorrect handling, return with an 'abort'
     if incorrect_shared_or_imm_handling {
         return Ok(NativeResult::err(
@@ -273,7 +274,7 @@ pub fn end_transaction(
         }
     }
     for (config, setting, ty, value) in config_settings {
-        object_runtime_ref.config_setting_cache_insert(config, setting, ty, value)
+        object_runtime_ref.config_setting_cache_update(config, setting, ty, value)
     }
     object_runtime_ref.state.input_objects = object_runtime_ref
         .test_inventories
@@ -284,7 +285,7 @@ pub fn end_transaction(
     // update inventories
     // check for bad updates to immutable values
     for (id, (ty, value)) in new_object_values {
-        debug_assert!(!all_active_child_objects.contains(&id));
+        debug_assert!(!all_active_child_objects_with_values.contains(&id));
         if let Some(prev_value) = object_runtime_ref
             .test_inventories
             .taken_immutable_values
@@ -308,7 +309,7 @@ pub fn end_transaction(
         object_runtime_ref.test_inventories.objects.remove(id);
     }
     // remove active child objects
-    for id in all_active_child_objects {
+    for id in all_active_child_objects_with_values {
         object_runtime_ref.test_inventories.objects.remove(&id);
     }