|
1 | 1 | use crate::bootloader::errors::BootloaderInterfaceError; |
2 | 2 | use crate::bootloader::runner::{run_till_completion, RunnerMemoryBuffers}; |
| 3 | +use crypto::sha3::{Digest, Keccak256}; |
3 | 4 | use errors::BootloaderSubsystemError; |
| 5 | +use system_hooks::addresses_constants::{ |
| 6 | + L2_ASSET_TRACKER_ADDRESS, L2_BASE_TOKEN_HOLDER_ADDRESS, L2_CHAIN_ASSET_HANDLER_ADDRESS, |
| 7 | +}; |
4 | 8 | use zk_ee::common_structs::system_hooks::HooksStorage; |
5 | 9 | use zk_ee::system::errors::subsystem::SubsystemError; |
6 | 10 | use zk_ee::system::errors::{runtime::RuntimeError, system::SystemError}; |
7 | 11 | use zk_ee::system::CallModifier; |
8 | 12 | use zk_ee::system::{EthereumLikeTypes, System}; |
| 13 | +use zk_ee::utils::Bytes32; |
9 | 14 | use zk_ee::{interface_error, internal_error, wrap_error}; |
10 | 15 |
|
11 | 16 | use super::*; |
@@ -53,6 +58,139 @@ where |
53 | 58 | } |
54 | 59 | })?; |
55 | 60 |
|
| 61 | + Self::update_saved_total_supply(system, resources)?; |
| 62 | + |
| 63 | + Ok(()) |
| 64 | + } |
| 65 | + |
| 66 | + /// Computes keccak256(abi.encode(key, base_slot)) for Solidity mapping slot derivation. |
| 67 | + fn solidity_mapping_slot(key: &[u8; 32], base_slot: u64) -> Bytes32 { |
| 68 | + let mut hasher = Keccak256::new(); |
| 69 | + hasher.update(key); |
| 70 | + hasher.update(&U256::from(base_slot).to_be_bytes::<32>()); |
| 71 | + Bytes32::from_array(hasher.finalize().into()) |
| 72 | + } |
| 73 | + |
| 74 | + /// Replicates the L2AssetTracker's `_getOrSaveTotalSupply` logic for the base token. |
| 75 | + /// |
| 76 | + /// On each mint, checks whether `savedTotalSupply[migrationNumber][baseTokenAssetId]` |
| 77 | + /// has been recorded in the L2AssetTracker contract. If not, computes the current |
| 78 | + /// total supply from the L2BaseTokenZKOS contract and writes it. |
| 79 | + /// |
| 80 | + /// This is needed because zksync-os mints tokens natively (bypassing the Solidity |
| 81 | + /// L2BaseToken.mint() which would normally call handleFinalizeBaseTokenBridgingOnL2 |
| 82 | + /// on the L2AssetTracker). |
| 83 | + fn update_saved_total_supply( |
| 84 | + system: &mut System<S>, |
| 85 | + resources: &mut S::Resources, |
| 86 | + ) -> Result<(), BootloaderSubsystemError> |
| 87 | + where |
| 88 | + S::IO: IOSubsystemExt, |
| 89 | + { |
| 90 | + let ee = ExecutionEnvironmentType::EVM; |
| 91 | + |
| 92 | + // 1. Read BASE_TOKEN_ASSET_ID from L2AssetTracker (slot 155) |
| 93 | + let base_token_asset_id = system.io.storage_read::<false>( |
| 94 | + ee, |
| 95 | + resources, |
| 96 | + &L2_ASSET_TRACKER_ADDRESS, |
| 97 | + &Bytes32::from_u256_be(&U256::from(155)), |
| 98 | + )?; |
| 99 | + if base_token_asset_id.is_zero() { |
| 100 | + // Not initialized yet (before genesis upgrade), skip |
| 101 | + return Ok(()); |
| 102 | + } |
| 103 | + |
| 104 | + // 2. Read migrationNumber[chainId] from L2ChainAssetHandler (base slot 207) |
| 105 | + let chain_id = system.get_chain_id(); |
| 106 | + let chain_id_bytes = U256::from(chain_id).to_be_bytes::<32>(); |
| 107 | + let migration_number_slot = |
| 108 | + Self::solidity_mapping_slot(&chain_id_bytes, 207); |
| 109 | + let migration_number = system.io.storage_read::<false>( |
| 110 | + ee, |
| 111 | + resources, |
| 112 | + &L2_CHAIN_ASSET_HANDLER_ADDRESS, |
| 113 | + &migration_number_slot, |
| 114 | + )?; |
| 115 | + |
| 116 | + // 3. Compute savedTotalSupply[migrationNumber][baseTokenAssetId] slot |
| 117 | + // level1 = keccak256(abi.encode(migrationNumber, 156)) |
| 118 | + // level2 = keccak256(abi.encode(baseTokenAssetId, level1)) |
| 119 | + let migration_number_bytes = migration_number.as_u8_array(); |
| 120 | + let level1_slot = |
| 121 | + Self::solidity_mapping_slot(&migration_number_bytes, 156); |
| 122 | + let level1_bytes = level1_slot.as_u8_array(); |
| 123 | + let asset_id_bytes = base_token_asset_id.as_u8_array(); |
| 124 | + let mut hasher = Keccak256::new(); |
| 125 | + hasher.update(&asset_id_bytes); |
| 126 | + hasher.update(&level1_bytes); |
| 127 | + let struct_base_slot = Bytes32::from_array(hasher.finalize().into()); |
| 128 | + |
| 129 | + // 4. Read isSaved (bool at struct_base_slot + 0) |
| 130 | + let is_saved_value = system.io.storage_read::<false>( |
| 131 | + ee, |
| 132 | + resources, |
| 133 | + &L2_ASSET_TRACKER_ADDRESS, |
| 134 | + &struct_base_slot, |
| 135 | + )?; |
| 136 | + if !is_saved_value.is_zero() { |
| 137 | + // Already saved, nothing to do |
| 138 | + return Ok(()); |
| 139 | + } |
| 140 | + |
| 141 | + // 5. Compute totalSupply = _zkosPreV31TotalSupply + (INITIAL_BASE_TOKEN_HOLDER_BALANCE - holderBalance) |
| 142 | + // _zkosPreV31TotalSupply is at slot 2 of L2BaseToken (0x800a) |
| 143 | + let pre_v31_supply_bytes = system.io.storage_read::<false>( |
| 144 | + ee, |
| 145 | + resources, |
| 146 | + &system_hooks::addresses_constants::L2_BASE_TOKEN_ADDRESS, |
| 147 | + &Bytes32::from_u256_be(&U256::from(2)), |
| 148 | + )?; |
| 149 | + let pre_v31_supply = pre_v31_supply_bytes.into_u256_be(); |
| 150 | + |
| 151 | + // INITIAL_BASE_TOKEN_HOLDER_BALANCE = 2^127 - 1 |
| 152 | + let initial_holder_balance: U256 = (U256::from(1) << 127) - U256::from(1); |
| 153 | + |
| 154 | + // Read L2_BASE_TOKEN_HOLDER native balance |
| 155 | + let holder_balance_u256 = system |
| 156 | + .io |
| 157 | + .get_nominal_token_balance(ee, resources, &L2_BASE_TOKEN_HOLDER_ADDRESS)?; |
| 158 | + |
| 159 | + let total_supply = pre_v31_supply |
| 160 | + .checked_add( |
| 161 | + initial_holder_balance |
| 162 | + .checked_sub(holder_balance_u256) |
| 163 | + .unwrap_or(U256::ZERO), |
| 164 | + ) |
| 165 | + .unwrap_or(U256::ZERO); |
| 166 | + |
| 167 | + // 6. Write savedTotalSupply[migrationNumber][assetId] = {isSaved: true, amount: totalSupply} |
| 168 | + // Struct slot 0: isSaved (bool, value = 1) |
| 169 | + // Struct slot 1: amount (uint256) |
| 170 | + system.io.storage_write::<false>( |
| 171 | + ee, |
| 172 | + resources, |
| 173 | + &L2_ASSET_TRACKER_ADDRESS, |
| 174 | + &struct_base_slot, |
| 175 | + &Bytes32::from_u256_be(&U256::from(1)), // isSaved = true |
| 176 | + )?; |
| 177 | + |
| 178 | + // Compute struct_base_slot + 1 for the amount field |
| 179 | + let amount_slot_u256 = struct_base_slot.into_u256_be() + U256::from(1); |
| 180 | + let amount_slot = Bytes32::from_u256_be(&amount_slot_u256); |
| 181 | + system.io.storage_write::<false>( |
| 182 | + ee, |
| 183 | + resources, |
| 184 | + &L2_ASSET_TRACKER_ADDRESS, |
| 185 | + &amount_slot, |
| 186 | + &Bytes32::from_u256_be(&total_supply), |
| 187 | + )?; |
| 188 | + |
| 189 | + #[cfg(not(target_arch = "riscv32"))] |
| 190 | + let _ = system.get_logger().write_fmt(format_args!( |
| 191 | + "Saved total supply {total_supply:?} for base token in L2AssetTracker\n" |
| 192 | + )); |
| 193 | + |
56 | 194 | Ok(()) |
57 | 195 | } |
58 | 196 |
|
|
0 commit comments