use sqlx::types::chrono::Utc;
use zksync_db_connection::{
connection::Connection,
error::DalResult,
instrument::{CopyStatement, InstrumentExt},
write_str, writeln_str,
};
use zksync_types::{tokens::TokenInfo, Address, L2BlockNumber};
use crate::{Core, CoreDal};
#[derive(Debug)]
pub struct TokensDal<'a, 'c> {
pub(crate) storage: &'a mut Connection<'c, Core>,
}
impl TokensDal<'_, '_> {
pub async fn add_tokens(&mut self, tokens: &[TokenInfo]) -> DalResult<()> {
let tokens_len = tokens.len();
let copy = CopyStatement::new(
"COPY tokens (l1_address, l2_address, name, symbol, decimals, well_known, created_at, updated_at)
FROM STDIN WITH (DELIMITER '|')",
)
.instrument("add_tokens")
.with_arg("tokens.len", &tokens_len)
.start(self.storage)
.await?;
let mut buffer = String::new();
let now = Utc::now().naive_utc().to_string();
for token_info in tokens {
write_str!(
&mut buffer,
"\\\\x{:x}|\\\\x{:x}|",
token_info.l1_address,
token_info.l2_address
);
writeln_str!(
&mut buffer,
"{}|{}|{}|FALSE|{now}|{now}",
token_info.metadata.name,
token_info.metadata.symbol,
token_info.metadata.decimals
);
}
copy.send(buffer.as_bytes()).await
}
pub async fn mark_token_as_well_known(&mut self, l1_address: Address) -> DalResult<()> {
sqlx::query!(
r#"
UPDATE tokens
SET
well_known = TRUE,
updated_at = NOW()
WHERE
l1_address = $1
"#,
l1_address.as_bytes()
)
.instrument("mark_token_as_well_known")
.with_arg("l1_address", &l1_address)
.execute(self.storage)
.await?;
Ok(())
}
pub async fn get_all_l2_token_addresses(&mut self) -> DalResult<Vec<Address>> {
let rows = sqlx::query!(
r#"
SELECT
l2_address
FROM
tokens
"#
)
.instrument("get_all_l2_token_addresses")
.report_latency()
.fetch_all(self.storage)
.await?;
Ok(rows
.into_iter()
.map(|row| Address::from_slice(&row.l2_address))
.collect())
}
pub async fn roll_back_tokens(&mut self, block_number: L2BlockNumber) -> DalResult<()> {
let all_token_addresses = self.get_all_l2_token_addresses().await?;
let token_deployment_data = self
.storage
.storage_logs_dal()
.filter_deployed_contracts(all_token_addresses.iter().copied(), None)
.await?;
let token_addresses_to_be_removed: Vec<_> = all_token_addresses
.into_iter()
.filter_map(|address| {
if address.is_zero() {
None
} else if let Some(deployed_at) = token_deployment_data.get(&address) {
(deployed_at > &block_number).then_some(address.0)
} else {
Some(address.0)
}
})
.collect();
sqlx::query!(
r#"
DELETE FROM tokens
WHERE
l2_address = ANY ($1)
"#,
&token_addresses_to_be_removed as &[_]
)
.instrument("roll_back_tokens")
.with_arg("block_number", &block_number)
.with_arg(
"token_addresses_to_be_removed.len",
&token_addresses_to_be_removed.len(),
)
.execute(self.storage)
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashSet, slice};
use zksync_system_constants::FAILED_CONTRACT_DEPLOYMENT_BYTECODE_HASH;
use zksync_types::{get_code_key, tokens::TokenMetadata, ProtocolVersion, StorageLog, H256};
use super::*;
use crate::{tests::create_l2_block_header, ConnectionPool, Core, CoreDal};
fn test_token_info() -> TokenInfo {
TokenInfo {
l1_address: Address::repeat_byte(1),
l2_address: Address::repeat_byte(2),
metadata: TokenMetadata {
name: "Test".to_string(),
symbol: "TST".to_string(),
decimals: 10,
},
}
}
fn eth_token_info() -> TokenInfo {
TokenInfo {
l1_address: Address::repeat_byte(0),
l2_address: Address::repeat_byte(0),
metadata: TokenMetadata {
name: "Ether".to_string(),
symbol: "ETH".to_string(),
decimals: 18,
},
}
}
async fn insert_l2_block(conn: &mut Connection<'_, Core>, number: u32, logs: Vec<StorageLog>) {
conn.blocks_dal()
.insert_l2_block(&create_l2_block_header(number))
.await
.unwrap();
conn.storage_logs_dal()
.insert_storage_logs(L2BlockNumber(number), &logs)
.await
.unwrap();
}
#[tokio::test]
async fn adding_and_getting_tokens() {
let pool = ConnectionPool::<Core>::test_pool().await;
let mut storage = pool.connection().await.unwrap();
storage
.protocol_versions_dal()
.save_protocol_version_with_tx(&ProtocolVersion::default())
.await
.unwrap();
let tokens = [test_token_info(), eth_token_info()];
storage.tokens_dal().add_tokens(&tokens).await.unwrap();
let storage_logs: Vec<_> = tokens
.iter()
.map(|token_info| {
StorageLog::new_write_log(
get_code_key(&token_info.l2_address),
H256::repeat_byte(0xff),
)
})
.collect();
insert_l2_block(&mut storage, 1, storage_logs).await;
let token_addresses = storage
.tokens_dal()
.get_all_l2_token_addresses()
.await
.unwrap();
assert_eq!(
token_addresses.into_iter().collect::<HashSet<_>>(),
tokens
.iter()
.map(|token| token.l2_address)
.collect::<HashSet<_>>(),
);
let all_tokens = storage
.tokens_web3_dal()
.get_all_tokens(None)
.await
.unwrap();
assert_eq!(all_tokens.len(), 2);
assert!(all_tokens.contains(&tokens[0]));
assert!(all_tokens.contains(&tokens[1]));
for token in &tokens {
storage
.tokens_dal()
.mark_token_as_well_known(token.l1_address)
.await
.unwrap();
}
let well_known_tokens = storage
.tokens_web3_dal()
.get_well_known_tokens()
.await
.unwrap();
assert_eq!(well_known_tokens.len(), 2);
assert!(well_known_tokens.contains(&tokens[0]));
assert!(well_known_tokens.contains(&tokens[1]));
}
#[tokio::test]
async fn rolling_back_tokens() {
let pool = ConnectionPool::<Core>::test_pool().await;
let mut storage = pool.connection().await.unwrap();
storage
.protocol_versions_dal()
.save_protocol_version_with_tx(&ProtocolVersion::default())
.await
.unwrap();
let eth_info = eth_token_info();
let eth_deployment_log =
StorageLog::new_write_log(get_code_key(ð_info.l2_address), H256::repeat_byte(1));
storage
.tokens_dal()
.add_tokens(slice::from_ref(ð_info))
.await
.unwrap();
insert_l2_block(&mut storage, 0, vec![eth_deployment_log]).await;
let test_info = test_token_info();
let test_deployment_log =
StorageLog::new_write_log(get_code_key(&test_info.l2_address), H256::repeat_byte(2));
storage
.tokens_dal()
.add_tokens(slice::from_ref(&test_info))
.await
.unwrap();
insert_l2_block(&mut storage, 2, vec![test_deployment_log]).await;
test_getting_all_tokens(&mut storage).await;
storage
.tokens_dal()
.roll_back_tokens(L2BlockNumber(2))
.await
.unwrap();
assert_eq!(
storage
.tokens_dal()
.get_all_l2_token_addresses()
.await
.unwrap(),
[eth_info.l2_address, test_info.l2_address]
);
storage
.tokens_dal()
.roll_back_tokens(L2BlockNumber(1))
.await
.unwrap();
assert_eq!(
storage
.tokens_dal()
.get_all_l2_token_addresses()
.await
.unwrap(),
[eth_info.l2_address]
);
}
async fn test_getting_all_tokens(storage: &mut Connection<'_, Core>) {
for at_l2_block in [None, Some(L2BlockNumber(2)), Some(L2BlockNumber(100))] {
let all_tokens = storage
.tokens_web3_dal()
.get_all_tokens(at_l2_block)
.await
.unwrap();
assert_eq!(all_tokens.len(), 2);
assert!(all_tokens.contains(ð_token_info()));
assert!(all_tokens.contains(&test_token_info()));
}
for at_l2_block in [L2BlockNumber(0), L2BlockNumber(1)] {
let all_tokens = storage
.tokens_web3_dal()
.get_all_tokens(Some(at_l2_block))
.await
.unwrap();
assert_eq!(all_tokens, [eth_token_info()]);
}
}
#[tokio::test]
async fn rolling_back_tokens_with_failed_deployment() {
let pool = ConnectionPool::<Core>::test_pool().await;
let mut storage = pool.connection().await.unwrap();
let test_info = test_token_info();
let failed_deployment_log = StorageLog::new_write_log(
get_code_key(&test_info.l2_address),
FAILED_CONTRACT_DEPLOYMENT_BYTECODE_HASH,
);
storage
.storage_logs_dal()
.insert_storage_logs(L2BlockNumber(1), &[failed_deployment_log])
.await
.unwrap();
let test_deployment_log =
StorageLog::new_write_log(get_code_key(&test_info.l2_address), H256::repeat_byte(2));
storage
.storage_logs_dal()
.insert_storage_logs(L2BlockNumber(100), &[test_deployment_log])
.await
.unwrap();
storage
.tokens_dal()
.add_tokens(slice::from_ref(&test_info))
.await
.unwrap();
assert_eq!(
storage
.tokens_dal()
.get_all_l2_token_addresses()
.await
.unwrap(),
[test_info.l2_address]
);
storage
.tokens_dal()
.roll_back_tokens(L2BlockNumber(99))
.await
.unwrap();
assert_eq!(
storage
.tokens_dal()
.get_all_l2_token_addresses()
.await
.unwrap(),
[]
);
}
}