From bec51bfd56a1a6d90d3c0fda453875a94f7b4242 Mon Sep 17 00:00:00 2001 From: Richard Watts Date: Mon, 23 Dec 2024 16:31:15 +0000 Subject: [PATCH] (fix) Check that the remote token manager exists before trying to use it. --- ...leaseOrNativeTokenManagerUpgradeableV4.sol | 60 +++++ .../LockProxyTokenManagerUpgradeableV4.sol | 70 ++++++ .../MintAndBurnTokenManagerUpgradeableV4.sol | 120 +++++++++ .../TokenManagerUpgradeableV4.sol | 234 ++++++++++++++++++ 4 files changed, 484 insertions(+) create mode 100644 smart-contracts/contracts/periphery/TokenManagerV4/LockAndReleaseOrNativeTokenManagerUpgradeableV4.sol create mode 100644 smart-contracts/contracts/periphery/TokenManagerV4/LockProxyTokenManagerUpgradeableV4.sol create mode 100644 smart-contracts/contracts/periphery/TokenManagerV4/MintAndBurnTokenManagerUpgradeableV4.sol create mode 100644 smart-contracts/contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol diff --git a/smart-contracts/contracts/periphery/TokenManagerV4/LockAndReleaseOrNativeTokenManagerUpgradeableV4.sol b/smart-contracts/contracts/periphery/TokenManagerV4/LockAndReleaseOrNativeTokenManagerUpgradeableV4.sol new file mode 100644 index 0000000..d5aa40a --- /dev/null +++ b/smart-contracts/contracts/periphery/TokenManagerV4/LockAndReleaseOrNativeTokenManagerUpgradeableV4.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {TokenManagerUpgradeableV3, ITokenManager} from "contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol"; +import {IERC20} from "contracts/periphery/LockAndReleaseTokenManagerUpgradeable.sol"; +import {IRelayer, CallMetadata} from "contracts/core/Relayer.sol"; + +interface ILockAndReleaseOrNativeTokenManager { + event Locked(address indexed token, address indexed from, uint amount); + event Released( + address indexed token, + address indexed recipient, + uint amount + ); +} + +contract LockAndReleaseOrNativeTokenManagerUpgradeableV3 is + ILockAndReleaseOrNativeTokenManager, + TokenManagerUpgradeableV3 +{ + address public constant NATIVE_ASSET_HASH = address(0); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @dev Allow this contract to receive native tokens. + receive() external payable {} + + // Outgoing + function _handleTransfer( + address token, + address from, + uint amount + ) internal override { + if (token == NATIVE_ASSET_HASH) { + (bool success, ) = payable(this).call{ value: amount }(""); + require(success, "Native asset transfer failed"); + } else { + IERC20(token).transferFrom(from, address(this), amount); + } + emit Locked(token, from, amount); + } + + // Incoming + function _handleAccept( + address token, + address recipient, + uint amount + ) internal override { + if (token == NATIVE_ASSET_HASH) { + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Native asset transfer failed"); + } else { + IERC20(token).transfer(recipient, amount); + } + emit Released(token, recipient, amount); + } +} diff --git a/smart-contracts/contracts/periphery/TokenManagerV4/LockProxyTokenManagerUpgradeableV4.sol b/smart-contracts/contracts/periphery/TokenManagerV4/LockProxyTokenManagerUpgradeableV4.sol new file mode 100644 index 0000000..b7a93be --- /dev/null +++ b/smart-contracts/contracts/periphery/TokenManagerV4/LockProxyTokenManagerUpgradeableV4.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {TokenManagerUpgradeableV4, ITokenManager} from "contracts/periphery/TokenManagerV3/TokenManagerUpgradeableV4.sol"; +import {BridgedToken} from "contracts/periphery/BridgedToken.sol"; +import {IERC20} from "contracts/periphery/LockAndReleaseTokenManagerUpgradeable.sol"; +import { ILockProxyTokenManagerStorage, LockProxyTokenManagerStorage } from "contracts/periphery/LockProxyTokenManagerStorage.sol"; +import { ILockProxyExtensionTransfer } from "contracts/periphery/ILockProxyExtensionTransfer.sol"; + +interface ILockProxyTokenManager is ILockProxyTokenManagerStorage { + // Args in this order to match other token managers. + event SentToLockProxy(address indexed token, address indexed sender, uint amount); + event WithdrawnFromLockProxy(address indexed token, address indexed receipient, uint amount); +} + +// This is the lock proxy token manager that runs on EVM chains. It talks to an EVM LockProxy. +contract LockProxyTokenManagerUpgradeableV4 is TokenManagerUpgradeableV4, ILockProxyTokenManager, LockProxyTokenManagerStorage { + address public constant NATIVE_ASSET_HASH = address(0); + + constructor() { + _disableInitializers(); + } + + function reinitialize(uint fees) external reinitializer(2) { + _setFees(fees); + } + + // Incoming currency - transfer into the lock proxy (directly!) + function _handleTransfer(address token, address from, uint amount) internal override { + address lockProxyAddress = getLockProxy(); + // Just transfer value to the lock proxy. + if (token == NATIVE_ASSET_HASH) { + (bool success, ) = lockProxyAddress.call{value: amount}(""); + emit SentToLockProxy(token, from, amount); + require(success, "Transfer failed"); + return; + } + + IERC20 erc20token = IERC20(token); + erc20token.transferFrom(from, address(lockProxyAddress), amount); + emit SentToLockProxy(token, from, amount); + } + + // Withdrawals are processed via the lockProxyProxy. + function _handleAccept(address token, address recipient, uint amount) internal override { + address lockProxyProxyAddress = getLockProxyProxy(); + address lockProxyAddress = getLockProxy(); + ILockProxyExtensionTransfer lp = ILockProxyExtensionTransfer(payable(lockProxyProxyAddress)); + // Sadly, extensionTransfer() takes the same arguments as the withdrawn event but in a + // different order. This will automagically transfer native token if token==0. + + // Native tokens are transferred by the call; for everyone else, it sets an allowance and we + // then do the transfer from here. + if (token == address(0)) { + lp.extensionTransfer(recipient, address(0), amount); + } else { + lp.extensionTransfer(address(this), token, amount); + IERC20 erc20token = IERC20(token); + // Although the lockProxyProxy is the registered extension, the tokens are held by the actual + // lockProxy + erc20token.transferFrom(lockProxyAddress, recipient, amount); + } + emit WithdrawnFromLockProxy(token, recipient, amount); + } + + function setLockProxyData(address lockProxy, address lockProxyProxy) external onlyOwner { + _setLockProxyData(lockProxy, lockProxyProxy); + } + +} diff --git a/smart-contracts/contracts/periphery/TokenManagerV4/MintAndBurnTokenManagerUpgradeableV4.sol b/smart-contracts/contracts/periphery/TokenManagerV4/MintAndBurnTokenManagerUpgradeableV4.sol new file mode 100644 index 0000000..d9e74e5 --- /dev/null +++ b/smart-contracts/contracts/periphery/TokenManagerV4/MintAndBurnTokenManagerUpgradeableV4.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {TokenManagerUpgradeableV4, ITokenManager} from "contracts/periphery/TokenManagerV3/TokenManagerUpgradeableV4.sol"; +import {BridgedToken} from "contracts/periphery/BridgedToken.sol"; + +interface IMintAndBurnTokenManager { + event Minted(address indexed token, address indexed recipient, uint amount); + event Burned(address indexed token, address indexed from, uint amount); + event BridgedTokenDeployed( + address token, + address remoteToken, + address remoteTokenManager, + uint remoteChainId + ); +} + +contract MintAndBurnTokenManagerUpgradeableV3 is + IMintAndBurnTokenManager, + TokenManagerUpgradeableV3 +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function deployToken( + string calldata name, + string calldata symbol, + uint8 decimals, + address remoteToken, + address tokenManager, + uint remoteChainId + ) external returns (BridgedToken) { + return + _deployToken( + name, + symbol, + decimals, + remoteToken, + tokenManager, + remoteChainId + ); + } + + function deployToken( + string calldata name, + string calldata symbol, + address remoteToken, + address tokenManager, + uint remoteChainId + ) external returns (BridgedToken) { + return + _deployToken( + name, + symbol, + 18, + remoteToken, + tokenManager, + remoteChainId + ); + } + + function _deployToken( + string calldata name, + string calldata symbol, + uint8 decimals, + address remoteToken, + address tokenManager, + uint remoteChainId + ) internal onlyOwner returns (BridgedToken) { + // TODO: deployed counterfactually + BridgedToken bridgedToken = new BridgedToken(name, symbol, decimals); + RemoteToken memory remoteTokenStruct = RemoteToken( + remoteToken, + tokenManager, + remoteChainId + ); + + _registerToken(address(bridgedToken), remoteTokenStruct); + + emit BridgedTokenDeployed( + address(bridgedToken), + remoteToken, + tokenManager, + remoteChainId + ); + + return bridgedToken; + } + + function transferTokenOwnership( + address localToken, + uint remoteChainId, + address newOwner + ) external onlyOwner { + BridgedToken(localToken).transferOwnership(newOwner); + _removeToken(localToken, remoteChainId); + } + + // Outgoing + function _handleTransfer( + address token, + address from, + uint amount + ) internal override { + BridgedToken(token).burnFrom(from, amount); + emit Burned(token, from, amount); + } + + // Incoming + function _handleAccept( + address token, + address recipient, + uint amount + ) internal override { + BridgedToken(token).mint(recipient, amount); + emit Minted(token, recipient, amount); + } +} diff --git a/smart-contracts/contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol b/smart-contracts/contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol new file mode 100644 index 0000000..14f19d6 --- /dev/null +++ b/smart-contracts/contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.20; + +import {IRelayer, CallMetadata} from "contracts/core/Relayer.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {ITokenManagerEvents, ITokenManagerStructs} from "contracts/periphery/TokenManagerUpgradeable.sol"; +import {TokenManagerFees, ITokenManagerFees} from "contracts/periphery/TokenManagerV2/TokenManagerFees.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; + +interface ITokenManager is + ITokenManagerEvents, + ITokenManagerStructs, + ITokenManagerFees +{ + error InvalidSourceChainId(); + error InvalidTokenManager(); + error NotGateway(); + error InvalidTokenRouting(); + + function getGateway() external view returns (address); + + function setGateway(address _gateway) external; + + function getRemoteTokens( + address token, + uint remoteChainId + ) external view returns (RemoteToken memory); + + function registerToken( + address token, + RemoteToken memory remoteToken + ) external; + + function setFees(uint newFees) external; + + function withdrawFees(address payable to) external; + + function pause() external; + + function unpause() external; + + function transfer( + address token, + uint remoteChainId, + address remoteRecipient, + uint amount + ) external payable; // Update to payable + + function accept( + CallMetadata calldata metadata, + bytes calldata args + ) external; +} + +abstract contract TokenManagerUpgradeableV4 is + ITokenManager, + Initializable, + UUPSUpgradeable, + Ownable2StepUpgradeable, // V3 changed to Ownable2StepUpgradeable + TokenManagerFees, + PausableUpgradeable +{ + /// @custom:storage-location erc7201:zilliqa.storage.TokenManager + struct TokenManagerStorage { + address gateway; + // localTokenAddress => remoteChainId => RemoteToken + mapping(address => mapping(uint => RemoteToken)) remoteTokens; + } + + // keccak256(abi.encode(uint256(keccak256("zilliqa.storage.TokenManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant Token_Manager_Storage_Location = + 0x4a6c2e6a7e6518c249bdcd1d934ea16ea5325bbae105af814eb678f5f49f3400; + + function _getTokenManagerStorage() + private + pure + returns (TokenManagerStorage storage $) + { + assembly { + $.slot := Token_Manager_Storage_Location + } + } + + function getGateway() public view returns (address) { + TokenManagerStorage storage $ = _getTokenManagerStorage(); + return $.gateway; + } + + function getRemoteTokens( + address token, + uint remoteChainId + ) public view returns (RemoteToken memory) { + TokenManagerStorage storage $ = _getTokenManagerStorage(); + return $.remoteTokens[token][remoteChainId]; + } + + modifier onlyGateway() { + if (_msgSender() != address(getGateway())) { + revert NotGateway(); + } + _; + } + + function __TokenManager_init(address _gateway) internal onlyInitializing { + __Ownable_init(_msgSender()); + _setGateway(_gateway); + } + + function _authorizeUpgrade(address) internal virtual override onlyOwner {} + + function _setGateway(address _gateway) internal { + TokenManagerStorage storage $ = _getTokenManagerStorage(); + $.gateway = _gateway; + } + + function setGateway(address _gateway) external onlyOwner { + _setGateway(_gateway); + } + + function _removeToken(address localToken, uint remoteChainId) internal { + TokenManagerStorage storage $ = _getTokenManagerStorage(); + delete $.remoteTokens[localToken][remoteChainId]; + emit TokenRemoved(localToken, remoteChainId); + } + + function _registerToken( + address localToken, + RemoteToken memory remoteToken + ) internal { + TokenManagerStorage storage $ = _getTokenManagerStorage(); + $.remoteTokens[localToken][remoteToken.chainId] = remoteToken; + emit TokenRegistered( + localToken, + remoteToken.token, + remoteToken.tokenManager, + remoteToken.chainId + ); + } + + // Token Overrides + function registerToken( + address token, + RemoteToken memory remoteToken + ) external virtual onlyOwner { + _registerToken(token, remoteToken); + } + + // V2 New Function + function setFees( + uint newFees + ) external override(ITokenManager, TokenManagerFees) onlyOwner { + _setFees(newFees); + } + + // V2 New Function + function withdrawFees( + address payable to + ) external override(ITokenManager, TokenManagerFees) onlyOwner { + _withdrawFees(to); + } + + // V2 New Function + function pause() external onlyOwner { + _pause(); + } + + // V2 New Function + function unpause() external onlyOwner { + _unpause(); + } + + // TO OVERRIDE – Incoming + function _handleTransfer( + address token, + address from, + uint amount + ) internal virtual; + + // TO OVERRIDE – Outgoing + function _handleAccept( + address token, + address recipient, + uint amount + ) internal virtual; + + // V2 Modified: `whenNotPaused` & `checkFees` modifiers, also made payable + function transfer( + address token, + uint remoteChainId, + address remoteRecipient, + uint amount + ) external payable virtual whenNotPaused checkFees { + RemoteToken memory remoteToken = getRemoteTokens(token, remoteChainId); + require(remoteToken.tokenManager != address(0)); + + _handleTransfer(token, _msgSender(), amount); + + IRelayer(getGateway()).relayWithMetadata( + remoteToken.chainId, + remoteToken.tokenManager, + this.accept.selector, + abi.encode(AcceptArgs(remoteToken.token, remoteRecipient, amount)), + 1_000_000 + ); + } + + // Incoming + // No pausing here because we want incoming txns to go through that have already initiated + function accept( + CallMetadata calldata metadata, + bytes calldata _args + ) external virtual onlyGateway { + AcceptArgs memory args = abi.decode(_args, (AcceptArgs)); + + RemoteToken memory remoteToken = getRemoteTokens( + args.token, + metadata.sourceChainId + ); + // We use a chainId != 0 as a proxy for the existence of + // this mapping entry. + require(remoteToken.tokenManager != address(0)); + + if (metadata.sourceChainId != remoteToken.chainId) { + revert InvalidSourceChainId(); + } + if (metadata.sender != remoteToken.tokenManager) { + revert InvalidTokenManager(); + } + + _handleAccept(args.token, args.recipient, args.amount); + } +}