Skip to content

Commit 8cadece

Browse files
authored
Merge pull request #248 from oraimoitel/main
feat: add migrate_escrow for atomic fund migration on upgrade
2 parents f1173ad + a32f65b commit 8cadece

2 files changed

Lines changed: 128 additions & 0 deletions

File tree

contracts/split/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ fn admin_key() -> Symbol {
4040
fn admins_key() -> Symbol {
4141
symbol_short!("admins")
4242
}
43+
fn pending_admin_key() -> Symbol {
44+
symbol_short!("pd_adm")
45+
}
4346
fn paused_key() -> Symbol {
4447
symbol_short!("paused")
4548
}
@@ -539,6 +542,16 @@ fn require_not_paused(env: &Env) {
539542
assert!(!is_paused(env), "contract is paused");
540543
}
541544

545+
fn require_admin(env: &Env) -> Address {
546+
let admin: Address = env
547+
.storage()
548+
.instance()
549+
.get(&admin_key())
550+
.expect("admin not set");
551+
admin.require_auth();
552+
admin
553+
}
554+
542555
fn require_role(env: &Env, admin: &Address, min_role: AdminRole) {
543556
admin.require_auth();
544557
let admins: Map<Address, AdminRole> = env
@@ -1517,6 +1530,61 @@ impl SplitContract {
15171530
save_invoice(&env, invoice_id, &invoice);
15181531
}
15191532

1533+
/// Migrate all escrowed funds to a new contract address atomically.
1534+
///
1535+
/// Iterates all active invoice IDs, sums their funded amounts, verifies
1536+
/// the total matches the actual token balance held by this contract, and
1537+
/// transfers everything to `new_contract`. Requires admin auth.
1538+
/// Panics if the calculated total does not match the actual token balance.
1539+
pub fn migrate_escrow(env: Env, admin: Address, new_contract: Address) {
1540+
let admin_addr = require_admin(&env);
1541+
let _ = admin;
1542+
1543+
let counter: u64 = env
1544+
.storage()
1545+
.persistent()
1546+
.get(&counter_key())
1547+
.unwrap_or(0u64);
1548+
1549+
// Group funded amounts by token
1550+
let mut token_totals: Map<Address, i128> = Map::new(&env);
1551+
for id in 1u64..=counter {
1552+
if env.storage().persistent().has(&invoice_key(id))
1553+
|| env.storage().instance().has(&invoice_key(id))
1554+
{
1555+
let invoice = load_invoice(&env, id);
1556+
if invoice.status == InvoiceStatus::Pending && invoice.funded > 0 {
1557+
let token = invoice.tokens.get(0).expect("no token");
1558+
let prev = token_totals.get(token.clone()).unwrap_or(0);
1559+
token_totals.set(token, prev + invoice.funded);
1560+
}
1561+
}
1562+
}
1563+
1564+
// Verify and transfer for each token
1565+
let mut grand_total: i128 = 0;
1566+
for (token, total) in token_totals.iter() {
1567+
let token_client = token::Client::new(&env, &token);
1568+
let actual_balance: i128 =
1569+
token_client.balance(&env.current_contract_address());
1570+
assert!(
1571+
total == actual_balance,
1572+
"escrow balance mismatch for token"
1573+
);
1574+
if total > 0 {
1575+
token_client.transfer(
1576+
&env.current_contract_address(),
1577+
&new_contract,
1578+
&total,
1579+
);
1580+
}
1581+
grand_total += total;
1582+
}
1583+
1584+
events::escrow_migrated(&env, grand_total, &new_contract);
1585+
append_audit_entry(&env, 0, symbol_short!("migrate"), &admin_addr);
1586+
}
1587+
15201588
// -----------------------------------------------------------------------
15211589
// Invoice creation
15221590
// -----------------------------------------------------------------------

contracts/split/src/test.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5038,3 +5038,63 @@ fn test_all_or_nothing_group_still_requires_all_funded() {
50385038
c.pay(&payer, &id1, &100_i128, &0_u64, &false, &false);
50395039
c.release(&id1); // should panic
50405040
}
5041+
5042+
// ---------------------------------------------------------------------------
5043+
// Escrow migration
5044+
// ---------------------------------------------------------------------------
5045+
5046+
#[test]
5047+
fn test_migrate_escrow_transfers_balance() {
5048+
let (env, contract_id, token_id) = setup();
5049+
let c = client(&env, &contract_id);
5050+
let tk = token_client(&env, &token_id);
5051+
5052+
let admin = Address::generate(&env);
5053+
let creator = Address::generate(&env);
5054+
let payer = Address::generate(&env);
5055+
let recipient = Address::generate(&env);
5056+
let new_contract = Address::generate(&env);
5057+
5058+
StellarAssetClient::new(&env, &token_id).mint(&payer, &500);
5059+
env.ledger().set_timestamp(1_000);
5060+
5061+
// Initialize with admin
5062+
let treasury = Address::generate(&env);
5063+
c.initialize(&admin, &0_i128, &treasury, &token_id, &0_u32, &None, &0_u32, &0_u32, &0_u64);
5064+
5065+
// Create a pending invoice with partial funds
5066+
let id = make_invoice(&env, &c, &creator, &recipient, 200, &token_id, 9_999);
5067+
c.pay(&payer, &id, &100_i128, &0_u64, &false, &false);
5068+
assert_eq!(c.get_invoice(&id).status, InvoiceStatus::Pending);
5069+
assert_eq!(c.get_invoice(&id).funded, 100);
5070+
5071+
// Check contract token balance before migration
5072+
let contract_balance_before = tk.balance(&contract_id);
5073+
assert_eq!(contract_balance_before, 100);
5074+
5075+
// Migrate escrow
5076+
c.migrate_escrow(&admin, &new_contract);
5077+
5078+
// Verify new contract received the funds
5079+
assert_eq!(tk.balance(&new_contract), 100);
5080+
assert_eq!(tk.balance(&contract_id), 0);
5081+
}
5082+
5083+
#[test]
5084+
fn test_migrate_escrow_zero_balance() {
5085+
let (env, contract_id, token_id) = setup();
5086+
let c = client(&env, &contract_id);
5087+
5088+
let admin = Address::generate(&env);
5089+
let new_contract = Address::generate(&env);
5090+
5091+
// Initialize with admin
5092+
let treasury = Address::generate(&env);
5093+
c.initialize(&admin, &0_i128, &treasury, &token_id, &0_u32, &None, &0_u32, &0_u32, &0_u64);
5094+
5095+
// No invoices with funds - migration should succeed with zero transfer
5096+
c.migrate_escrow(&admin, &new_contract);
5097+
5098+
let tk = token_client(&env, &token_id);
5099+
assert_eq!(tk.balance(&new_contract), 0);
5100+
}

0 commit comments

Comments
 (0)