Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ZK as gateway token #951

Open
wants to merge 30 commits into
base: release-v26-1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2511c27
(test)
Oct 8, 2024
bbb0d8c
(fix): still in progress but working
Oct 16, 2024
17b7464
(feat): refactored distribution of base ERC token
Oct 21, 2024
d5316d9
(fix): formatting
Oct 21, 2024
ae5b8c1
(fix): small fixes
Oct 21, 2024
d739168
(fix): revert with error if duplicate asset id registration is attempted
Oct 22, 2024
7f515e0
(fix): cleanup the deploy zk script
Oct 22, 2024
bc22869
(fix): update scripts
Oct 22, 2024
167dafc
(fix): distribution amounts
Oct 22, 2024
1dbbf05
(fix): add script to fund chain governor
Oct 23, 2024
c9769a0
better finalization script
StanislavBreadless Jan 23, 2025
3d4083a
Merge remote-tracking branch 'origin/release-v26' into sb-better-fina…
StanislavBreadless Jan 23, 2025
0dcccc6
fmt
StanislavBreadless Jan 23, 2025
186f3a1
scripts for finalization
StanislavBreadless Jan 24, 2025
d16682f
add script for emergency upgrade on staging env
StanislavBreadless Jan 28, 2025
402327e
env vars
StanislavBreadless Feb 3, 2025
cdb22f8
fmt for scripts
StanislavBreadless Feb 3, 2025
492fd23
typos
StanislavBreadless Feb 3, 2025
decf61c
Merge branch 'release-v26' into sb-better-finalization
StanislavBreadless Feb 3, 2025
f7ecdb9
fix: add attester to attestation data (#1227)
QEDK Feb 20, 2025
fb5a1cf
Bridgehub operations by ChainAdmin
sanekmelnikov Feb 21, 2025
fb9618d
lint
sanekmelnikov Feb 21, 2025
bd79f47
args
sanekmelnikov Feb 21, 2025
2489b4b
Merge branch 'alm-bridgehub-admin' into sb-latest-release-with-correc…
StanislavBreadless Feb 21, 2025
c528fa6
add testnet data
StanislavBreadless Feb 27, 2025
46a5fff
fmt
StanislavBreadless Feb 27, 2025
4af7b51
Merge branch 'release-v26-1' into sb-latest-release-with-correct-sciprt
StanislavBreadless Feb 27, 2025
352e0dd
Merge commit '4af7b51' into sb-add-testnet-data
StanislavBreadless Mar 3, 2025
159f810
sync with the upstream
StanislavBreadless Mar 5, 2025
52fa173
Merge branch 'release-v26-1' into ra/zk-as-gateway-token
StanislavBreadless Mar 5, 2025
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: 3 additions & 0 deletions l1-contracts/.env
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ ETH_SENDER_SENDER_OPERATOR_BLOBS_ETH_ADDR=0x000000000000000000000000000000000000
CONTRACTS_SHARED_BRIDGE_UPGRADE_STORAGE_SWITCH=0
CONTRACTS_MAX_NUMBER_OF_ZK_CHAINS=100
L1_CONFIG=/script-config/config-deploy-l1.toml
L2_CONFIG=/script-config/config-deploy-l2-contracts.toml
L1_OUTPUT=/script-out/output-deploy-l1.toml
TOKENS_CONFIG=/script-config/config-deploy-erc20.toml
ZK_TOKEN_CONFIG=/script-config/config-deploy-zk.toml
ZK_TOKEN_OUTPUT=/script-out/output-deploy-zk-token.toml
ZK_CHAIN_CONFIG=/script-config/register-zk-chain.toml
ZK_CHAIN_OUTPUT=/script-out/output-deploy-zk-chain-era.toml
FORCE_DEPLOYMENTS_CONFIG=/script-config/generate-force-deployments-data.toml
Expand Down
3 changes: 3 additions & 0 deletions l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2
/// @dev It does not perform any checks for the correctnesss of the token contract.
/// @param _nativeToken The address of the token to be registered.
function _unsafeRegisterNativeToken(address _nativeToken) internal {
if (assetId[_nativeToken] != bytes32(0)) {
return;
}
bytes32 newAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _nativeToken);
ASSET_ROUTER.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_nativeToken))), address(this));
tokenAddress[newAssetId] = _nativeToken;
Expand Down
269 changes: 269 additions & 0 deletions l1-contracts/deploy-scripts/DeployZKAndBridgeToL1.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

// solhint-disable no-console

import {Vm} from "forge-std/Vm.sol";
import {Script, console2 as console} from "forge-std/Script.sol";
import {stdToml} from "forge-std/StdToml.sol";

// It's required to disable lints to force the compiler to compile the contracts
// solhint-disable no-unused-import
import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol";
// solhint-disable no-unused-import
import {WETH9} from "contracts/dev-contracts/WETH9.sol";

import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol";

import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol";
import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol";
import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol";
import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol";
import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol";
import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol";
import {Utils} from "./Utils.sol";
import {MintFailed} from "./ZkSyncScriptErrors.sol";

contract DeployZKScript is Script {
using stdToml for string;

struct Config {
TokenDescription zkToken;
address deployerAddress;
address[] additionalAddressesForMinting;
address create2FactoryAddr;
bytes32 create2FactorySalt;
uint256 chainId;
address l1SharedBridge;
address bridgehub;
address l1Nullifier;
address chainAdmin;
address governance;
address deployer;
address owner;
address anotherOwner;
}

struct TokenDescription {
address addr;
string name;
string symbol;
uint256 decimals;
string implementation;
uint256 mint;
bytes32 assetId;
}

Config internal config;

function run() public {
initializeConfig();
deployZkToken();
saveOutput();
}

function getTokenAddress() public view returns (address) {
return config.zkToken.addr;
}

function initializeConfig() internal {
config.deployerAddress = msg.sender;

string memory root = vm.projectRoot();

// Grab config from output of l1 deployment
string memory path = string.concat(root, vm.envString("TOKENS_CONFIG"));
string memory toml = vm.readFile(path);

config.additionalAddressesForMinting = vm.parseTomlAddressArray(toml, "$.additional_addresses_for_minting");

// Parse the ZK token configuration
string memory key = "$.tokens.ZK";
config.zkToken.name = toml.readString(string.concat(key, ".name"));
config.zkToken.symbol = toml.readString(string.concat(key, ".symbol"));
config.zkToken.decimals = toml.readUint(string.concat(key, ".decimals"));
config.zkToken.implementation = toml.readString(string.concat(key, ".implementation"));
config.zkToken.mint = toml.readUint(string.concat(key, ".mint"));

// Grab config from custom config file
path = string.concat(root, vm.envString("ZK_CHAIN_CONFIG"));
toml = vm.readFile(path);

config.bridgehub = toml.readAddress("$.deployed_addresses.bridgehub.bridgehub_proxy_addr");
config.l1SharedBridge = toml.readAddress("$.deployed_addresses.bridges.shared_bridge_proxy_addr");
config.l1Nullifier = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr");
config.chainId = toml.readUint("$.chain.chain_chain_id");
}

function initializeAdditionalConfig() internal {
string memory root = vm.projectRoot();
string memory path = string.concat(root, vm.envString("L1_OUTPUT"));
string memory toml = vm.readFile(path);

config.owner = toml.readAddress("$.owner_address");
}

function deployZkToken() internal {
uint256 someBigAmount = 100000000000000000000000000000000;
TokenDescription storage token = config.zkToken;
console.log("Deploying token:", token.name);

vm.startBroadcast();
address zkTokenAddress = deployErc20({
name: token.name,
symbol: token.symbol,
decimals: token.decimals,
implementation: token.implementation,
mint: token.mint,
additionalAddressesForMinting: config.additionalAddressesForMinting
});
console.log("Token deployed at:", zkTokenAddress);
token.addr = zkTokenAddress;
address deployer = msg.sender;
TestnetERC20Token zkToken = TestnetERC20Token(zkTokenAddress);
zkToken.mint(deployer, someBigAmount);
uint256 deployerBalance = zkToken.balanceOf(deployer);
console.log("Deployer balance:", deployerBalance);
L2AssetRouter l2AR = L2AssetRouter(L2_ASSET_ROUTER_ADDR);
L2NativeTokenVault l2NTV = L2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR);
l2NTV.registerToken(zkTokenAddress);
bytes32 zkTokenAssetId = l2NTV.assetId(zkTokenAddress);
config.zkToken.assetId = zkTokenAssetId;
console.log("zkTokenAssetId:", uint256(zkTokenAssetId));
zkToken.approve(L2_NATIVE_TOKEN_VAULT_ADDR, someBigAmount);
vm.stopBroadcast();

vm.broadcast();
l2AR.withdraw(zkTokenAssetId, abi.encode(someBigAmount, deployer));
uint256 deployerBalanceAfterWithdraw = zkToken.balanceOf(deployer);
console.log("Deployed balance after withdraw:", deployerBalanceAfterWithdraw);
}

/// TODO(EVM-748): make that function support non-ETH based chains
function supplyEraWallet(address addr, uint256 amount) public {
initializeConfig();

Utils.runL1L2Transaction(
hex"",
Utils.MAX_PRIORITY_TX_GAS,
amount,
new bytes[](0),
addr,
config.chainId,
config.bridgehub,
config.l1SharedBridge
);
}

function finalizeZkTokenWithdrawal(
uint256 _chainId,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes memory _message,
bytes32[] memory _merkleProof
) public {
initializeConfig();

L1Nullifier l1Nullifier = L1Nullifier(config.l1Nullifier);

vm.broadcast();
l1Nullifier.finalizeDeposit(
FinalizeL1DepositParams({
chainId: _chainId,
l2BatchNumber: _l2BatchNumber,
l2MessageIndex: _l2MessageIndex,
l2Sender: L2_ASSET_ROUTER_ADDR,
l2TxNumberInBatch: _l2TxNumberInBatch,
message: _message,
merkleProof: _merkleProof
})
);
}

function saveL1Address() public {
initializeConfig();
initializeAdditionalConfig();

string memory root = vm.projectRoot();
string memory path = string.concat(root, vm.envString("ZK_TOKEN_OUTPUT"));

string memory toml = vm.readFile(path);

bytes32 zkTokenAssetId = toml.readBytes32("$.ZK.assetId");

L1AssetRouter l1AR = L1AssetRouter(config.l1SharedBridge);
console.log("L1 AR address", address(l1AR));
IL1NativeTokenVault nativeTokenVault = IL1NativeTokenVault(address(l1AR.nativeTokenVault()));
address l1ZKAddress = nativeTokenVault.tokenAddress(zkTokenAssetId);
console.log("L1 ZK address", l1ZKAddress);
TestnetERC20Token l1ZK = TestnetERC20Token(l1ZKAddress);

uint256 balance = l1ZK.balanceOf(config.deployerAddress);
vm.broadcast();
l1ZK.transfer(config.owner, balance / 2);
string memory tokenInfo = vm.serializeAddress("ZK", "l1Address", l1ZKAddress);
vm.writeToml(tokenInfo, path, ".ZK.l1Address");
}

function deployErc20(
string memory name,
string memory symbol,
uint256 decimals,
string memory implementation,
uint256 mint,
address[] storage additionalAddressesForMinting
) internal returns (address) {
bytes memory args;
// WETH9 constructor has no arguments
if (keccak256(bytes(implementation)) != keccak256(bytes("WETH9.sol"))) {
args = abi.encode(name, symbol, decimals);
}

bytes memory bytecode = abi.encodePacked(vm.getCode(implementation), args);

address tokenAddress = address(new TestnetERC20Token(name, symbol, uint8(decimals))); // No salt for testing

if (mint > 0) {
additionalAddressesForMinting.push(config.deployerAddress);
uint256 addressMintListLength = additionalAddressesForMinting.length;
for (uint256 i = 0; i < addressMintListLength; ++i) {
(bool success, ) = tokenAddress.call(
abi.encodeWithSignature("mint(address,uint256)", additionalAddressesForMinting[i], mint)
);
if (!success) {
revert MintFailed();
}
console.log("Minting to:", additionalAddressesForMinting[i]);
if (!success) {
revert MintFailed();
}
}
}

return tokenAddress;
}

function saveOutput() internal {
TokenDescription memory token = config.zkToken;
string memory section = token.symbol;

// Serialize each attribute directly under the token's symbol (e.g., [ZK])
vm.serializeString(section, "name", token.name);
vm.serializeString(section, "symbol", token.symbol);
vm.serializeUint(section, "decimals", token.decimals);
vm.serializeString(section, "implementation", token.implementation);
vm.serializeUintToHex(section, "mint", token.mint);
vm.serializeBytes32(section, "assetId", token.assetId);
vm.serializeAddress(token.symbol, "l1Address", address(0));

string memory tokenInfo = vm.serializeAddress(token.symbol, "address", token.addr);
string memory toml = vm.serializeString("root", "ZK", tokenInfo);
string memory root = vm.projectRoot();
string memory path = string.concat(root, vm.envString("ZK_TOKEN_OUTPUT"));
vm.writeToml(toml, path);
}

// add this to be excluded from coverage report
function test() internal {}
}
46 changes: 44 additions & 2 deletions l1-contracts/deploy-scripts/GatewayCTMFromL1.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {Script, console2 as console} from "forge-std/Script.sol";
// import {Vm} from "forge-std/Vm.sol";
import {stdToml} from "forge-std/StdToml.sol";

// It's required to disable lints to force the compiler to compile the contracts
// solhint-disable no-unused-import
import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol";

import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol";
import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol";
import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol";
Expand All @@ -16,6 +20,8 @@ import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol
import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS} from "./Utils.sol";
import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol";
import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol";
import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol";
import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol";

import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol";
import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol";
Expand Down Expand Up @@ -55,6 +61,9 @@ contract GatewayCTMFromL1 is Script {
address chainTypeManagerProxy;
address sharedBridgeProxy;
address governance;
address governanceAddr;
address deployerAddr;
address baseToken;
uint256 chainChainId;
uint256 eraChainId;
uint256 l1ChainId;
Expand Down Expand Up @@ -93,6 +102,9 @@ contract GatewayCTMFromL1 is Script {
console.log("Setting up the Gateway script");

initializeConfig();
if (config.baseToken != ADDRESS_ONE) {
distributeBaseToken();
}
deployGatewayContracts();

saveOutput();
Expand Down Expand Up @@ -137,8 +149,39 @@ contract GatewayCTMFromL1 is Script {
genesisRollupLeafIndex: toml.readUint("$.genesis_rollup_leaf_index"),
genesisBatchCommitment: toml.readBytes32("$.genesis_batch_commitment"),
latestProtocolVersion: toml.readUint("$.latest_protocol_version"),
forceDeploymentsData: toml.readBytes("$.force_deployments_data")
forceDeploymentsData: toml.readBytes("$.force_deployments_data"),
governanceAddr: address(0),
deployerAddr: address(0),
baseToken: address(0)
});

path = string.concat(root, vm.envString("L1_OUTPUT"));
toml = vm.readFile(path);

config.governanceAddr = toml.readAddress("$.deployed_addresses.governance_addr");
config.deployerAddr = toml.readAddress("$.deployer_addr");

path = string.concat(root, vm.envString("ZK_CHAIN_CONFIG"));
toml = vm.readFile(path);
config.baseToken = toml.readAddress("$.chain.base_token_addr");
}

function distributeBaseToken() internal {
deployerAddress = msg.sender;
L1AssetRouter l1AR = L1AssetRouter(config.sharedBridgeProxy);
IL1NativeTokenVault nativeTokenVault = IL1NativeTokenVault(address(l1AR.nativeTokenVault()));
bytes32 baseTokenAssetID = nativeTokenVault.assetId(config.baseToken);
uint256 baseTokenOriginChainId = nativeTokenVault.originChainId(baseTokenAssetID);
TestnetERC20Token baseToken = TestnetERC20Token(config.baseToken);
uint256 deployerBalance = baseToken.balanceOf(deployerAddress);

vm.startBroadcast();
if (baseTokenOriginChainId == block.chainid) {
baseToken.mint(config.governanceAddr, deployerBalance / 3);
} else {
baseToken.transfer(config.governanceAddr, deployerBalance / 3);
}
vm.stopBroadcast();
}

function saveOutput() internal {
Expand Down Expand Up @@ -203,7 +246,6 @@ contract GatewayCTMFromL1 is Script {
/// @dev The sender may not have any privileges
function deployGatewayContracts() public {
output.multicall3 = _deployInternal(L2ContractsBytecodesLib.readMulticall3Bytecode(), hex"");

deployGatewayFacets();

output.gatewayStateTransition.verifier = deployGatewayVerifier();
Expand Down
Loading
Loading