Skip to content

Commit de4fdfb

Browse files
committed
Locking approach (serialized txs) for staking
Using Lockable liens
1 parent 5bc9e98 commit de4fdfb

File tree

4 files changed

+100
-51
lines changed

4 files changed

+100
-51
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/provider/vault/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mt = ["library", "sylvia/mt"]
2020

2121
[dependencies]
2222
mesh-apis = { workspace = true }
23+
mesh-sync = { workspace = true }
2324

2425
sylvia = { workspace = true }
2526
cosmwasm-schema = { workspace = true }

contracts/provider/vault/src/contract.rs

Lines changed: 94 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use mesh_apis::local_staking_api::{
1111
LocalStakingApiHelper, LocalStakingApiQueryMsg, MaxSlashResponse,
1212
};
1313
use mesh_apis::vault_api::{self, VaultApi};
14+
use mesh_sync::Lockable;
1415
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, ReplyCtx};
1516
use sylvia::{contract, schemars};
1617

@@ -48,7 +49,7 @@ pub struct VaultContract<'a> {
4849
/// All liens in the protocol
4950
///
5051
/// Liens are indexed with (user, creditor), as this pair has to be unique
51-
pub liens: Map<'a, (&'a Addr, &'a Addr), Lien>,
52+
pub liens: Map<'a, (&'a Addr, &'a Addr), Lockable<Lien>>,
5253
/// Per-user information
5354
pub users: Map<'a, &'a Addr, UserInfo>,
5455
/// Pending txs information
@@ -180,7 +181,7 @@ impl VaultContract<'_> {
180181
let contract = CrossStakingApiHelper(contract);
181182
let slashable = contract.max_slash(ctx.deps.as_ref())?;
182183

183-
let tx_id = self.maybe_stake(
184+
let tx_id = self.stake_tx(
184185
&mut ctx,
185186
&config,
186187
&contract.0,
@@ -288,8 +289,10 @@ impl VaultContract<'_> {
288289
let lienholder = ctx.deps.api.addr_validate(&lienholder)?;
289290

290291
self.liens
291-
.load(ctx.deps.storage, (&account, &lienholder))
292+
.load(ctx.deps.storage, (&account, &lienholder))?
293+
.read()
292294
.map_err(Into::into)
295+
.cloned()
293296
}
294297

295298
/// Returns paginated claims list for an user
@@ -312,8 +315,10 @@ impl VaultContract<'_> {
312315
.liens
313316
.prefix(&account)
314317
.range(ctx.deps.storage, bound, None, Order::Ascending)
315-
.map(|lien| {
316-
lien.map(|(lienholder, lien)| LienInfo {
318+
.map(|item| {
319+
let (lienholder, lien) = item?;
320+
let lien = lien.read()?;
321+
Ok::<LienInfo, ContractError>(LienInfo {
317322
lienholder: lienholder.into(),
318323
amount: lien.amount,
319324
})
@@ -428,10 +433,11 @@ impl VaultContract<'_> {
428433
let mut lien = self
429434
.liens
430435
.may_load(ctx.deps.storage, (&ctx.info.sender, lienholder))?
431-
.unwrap_or(Lien {
436+
.unwrap_or(Lockable::new(Lien {
432437
amount: Uint128::zero(),
433438
slashable,
434-
});
439+
}));
440+
let lien = lien.write()?;
435441
lien.amount += amount;
436442

437443
let mut user = self
@@ -443,8 +449,11 @@ impl VaultContract<'_> {
443449

444450
ensure!(user.verify_collateral(), ContractError::InsufficentBalance);
445451

446-
self.liens
447-
.save(ctx.deps.storage, (&ctx.info.sender, lienholder), &lien)?;
452+
self.liens.save(
453+
ctx.deps.storage,
454+
(&ctx.info.sender, lienholder),
455+
&Lockable::new(lien.clone()),
456+
)?;
448457

449458
self.users.save(ctx.deps.storage, &ctx.info.sender, &user)?;
450459

@@ -458,7 +467,7 @@ impl VaultContract<'_> {
458467
///
459468
/// Config is taken in argument as it sometimes is used outside of this function, so
460469
/// we want to avoid double-fetching it
461-
fn maybe_stake(
470+
fn stake_tx(
462471
&self,
463472
ctx: &mut ExecCtx,
464473
config: &Config,
@@ -471,24 +480,21 @@ impl VaultContract<'_> {
471480
ContractError::UnexpectedDenom(config.denom.clone())
472481
);
473482

474-
// Check that there are no pending txs for this user
475-
let txs = self
476-
.pending
477-
.txs_by_user(ctx.deps.storage, &ctx.info.sender)?;
478-
ensure!(txs.is_empty(), ContractError::PendingTx(txs[0].id));
479-
480483
// Tx starts here
481484
let amount = amount.amount;
482-
// Load user and update (but do not save) max lien and total slashable
483-
let mut lien = self
485+
// Load user and update max lien and total slashable
486+
// Write lock lien
487+
let mut lien_lock = self
484488
.liens
485489
.may_load(ctx.deps.storage, (&ctx.info.sender, lienholder))?
486-
.unwrap_or(Lien {
490+
.unwrap_or(Lockable::new(Lien {
487491
amount: Uint128::zero(),
488492
slashable,
489-
});
493+
}));
494+
let lien = lien_lock.write()?;
490495
lien.amount += amount;
491496

497+
// TODO?: Lockable
492498
let mut user = self
493499
.users
494500
.may_load(ctx.deps.storage, &ctx.info.sender)?
@@ -498,7 +504,14 @@ impl VaultContract<'_> {
498504

499505
ensure!(user.verify_collateral(), ContractError::InsufficentBalance);
500506

501-
// Passed. Create new tx
507+
// Write lock it
508+
lien_lock.lock_write()?;
509+
self.liens
510+
.save(ctx.deps.storage, (&ctx.info.sender, lienholder), &lien_lock)?;
511+
512+
self.users.save(ctx.deps.storage, &ctx.info.sender, &user)?;
513+
514+
// Create new tx
502515
let tx_id = self.next_tx_id(ctx.deps.storage)?;
503516

504517
let new_tx = Tx {
@@ -529,28 +542,19 @@ impl VaultContract<'_> {
529542
);
530543

531544
// Load lien
532-
let mut lien = self
545+
let mut lien_lock = self
533546
.liens
534-
.may_load(ctx.deps.storage, (&tx.user, &tx.lienholder))?
535-
.unwrap_or(Lien {
536-
amount: Uint128::zero(),
537-
slashable: tx.slashable,
538-
});
539-
lien.amount += tx.amount;
540-
541-
let mut user = self
542-
.users
543-
.may_load(ctx.deps.storage, &tx.user)?
544-
.unwrap_or_default();
545-
user.max_lien = user.max_lien.max(lien.amount);
546-
user.total_slashable += tx.amount * lien.slashable;
547-
548-
self.liens
549-
.save(ctx.deps.storage, (&ctx.info.sender, &tx.lienholder), &lien)?;
550-
551-
self.users.save(ctx.deps.storage, &ctx.info.sender, &user)?;
547+
.load(ctx.deps.storage, (&tx.user, &tx.lienholder))?;
548+
// Unlock it
549+
lien_lock.unlock_write()?;
550+
// Save it
551+
self.liens.save(
552+
ctx.deps.storage,
553+
(&ctx.info.sender, &tx.lienholder),
554+
&lien_lock,
555+
)?;
552556

553-
// And remove tx
557+
// Remove tx
554558
self.pending.txs.remove(ctx.deps.storage, tx_id)?;
555559

556560
Ok(())
@@ -570,7 +574,43 @@ impl VaultContract<'_> {
570574
ContractError::WrongContractTx(tx_id, ctx.info.sender.clone())
571575
);
572576

573-
// Just remove tx
577+
// Load lien
578+
let mut lien_lock = self
579+
.liens
580+
.load(ctx.deps.storage, (&tx.user, &tx.lienholder))?;
581+
// Rollback amount (need to unlock it first)
582+
lien_lock.unlock_write()?;
583+
let lien = lien_lock.write()?;
584+
lien.amount -= tx.amount;
585+
// Unlock lien
586+
lien_lock.unlock_write()?;
587+
// Save it unlocked
588+
self.liens.save(
589+
ctx.deps.storage,
590+
(&ctx.info.sender, &tx.lienholder),
591+
&lien_lock,
592+
)?;
593+
594+
// Rollback user's max_lien
595+
let mut user = self.users.load(ctx.deps.storage, &tx.user)?;
596+
597+
// Max lien has to be recalculated from scratch; the just rolled back lien
598+
// is already written to storage
599+
user.max_lien = self
600+
.liens
601+
.prefix(&tx.user)
602+
.range(ctx.deps.storage, None, None, Order::Ascending)
603+
.try_fold(Uint128::zero(), |max_lien, item| {
604+
let (_, lien_lock) = item?;
605+
// Shouldn't fail, because unlocked already
606+
let lien = lien_lock.read().map_err(Into::<ContractError>::into);
607+
lien.map(|lien| max_lien.max(lien.amount))
608+
})?;
609+
610+
user.total_slashable -= tx.amount * tx.slashable;
611+
self.users.save(ctx.deps.storage, &tx.user, &user)?;
612+
613+
// Remove tx
574614
self.pending.txs.remove(ctx.deps.storage, tx_id)?;
575615

576616
Ok(())
@@ -586,30 +626,36 @@ impl VaultContract<'_> {
586626
let amount = amount.amount;
587627

588628
let owner = Addr::unchecked(owner);
589-
let mut lien = self
629+
// TODO: Txs
630+
let mut lien_lock = self
590631
.liens
591632
.may_load(ctx.deps.storage, (&owner, &ctx.info.sender))?
592633
.ok_or(ContractError::UnknownLienholder)?;
634+
let lien = lien_lock.write()?;
593635

594636
ensure!(lien.amount >= amount, ContractError::InsufficientLien);
637+
let slashable = lien.slashable;
595638
lien.amount -= amount;
596639

597640
self.liens
598-
.save(ctx.deps.storage, (&owner, &ctx.info.sender), &lien)?;
641+
.save(ctx.deps.storage, (&owner, &ctx.info.sender), &lien_lock)?;
599642

600643
let mut user = self.users.load(ctx.deps.storage, &owner)?;
601644

602-
// Max lien has to be recalculated from scratch; the just released lien
645+
// Max lien has to be recalculated from scratch; the just saved lien
603646
// is already written to storage
604647
user.max_lien = self
605648
.liens
606649
.prefix(&owner)
607650
.range(ctx.deps.storage, None, None, Order::Ascending)
608-
.try_fold(Uint128::zero(), |max_lien, lien| {
609-
lien.map(|(_, lien)| max_lien.max(lien.amount))
651+
.try_fold(Uint128::zero(), |max_lien, item| {
652+
let (_, lien_lock) = item?;
653+
// FIXME: Fails as write locked
654+
let lien = lien_lock.read().map_err(Into::<ContractError>::into);
655+
lien.map(|lien| max_lien.max(lien.amount))
610656
})?;
611657

612-
user.total_slashable -= amount * lien.slashable;
658+
user.total_slashable -= amount * slashable;
613659
self.users.save(ctx.deps.storage, &owner, &user)?;
614660

615661
Ok(())

contracts/provider/vault/src/error.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use cosmwasm_std::{Addr, StdError, Uint128};
22
use cw_utils::{ParseReplyError, PaymentError};
3+
use mesh_sync::LockError;
34
use thiserror::Error;
45

56
#[derive(Error, Debug, PartialEq)]
@@ -13,6 +14,9 @@ pub enum ContractError {
1314
#[error("{0}")]
1415
ParseReply(#[from] ParseReplyError),
1516

17+
#[error("{0}")]
18+
Lock(#[from] LockError),
19+
1620
#[error("Unauthorized")]
1721
Unauthorized {},
1822

@@ -34,9 +38,6 @@ pub enum ContractError {
3438
#[error("Invalid reply id: {0}")]
3539
InvalidReplyId(u64),
3640

37-
#[error("Transaction {0} is still pending")]
38-
PendingTx(u64),
39-
4041
#[error("The tx {0} exists but comes from the wrong address: {1}")]
4142
WrongContractTx(u64, Addr),
4243
}

0 commit comments

Comments
 (0)