added singed storage types

This commit is contained in:
viraladmin 2026-07-15 18:18:13 -06:00
parent b999feee94
commit 800729bad7
27 changed files with 1407 additions and 50 deletions

View File

@ -1,7 +1,9 @@
use contractless::blocks::storage_key::{StorageKey, UnsignedStorageKey};
use contractless::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
UnsignedStorageBool, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, UnsignedStorageBool,
UnsignedStorageI128, UnsignedStorageI16, UnsignedStorageI32, UnsignedStorageI64,
UnsignedStorageI8, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8, STORAGE_KEY_HASH_BYTES,
STORAGE_STRING_VALUE_BYTES, STORAGE_VALUE_KEY_BYTES,
};
@ -9,7 +11,8 @@ use contractless::common::cli_prompts::{
prompt_hidden_nonempty, prompt_visible, prompt_visible_with_default, prompt_wallet_path,
};
use contractless::common::types::{
STORAGE_BOOL_TYPE, STORAGE_KEY_FEE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE,
STORAGE_I8_TYPE, STORAGE_KEY_FEE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, STORAGE_VALUE_FEE,
};
use contractless::wallets::structures::Wallet;
@ -29,6 +32,11 @@ fn usage() {
println!(" ./data_storage_tx u32|32bit <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx u64|64bit <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx u128|128bit <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx i8 <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx i16 <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx i32 <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx i64 <storage_key_hash> <field_key> <value> [txfee]");
println!(" ./data_storage_tx i128 <storage_key_hash> <field_key> <value> [txfee]");
println!(
" ./data_storage_tx string <storage_key_hash> <field_key> <value> [previous_hash|0] [txfee]"
);
@ -105,6 +113,11 @@ fn normalize_storage_type(input: &str) -> Option<&'static str> {
"u32" | "32bit" | "32" => Some("u32"),
"u64" | "64bit" | "64" => Some("u64"),
"u128" | "128bit" | "128" => Some("u128"),
"i8" | "signed8" | "i8bit" => Some("i8"),
"i16" | "signed16" | "i16bit" => Some("i16"),
"i32" | "signed32" | "i32bit" => Some("i32"),
"i64" | "signed64" | "i64bit" => Some("i64"),
"i128" | "signed128" | "i128bit" => Some("i128"),
"string" | "str" | "text" => Some("string"),
_ => None,
}
@ -275,12 +288,17 @@ macro_rules! create_numeric_storage {
}
};
let hash = transaction.$unsigned_field.hash().await;
let output_value = if $label.ends_with("128") {
serde_json::Value::String(transaction.$unsigned_field.value.to_string())
} else {
json!(transaction.$unsigned_field.value)
};
let output = json!({
"txtype": $txtype,
"timestamp": $timestamp,
"storagekey": transaction.$unsigned_field.storagekey,
"key": transaction.$unsigned_field.key,
"value": transaction.$unsigned_field.value,
"value": output_value,
"address": address,
"txfee": txfee,
"hash": hash,
@ -453,7 +471,7 @@ async fn main() {
args[1].clone()
} else {
prompt_visible(
"Please enter storage transaction type (create_storage, bool, u8, u16, u32, u64, u128, string): ",
"Please enter storage transaction type (create_storage, bool, u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, string): ",
)
.await
};
@ -556,6 +574,81 @@ async fn main() {
"u128"
)
}
"i8" => {
create_numeric_storage!(
args,
timestamp,
wallet,
storagekey,
key,
i8,
UnsignedStorageI8,
StorageI8,
unsigned_storagei8,
STORAGE_I8_TYPE,
"i8"
)
}
"i16" => {
create_numeric_storage!(
args,
timestamp,
wallet,
storagekey,
key,
i16,
UnsignedStorageI16,
StorageI16,
unsigned_storagei16,
STORAGE_I16_TYPE,
"i16"
)
}
"i32" => {
create_numeric_storage!(
args,
timestamp,
wallet,
storagekey,
key,
i32,
UnsignedStorageI32,
StorageI32,
unsigned_storagei32,
STORAGE_I32_TYPE,
"i32"
)
}
"i64" => {
create_numeric_storage!(
args,
timestamp,
wallet,
storagekey,
key,
i64,
UnsignedStorageI64,
StorageI64,
unsigned_storagei64,
STORAGE_I64_TYPE,
"i64"
)
}
"i128" => {
create_numeric_storage!(
args,
timestamp,
wallet,
storagekey,
key,
i128,
UnsignedStorageI128,
StorageI128,
unsigned_storagei128,
STORAGE_I128_TYPE,
"i128"
)
}
"string" => create_string_storage(&args, timestamp, &wallet, storagekey, key).await,
_ => usage(),
}

View File

@ -0,0 +1,154 @@
use contractless::blocks::delete_key::{DeleteKey, UnsignedDeleteKey};
use contractless::blocks::storage_values::{STORAGE_KEY_HASH_BYTES, STORAGE_VALUE_KEY_BYTES};
use contractless::common::cli_prompts::{
prompt_hidden_nonempty, prompt_visible, prompt_visible_with_default, prompt_wallet_path,
};
use contractless::common::types::{DELETE_KEY_TYPE, STORAGE_VALUE_FEE};
use contractless::wallets::structures::Wallet;
use contractless::{create_dir_all, decode, env, json, AsyncWriteExt, File, Utc};
fn display_fee(value: u64) -> f64 {
value as f64 / 100_000_000.0
}
fn parse_fee(value: &str) -> Option<u64> {
let parsed = value.trim().parse::<f64>().ok()?;
if parsed.is_sign_negative() {
return None;
}
Some((parsed * 100_000_000.0).round() as u64)
}
fn valid_storage_key_hash(value: &str) -> bool {
matches!(decode(value.trim()), Ok(bytes) if bytes.len() == STORAGE_KEY_HASH_BYTES)
}
fn fixed_field_key(input: &str) -> Option<String> {
let trimmed = input.trim();
if trimmed.is_empty() || trimmed.as_bytes().len() > STORAGE_VALUE_KEY_BYTES {
return None;
}
Some(format!("{trimmed:<STORAGE_VALUE_KEY_BYTES$}"))
}
async fn write_transaction(hash: &str, output: &serde_json::Value) -> Result<(), String> {
let output = serde_json::to_string_pretty(output)
.map_err(|err| format!("Failed to format transaction JSON: {err}"))?;
create_dir_all("./transactions")
.await
.map_err(|err| format!("Failed to create transactions directory: {err}"))?;
let path = format!("./transactions/{hash}.json");
let mut file = File::create(&path)
.await
.map_err(|err| format!("Failed to create transaction file: {err}"))?;
file.write_all(output.as_bytes())
.await
.map_err(|err| format!("Failed to write transaction file: {err}"))?;
println!("{output}");
Ok(())
}
#[tokio::main]
async fn main() {
let args: Vec<String> = env::args().collect();
let storagekey = if args.len() > 1 {
args[1].trim().to_string()
} else {
prompt_visible("Please enter the storage key hash (32-byte / 64-character hex): ")
.await
.trim()
.to_string()
};
if !valid_storage_key_hash(&storagekey) {
eprintln!("Storage key hash must be a 32-byte / 64-character hex string.");
return;
}
let key_input = if args.len() > 2 {
args[2].clone()
} else {
prompt_visible(
"Please enter the data key to delete (a base key deletes its scalar value and _00 through _19 chunks): ",
)
.await
};
let Some(key) = fixed_field_key(&key_input) else {
eprintln!("Data key must be 1 to 50 bytes.");
return;
};
let txfee = if args.len() > 3 {
match parse_fee(&args[3]) {
Some(fee) => fee,
None => {
eprintln!("Please enter a valid transaction fee.");
return;
}
}
} else {
let default = format!("{:.8}", display_fee(STORAGE_VALUE_FEE));
loop {
let value =
prompt_visible_with_default("Please enter the delete transaction fee", &default)
.await;
if let Some(fee) = parse_fee(&value) {
break fee;
}
println!("Please enter a valid transaction fee.");
}
};
if txfee < STORAGE_VALUE_FEE {
eprintln!(
"Delete transaction fee must be at least {:.8}.",
display_fee(STORAGE_VALUE_FEE)
);
return;
}
let wallet_path = prompt_wallet_path().await;
let wallet_key = prompt_hidden_nonempty(
"What is your wallet decryption key? ",
"Wallet key cannot be empty. Please try again.",
)
.await;
let wallet = match Wallet::try_obtain_wallet(wallet_key, Some(&wallet_path)).await {
Ok(wallet) => wallet,
Err(err) => {
eprintln!("Wallet decryption failed: {err}");
return;
}
};
let address = wallet.saved.short_address.trim().to_string();
let timestamp = Utc::now().timestamp() as u32;
let unsigned = UnsignedDeleteKey {
txtype: DELETE_KEY_TYPE,
time: timestamp,
storagekey,
key,
address: address.clone(),
txfee,
};
let transaction = match DeleteKey::new(unsigned.clone(), &wallet.saved.private_key).await {
Ok(transaction) => transaction,
Err(err) => {
eprintln!("Failed to sign delete-key transaction: {err}");
return;
}
};
let hash = unsigned.hash().await;
let output = json!({
"txtype": DELETE_KEY_TYPE,
"timestamp": timestamp,
"storagekey": &unsigned.storagekey,
"key": &unsigned.key,
"address": address,
"txfee": txfee,
"hash": hash,
"unsigned_deletekey": unsigned,
"signature": transaction.signature,
});
if let Err(err) = write_transaction(&hash, &output).await {
eprintln!("{err}");
}
}

View File

@ -375,6 +375,30 @@ impl Block {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::StorageI8(storage_tx) => {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::StorageI16(storage_tx) => {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::StorageI32(storage_tx) => {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::StorageI64(storage_tx) => {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::StorageI128(storage_tx) => {
let tx_bytes = storage_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
Transaction::DeleteKey(delete_key_tx) => {
let tx_bytes = delete_key_tx.to_bytes().await?;
buffer.extend_from_slice(&tx_bytes);
}
}
}
Ok(buffer)

View File

@ -1,6 +1,7 @@
pub mod block;
pub mod burn;
pub mod collateral;
pub mod delete_key;
pub mod genesis;
pub mod issue_token;
pub mod loan_payment;

View File

@ -1,5 +1,6 @@
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::genesis::GenesisTransaction;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
@ -9,7 +10,8 @@ use crate::blocks::nft::CreateNftTransaction;
use crate::blocks::rewards::RewardsTransaction;
use crate::blocks::storage_key::StorageKey;
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
};
use crate::blocks::swap::SwapTransaction;
use crate::blocks::token::CreateTokenTransaction;
@ -40,6 +42,12 @@ pub const STORAGE_U32_TYPE: u8 = 104;
pub const STORAGE_U64_TYPE: u8 = 105;
pub const STORAGE_U128_TYPE: u8 = 106;
pub const STORAGE_STRING_TYPE: u8 = 107;
pub const STORAGE_I8_TYPE: u8 = 108;
pub const STORAGE_I16_TYPE: u8 = 109;
pub const STORAGE_I32_TYPE: u8 = 110;
pub const STORAGE_I64_TYPE: u8 = 111;
pub const STORAGE_I128_TYPE: u8 = 112;
pub const DELETE_KEY_TYPE: u8 = 113;
// Coin and asset names are stored in fixed 15-byte fields.
pub const COIN_LENGTH: usize = 15;
@ -116,4 +124,10 @@ pub enum Transaction {
StorageU64(StorageU64),
StorageU128(StorageU128),
StorageString(StorageString),
StorageI8(StorageI8),
StorageI16(StorageI16),
StorageI32(StorageI32),
StorageI64(StorageI64),
StorageI128(StorageI128),
DeleteKey(DeleteKey),
}

View File

@ -9,6 +9,7 @@ use crate::orphans::undo_transactions::undo_burn::undo_burn_transaction;
use crate::orphans::undo_transactions::undo_collateral::undo_collateral_transaction;
use crate::orphans::undo_transactions::undo_create_nft::undo_create_nft_transaction;
use crate::orphans::undo_transactions::undo_create_token::undo_create_token_transaction;
use crate::orphans::undo_transactions::undo_delete_key::undo_delete_key_transaction;
use crate::orphans::undo_transactions::undo_issue_token::undo_issue_token_transaction;
use crate::orphans::undo_transactions::undo_loan_creation::undo_loan_creation_transaction;
use crate::orphans::undo_transactions::undo_marketing::undo_marketing_transaction;
@ -147,7 +148,12 @@ pub async fn undo_transactions(
| Transaction::StorageU32(_)
| Transaction::StorageU64(_)
| Transaction::StorageU128(_)
| Transaction::StorageString(_) => {
| Transaction::StorageString(_)
| Transaction::StorageI8(_)
| Transaction::StorageI16(_)
| Transaction::StorageI32(_)
| Transaction::StorageI64(_)
| Transaction::StorageI128(_) => {
undo_storage_value_transaction(
transaction.clone(),
&mining_receiver,
@ -156,6 +162,15 @@ pub async fn undo_transactions(
.await?;
rolled_back_transactions.push(transaction);
}
Transaction::DeleteKey(delete_key_tx) => {
undo_delete_key_transaction(
delete_key_tx.clone(),
&mining_receiver,
&params.db,
)
.await?;
rolled_back_transactions.push(Transaction::DeleteKey(delete_key_tx));
}
}
}
// The block miner loses one mined-block count when this block is removed.

View File

@ -4,6 +4,7 @@ pub mod undo_burn;
pub mod undo_collateral;
pub mod undo_create_nft;
pub mod undo_create_token;
pub mod undo_delete_key;
pub mod undo_issue_token;
pub mod undo_loan_creation;
pub mod undo_marketing;

View File

@ -1,5 +1,6 @@
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
use crate::blocks::loans::LoanContractTransaction;
@ -346,6 +347,19 @@ where
restore_if_spendable(tx_label, "unknown", &[signature], spendable, insert).await;
}
async fn restore_delete_key(transaction: &DeleteKey, db: &Db) {
let delete = &transaction.unsigned_deletekey;
let spendable = balance_checkup(db, 0, delete.txfee, BASECOIN.clone(), &delete.address).await;
restore_if_spendable(
"delete_key",
"unknown",
std::slice::from_ref(&transaction.signature),
spendable,
|| async { transaction.add_to_memory().await },
)
.await;
}
pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) {
match transaction {
Transaction::Transfer(tx) => restore_transfer(tx, db).await,
@ -393,6 +407,28 @@ pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db)
})
.await
}
Transaction::StorageI8(tx) => {
restore_storage_value("storage_i8", tx, db, || async { tx.add_to_memory().await }).await
}
Transaction::StorageI16(tx) => {
restore_storage_value("storage_i16", tx, db, || async { tx.add_to_memory().await })
.await
}
Transaction::StorageI32(tx) => {
restore_storage_value("storage_i32", tx, db, || async { tx.add_to_memory().await })
.await
}
Transaction::StorageI64(tx) => {
restore_storage_value("storage_i64", tx, db, || async { tx.add_to_memory().await })
.await
}
Transaction::StorageI128(tx) => {
restore_storage_value("storage_i128", tx, db, || async {
tx.add_to_memory().await
})
.await
}
Transaction::DeleteKey(tx) => restore_delete_key(tx, db).await,
Transaction::Genesis(_) | Transaction::Rewards(_) => {}
}
}

View File

@ -0,0 +1,68 @@
use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey};
use crate::common::types::STORAGE_VALUE_ROLLBACK_TREE;
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
use crate::records::memory::mempool::BASECOIN;
use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index;
use crate::{decode, sled::Db};
pub async fn undo_delete_key_transaction(
transaction: DeleteKey,
mining_receiver: &str,
db: &Db,
) -> Result<(), String> {
let delete = &transaction.unsigned_deletekey;
let storage_tree_name = decode(&delete.storagekey)
.map_err(|err| format!("Could not decode delete-key storage tree during undo: {err}"))?;
let txhash = delete.hash().await;
let txhash_bytes = decode(&txhash)
.map_err(|err| format!("Could not decode delete-key txhash during undo: {err}"))?;
let targets = delete_record_keys(&delete.address, &delete.key)?;
let _ = balance_sheet_operation_with_db(
db,
mining_receiver,
delete.txfee,
&BASECOIN,
"subtraction",
);
let _ =
balance_sheet_operation_with_db(db, &delete.address, delete.txfee, &BASECOIN, "addition");
let rollback_tree = db
.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
.map_err(|err| format!("Could not open delete-key rollback tree: {err}"))?;
let storage_tree = db
.open_tree(storage_tree_name)
.map_err(|err| format!("Could not open storage tree during delete-key undo: {err}"))?;
for (target_index, target) in targets.into_iter().enumerate() {
let rollback_key = delete_rollback_key(&txhash_bytes, target_index)?;
let rollback_value = rollback_tree
.get(&rollback_key)
.map_err(|err| format!("Could not read delete-key rollback value: {err}"))?
.ok_or_else(|| "Missing delete-key rollback record.".to_string())?;
match rollback_value.first().copied() {
Some(0) => {
storage_tree
.remove(&target)
.map_err(|err| format!("Could not preserve absent deleted value: {err}"))?;
}
Some(1) => {
storage_tree
.insert(&target, &rollback_value[1..])
.map_err(|err| format!("Could not restore deleted storage value: {err}"))?;
}
_ => return Err("Invalid delete-key rollback marker.".to_string()),
}
rollback_tree
.remove(rollback_key)
.map_err(|err| format!("Could not remove delete-key rollback record: {err}"))?;
}
let _ = remove_wallet_transaction_index(db, &[&delete.address, mining_receiver], &txhash_bytes);
db.open_tree("txid")
.map_err(|err| format!("Could not open txid tree during delete-key undo: {err}"))?
.remove(txhash_bytes)
.map_err(|err| format!("Could not remove delete-key txid mapping: {err}"))?;
Ok(())
}

View File

@ -25,6 +25,11 @@ fn storage_undo_parts(transaction: &Transaction) -> Result<StorageUndoParts, Str
Transaction::StorageU64(tx) => tx.storage_parts(),
Transaction::StorageU128(tx) => tx.storage_parts(),
Transaction::StorageString(tx) => tx.storage_parts(),
Transaction::StorageI8(tx) => tx.storage_parts(),
Transaction::StorageI16(tx) => tx.storage_parts(),
Transaction::StorageI32(tx) => tx.storage_parts(),
Transaction::StorageI64(tx) => tx.storage_parts(),
Transaction::StorageI128(tx) => tx.storage_parts(),
_ => return Err("Unsupported storage value transaction undo.".to_string()),
};

View File

@ -1,11 +1,14 @@
use super::*;
use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey};
use crate::blocks::storage_values::{
storage_record_key, storage_string_record_key, StorageBool, StorageString, StorageU128,
StorageU16, StorageU32, StorageU64, StorageU8, StorageValueTransaction,
storage_record_key, storage_string_record_key, StorageBool, StorageI128, StorageI16,
StorageI32, StorageI64, StorageI8, StorageString, StorageU128, StorageU16, StorageU32,
StorageU64, StorageU8, StorageValueTransaction,
};
use crate::common::types::{
Transaction, STORAGE_BOOL_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
Transaction, DELETE_KEY_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE,
STORAGE_I32_TYPE, STORAGE_I64_TYPE, STORAGE_I8_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
};
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
@ -43,6 +46,24 @@ async fn storage_value_transaction_from_original(original: &[u8]) -> Result<Tran
STORAGE_STRING_TYPE => Ok(Transaction::StorageString(
StorageString::from_bytes(txtype, body).await?,
)),
STORAGE_I8_TYPE => Ok(Transaction::StorageI8(
StorageI8::from_bytes(txtype, body).await?,
)),
STORAGE_I16_TYPE => Ok(Transaction::StorageI16(
StorageI16::from_bytes(txtype, body).await?,
)),
STORAGE_I32_TYPE => Ok(Transaction::StorageI32(
StorageI32::from_bytes(txtype, body).await?,
)),
STORAGE_I64_TYPE => Ok(Transaction::StorageI64(
StorageI64::from_bytes(txtype, body).await?,
)),
STORAGE_I128_TYPE => Ok(Transaction::StorageI128(
StorageI128::from_bytes(txtype, body).await?,
)),
DELETE_KEY_TYPE => Ok(Transaction::DeleteKey(
DeleteKey::from_bytes(txtype, body).await?,
)),
_ => Err(anyhow!(
"Unsupported storage value transaction type {txtype}"
)),
@ -58,6 +79,11 @@ fn selected_storage_value_parts(transaction: &Transaction) -> Result<SelectedSto
Transaction::StorageU64(tx) => tx.storage_parts(),
Transaction::StorageU128(tx) => tx.storage_parts(),
Transaction::StorageString(tx) => tx.storage_parts(),
Transaction::StorageI8(tx) => tx.storage_parts(),
Transaction::StorageI16(tx) => tx.storage_parts(),
Transaction::StorageI32(tx) => tx.storage_parts(),
Transaction::StorageI64(tx) => tx.storage_parts(),
Transaction::StorageI128(tx) => tx.storage_parts(),
_ => return Err(anyhow!("Unsupported storage value transaction")),
};
@ -77,6 +103,11 @@ fn selected_storage_value_bytes(transaction: &Transaction) -> Result<Vec<u8>> {
Transaction::StorageU64(tx) => tx.storage_value_bytes(),
Transaction::StorageU128(tx) => tx.storage_value_bytes(),
Transaction::StorageString(tx) => tx.storage_value_bytes(),
Transaction::StorageI8(tx) => tx.storage_value_bytes(),
Transaction::StorageI16(tx) => tx.storage_value_bytes(),
Transaction::StorageI32(tx) => tx.storage_value_bytes(),
Transaction::StorageI64(tx) => tx.storage_value_bytes(),
Transaction::StorageI128(tx) => tx.storage_value_bytes(),
_ => Err("Unsupported storage value transaction".to_string()),
}
.map_err(|err| anyhow!(err))
@ -1110,24 +1141,40 @@ pub async fn apply_selected_transaction_math(
..
} => {
let transaction = storage_value_transaction_from_original(original).await?;
let parts = selected_storage_value_parts(&transaction)?;
let storage_tree = decode(&parts.storagekey)?;
let txhash_bytes = decode(hash)?;
let storage_record_key = match &transaction {
Transaction::StorageString(tx) => storage_string_record_key(db, tx)
.await
.map_err(|err| anyhow!(err))?,
_ => storage_record_key(&parts.address, &parts.key)
.map_err(|err| anyhow!(err))?,
};
let value_bytes = selected_storage_value_bytes(&transaction)?;
pending_effects.set_dynamic_tree(
storage_tree,
storage_record_key,
value_bytes,
txhash_bytes.clone(),
);
if let Transaction::DeleteKey(tx) = &transaction {
let delete = &tx.unsigned_deletekey;
let storage_tree = decode(&delete.storagekey)?;
for (target_index, target) in delete_record_keys(&delete.address, &delete.key)
.map_err(|err| anyhow!(err))?
.into_iter()
.enumerate()
{
pending_effects.remove_dynamic_tree(
storage_tree.clone(),
target,
delete_rollback_key(&txhash_bytes, target_index)
.map_err(|err| anyhow!(err))?,
);
}
} else {
let parts = selected_storage_value_parts(&transaction)?;
let storage_tree = decode(&parts.storagekey)?;
let storage_record_key = match &transaction {
Transaction::StorageString(tx) => storage_string_record_key(db, tx)
.await
.map_err(|err| anyhow!(err))?,
_ => storage_record_key(&parts.address, &parts.key)
.map_err(|err| anyhow!(err))?,
};
let value_bytes = selected_storage_value_bytes(&transaction)?;
pending_effects.set_dynamic_tree(
storage_tree,
storage_record_key,
value_bytes,
txhash_bytes.clone(),
);
}
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
add_balance_change_bytes(
&mut balance_changes,

View File

@ -0,0 +1,60 @@
use crate::blocks::delete_key::{delete_record_keys, delete_rollback_key, DeleteKey};
use crate::decode;
use crate::records::memory::mempool::BASECOIN;
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
use crate::{Arc, Mutex};
pub async fn process_delete_key(
transaction: &DeleteKey,
mut binary_data: Vec<u8>,
index_counter: Arc<Mutex<&mut usize>>,
miner: String,
block_header_number: u32,
pending_effects: &mut PendingEffects,
) -> Result<Vec<u8>, String> {
let mut index = index_counter.lock().await;
**index += 1;
let delete = &transaction.unsigned_deletekey;
let storage_tree = decode(&delete.storagekey)
.map_err(|err| format!("Could not decode delete-key storage tree: {err}"))?;
let txhash = delete.hash().await;
let txhash_bytes =
decode(&txhash).map_err(|err| format!("Could not decode delete-key txhash: {err}"))?;
let targets = delete_record_keys(&delete.address, &delete.key)?;
binary_data.extend(
transaction
.to_bytes()
.await
.map_err(|err| format!("Could not serialize delete-key transaction: {err}"))?,
);
for (target_index, target) in targets.into_iter().enumerate() {
pending_effects.remove_dynamic_tree(
storage_tree.clone(),
target,
delete_rollback_key(&txhash_bytes, target_index)?,
);
}
pending_effects.add_balance(&miner, delete.txfee, &BASECOIN, BalanceOperand::Addition);
pending_effects.add_balance(
&delete.address,
delete.txfee,
&BASECOIN,
BalanceOperand::Subtraction,
);
let txvalue = format!("{block_header_number}:{}", **index);
pending_effects.set_tree("txid", txhash_bytes.clone(), txvalue.into_bytes());
index_wallet_transaction(
pending_effects,
&[&delete.address],
block_header_number,
**index as u32,
&txhash_bytes,
)?;
Ok(binary_data)
}

View File

@ -3,6 +3,7 @@ pub mod add_payments_db;
pub mod borrower_tx;
pub mod burn_tx;
pub mod collateral_tx;
pub mod delete_key_tx;
pub mod genesis_tx;
pub mod header_number;
pub mod issue_token_tx;

View File

@ -3,6 +3,7 @@ use crate::common::types::Transaction;
use crate::records::record_chain::borrower_tx::process_borrower;
use crate::records::record_chain::burn_tx::process_burn;
use crate::records::record_chain::collateral_tx::process_collateral;
use crate::records::record_chain::delete_key_tx::process_delete_key;
use crate::records::record_chain::genesis_tx::process_genesis;
use crate::records::record_chain::issue_token_tx::process_issue_token;
use crate::records::record_chain::lender_tx::process_lender;
@ -208,7 +209,12 @@ pub async fn handle_transactions(
| Transaction::StorageU32(_)
| Transaction::StorageU64(_)
| Transaction::StorageU128(_)
| Transaction::StorageString(_) => {
| Transaction::StorageString(_)
| Transaction::StorageI8(_)
| Transaction::StorageI16(_)
| Transaction::StorageI32(_)
| Transaction::StorageI64(_)
| Transaction::StorageI128(_) => {
process_storage_value(
transaction,
binary_data.clone(),
@ -220,6 +226,17 @@ pub async fn handle_transactions(
)
.await
}
Transaction::DeleteKey(delete_key_transaction) => {
process_delete_key(
delete_key_transaction,
binary_data.clone(),
index_mutex.clone(),
miner.clone(),
block_header_number,
pending_effects,
)
.await
}
};
match result {
Ok(new_binary_data) => {

View File

@ -48,6 +48,11 @@ pub enum PendingEffect {
value: Vec<u8>,
rollback_key: Vec<u8>,
},
DynamicTreeRemove {
tree: Vec<u8>,
key: Vec<u8>,
rollback_key: Vec<u8>,
},
TreeRemove {
tree: &'static str,
key: Vec<u8>,
@ -133,6 +138,14 @@ impl PendingEffects {
});
}
pub fn remove_dynamic_tree(&mut self, tree: Vec<u8>, key: Vec<u8>, rollback_key: Vec<u8>) {
self.push(PendingEffect::DynamicTreeRemove {
tree,
key,
rollback_key,
});
}
pub fn remove_tree(&mut self, tree: &'static str, key: Vec<u8>) {
self.push(PendingEffect::TreeRemove { tree, key });
}
@ -337,6 +350,39 @@ fn apply_effect(db: &Db, effect: &PendingEffect) -> Result<AppliedEffect, String
previous_rollback,
})
}
PendingEffect::DynamicTreeRemove {
tree,
key,
rollback_key,
} => {
let tree_handle = db
.open_tree(tree)
.map_err(|err| format!("Failed to open dynamic storage tree: {err}"))?;
let previous = tree_handle
.remove(key)
.map_err(|err| format!("Failed to remove dynamic storage value: {err}"))?
.map(|value| value.to_vec());
let rollback_tree = db
.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
.map_err(|err| format!("Failed to open storage value rollback tree: {err}"))?;
let previous_rollback = rollback_tree
.get(rollback_key)
.map_err(|err| format!("Failed to read previous storage rollback value: {err}"))?
.map(|value| value.to_vec());
rollback_tree
.insert(
rollback_key,
encode_storage_rollback_value(previous.as_deref()),
)
.map_err(|err| format!("Failed to write storage rollback value: {err}"))?;
Ok(AppliedEffect::DynamicTreeMutation {
tree: tree.clone(),
key: key.clone(),
previous,
rollback_key: rollback_key.clone(),
previous_rollback,
})
}
PendingEffect::TreeRemove { tree, key } => {
let tree_handle = db
.open_tree(tree)
@ -606,6 +652,71 @@ fn encode_storage_rollback_value(previous: Option<&[u8]>) -> Vec<u8> {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dynamic_tree_remove_is_reversible() {
let db = crate::sled::Config::new()
.temporary(true)
.open()
.expect("temporary sled database should open");
let tree_name = vec![7_u8; 32];
let record_key = b"wallet-and-data-key".to_vec();
let record_value = b"stored-value".to_vec();
let rollback_key = vec![9_u8; 33];
db.open_tree(tree_name.clone())
.unwrap()
.insert(&record_key, record_value.as_slice())
.unwrap();
let mut pending = PendingEffects::default();
pending.remove_dynamic_tree(tree_name.clone(), record_key.clone(), rollback_key.clone());
let mut applied = pending.apply(&db).expect("remove effect should apply");
assert!(db
.open_tree(tree_name.clone())
.unwrap()
.get(&record_key)
.unwrap()
.is_none());
assert_eq!(
db.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
.unwrap()
.get(&rollback_key)
.unwrap()
.unwrap()
.as_ref(),
[1_u8]
.iter()
.chain(record_value.iter())
.copied()
.collect::<Vec<_>>()
.as_slice()
);
applied
.rollback(&db)
.expect("remove effect should roll back");
assert_eq!(
db.open_tree(tree_name)
.unwrap()
.get(record_key)
.unwrap()
.unwrap()
.as_ref(),
record_value.as_slice()
);
assert!(db
.open_tree(STORAGE_VALUE_ROLLBACK_TREE)
.unwrap()
.get(rollback_key)
.unwrap()
.is_none());
}
}
fn append_tree_value(
db: &Db,
tree: &'static str,

View File

@ -48,6 +48,26 @@ async fn storage_transaction_bytes(transaction: &Transaction) -> Result<Vec<u8>,
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage string transaction: {err}")),
Transaction::StorageI8(tx) => tx
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage i8 transaction: {err}")),
Transaction::StorageI16(tx) => tx
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage i16 transaction: {err}")),
Transaction::StorageI32(tx) => tx
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage i32 transaction: {err}")),
Transaction::StorageI64(tx) => tx
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage i64 transaction: {err}")),
Transaction::StorageI128(tx) => tx
.to_bytes()
.await
.map_err(|err| format!("Could not serialize storage i128 transaction: {err}")),
_ => Err("Unsupported storage value transaction.".to_string()),
}
}
@ -61,6 +81,11 @@ fn storage_parts(transaction: &Transaction) -> Result<OwnedStorageParts, String>
Transaction::StorageU64(tx) => tx.storage_parts(),
Transaction::StorageU128(tx) => tx.storage_parts(),
Transaction::StorageString(tx) => tx.storage_parts(),
Transaction::StorageI8(tx) => tx.storage_parts(),
Transaction::StorageI16(tx) => tx.storage_parts(),
Transaction::StorageI32(tx) => tx.storage_parts(),
Transaction::StorageI64(tx) => tx.storage_parts(),
Transaction::StorageI128(tx) => tx.storage_parts(),
_ => return Err("Unsupported storage value transaction.".to_string()),
};
@ -82,6 +107,11 @@ fn storage_value_bytes(transaction: &Transaction) -> Result<Vec<u8>, String> {
Transaction::StorageU64(tx) => tx.storage_value_bytes(),
Transaction::StorageU128(tx) => tx.storage_value_bytes(),
Transaction::StorageString(tx) => tx.storage_value_bytes(),
Transaction::StorageI8(tx) => tx.storage_value_bytes(),
Transaction::StorageI16(tx) => tx.storage_value_bytes(),
Transaction::StorageI32(tx) => tx.storage_value_bytes(),
Transaction::StorageI64(tx) => tx.storage_value_bytes(),
Transaction::StorageI128(tx) => tx.storage_value_bytes(),
_ => Err("Unsupported storage value transaction.".to_string()),
}
}

View File

@ -1,6 +1,7 @@
use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES};
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::genesis::GenesisTransaction;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
@ -10,7 +11,8 @@ use crate::blocks::nft::CreateNftTransaction;
use crate::blocks::rewards::RewardsTransaction;
use crate::blocks::storage_key::StorageKey;
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
};
use crate::blocks::swap::SwapTransaction;
use crate::blocks::token::CreateTokenTransaction;
@ -18,9 +20,11 @@ use crate::blocks::transfer::TransferTransaction;
use crate::blocks::vanity::VanityAddressTransaction;
use crate::common::types::{
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, STORAGE_BOOL_TYPE,
STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE,
STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
DELETE_KEY_TYPE, GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE,
STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE,
STORAGE_I8_TYPE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE,
VANITY_ADDRESS_TYPE,
};
use crate::rpc::command_maps::get_bytes;
@ -269,6 +273,60 @@ pub async fn load_block_from_binary(binary_data: &[u8]) -> Result<Block, String>
i += body_len;
storage
}
STORAGE_I8_TYPE => {
let storage = Transaction::StorageI8(
StorageI8::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I16_TYPE => {
let storage = Transaction::StorageI16(
StorageI16::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I32_TYPE => {
let storage = Transaction::StorageI32(
StorageI32::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I64_TYPE => {
let storage = Transaction::StorageI64(
StorageI64::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I128_TYPE => {
let storage = Transaction::StorageI128(
StorageI128::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
DELETE_KEY_TYPE => {
let delete_key = Transaction::DeleteKey(
DeleteKey::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
delete_key
}
_ => {
return Err(format!("Unsupported transaction type: {txtype}"));
}

View File

@ -1,6 +1,7 @@
use crate::blocks::block::{Block, VrfBlock, VRF_BLOCK_BYTES};
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::genesis::GenesisTransaction;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
@ -10,7 +11,8 @@ use crate::blocks::nft::CreateNftTransaction;
use crate::blocks::rewards::RewardsTransaction;
use crate::blocks::storage_key::StorageKey;
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
};
use crate::blocks::swap::SwapTransaction;
use crate::blocks::token::CreateTokenTransaction;
@ -19,9 +21,11 @@ use crate::blocks::vanity::VanityAddressTransaction;
use crate::common::network_paths_and_settings::block_extension_and_paths;
use crate::common::types::{
Transaction, BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE, STORAGE_BOOL_TYPE,
STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE,
STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
DELETE_KEY_TYPE, GENESIS_TYPE, ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, REWARDS_TYPE,
STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE,
STORAGE_I8_TYPE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE,
VANITY_ADDRESS_TYPE,
};
use crate::fs;
use crate::rpc::command_maps::get_bytes;
@ -286,6 +290,60 @@ pub async fn load_block(block_number: u32) -> Result<Block, String> {
i += body_len;
storage
}
STORAGE_I8_TYPE => {
let storage = Transaction::StorageI8(
StorageI8::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I16_TYPE => {
let storage = Transaction::StorageI16(
StorageI16::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I32_TYPE => {
let storage = Transaction::StorageI32(
StorageI32::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I64_TYPE => {
let storage = Transaction::StorageI64(
StorageI64::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
STORAGE_I128_TYPE => {
let storage = Transaction::StorageI128(
StorageI128::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
storage
}
DELETE_KEY_TYPE => {
let delete_key = Transaction::DeleteKey(
DeleteKey::from_bytes(txtype, body)
.await
.map_err(|e| e.to_string())?,
);
i += body_len;
delete_key
}
_ => {
return Err(format!("Unsupported transaction type: {txtype}"));
}

View File

@ -1,5 +1,6 @@
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::genesis::GenesisTransaction;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
@ -9,7 +10,8 @@ use crate::blocks::nft::CreateNftTransaction;
use crate::blocks::rewards::RewardsTransaction;
use crate::blocks::storage_key::StorageKey;
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
};
use crate::blocks::swap::SwapTransaction;
use crate::blocks::token::CreateTokenTransaction;
@ -100,6 +102,12 @@ pub fn get_bytes(tx_type: u8) -> usize {
105 => StorageU64::BYTE_LENGTH,
106 => StorageU128::BYTE_LENGTH,
107 => StorageString::BYTE_LENGTH,
108 => StorageI8::BYTE_LENGTH,
109 => StorageI16::BYTE_LENGTH,
110 => StorageI32::BYTE_LENGTH,
111 => StorageI64::BYTE_LENGTH,
112 => StorageI128::BYTE_LENGTH,
113 => DeleteKey::BYTE_LENGTH,
_ => 0,
}
}

View File

@ -136,6 +136,12 @@ async fn miner_earnings_from_transactions(
Transaction::StorageU64(tx) => earnings.add_fee(tx.unsigned_storageu64.txfee)?,
Transaction::StorageU128(tx) => earnings.add_fee(tx.unsigned_storageu128.txfee)?,
Transaction::StorageString(tx) => earnings.add_fee(tx.unsigned_storage.txfee)?,
Transaction::StorageI8(tx) => earnings.add_fee(tx.unsigned_storagei8.txfee)?,
Transaction::StorageI16(tx) => earnings.add_fee(tx.unsigned_storagei16.txfee)?,
Transaction::StorageI32(tx) => earnings.add_fee(tx.unsigned_storagei32.txfee)?,
Transaction::StorageI64(tx) => earnings.add_fee(tx.unsigned_storagei64.txfee)?,
Transaction::StorageI128(tx) => earnings.add_fee(tx.unsigned_storagei128.txfee)?,
Transaction::DeleteKey(tx) => earnings.add_fee(tx.unsigned_deletekey.txfee)?,
}
}
@ -154,7 +160,7 @@ async fn miner_earnings_for_block(db: &Db, block_height: u32) -> Result<Vec<u8>,
fn signature_count(transaction_type: u8) -> usize {
match transaction_type {
6 | 7 => 2,
2..=5 | 8..=12 | 100..=107 => 1,
2..=5 | 8..=12 | 100..=113 => 1,
_ => 0,
}
}

View File

@ -1,5 +1,6 @@
use crate::blocks::burn::BurnTransaction;
use crate::blocks::collateral::CollateralClaimTransaction;
use crate::blocks::delete_key::DeleteKey;
use crate::blocks::issue_token::IssueTokenTransaction;
use crate::blocks::loan_payment::ContractPaymentTransaction;
use crate::blocks::loans::LoanContractTransaction;
@ -7,15 +8,17 @@ use crate::blocks::marketing::MarketingTransaction;
use crate::blocks::nft::CreateNftTransaction;
use crate::blocks::storage_key::StorageKey;
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
};
use crate::blocks::swap::SwapTransaction;
use crate::blocks::token::CreateTokenTransaction;
use crate::blocks::transfer::TransferTransaction;
use crate::blocks::vanity::VanityAddressTransaction;
use crate::common::types::{
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE,
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, STORAGE_BOOL_TYPE, STORAGE_KEY_TYPE,
BORROWER_TYPE, BURN_TYPE, COLLATERAL_TYPE, CREATE_NFT_TYPE, CREATE_TOKEN_TYPE, DELETE_KEY_TYPE,
ISSUE_TOKEN_TYPE, LENDER_TYPE, MARKETING_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE,
STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE, STORAGE_I8_TYPE, STORAGE_KEY_TYPE,
STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE,
STORAGE_U8_TYPE, SWAP_TYPE, TRANSFER_TYPE, VANITY_ADDRESS_TYPE,
};
@ -605,6 +608,12 @@ pub async fn save_and_submit(
STORAGE_U64_TYPE => submit_storage_value!(StorageU64, txtype, tx, db, map),
STORAGE_U128_TYPE => submit_storage_value!(StorageU128, txtype, tx, db, map),
STORAGE_STRING_TYPE => submit_storage_value!(StorageString, txtype, tx, db, map),
STORAGE_I8_TYPE => submit_storage_value!(StorageI8, txtype, tx, db, map),
STORAGE_I16_TYPE => submit_storage_value!(StorageI16, txtype, tx, db, map),
STORAGE_I32_TYPE => submit_storage_value!(StorageI32, txtype, tx, db, map),
STORAGE_I64_TYPE => submit_storage_value!(StorageI64, txtype, tx, db, map),
STORAGE_I128_TYPE => submit_storage_value!(StorageI128, txtype, tx, db, map),
DELETE_KEY_TYPE => submit_storage_value!(DeleteKey, txtype, tx, db, map),
_ => {
let msg = "error: No such transaction type"
.to_string()

View File

@ -1,5 +1,6 @@
use crate::blocks::burn::{BurnTransaction, UnsignedBurnTransaction};
use crate::blocks::collateral::{CollateralClaimTransaction, UnsignedCollateralClaimTransaction};
use crate::blocks::delete_key::{DeleteKey, UnsignedDeleteKey};
use crate::blocks::issue_token::{IssueTokenTransaction, UnsignedIssueTokenTransaction};
use crate::blocks::loan_payment::{ContractPaymentTransaction, UnsignedContractPaymentTransaction};
use crate::blocks::loans::{LoanContractTransaction, UnsignedLoanContractTransaction};
@ -7,8 +8,10 @@ use crate::blocks::marketing::{MarketingTransaction, UnsignedMarketingTransactio
use crate::blocks::nft::{CreateNftTransaction, UnsignedCreateNftTransaction};
use crate::blocks::storage_key::{StorageKey, UnsignedStorageKey};
use crate::blocks::storage_values::{
StorageBool, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
UnsignedStorageBool, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
StorageBool, StorageI128, StorageI16, StorageI32, StorageI64, StorageI8, StorageString,
StorageU128, StorageU16, StorageU32, StorageU64, StorageU8, UnsignedStorageBool,
UnsignedStorageI128, UnsignedStorageI16, UnsignedStorageI32, UnsignedStorageI64,
UnsignedStorageI8, UnsignedStorageString, UnsignedStorageU128, UnsignedStorageU16,
UnsignedStorageU32, UnsignedStorageU64, UnsignedStorageU8,
};
use crate::blocks::swap::{SwapTransaction, UnsignedSwapTransaction};
@ -16,8 +19,9 @@ use crate::blocks::token::{CreateTokenTransaction, UnsignedCreateTokenTransactio
use crate::blocks::transfer::{TransferTransaction, UnsignedTransferTransaction};
use crate::blocks::vanity::{UnsignedVanityAddressTransaction, VanityAddressTransaction};
use crate::common::types::{
STORAGE_BOOL_TYPE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE, STORAGE_U16_TYPE,
STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
DELETE_KEY_TYPE, STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE,
STORAGE_I64_TYPE, STORAGE_I8_TYPE, STORAGE_KEY_TYPE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE,
};
use crate::records::memory::response_channels::Byte3;
use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION;
@ -88,6 +92,28 @@ fn read_transaction_type(transaction: &Value) -> Option<u8> {
.and_then(|value| value.get("txtype"))
.and_then(|value| value.as_u64())
})
.or_else(|| {
transaction
.get("unsigned_deletekey")
.and_then(|value| value.get("txtype"))
.and_then(|value| value.as_u64())
})
.or_else(|| {
[
"unsigned_storagei8",
"unsigned_storagei16",
"unsigned_storagei32",
"unsigned_storagei64",
"unsigned_storagei128",
]
.iter()
.find_map(|field| {
transaction
.get(field)
.and_then(|value| value.get("txtype"))
.and_then(|value| value.as_u64())
})
})
.map(|value| value as u8)
}
@ -102,6 +128,14 @@ fn read_u128_field(transaction: &Value, field: &str) -> Option<u128> {
value.to_string().parse::<u128>().ok()
}
fn read_i128_field(transaction: &Value, field: &str) -> Option<i128> {
let value = transaction.get(field)?;
if let Some(value) = value.as_i64() {
return Some(value as i128);
}
value.as_str()?.trim().parse::<i128>().ok()
}
pub async fn create_transaction_request(
command_input: String,
hashkey: Byte3,
@ -801,6 +835,213 @@ async fn create_transaction_bytes(transaction: &Value) -> Result<Vec<u8>, String
.await
.map_err(|e| format!("Failed to serialize storage-string transaction: {e}"))?
}
STORAGE_I8_TYPE => {
let payload = storage_payload(transaction, "unsigned_storagei8");
let unsigned = UnsignedStorageI8 {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
value: i8::try_from(read_i128_field(payload, "value").ok_or("Missing i8 value.")?)
.map_err(|_| "Invalid i8 value.")?,
address: payload["address"]
.as_str()
.ok_or("Missing storage writer wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
StorageI8::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize storage-i8 transaction: {e}"))?
}
STORAGE_I16_TYPE => {
let payload = storage_payload(transaction, "unsigned_storagei16");
let unsigned = UnsignedStorageI16 {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
value: i16::try_from(
read_i128_field(payload, "value").ok_or("Missing i16 value.")?,
)
.map_err(|_| "Invalid i16 value.")?,
address: payload["address"]
.as_str()
.ok_or("Missing storage writer wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
StorageI16::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize storage-i16 transaction: {e}"))?
}
STORAGE_I32_TYPE => {
let payload = storage_payload(transaction, "unsigned_storagei32");
let unsigned = UnsignedStorageI32 {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
value: i32::try_from(
read_i128_field(payload, "value").ok_or("Missing i32 value.")?,
)
.map_err(|_| "Invalid i32 value.")?,
address: payload["address"]
.as_str()
.ok_or("Missing storage writer wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
StorageI32::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize storage-i32 transaction: {e}"))?
}
STORAGE_I64_TYPE => {
let payload = storage_payload(transaction, "unsigned_storagei64");
let unsigned = UnsignedStorageI64 {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
value: i64::try_from(
read_i128_field(payload, "value").ok_or("Missing i64 value.")?,
)
.map_err(|_| "Invalid i64 value.")?,
address: payload["address"]
.as_str()
.ok_or("Missing storage writer wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
StorageI64::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize storage-i64 transaction: {e}"))?
}
STORAGE_I128_TYPE => {
let payload = storage_payload(transaction, "unsigned_storagei128");
let unsigned = UnsignedStorageI128 {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
value: read_i128_field(payload, "value").ok_or("Missing i128 value.")?,
address: payload["address"]
.as_str()
.ok_or("Missing storage writer wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
StorageI128::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize storage-i128 transaction: {e}"))?
}
DELETE_KEY_TYPE => {
let payload = storage_payload(transaction, "unsigned_deletekey");
let unsigned = UnsignedDeleteKey {
txtype,
time: read_u32_field(payload, "timestamp", "time").ok_or("Missing timestamp.")?,
storagekey: payload["storagekey"]
.as_str()
.ok_or("Missing storage key hash.")?
.to_string(),
key: payload["key"]
.as_str()
.ok_or("Missing storage field key.")?
.to_string(),
address: payload["address"]
.as_str()
.ok_or("Missing storage owner wallet address.")?
.to_string(),
txfee: payload["txfee"]
.as_u64()
.ok_or("Missing or invalid txfee.")?,
};
DeleteKey::load(
unsigned,
transaction["signature"]
.as_str()
.ok_or("Missing transaction signature.")?,
)
.await
.to_bytes()
.await
.map_err(|e| format!("Failed to serialize delete-key transaction: {e}"))?
}
_ => return Err("No such transaction type".to_string()),
};

View File

@ -569,6 +569,97 @@ async fn transaction_balance_view(
check_saved_txid: true,
})
}
Transaction::StorageI8(tx) => {
let storage = &tx.unsigned_storagei8;
let mut debits = Vec::new();
add_debit(
&mut debits,
&storage.address,
BASECOIN.clone(),
storage.txfee,
);
Ok(TransactionBalanceView {
hash: storage.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
Transaction::StorageI16(tx) => {
let storage = &tx.unsigned_storagei16;
let mut debits = Vec::new();
add_debit(
&mut debits,
&storage.address,
BASECOIN.clone(),
storage.txfee,
);
Ok(TransactionBalanceView {
hash: storage.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
Transaction::StorageI32(tx) => {
let storage = &tx.unsigned_storagei32;
let mut debits = Vec::new();
add_debit(
&mut debits,
&storage.address,
BASECOIN.clone(),
storage.txfee,
);
Ok(TransactionBalanceView {
hash: storage.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
Transaction::StorageI64(tx) => {
let storage = &tx.unsigned_storagei64;
let mut debits = Vec::new();
add_debit(
&mut debits,
&storage.address,
BASECOIN.clone(),
storage.txfee,
);
Ok(TransactionBalanceView {
hash: storage.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
Transaction::StorageI128(tx) => {
let storage = &tx.unsigned_storagei128;
let mut debits = Vec::new();
add_debit(
&mut debits,
&storage.address,
BASECOIN.clone(),
storage.txfee,
);
Ok(TransactionBalanceView {
hash: storage.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
Transaction::DeleteKey(tx) => {
let delete = &tx.unsigned_deletekey;
let mut debits = Vec::new();
add_debit(&mut debits, &delete.address, BASECOIN.clone(), delete.txfee);
Ok(TransactionBalanceView {
hash: delete.hash().await,
signatures: vec![tx.signature.clone()],
debits,
check_saved_txid: true,
})
}
}
}

View File

@ -11,6 +11,7 @@ pub mod verify_burn;
pub mod verify_collateral;
pub mod verify_create_nft;
pub mod verify_create_token;
pub mod verify_delete_key;
pub mod verify_genesis;
pub mod verify_issue_token;
pub mod verify_lender;

View File

@ -355,6 +355,84 @@ async fn verify_transaction(
}
}
}
if let Transaction::StorageI8(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for storage i8 transaction: {err}"
));
}
}
}
if let Transaction::StorageI16(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for storage i16 transaction: {err}"
));
}
}
}
if let Transaction::StorageI32(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for storage i32 transaction: {err}"
));
}
}
}
if let Transaction::StorageI64(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for storage i64 transaction: {err}"
));
}
}
}
if let Transaction::StorageI128(storage_tx) = &transaction {
match storage_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for storage i128 transaction: {err}"
));
}
}
}
if let Transaction::DeleteKey(delete_key_tx) = &transaction {
match delete_key_tx.verify(db).await {
Ok(value) => {
reserve_verified_transaction(db, &transaction, balance_tracker).await?;
return Ok(value);
}
Err(err) => {
return Err(format!(
"Validation failed for delete-key transaction: {err}"
));
}
}
}
Err(
"Validation failed: unsupported transaction variant reached verification dispatch."
.to_string(),

View File

@ -0,0 +1,98 @@
use crate::blocks::delete_key::{delete_record_keys, DeleteKey};
use crate::blocks::storage_values::{STORAGE_KEY_HASH_BYTES, STORAGE_VALUE_KEY_BYTES};
use crate::common::types::{DELETE_KEY_TYPE, STORAGE_KEY_INDEX_TREE, STORAGE_VALUE_FEE};
use crate::records::memory::mempool::BASECOIN;
use crate::records::wallet_registry::{
require_canonical_registered_short_address, resolve_pubkey_from_short_address,
};
use crate::verifications::async_funcs::checks::balance_check::balance_checkup;
use crate::verifications::async_funcs::checks::mempool_check::memcheck;
use crate::verifications::async_funcs::checks::verify_db::db_hex_verification;
use crate::wallets::structures::Wallet;
use crate::{decode, encode, sled::Db};
impl DeleteKey {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
let delete = &self.unsigned_deletekey;
let hash = delete.hash().await;
// Existing mempool entries short-circuit before the storage-state checks.
if memcheck(&self.signature, &hash).await {
return Ok(self.signature.clone());
}
if delete.txtype != DELETE_KEY_TYPE {
return Err("Delete-key transaction type is invalid.".to_string());
}
// The namespace must be a previously confirmed StorageKey transaction.
let storagekey = decode(&delete.storagekey)
.map_err(|err| format!("Delete-key storage hash is invalid: {err}"))?;
if storagekey.len() != STORAGE_KEY_HASH_BYTES {
return Err("Delete-key storage hash length is invalid.".to_string());
}
if delete.key.as_bytes().len() != STORAGE_VALUE_KEY_BYTES {
return Err("Delete-key field key length is invalid.".to_string());
}
let storage_index = db
.open_tree(STORAGE_KEY_INDEX_TREE)
.map_err(|err| format!("Could not open storage key index: {err}"))?;
if !storage_index
.contains_key(&storagekey)
.map_err(|err| format!("Could not check storage key index: {err}"))?
{
return Err("Delete-key storage key does not exist.".to_string());
}
// Only the registered owner of this address-scoped data can delete it.
if !Wallet::short_address_validation(&delete.address) {
return Err("Delete-key wallet address is invalid.".to_string());
}
require_canonical_registered_short_address(
db,
&delete.address,
"Delete-key wallet address",
)?;
let public_key = resolve_pubkey_from_short_address(db, &delete.address)
.map_err(|_| "Delete-key wallet address is not registered.".to_string())?
.ok_or_else(|| "Delete-key wallet address is not registered.".to_string())?;
if !Wallet::verify_transaction_with_public_key(&hash, &self.signature, &encode(public_key))
.await
{
return Err("Invalid signature for the Delete Key Transaction.".to_string());
}
if delete.txfee < STORAGE_VALUE_FEE {
return Err(format!(
"Delete-key transaction fee is below the minimum required fee of {STORAGE_VALUE_FEE}."
));
}
if !balance_checkup(db, 0, delete.txfee, BASECOIN.clone(), &delete.address).await {
return Err("Insuficient funds for this Delete Key Transaction!".to_string());
}
if !db_hex_verification(db, "txid", &hash).await {
return Err("This transaction already exists.".to_string());
}
// Reject no-op deletes. Base keys inspect the scalar and all 20 string chunks;
// explicit _00 through _19 keys inspect only that exact chunk.
let tree = db
.open_tree(&storagekey)
.map_err(|err| format!("Could not open storage tree: {err}"))?;
let targets = delete_record_keys(&delete.address, &delete.key)?;
let mut found = false;
for target in targets {
if tree
.contains_key(target)
.map_err(|err| format!("Could not inspect storage record: {err}"))?
{
found = true;
break;
}
}
if !found {
return Err("Delete-key target does not exist.".to_string());
}
Ok(String::new())
}
}

View File

@ -1,10 +1,12 @@
use crate::blocks::storage_values::{
storage_string_record_key, StorageBool, StorageString, StorageU128, StorageU16, StorageU32,
StorageU64, StorageU8, StorageValueTransaction, STORAGE_KEY_HASH_BYTES,
STORAGE_STRING_VALUE_BYTES, STORAGE_VALUE_KEY_BYTES,
storage_string_record_key, StorageBool, StorageI128, StorageI16, StorageI32, StorageI64,
StorageI8, StorageString, StorageU128, StorageU16, StorageU32, StorageU64, StorageU8,
StorageValueTransaction, STORAGE_KEY_HASH_BYTES, STORAGE_STRING_VALUE_BYTES,
STORAGE_VALUE_KEY_BYTES,
};
use crate::common::types::{
STORAGE_BOOL_TYPE, STORAGE_KEY_INDEX_TREE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_BOOL_TYPE, STORAGE_I128_TYPE, STORAGE_I16_TYPE, STORAGE_I32_TYPE, STORAGE_I64_TYPE,
STORAGE_I8_TYPE, STORAGE_KEY_INDEX_TREE, STORAGE_STRING_TYPE, STORAGE_U128_TYPE,
STORAGE_U16_TYPE, STORAGE_U32_TYPE, STORAGE_U64_TYPE, STORAGE_U8_TYPE, STORAGE_VALUE_FEE,
};
use crate::decode;
@ -151,6 +153,36 @@ impl StorageU128 {
}
}
impl StorageI8 {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
verify_storage_value(self, db, STORAGE_I8_TYPE, "Storage I8").await
}
}
impl StorageI16 {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
verify_storage_value(self, db, STORAGE_I16_TYPE, "Storage I16").await
}
}
impl StorageI32 {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
verify_storage_value(self, db, STORAGE_I32_TYPE, "Storage I32").await
}
}
impl StorageI64 {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
verify_storage_value(self, db, STORAGE_I64_TYPE, "Storage I64").await
}
}
impl StorageI128 {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
verify_storage_value(self, db, STORAGE_I128_TYPE, "Storage I128").await
}
}
impl StorageString {
pub async fn verify(&self, db: &Db) -> Result<String, String> {
// String storage has extra chain-linking rules before it can use