Skip to content

Commit 0a5b7e5

Browse files
committed
feat: add update of saved total supply on first mint after migration
1 parent 8342153 commit 0a5b7e5

File tree

2 files changed

+146
-0
lines changed

2 files changed

+146
-0
lines changed

basic_bootloader/src/bootloader/run_single_interaction.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
use crate::bootloader::errors::BootloaderInterfaceError;
22
use crate::bootloader::runner::{run_till_completion, RunnerMemoryBuffers};
3+
use crypto::sha3::{Digest, Keccak256};
34
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+
};
48
use zk_ee::common_structs::system_hooks::HooksStorage;
59
use zk_ee::system::errors::subsystem::SubsystemError;
610
use zk_ee::system::errors::{runtime::RuntimeError, system::SystemError};
711
use zk_ee::system::CallModifier;
812
use zk_ee::system::{EthereumLikeTypes, System};
13+
use zk_ee::utils::Bytes32;
914
use zk_ee::{interface_error, internal_error, wrap_error};
1015

1116
use super::*;
@@ -53,6 +58,139 @@ where
5358
}
5459
})?;
5560

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+
56194
Ok(())
57195
}
58196

system_hooks/src/addresses_constants.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,11 @@ pub const COMPRESSOR_ADDRESS: B160 = B160::from_limbs([0x800e, 0, 0]);
4242
pub const COMPLEX_UPGRADER_ADDRESS: B160 = B160::from_limbs([0x800f, 0, 0]);
4343
pub const KECCAK_SYSTEM_CONTRACT_ADDRESS: B160 = B160::from_limbs([0x8010, 0, 0]);
4444
pub const PUBDATA_CHUNK_PUBLISHER_ADDRESS: B160 = B160::from_limbs([0x8011, 0, 0]);
45+
46+
// Built-in user space contracts (offset 0x10000)
47+
/// L2 Chain Asset Handler address (0x1000a)
48+
pub const L2_CHAIN_ASSET_HANDLER_ADDRESS: B160 = B160::from_limbs([0x1000a, 0, 0]);
49+
/// L2 Asset Tracker address (0x1000f)
50+
pub const L2_ASSET_TRACKER_ADDRESS: B160 = B160::from_limbs([0x1000f, 0, 0]);
51+
/// L2 Base Token Holder address (0x10011)
52+
pub const L2_BASE_TOKEN_HOLDER_ADDRESS: B160 = B160::from_limbs([0x10011, 0, 0]);

0 commit comments

Comments
 (0)