88/// because there is not a rebalancing mechanism that can ensure pools maintain sufficient balance.
99module oft ::move_oft_adapter {
1010 use std::coin::Coin ;
11+ use std::event::emit;
1112 use std::fungible_asset::{Self , FungibleAsset , Metadata };
1213 use std::object::{Self , address_to_object, ExtendRef , Object , object_exists};
1314 use std::option::{Self , Option };
15+ use std::vector;
1416 use std::primary_fungible_store;
1517 use std::signer::address_of;
18+ use aptos_framework::account;
1619
1720 use endpoint_v2_common::bytes32::Bytes32 ;
18- use oft::oapp_core::{assert_admin, combine_options};
21+ use oft::oapp_core::{assert_admin, combine_options, get_admin };
1922 use oft::oapp_store::OAPP_ADDRESS ;
2023 use oft::oft_core;
2124 use oft::oft_impl_config::{
@@ -39,6 +42,11 @@ module oft::move_oft_adapter {
3942 escrow_extend_ref: ExtendRef ,
4043 }
4144
45+ // =================================================== Pauser Store ==================================================
46+
47+ /// Separate resource to keep upgrades safe (no change to OftImpl layout)
48+ struct PauserStore has key { pausers: vector <address >, paused: bool }
49+
4250 // ================================================= OFT Handlers =================================================
4351
4452 /// The default *credit* behavior for a Adapter OFT is to unlock the amount from escrow and credit the recipient
@@ -47,7 +55,9 @@ module oft::move_oft_adapter {
4755 amount_ld: u64 ,
4856 src_eid: u32 ,
4957 lz_receive_value: Option <FungibleAsset >,
50- ): u64 acquires OftImpl {
58+ ): u64 acquires OftImpl , PauserStore {
59+ // Global inflow pause gate
60+ assert ! (!is_paused (), EPAUSED );
5161 // Default implementation does not make special use of LZ Receive Value sent; just deposit to the OFT address
5262 option::for_each (lz_receive_value, |fa| primary_fungible_store::deposit (@oft_admin , fa));
5363
@@ -57,6 +67,9 @@ module oft::move_oft_adapter {
5767 // unlock the amount from escrow
5868 let escrow_signer = &object::generate_signer_for_extending (&store ().escrow_extend_ref);
5969
70+ // Create recipient account if it doesn't exist
71+ account::create_account_if_does_not_exist (to);
72+
6073 // Deposit the extracted amount to the recipient, or redirect to the admin if the recipient is blocklisted
6174 primary_fungible_store::transfer (
6275 escrow_signer,
@@ -76,9 +89,11 @@ module oft::move_oft_adapter {
7689 fa: &mut FungibleAsset ,
7790 min_amount_ld: u64 ,
7891 dst_eid: u32 ,
79- ): (u64 , u64 ) acquires OftImpl {
92+ ): (u64 , u64 ) acquires OftImpl , PauserStore {
8093 assert_not_blocklisted (sender);
8194 assert_metadata (fa);
95+ // Global outflow pause gate
96+ assert ! (!is_paused (), EPAUSED );
8297
8398 // Calculate the exact send amount
8499 let amount_ld = fungible_asset::amount (fa);
@@ -266,6 +281,65 @@ module oft::move_oft_adapter {
266281 oft_limit::new_oft_limit (0 , oft_impl_config::rate_limit_capacity (eid))
267282 }
268283
284+ // ==================================================== Pauser Logic ===================================================
285+
286+ #[view]
287+ public fun is_paused (): bool acquires PauserStore {
288+ let admin_addr = get_admin ();
289+ if (!exists <PauserStore >(admin_addr)) { return false };
290+ borrow_global <PauserStore >(admin_addr).paused
291+ }
292+
293+ /// Add a pauser address (admin-only). Creates the store at current admin address if missing.
294+ public entry fun set_pauser (admin: &signer , pauser: address ) acquires PauserStore {
295+ let admin_addr = address_of (admin);
296+ assert_admin (admin_addr);
297+ if (exists <PauserStore >(admin_addr)) {
298+ let store_mut = borrow_global_mut <PauserStore >(admin_addr);
299+ if (!vector ::contains (&store_mut.pausers, &pauser)) {
300+ vector ::push_back (&mut store_mut.pausers, pauser);
301+ };
302+ } else {
303+ move_to <PauserStore >(admin, PauserStore { pausers: vector [pauser], paused: false });
304+ }
305+ }
306+
307+ /// Remove a pauser address (admin-only). No-op if the address is not present.
308+ public entry fun remove_pauser (admin: &signer , pauser: address ) acquires PauserStore {
309+ let admin_addr = address_of (admin);
310+ assert_admin (admin_addr);
311+ assert ! (exists <PauserStore >(admin_addr), ENOT_INITIALIZED );
312+ let store_mut = borrow_global_mut <PauserStore >(admin_addr);
313+ let i = 0 u64 ;
314+ let n = vector ::length (&store_mut.pausers);
315+ while (i < n) {
316+ if (*vector ::borrow (&store_mut.pausers, i) == pauser) {
317+ vector ::remove (&mut store_mut.pausers, i);
318+ return
319+ };
320+ i = i + 1 ;
321+ };
322+ }
323+
324+ /// Toggle pause (pauser or admin can call)
325+ public entry fun set_paused (caller: &signer , paused: bool ) acquires PauserStore {
326+ let admin_addr = get_admin ();
327+ assert ! (exists <PauserStore >(admin_addr), ENOT_INITIALIZED );
328+ let caller_addr = address_of (caller);
329+ let store_mut = borrow_global_mut <PauserStore >(admin_addr);
330+ assert ! (caller_addr == admin_addr || vector ::contains (&store_mut.pausers, &caller_addr), EUNAUTHORIZED );
331+ assert ! (store_mut.paused != paused, ENO_CHANGE );
332+ store_mut.paused = paused;
333+ emit (PauseSet { paused });
334+ }
335+
336+ #[view]
337+ public fun is_pauser (addr: address ): bool acquires PauserStore {
338+ let admin_addr = get_admin ();
339+ if (!exists <PauserStore >(admin_addr)) { return false };
340+ vector ::contains (&borrow_global <PauserStore >(admin_addr).pausers, &addr)
341+ }
342+
269343 #[view]
270344 /// Total value locked in the contract
271345 public fun tvl (): u64 acquires OftImpl {
@@ -313,6 +387,10 @@ module oft::move_oft_adapter {
313387 init_module (&std::account ::create_signer_for_test (OAPP_ADDRESS ()));
314388 }
315389
390+ // ================================================ Tests Helpers =================================================
391+ #[test_only]
392+ public fun pause_set_event (paused: bool ): PauseSet { PauseSet { paused } }
393+
316394 // ================================================ Storage Helpers ===============================================
317395
318396 #[view]
@@ -333,4 +411,13 @@ module oft::move_oft_adapter {
333411 const EINVALID_METADATA_ADDRESS : u64 = 1 ;
334412 const ENOT_IMPLEMENTED : u64 = 2 ;
335413 const EWRONG_FA_METADATA : u64 = 3 ;
414+ const EPAUSED : u64 = 4 ;
415+ const EUNAUTHORIZED : u64 = 5 ;
416+ const ENO_CHANGE : u64 = 6 ;
417+ const ENOT_INITIALIZED : u64 = 7 ;
418+
419+ // ==================================================== Events ====================================================
420+
421+ #[event]
422+ struct PauseSet has store , drop { paused: bool }
336423}
0 commit comments