Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ polygon_pos = "https://polygon-mainnet.infura.io/v3/${INFURA_TOKEN}"
polygon_zkevm = "https://zkevm-rpc.com"
polygon_zkevm_testnet = "https://rpc.public.zkevm-test.net"
tatara = "https://rpc.tatara.katanarpc.com/${TATARA_TOKEN}"
katana = "https://rpc.katanarpc.com/${KATANA_TOKEN}"

[etherscan]
mainnet = { key = "${API_KEY}" }
Expand Down
101 changes: 101 additions & 0 deletions script/DeployLayerY.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: LicenseRef-PolygonLabs-Open-Attribution OR LicenseRef-PolygonLabs-Source-Available
pragma solidity ^0.8.29;

import "forge-std/Script.sol";
import "../src/custom-tokens/GenericCustomToken.sol";
import "../src/custom-tokens/GenericNativeConverter.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ERC1967Proxy, ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract DeployLayerY is Script {
using stdJson for string;

uint256 deployerPrivateKey = uint256(uint160(address(this))); // default placeholder for tests

function run() public {
deployerPrivateKey = vm.promptSecretUint("PRIVATE_KEY");

deployLayerY();
}

function deployLayerY() public {
vm.startBroadcast(deployerPrivateKey);

string memory input = vm.readFile("script/input.json");

string memory slug = string(abi.encodePacked('["', vm.toString(block.chainid), '"]'));

address polygonEngineeringMultisig = input.readAddress(string.concat(slug, ".polygonEngineeringMultisig"));
address migrationManagerAddress = input.readAddress(string.concat(slug, ".migrationManager"));
address lxlyBridge = input.readAddress(string.concat(slug, ".lxlyBridge"));

GenericNativeConverter[] memory nativeConverters = new GenericNativeConverter[](5);

string[] memory vbTokens = new string[](4);
vbTokens[0] = "vbUSDC";
vbTokens[1] = "vbUSDT";
vbTokens[2] = "vbWBTC";
vbTokens[3] = "vbUSDS";

// deploy token impl
GenericCustomToken customTokenImpl = new GenericCustomToken();
GenericNativeConverter nativeConverterImpl = new GenericNativeConverter();

for (uint256 i = 0; i < vbTokens.length; i++) {
string memory vbSlug =
string(abi.encodePacked('["', vm.toString(block.chainid), '"]', '.["', vbTokens[i], '"]'));

address customToken = input.readAddress(string.concat(vbSlug, ".customToken"));
address underlyingToken = input.readAddress(string.concat(vbSlug, ".underlyingToken"));
string memory name = input.readString(string.concat(vbSlug, ".name"));
string memory symbol = input.readString(string.concat(vbSlug, ".symbol"));
uint8 decimals = uint8(input.readUint(string.concat(vbSlug, ".decimals")));
uint256 nonMigratableBackingPercentage =
input.readUint(string.concat(vbSlug, ".nonMigratableBackingPercentage"));

bytes memory initNativeConverter = abi.encodeCall(
GenericNativeConverter.initialize,
(
polygonEngineeringMultisig,
decimals,
customToken,
underlyingToken,
lxlyBridge,
0,
nonMigratableBackingPercentage,
migrationManagerAddress
)
);
address nativeConverter =
_proxify(address(nativeConverterImpl), polygonEngineeringMultisig, initNativeConverter);

nativeConverters[i] = GenericNativeConverter(nativeConverter);

console.log("Native converter ", vbTokens[i], " deployed at: ", nativeConverter);

// update custom token
bytes memory data = abi.encodeCall(
GenericCustomToken.reinitialize,
(polygonEngineeringMultisig, name, symbol, decimals, lxlyBridge, nativeConverter)
);

IERC1967Proxy customTokenProxy = IERC1967Proxy(payable(customToken));
bytes memory payload = abi.encodeCall(customTokenProxy.upgradeToAndCall, (address(customTokenImpl), data));

console.log("Payload for upgrading custom token", vbTokens[i]);
console.logBytes(payload);
}

console.log("Use this multisig: ", polygonEngineeringMultisig);

vm.stopBroadcast();
}

function _proxify(address logic, address admin, bytes memory initData) internal returns (address payable proxy) {
proxy = payable(new TransparentUpgradeableProxy(logic, admin, initData));
}
}

interface IERC1967Proxy {
function upgradeToAndCall(address newImplementation, bytes calldata data) external;
}
88 changes: 88 additions & 0 deletions script/DeployLayerY_WETH.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: LicenseRef-PolygonLabs-Open-Attribution OR LicenseRef-PolygonLabs-Source-Available
pragma solidity ^0.8.29;

import "forge-std/Script.sol";
import "../src/custom-tokens/WETH/WETH.sol";
import "../src/custom-tokens/WETH/WETHNativeConverter.sol";
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ERC1967Proxy, ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract DeployLayerY_WETH is Script {
using stdJson for string;

uint256 deployerPrivateKey = uint256(uint160(address(this))); // default placeholder for tests

function run() public {
deployerPrivateKey = vm.promptSecretUint("PRIVATE_KEY");

deployLayerY_WETH();
}

function deployLayerY_WETH() public {
vm.startBroadcast(deployerPrivateKey);

string memory input = vm.readFile("script/input.json");

string memory slug = string(abi.encodePacked('["', vm.toString(block.chainid), '"]'));

address polygonEngineeringMultisig = input.readAddress(string.concat(slug, ".polygonEngineeringMultisig"));
address migrationManagerAddress = input.readAddress(string.concat(slug, ".migrationManager"));
address lxlyBridge = input.readAddress(string.concat(slug, ".lxlyBridge"));

string memory vbETHSlug = string(abi.encodePacked('["', vm.toString(block.chainid), '"]', '.["vbETH"]'));

address vbWETH = input.readAddress(string.concat(vbETHSlug, ".customToken"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended to Use CustomToken and underlying token. Using vbETH for W-vbETH and WETH for W-WETH is misleading.

address wETH = input.readAddress(string.concat(vbETHSlug, ".underlyingToken"));
string memory name = input.readString(string.concat(vbETHSlug, ".name"));
string memory symbol = input.readString(string.concat(vbETHSlug, ".symbol"));
uint8 decimals = uint8(input.readUint(string.concat(vbETHSlug, ".decimals")));
uint256 nonMigratableGasBackingPercentage =
input.readUint(string.concat(vbETHSlug, ".nonMigratableGasBackingPercentage"));

WETHNativeConverter nativeConverterImpl = new WETHNativeConverter();

bytes memory initNativeConverter = abi.encodeCall(
WETHNativeConverter.initialize,
(
polygonEngineeringMultisig,
decimals,
vbWETH,
wETH,
lxlyBridge,
0,
0,
migrationManagerAddress,
nonMigratableGasBackingPercentage
)
);
address wethNativeConverter =
_proxify(address(nativeConverterImpl), polygonEngineeringMultisig, initNativeConverter);

// deploy vbWETH impl
WETH wethImpl = new WETH();

// update vbWETH
bytes memory data = abi.encodeCall(
WETH.reinitialize, (polygonEngineeringMultisig, name, symbol, decimals, lxlyBridge, wethNativeConverter)
);

IERC1967Proxy vbWethProxy = IERC1967Proxy(payable(vbWETH));
bytes memory payload = abi.encodeCall(vbWethProxy.upgradeToAndCall, (address(wethImpl), data));

console.log("Payload for upgrading vbWETH", "use this multisig: ", polygonEngineeringMultisig);
console.logBytes(payload);

/* bytes32 implementation = vm.load(vbWETH, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
vm.assertEq(implementation, bytes32(uint256(uint160(address(wethImpl))))); */

vm.stopBroadcast();
}

function _proxify(address logic, address admin, bytes memory initData) internal returns (address payable proxy) {
proxy = payable(new TransparentUpgradeableProxy(logic, admin, initData));
}
}

interface IERC1967Proxy {
function upgradeToAndCall(address newImplementation, bytes calldata data) external;
}
33 changes: 33 additions & 0 deletions script/DepositAndBridge.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: LicenseRef-PolygonLabs-Open-Attribution OR LicenseRef-PolygonLabs-Source-Available
pragma solidity ^0.8.29;

import "forge-std/Script.sol";
import "../src/vault-bridge-tokens/vbETH/VbETH.sol";

/// @dev this can be used to send some initial ETH to LayerY. Needs to be replicated for other tokens as well,
/// @dev but can also be done manually. Ly token addresses are necessary for the rest of the deployment process.
contract DepositAndBridge is Script {
using stdJson for string;

uint256 deployerPrivateKey = uint256(uint160(address(this))); // default placeholder for tests

uint256 depositAmount = 0.001 ether;
uint32 NETWORK_ID_L2 = 20;
address receiver = 0x32bdc6A4e8C654dF65503CBb0eDc82B4Ce9158e6;

function run() public {
deployerPrivateKey = vm.promptSecretUint("PRIVATE_KEY");

vm.startBroadcast(deployerPrivateKey);

console.log(receiver);

VbETH vbETH = VbETH(payable(0x2DC70fb75b88d2eB4715bc06E1595E6D97c34DFF));

uint256 shares = vbETH.depositGasTokenAndBridge{value: depositAmount}(receiver, NETWORK_ID_L2, true);

console.log(shares);

vm.stopBroadcast();
}
}
73 changes: 73 additions & 0 deletions script/RegisterNativeConverters.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: LicenseRef-PolygonLabs-Open-Attribution OR LicenseRef-PolygonLabs-Source-Available
pragma solidity ^0.8.29;

import "forge-std/Script.sol";
import "../src/MigrationManager.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract RegisterNativeConverters is Script {
using stdJson for string;

uint32 NETWORK_ID_Y = 20;

address polygonSecurityMultisig = 0x9d851f8b8751c5FbC09b9E74E6e68E9950949052;

function run() public {
string memory input = vm.readFile("script/input.json");

string memory migrationManagerSlug =
string(abi.encodePacked('["', vm.toString(block.chainid), '"]', '.["migrationManager"]'));

// Read from input.json based on current chain ID
address migrationManagerAddress = input.readAddress(string.concat(migrationManagerSlug, ".address"));

MigrationManager migrationManager = MigrationManager(payable(migrationManagerAddress));

string[] memory vbTokens = new string[](5);
vbTokens[0] = "vbUSDS";
vbTokens[1] = "vbUSDT";
vbTokens[2] = "vbUSDC";
vbTokens[3] = "vbWBTC";
vbTokens[4] = "vbETH";

vm.startBroadcast(polygonSecurityMultisig);

// register NativeConverters

for (uint256 i = 0; i < vbTokens.length; i++) {
address nativeConverter =
input.readAddress(string.concat(migrationManagerSlug, ".", vbTokens[i], "NativeConverter"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beware that all input.json "vbTokens[i].NativeConverters" are still fake (all of them are using address 0x000059dE96F9C28e3a343b831cbDC2B93c8C4855) , so we MUST update them once we have each of the NativeConverters deployed. Count with us to review before executing.

address vbToken = input.readAddress(string.concat(migrationManagerSlug, ".", vbTokens[i]));

uint32[] memory layerYLxlyIds = new uint32[](1);
layerYLxlyIds[0] = NETWORK_ID_Y;
address[] memory nativeConverters = new address[](1);
nativeConverters[0] = nativeConverter;

// migrationManager.configureNativeConverters(layerYLxlyIds, nativeConverters, payable(address(vbToken)));
bytes memory payload = abi.encodeCall(
migrationManager.configureNativeConverters, (layerYLxlyIds, nativeConverters, payable(address(vbToken)))
);

console.log("Payload to be sent from", polygonSecurityMultisig);
console.logBytes(payload);

/* MigrationManager.TokenPair memory tokenPair =
migrationManager.nativeConvertersConfiguration(NETWORK_ID_Y, nativeConverter);
string memory vbTokenSlug =
string(abi.encodePacked('["', vm.toString(block.chainid), '"]', '.["', vbTokens[i], '"]'));
address underlyingToken = input.readAddress(string.concat(vbTokenSlug, ".underlyingToken"));
vm.assertEq(address(tokenPair.vbToken), address(vbToken));
vm.assertEq(address(tokenPair.underlyingToken), address(underlyingToken));
vm.assertEq(
IERC20(underlyingToken).allowance(address(migrationManager), address(vbToken)), type(uint256).max
); */
}

vm.stopBroadcast();
}
}
65 changes: 62 additions & 3 deletions script/input.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@
"migrationManager": {
"proxyAdmin": "0x9d851f8b8751c5FbC09b9E74E6e68E9950949052",
"ownerMigrationManager": "0x9d851f8b8751c5FbC09b9E74E6e68E9950949052",
"lxlyBridge": "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe"
"lxlyBridge": "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe",
"address": "0x417d01B64Ea30C4E163873f3a1f77b727c689e02",
"layerYNetworkId": 20,
"vbUSDSNativeConverter": "0x000059dE96F9C28e3a343b831cbDC2B93c8C4855",
"vbUSDTNativeConverter": "0x000059dE96F9C28e3a343b831cbDC2B93c8C4855",
"vbUSDCNativeConverter": "0x000059dE96F9C28e3a343b831cbDC2B93c8C4855",
"vbWBTCNativeConverter": "0x000059dE96F9C28e3a343b831cbDC2B93c8C4855",
"vbETHNativeConverter": "0x000059dE96F9C28e3a343b831cbDC2B93c8C4855",
Comment on lines +9 to +13

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update them before executing the RegisterNativeConverters.s.sol script

"vbUSDS": "0x3DD459dE96F9C28e3a343b831cbDC2B93c8C4855",
"vbUSDT": "0x6d4f9f9f8f0155509ecd6Ac6c544fF27999845CC",
"vbUSDC": "0x53E82ABbb12638F09d9e624578ccB666217a765e",
"vbWBTC": "0x2C24B57e2CCd1f273045Af6A5f632504C432374F",
"vbETH": "0x2DC70fb75b88d2eB4715bc06E1595E6D97c34DFF"
},
"vbUSDS": {
"proxyAdmin": "0xA8C31B2edd84c654d06d626383f4154D1E40C5Ff",
Expand Down Expand Up @@ -77,7 +89,54 @@
}
},
"747474": {

"vbUSDS": {
"customToken": "0x62D6A123E8D19d06d68cf0d2294F9A3A0362c6b3",
"underlyingToken": "0x2134866886ce784fE2E0DE819118E4D32b4Be32C",
"owner": "0xA8C31B2edd84c654d06d626383f4154D1E40C5Ff",
"name": "Vault Bridge USDS",
"symbol": "vbUSDS",
"decimals": 18,
"nonMigratableBackingPercentage": 0.01e18
},
"vbUSDT": {
"customToken": "0x2DCa96907fde857dd3D816880A0df407eeB2D2F2",
"underlyingToken": "0xf44e3BCB7A2461CC08185E127B324f2486a74E20",
"owner": "0x2De242e27386e224E5fbF110EA8406d5B70740ec",
"name": "Vault Bridge USDT",
"symbol": "vbUSDT",
"decimals": 6,
"nonMigratableBackingPercentage": 0.02e18
},
"vbUSDC": {
"customToken": "0x203A662b0BD271A6ed5a60EdFbd04bFce608FD36",
"underlyingToken": "0xfd415D011FfaA8e6f17fa753CdB080d1dE266784",
"owner": "0xf4F2f5F6bAdBE05433C4604320ecC56BbECBC04E",
"name": "Vault Bridge USDC",
"symbol": "vbUSDC",
"decimals": 6,
"nonMigratableBackingPercentage": 0.02e18
},
"vbWBTC": {
"customToken": "0x0913DA6Da4b42f538B445599b46Bb4622342Cf52",
"underlyingToken": "0xB33e43A3F276e8e75792b941bccC996EcB2c0bBD",
"owner": "0x2De242e27386e224E5fbF110EA8406d5B70740ec",
"name": "Vault Bridge WBTC",
"symbol": "vbWBTC",
"decimals": 8,
"nonMigratableBackingPercentage": 0.01e18
},
"vbETH": {
"customToken": "0xEE7D8BCFb72bC1880D0Cf19822eB0A2e6577aB62",
"underlyingToken": "0x815955d051C6262C16c720b19D735426254Bec5B",
"owner": "0x2De242e27386e224E5fbF110EA8406d5B70740ec",
"name": "Wrapped Ether",
Copy link

@web3security web3security May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dangerous breaking change, be careful.

Metadata changes are usually discouraged for every sovereign token. This is due to the fact that if it is sent to a non-upgradeable bridge version in a different network this getTokenMetadata(token) call at https://github.com/agglayer/agglayer-contracts/blob/v10.1.0-rc.5/contracts/v2/PolygonZkEVMBridgeV2.sol#L397 will produce a different token address in destination network as unexpected.

This seems safe for an already deployed "upgradeable token" deployed on Katana coming from a bridge from L1 (mainnet) as it will take tokenInfoHash.originToken but beware that if sent to a 3rd network (with non upgradeable tokens - 99% of them) then it will have side effects.

To avoid address change it is encouraged to use the same metadata.

Here the issue documented https://github.com/0xPolygonHermez/internal-audit/issues/160

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking with the Product Team if the naming is crucial. If yes, we can override name, symbol, and decimals functions to report different values to LxLy Bridge.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me put an example:

  • User sends L1 vbTOK to L2 Katana (gets an upgradeable token 0xA)
  • Katana W-vbTOK token 0xA's owner changes its metadata to a different name / symbol (i.WETH).
  • User sends Katana 0xA W-vbTOK (renamed as WETH) to network LY with a non-uogradeable version (gets a different address 0xB
  • the problem is not 0xA != 0xB because those networks use different bridge type (upgradeable wrapped tokens vs non upgradeable)
  • the problem is that non-upgradeable networks will have different addresses for the same asset depending on whether or not the first deposit came from Katana (with a renamed metadata) or for example L1 with the original naming.
  • so the problem is that if a user sends vbTOK from L1 to LY+1, that W-vbTOK will get an address of 0xC which is different from != 0xB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution I proposed fixes the issue because LxLy Bridge will see the original metadata, while everyone else will see the new metadata. So, the same bridge-wrapped token will be used no matter if you bridge from Ethereum or Katana.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simonDos Per the internal discussions, revert the naming to Vault Bridge ETH / vbETH.

"symbol": "WETH",
"decimals": 18,
"nonMigratableGasBackingPercentage": 0.1e18
},
"migrationManager": "0x417d01B64Ea30C4E163873f3a1f77b727c689e02",
"polygonEngineeringMultisig": "0x4e981bAe8E3cd06Ca911ffFE5504B2653ac1C38a",
"lxlyBridge": "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe"
},
"31337": {
"proxyAdmin": "0x0000c6A4e8C654dF65503CBb0eDc82B4Ce9158e6",
Expand Down Expand Up @@ -151,4 +210,4 @@
"yieldVaultMaximumSlippagePercentage": 0
}
}
}
}
Loading