Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions contract/src/pause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ use crate::{api::PauseApi, event::Event, Contract, ContractExt, Role};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Feature {
/// Token movement: `ft_transfer`, `ft_transfer_call`, `burn`,
/// `storage_unregister`.
/// Token movement: `ft_transfer`, `ft_transfer_call`, `burn`.
Token,
/// Reward minting: `defer_batch`.
Minting,
Expand Down
30 changes: 7 additions & 23 deletions contract/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use near_contract_standards::{
fungible_token::events::FtBurn,
storage_management::{StorageBalance, StorageBalanceBounds, StorageManagement},
};
use near_contract_standards::storage_management::{StorageBalance, StorageBalanceBounds, StorageManagement};
use near_sdk::{env, near, AccountId, NearToken};

use crate::{Contract, ContractExt, Feature};
use crate::{Contract, ContractExt};

#[near]
impl StorageManagement for Contract {
Expand All @@ -19,25 +16,12 @@ impl StorageManagement for Contract {
}

#[payable]
#[allow(unused_variables)]
fn storage_unregister(&mut self, force: Option<bool>) -> bool {
self.assert_feature_enabled(Feature::Token);
self.assert_not_in_denylist(vec![&env::predecessor_account_id()]);

self.token
.internal_storage_unregister(force)
.inspect(|(account_id, balance)| {
if *balance == 0 {
return;
}

FtBurn {
owner_id: account_id,
amount: (*balance).into(),
memo: None,
}
.emit();
})
.is_some()
// Unregistering is intentionally disabled: the Sweat Foundation
// subsidizes the storage of accounts created with Sweat Wallet, so we
// don't allow users to unregister and reclaim their staked funds.
env::panic_str("storage_unregister is disabled");
}

fn storage_balance_bounds(&self) -> StorageBalanceBounds {
Expand Down
12 changes: 0 additions & 12 deletions integration-tests/tests/pause.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,18 +311,6 @@ async fn test_pause_token() -> anyhow::Result<()> {
assert!(result.has_panic("Feature 'token' is paused"));
info!("burn: {result:?}");

info!("call storage_unregister(force=true) [signer=alice] — pause check fires before denylist check");
let result = context
.alice
.call(context.sweat.id(), "storage_unregister")
.args_json(json!({ "force": true }))
.deposit(NearToken::from_yoctonear(1))
.transact()
.await?
.into_result();
assert!(result.has_panic("Feature 'token' is paused"));
info!("storage_unregister: {result:?}");

info!("call defer_batch([(alice, 10_000)]) [signer=oracle] — minting feature not paused, should succeed");
context
.oracle()
Expand Down
58 changes: 58 additions & 0 deletions integration-tests/tests/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use near_workspaces::types::NearToken;
use serde_json::{json, Value};
use tracing::info;

mod common;
use common::{panic::PanicFinder, prepare::Context};

/// Unregistering is intentionally disabled: the Sweat Foundation subsidizes the
/// storage of accounts created with Sweat Wallet, so `storage_unregister` must
/// never let a user reclaim their staked funds. It panics for every `force`
/// value and leaves the account registered.
#[tokio::test]
#[tracing::instrument]
async fn test_storage_unregister_is_disabled() -> anyhow::Result<()> {
let context = Context::builder().build().await?;

info!("view storage_balance_of(alice) — registered during setup");
let balance_before: Option<Value> = context
.sweat
.view("storage_balance_of")
.args_json(json!({ "account_id": context.alice.id() }))
.await?
.json()?;
assert!(balance_before.is_some(), "alice must be registered before the call");
info!("storage_balance_of: {balance_before:?}");

for force in [None, Some(false), Some(true)] {
info!("call storage_unregister(force={force:?}) [signer=alice]");
let result = context
.alice
.call(context.sweat.id(), "storage_unregister")
.args_json(json!({ "force": force }))
.deposit(NearToken::from_yoctonear(1))
.transact()
.await?
.into_result();
assert!(
result.has_panic("storage_unregister is disabled"),
"storage_unregister must panic (force={force:?})"
);
info!("storage_unregister: {result:?}");

info!("view storage_balance_of(alice) — must stay registered");
let balance_after: Option<Value> = context
.sweat
.view("storage_balance_of")
.args_json(json!({ "account_id": context.alice.id() }))
.await?
.json()?;
assert_eq!(
balance_after, balance_before,
"alice's storage balance must be untouched (force={force:?})"
);
info!("storage_balance_of: {balance_after:?}");
}

Ok(())
}
Binary file modified res/contract.wasm
Binary file not shown.
4 changes: 2 additions & 2 deletions res/contract_abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"compiler": "rustc 1.86.0",
"builder": "cargo-near cargo-near-build 0.11.0"
},
"wasm_hash": "GmVHEmAEZx6FjHiKJJbDVPRF3cNdCfYAYDoJJzBGk2Vt"
"wasm_hash": "5eVVDjnVTvVQP7oKbkDDyAA2LQvFodCy45xZqKhtsDCK"
},
"body": {
"functions": [
Expand Down Expand Up @@ -1437,7 +1437,7 @@
"description": "A contract feature that can be independently paused.\n\nSerialized in snake_case so call args read e.g. `{\"features\": [\"token\", \"minting\"]}`.",
"oneOf": [
{
"description": "Token movement: `ft_transfer`, `ft_transfer_call`, `burn`, `storage_unregister`.",
"description": "Token movement: `ft_transfer`, `ft_transfer_call`, `burn`.",
"type": "string",
"enum": [
"token"
Expand Down
Binary file modified res/contract_abi.zst
Binary file not shown.
Loading