Contract Name:
FreeMintERC20
Contract Source Code:
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../common/AddressResolver.sol";
import {EssentialContract} from "../common/EssentialContract.sol";
import {Proxied} from "../common/Proxied.sol";
import {IBridge} from "./IBridge.sol";
import {BridgeErrors} from "./BridgeErrors.sol";
import {LibBridgeData} from "./libs/LibBridgeData.sol";
import {LibBridgeProcess} from "./libs/LibBridgeProcess.sol";
import {LibBridgeRelease} from "./libs/LibBridgeRelease.sol";
import {LibBridgeRetry} from "./libs/LibBridgeRetry.sol";
import {LibBridgeSend} from "./libs/LibBridgeSend.sol";
import {LibBridgeStatus} from "./libs/LibBridgeStatus.sol";
/**
* Bridge contract which is deployed on both L1 and L2. Mostly a thin wrapper
* which calls the library implementations. See _IBridge_ for more details.
* @dev The code hash for the same address on L1 and L2 may be different.
* @custom:security-contact [email protected]
*/
contract Bridge is EssentialContract, IBridge, BridgeErrors {
using LibBridgeData for Message;
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
LibBridgeData.State private _state; // 50 slots reserved
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event MessageStatusChanged(
bytes32 indexed msgHash, LibBridgeStatus.MessageStatus status, address transactor
);
event DestChainEnabled(uint256 indexed chainId, bool enabled);
/*//////////////////////////////////////////////////////////////
USER-FACING FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// Allow Bridge to receive ETH from the TaikoL1, TokenVault or EtherVault.
receive() external payable {
if (
msg.sender != resolve("token_vault", true) && msg.sender != resolve("ether_vault", true)
&& msg.sender != resolve("taiko", true) && msg.sender != owner()
) {
revert B_CANNOT_RECEIVE();
}
}
/// @dev Initializer to be called after being deployed behind a proxy.
function init(address _addressManager) external initializer {
EssentialContract._init(_addressManager);
}
function sendMessage(Message calldata message)
external
payable
nonReentrant
returns (bytes32 msgHash)
{
return LibBridgeSend.sendMessage({
state: _state,
resolver: AddressResolver(this),
message: message
});
}
function releaseEther(IBridge.Message calldata message, bytes calldata proof)
external
nonReentrant
{
return LibBridgeRelease.releaseEther({
state: _state,
resolver: AddressResolver(this),
message: message,
proof: proof
});
}
function processMessage(Message calldata message, bytes calldata proof) external nonReentrant {
return LibBridgeProcess.processMessage({
state: _state,
resolver: AddressResolver(this),
message: message,
proof: proof
});
}
function retryMessage(Message calldata message, bool isLastAttempt) external nonReentrant {
return LibBridgeRetry.retryMessage({
state: _state,
resolver: AddressResolver(this),
message: message,
isLastAttempt: isLastAttempt
});
}
function isMessageSent(bytes32 msgHash) public view virtual returns (bool) {
return LibBridgeSend.isMessageSent(AddressResolver(this), msgHash);
}
function isMessageReceived(bytes32 msgHash, uint256 srcChainId, bytes calldata proof)
public
view
virtual
override
returns (bool)
{
return LibBridgeSend.isMessageReceived({
resolver: AddressResolver(this),
msgHash: msgHash,
srcChainId: srcChainId,
proof: proof
});
}
function isMessageFailed(bytes32 msgHash, uint256 destChainId, bytes calldata proof)
public
view
virtual
override
returns (bool)
{
return LibBridgeStatus.isMessageFailed({
resolver: AddressResolver(this),
msgHash: msgHash,
destChainId: destChainId,
proof: proof
});
}
function getMessageStatus(bytes32 msgHash)
public
view
virtual
returns (LibBridgeStatus.MessageStatus)
{
return LibBridgeStatus.getMessageStatus(msgHash);
}
function context() public view returns (Context memory) {
return _state.ctx;
}
function isEtherReleased(bytes32 msgHash) public view returns (bool) {
return _state.etherReleased[msgHash];
}
function isDestChainEnabled(uint256 _chainId) public view returns (bool enabled) {
(enabled,) = LibBridgeSend.isDestChainEnabled(AddressResolver(this), _chainId);
}
function hashMessage(Message calldata message) public pure override returns (bytes32) {
return LibBridgeData.hashMessage(message);
}
function getMessageStatusSlot(bytes32 msgHash) public pure returns (bytes32) {
return LibBridgeStatus.getMessageStatusSlot(msgHash);
}
}
contract ProxiedBridge is Proxied, Bridge {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {
IERC20Upgradeable,
ERC20Upgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {IERC20MetadataUpgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {EssentialContract} from "../common/EssentialContract.sol";
import {Proxied} from "../common/Proxied.sol";
import {BridgeErrors} from "./BridgeErrors.sol";
/// @custom:security-contact [email protected]
contract BridgedERC20 is
EssentialContract,
IERC20Upgradeable,
IERC20MetadataUpgradeable,
ERC20Upgradeable,
BridgeErrors
{
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
address public srcToken;
uint256 public srcChainId;
uint8 private srcDecimals;
uint256[47] private __gap;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event BridgeMint(address indexed account, uint256 amount);
event BridgeBurn(address indexed account, uint256 amount);
/*//////////////////////////////////////////////////////////////
USER-FACING FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @dev Initializer to be called after being deployed behind a proxy.
// Intention is for a different BridgedERC20 Contract to be deployed
// per unique _srcToken i.e. one for USDC, one for USDT etc.
function init(
address _addressManager,
address _srcToken,
uint256 _srcChainId,
uint8 _decimals,
string memory _symbol,
string memory _name
) external initializer {
if (
_srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid
|| bytes(_symbol).length == 0 || bytes(_name).length == 0
) {
revert B_INIT_PARAM_ERROR();
}
EssentialContract._init(_addressManager);
ERC20Upgradeable.__ERC20_init({name_: _name, symbol_: _symbol});
srcToken = _srcToken;
srcChainId = _srcChainId;
srcDecimals = _decimals;
}
/// @dev only a TokenVault can call this function
function bridgeMintTo(address account, uint256 amount) public onlyFromNamed("token_vault") {
_mint(account, amount);
emit BridgeMint(account, amount);
}
/// @dev only a TokenVault can call this function
function bridgeBurnFrom(address account, uint256 amount) public onlyFromNamed("token_vault") {
_burn(account, amount);
emit BridgeBurn(account, amount);
}
/// @dev any address can call this
// caller must have at least amount to call this
function transfer(address to, uint256 amount)
public
override(ERC20Upgradeable, IERC20Upgradeable)
returns (bool)
{
if (to == address(this)) {
revert B_ERC20_CANNOT_RECEIVE();
}
return ERC20Upgradeable.transfer(to, amount);
}
/// @dev any address can call this
// caller must have allowance of at least 'amount'
// for 'from's tokens.
function transferFrom(address from, address to, uint256 amount)
public
override(ERC20Upgradeable, IERC20Upgradeable)
returns (bool)
{
if (to == address(this)) {
revert B_ERC20_CANNOT_RECEIVE();
}
return ERC20Upgradeable.transferFrom(from, to, amount);
}
function decimals()
public
view
override(ERC20Upgradeable, IERC20MetadataUpgradeable)
returns (uint8)
{
return srcDecimals;
}
/// @dev returns the srcToken being bridged and the srcChainId
// of the tokens being bridged
function source() public view returns (address, uint256) {
return (srcToken, srcChainId);
}
}
contract ProxiedBridgedERC20 is Proxied, BridgedERC20 {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
abstract contract BridgeErrors {
error B_CANNOT_RECEIVE();
error B_DENIED();
error B_ERC20_CANNOT_RECEIVE();
error B_ETHER_RELEASED_ALREADY();
error B_EV_DO_NOT_BURN();
error B_EV_NOT_AUTHORIZED();
error B_EV_PARAM();
error B_FAILED_TRANSFER();
error B_FORBIDDEN();
error B_GAS_LIMIT();
error B_INCORRECT_VALUE();
error B_INIT_PARAM_ERROR();
error B_MSG_HASH_NULL();
error B_MSG_NON_RETRIABLE();
error B_MSG_NOT_FAILED();
error B_NULL_APP_ADDR();
error B_OWNER_IS_NULL();
error B_SIGNAL_NOT_RECEIVED();
error B_STATUS_MISMATCH();
error B_WRONG_CHAIN_ID();
error B_WRONG_TO_ADDRESS();
error B_ZERO_SIGNAL();
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {SafeERC20Upgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {Create2Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/Create2Upgradeable.sol";
import {EssentialContract} from "../common/EssentialContract.sol";
import {Proxied} from "../common/Proxied.sol";
import {LibAddress} from "../libs/LibAddress.sol";
import {BridgeErrors} from "./BridgeErrors.sol";
/**
* @custom:security-contact [email protected]
* EtherVault is a special vault contract that:
* - Is initialized with 2^128 Ether.
* - Allows the contract owner to authorize addresses.
* - Allows authorized addresses to send/release Ether.
*/
contract EtherVault is EssentialContract, BridgeErrors {
using LibAddress for address;
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
mapping(address addr => bool isAuthorized) private _authorizedAddrs;
uint256[49] private __gap;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Authorized(address indexed addr, bool authorized);
event EtherReleased(address indexed to, uint256 amount);
/*//////////////////////////////////////////////////////////////
MODIFIERS
//////////////////////////////////////////////////////////////*/
modifier onlyAuthorized() {
if (!isAuthorized(msg.sender)) {
revert B_EV_NOT_AUTHORIZED();
}
_;
}
/*//////////////////////////////////////////////////////////////
USER-FACING FUNCTIONS
//////////////////////////////////////////////////////////////*/
receive() external payable {
// EthVault's balance must == 0 OR the sender isAuthorized.
if (address(this).balance != 0 && !isAuthorized(msg.sender)) {
revert B_EV_NOT_AUTHORIZED();
}
}
function init(address addressManager) external initializer {
EssentialContract._init(addressManager);
}
/**
* Transfer Ether from EtherVault to the sender, checking that the sender
* is authorized.
* @param amount Amount of Ether to send.
*/
function releaseEther(uint256 amount) public onlyAuthorized nonReentrant {
msg.sender.sendEther(amount);
emit EtherReleased(msg.sender, amount);
}
/**
* Transfer Ether from EtherVault to a designated address, checking that the
* sender is authorized.
* @param recipient Address to receive Ether.
* @param amount Amount of ether to send.
*/
function releaseEther(address recipient, uint256 amount) public onlyAuthorized nonReentrant {
if (recipient == address(0)) {
revert B_EV_DO_NOT_BURN();
}
recipient.sendEther(amount);
emit EtherReleased(recipient, amount);
}
/**
* Set the authorized status of an address, only the owner can call this.
* @param addr Address to set the authorized status of.
* @param authorized Authorized status to set.
*/
function authorize(address addr, bool authorized) public onlyOwner {
if (addr == address(0) || _authorizedAddrs[addr] == authorized) {
revert B_EV_PARAM();
}
_authorizedAddrs[addr] = authorized;
emit Authorized(addr, authorized);
}
/**
* Get the authorized status of an address.
* @param addr Address to get the authorized status of.
*/
function isAuthorized(address addr) public view returns (bool) {
return _authorizedAddrs[addr];
}
}
contract ProxiedEtherVault is Proxied, EtherVault {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
/**
* Bridge interface.
* @dev Ether is held by Bridges on L1 and by the EtherVault on L2,
* not TokenVaults.
*/
interface IBridge {
struct Message {
// Message ID.
uint256 id;
// Message sender address (auto filled).
address sender;
// Source chain ID (auto filled).
uint256 srcChainId;
// Destination chain ID where the `to` address lives (auto filled).
uint256 destChainId;
// Owner address of the bridged asset.
address owner;
// Destination owner address.
address to;
// Alternate address to send any refund. If blank, defaults to owner.
address refundAddress;
// Deposited Ether minus the processingFee.
uint256 depositValue;
// callValue to invoke on the destination chain, for ERC20 transfers.
uint256 callValue;
// Processing fee for the relayer. Zero if owner will process themself.
uint256 processingFee;
// gasLimit to invoke on the destination chain, for ERC20 transfers.
uint256 gasLimit;
// callData to invoke on the destination chain, for ERC20 transfers.
bytes data;
// Optional memo.
string memo;
}
struct Context {
bytes32 msgHash; // messageHash
address sender;
uint256 srcChainId;
}
event SignalSent(address sender, bytes32 msgHash);
event MessageSent(bytes32 indexed msgHash, Message message);
event EtherReleased(bytes32 indexed msgHash, address to, uint256 amount);
/// Sends a message to the destination chain and takes custody
/// of Ether required in this contract. All extra Ether will be refunded.
function sendMessage(Message memory message) external payable returns (bytes32 msgHash);
// Release Ether with a proof that the message processing on the destination
// chain has been failed.
function releaseEther(IBridge.Message calldata message, bytes calldata proof) external;
/// Checks if a msgHash has been stored on the bridge contract by the
/// current address.
function isMessageSent(bytes32 msgHash) external view returns (bool);
/// Checks if a msgHash has been received on the destination chain and
/// sent by the src chain.
function isMessageReceived(bytes32 msgHash, uint256 srcChainId, bytes calldata proof)
external
view
returns (bool);
/// Checks if a msgHash has been failed on the destination chain.
function isMessageFailed(bytes32 msgHash, uint256 destChainId, bytes calldata proof)
external
view
returns (bool);
/// Returns the bridge state context.
function context() external view returns (Context memory context);
function hashMessage(IBridge.Message calldata message) external pure returns (bytes32);
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {BlockHeader} from "../../libs/LibBlockHeader.sol";
import {IBridge} from "../IBridge.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibMath} from "../../libs/LibMath.sol";
/**
* Stores message metadata on the Bridge.
*/
library LibBridgeData {
struct State {
uint256 nextMessageId;
IBridge.Context ctx; // 3 slots
mapping(bytes32 msgHash => bool released) etherReleased;
uint256[45] __gap;
}
struct StatusProof {
BlockHeader header;
bytes proof;
}
bytes32 internal constant MESSAGE_HASH_PLACEHOLDER = bytes32(uint256(1));
uint256 internal constant CHAINID_PLACEHOLDER = type(uint256).max;
address internal constant SRC_CHAIN_SENDER_PLACEHOLDER = address(uint160(uint256(1)));
// Note: These events must match the ones defined in Bridge.sol.
event MessageSent(bytes32 indexed msgHash, IBridge.Message message);
event DestChainEnabled(uint256 indexed chainId, bool enabled);
/**
* @return msgHash The keccak256 hash of the message.
*/
function hashMessage(IBridge.Message memory message) internal pure returns (bytes32) {
return keccak256(abi.encode(message));
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {IBridge} from "../IBridge.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
library LibBridgeInvoke {
using LibAddress for address;
using LibBridgeData for IBridge.Message;
error B_GAS_LIMIT();
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function invokeMessageCall(
LibBridgeData.State storage state,
IBridge.Message calldata message,
bytes32 msgHash,
uint256 gasLimit
) internal returns (bool success) {
if (gasLimit == 0) {
revert B_GAS_LIMIT();
}
state.ctx = IBridge.Context({
msgHash: msgHash,
sender: message.sender,
srcChainId: message.srcChainId
});
(success,) = message.to.call{value: message.callValue, gas: gasLimit}(message.data);
state.ctx = IBridge.Context({
msgHash: LibBridgeData.MESSAGE_HASH_PLACEHOLDER,
sender: LibBridgeData.SRC_CHAIN_SENDER_PLACEHOLDER,
srcChainId: LibBridgeData.CHAINID_PLACEHOLDER
});
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {EtherVault} from "../EtherVault.sol";
import {IBridge} from "../IBridge.sol";
import {ISignalService} from "../../signal/ISignalService.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
import {LibBridgeInvoke} from "./LibBridgeInvoke.sol";
import {LibBridgeStatus} from "./LibBridgeStatus.sol";
import {LibMath} from "../../libs/LibMath.sol";
/**
* Process bridge messages on the destination chain.
* @title LibBridgeProcess
*/
library LibBridgeProcess {
using LibMath for uint256;
using LibAddress for address;
using LibBridgeData for IBridge.Message;
using LibBridgeData for LibBridgeData.State;
error B_FORBIDDEN();
error B_SIGNAL_NOT_RECEIVED();
error B_STATUS_MISMATCH();
error B_WRONG_CHAIN_ID();
/**
* Process the bridge message on the destination chain. It can be called by
* any address, including `message.owner`. It starts by hashing the message,
* and doing a lookup in the bridge state to see if the status is "NEW". It
* then takes custody of the ether from the EtherVault and attempts to
* invoke the messageCall, changing the message's status accordingly.
* Finally, it refunds the processing fee if needed.
* @param state The bridge state.
* @param resolver The address resolver.
* @param message The message to process.
* @param proof The msgHash proof from the source chain.
*/
function processMessage(
LibBridgeData.State storage state,
AddressResolver resolver,
IBridge.Message calldata message,
bytes calldata proof
) internal {
// If the gas limit is set to zero, only the owner can process the message.
if (message.gasLimit == 0 && msg.sender != message.owner) {
revert B_FORBIDDEN();
}
if (message.destChainId != block.chainid) {
revert B_WRONG_CHAIN_ID();
}
// The message status must be "NEW"; "RETRIABLE" is handled in
// LibBridgeRetry.sol.
bytes32 msgHash = message.hashMessage();
if (LibBridgeStatus.getMessageStatus(msgHash) != LibBridgeStatus.MessageStatus.NEW) {
revert B_STATUS_MISMATCH();
}
// Message must have been "received" on the destChain (current chain)
address srcBridge = resolver.resolve(message.srcChainId, "bridge", false);
if (
!ISignalService(resolver.resolve("signal_service", false)).isSignalReceived({
srcChainId: message.srcChainId,
app: srcBridge,
signal: msgHash,
proof: proof
})
) {
revert B_SIGNAL_NOT_RECEIVED();
}
uint256 allValue = message.depositValue + message.callValue + message.processingFee;
// We retrieve the necessary ether from EtherVault if receiving on
// Taiko, otherwise it is already available in this Bridge.
address ethVault = resolver.resolve("ether_vault", true);
if (ethVault != address(0) && (allValue > 0)) {
EtherVault(payable(ethVault)).releaseEther(allValue);
}
// We send the Ether before the message call in case the call will
// actually consume Ether.
message.owner.sendEther(message.depositValue);
LibBridgeStatus.MessageStatus status;
uint256 refundAmount;
// if the user is sending to the bridge or zero-address, just process as DONE
// and refund the owner
if (message.to == address(this) || message.to == address(0)) {
// For these two special addresses, the call will not be actually
// invoked but will be marked DONE. The callValue will be refunded.
status = LibBridgeStatus.MessageStatus.DONE;
refundAmount = message.callValue;
} else {
// use the specified message gas limit if not called by the owner
uint256 gasLimit = msg.sender == message.owner ? gasleft() : message.gasLimit;
bool success = LibBridgeInvoke.invokeMessageCall({
state: state,
message: message,
msgHash: msgHash,
gasLimit: gasLimit
});
if (success) {
status = LibBridgeStatus.MessageStatus.DONE;
} else {
status = LibBridgeStatus.MessageStatus.RETRIABLE;
ethVault.sendEther(message.callValue);
}
}
// Mark the status as DONE or RETRIABLE.
LibBridgeStatus.updateMessageStatus(msgHash, status);
address refundAddress =
message.refundAddress == address(0) ? message.owner : message.refundAddress;
// if sender is the refundAddress
if (msg.sender == refundAddress) {
uint256 amount = message.processingFee + refundAmount;
refundAddress.sendEther(amount);
} else {
// if sender is another address (eg. the relayer)
// First attempt relayer is rewarded the processingFee
// message.owner has to eat the cost
msg.sender.sendEther(message.processingFee);
refundAddress.sendEther(refundAmount);
}
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {EtherVault} from "../EtherVault.sol";
import {IBridge} from "../IBridge.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
import {LibBridgeStatus} from "./LibBridgeStatus.sol";
library LibBridgeRelease {
using LibBridgeData for IBridge.Message;
event EtherReleased(bytes32 indexed msgHash, address to, uint256 amount);
error B_ETHER_RELEASED_ALREADY();
error B_FAILED_TRANSFER();
error B_MSG_NOT_FAILED();
error B_OWNER_IS_NULL();
error B_WRONG_CHAIN_ID();
/**
* Release Ether to the message owner, only if the Taiko Bridge state says:
* - Ether for this message has not been released before.
* - The message is in a failed state.
*/
function releaseEther(
LibBridgeData.State storage state,
AddressResolver resolver,
IBridge.Message calldata message,
bytes calldata proof
) internal {
if (message.owner == address(0)) {
revert B_OWNER_IS_NULL();
}
if (message.srcChainId != block.chainid) {
revert B_WRONG_CHAIN_ID();
}
bytes32 msgHash = message.hashMessage();
if (state.etherReleased[msgHash] == true) {
revert B_ETHER_RELEASED_ALREADY();
}
if (!LibBridgeStatus.isMessageFailed(resolver, msgHash, message.destChainId, proof)) {
revert B_MSG_NOT_FAILED();
}
state.etherReleased[msgHash] = true;
uint256 releaseAmount = message.depositValue + message.callValue;
if (releaseAmount > 0) {
address ethVault = resolver.resolve("ether_vault", true);
// if on Taiko
if (ethVault != address(0)) {
EtherVault(payable(ethVault)).releaseEther(message.owner, releaseAmount);
} else {
// if on Ethereum
(bool success,) = message.owner.call{value: releaseAmount}("");
if (!success) {
revert B_FAILED_TRANSFER();
}
}
}
emit EtherReleased(msgHash, message.owner, releaseAmount);
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {EtherVault} from "../EtherVault.sol";
import {IBridge} from "../IBridge.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
import {LibBridgeInvoke} from "./LibBridgeInvoke.sol";
import {LibBridgeStatus} from "./LibBridgeStatus.sol";
/**
* Retry bridge messages.
* @title LibBridgeRetry
*/
library LibBridgeRetry {
using LibAddress for address;
using LibBridgeData for IBridge.Message;
using LibBridgeData for LibBridgeData.State;
error B_DENIED();
error B_MSG_NON_RETRIABLE();
/**
* Retries to invoke the messageCall, the owner has already been sent Ether.
* - This function can be called by any address, including `message.owner`.
* - Can only be called on messages marked "RETRIABLE".
* - It attempts to reinvoke the messageCall.
* - If it succeeds, the message is marked as "DONE".
* - If it fails and `isLastAttempt` is set to true, the message is marked
* as "FAILED" and cannot be retried.
* @param state The bridge state.
* @param resolver The address resolver.
* @param message The message to retry.
* @param isLastAttempt Specifies if this is the last attempt to retry the
* message.
*/
function retryMessage(
LibBridgeData.State storage state,
AddressResolver resolver,
IBridge.Message calldata message,
bool isLastAttempt
) internal {
// If the gasLimit is not set to 0 or isLastAttempt is true, the
// address calling this function must be message.owner.
if (message.gasLimit == 0 || isLastAttempt) {
if (msg.sender != message.owner) revert B_DENIED();
}
bytes32 msgHash = message.hashMessage();
if (LibBridgeStatus.getMessageStatus(msgHash) != LibBridgeStatus.MessageStatus.RETRIABLE) {
revert B_MSG_NON_RETRIABLE();
}
address ethVault = resolver.resolve("ether_vault", true);
if (ethVault != address(0)) {
EtherVault(payable(ethVault)).releaseEther(message.callValue);
}
// successful invocation
if (
LibBridgeInvoke
// The message.gasLimit only apply for processMessage, if it fails
// then whoever calls retryMessage will use the tx's gasLimit.
.invokeMessageCall({
state: state,
message: message,
msgHash: msgHash,
gasLimit: gasleft()
})
) {
LibBridgeStatus.updateMessageStatus(msgHash, LibBridgeStatus.MessageStatus.DONE);
} else if (isLastAttempt) {
LibBridgeStatus.updateMessageStatus(msgHash, LibBridgeStatus.MessageStatus.FAILED);
address refundAddress =
message.refundAddress == address(0) ? message.owner : message.refundAddress;
refundAddress.sendEther(message.callValue);
} else {
ethVault.sendEther(message.callValue);
}
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {IBridge} from "../IBridge.sol";
import {ISignalService} from "../../signal/ISignalService.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
/**
* Entry point for starting a bridge transaction.
*
* @title LibBridgeSend
*/
library LibBridgeSend {
using LibAddress for address;
using LibBridgeData for IBridge.Message;
error B_INCORRECT_VALUE();
error B_OWNER_IS_NULL();
error B_WRONG_CHAIN_ID();
error B_WRONG_TO_ADDRESS();
/**
* Send a message to the Bridge with the details of the request. The Bridge
* takes custody of the funds, unless the source chain is Taiko, in which
* the funds are sent to and managed by the EtherVault.
*
* @param message Specifies the `depositValue`, `callValue`,
* and `processingFee`. These must sum to `msg.value`. It also specifies the
* `destChainId` which must have a `bridge` address set on the
* AddressResolver and differ from the current chain ID.
*
* @return msgHash The hash of message sent.
* This is picked up by an off-chain relayer which indicates a
* bridge message has been sent and is ready to be processed on the
* destination chain.
*/
function sendMessage(
LibBridgeData.State storage state,
AddressResolver resolver,
IBridge.Message memory message
) internal returns (bytes32 msgHash) {
if (message.owner == address(0)) {
revert B_OWNER_IS_NULL();
}
(bool destChainEnabled, address destChain) =
isDestChainEnabled(resolver, message.destChainId);
if (!destChainEnabled || message.destChainId == block.chainid) {
revert B_WRONG_CHAIN_ID();
}
if (message.to == address(0) || message.to == destChain) {
revert B_WRONG_TO_ADDRESS();
}
uint256 expectedAmount = message.depositValue + message.callValue + message.processingFee;
if (expectedAmount != msg.value) {
revert B_INCORRECT_VALUE();
}
// If on Taiko, send the expectedAmount to the EtherVault. Otherwise,
// store it here on the Bridge. Processing will release Ether from the
// EtherVault or the Bridge on the destination chain.
address ethVault = resolver.resolve("ether_vault", true);
ethVault.sendEther(expectedAmount);
message.id = state.nextMessageId++;
message.sender = msg.sender;
message.srcChainId = block.chainid;
msgHash = message.hashMessage();
// Store a key which is the hash of this contract address and the
// msgHash, with a value of 1.
ISignalService(resolver.resolve("signal_service", false)).sendSignal(msgHash);
emit LibBridgeData.MessageSent(msgHash, message);
}
function isDestChainEnabled(AddressResolver resolver, uint256 chainId)
internal
view
returns (bool enabled, address destBridge)
{
destBridge = resolver.resolve(chainId, "bridge", true);
enabled = destBridge != address(0);
}
function isMessageSent(AddressResolver resolver, bytes32 msgHash)
internal
view
returns (bool)
{
return ISignalService(resolver.resolve("signal_service", false)).isSignalSent({
app: address(this),
signal: msgHash
});
}
function isMessageReceived(
AddressResolver resolver,
bytes32 msgHash,
uint256 srcChainId,
bytes calldata proof
) internal view returns (bool) {
address srcBridge = resolver.resolve(srcChainId, "bridge", false);
return ISignalService(resolver.resolve("signal_service", false)).isSignalReceived({
srcChainId: srcChainId,
app: srcBridge,
signal: msgHash,
proof: proof
});
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {BlockHeader, LibBlockHeader} from "../../libs/LibBlockHeader.sol";
import {ICrossChainSync} from "../../common/ICrossChainSync.sol";
import {LibBridgeData} from "./LibBridgeData.sol";
import {LibTrieProof} from "../../libs/LibTrieProof.sol";
library LibBridgeStatus {
using LibBlockHeader for BlockHeader;
enum MessageStatus {
NEW,
RETRIABLE,
DONE,
FAILED
}
event MessageStatusChanged(bytes32 indexed msgHash, MessageStatus status, address transactor);
error B_MSG_HASH_NULL();
error B_WRONG_CHAIN_ID();
/**
* @dev If messageStatus is same as in the messageStatus mapping,
* does nothing.
* @param msgHash The messageHash of the message.
* @param status The status of the message.
*/
function updateMessageStatus(bytes32 msgHash, MessageStatus status) internal {
if (getMessageStatus(msgHash) != status) {
_setMessageStatus(msgHash, status);
emit MessageStatusChanged(msgHash, status, msg.sender);
}
}
function getMessageStatus(bytes32 msgHash) internal view returns (MessageStatus) {
bytes32 slot = getMessageStatusSlot(msgHash);
uint256 value;
assembly {
value := sload(slot)
}
return MessageStatus(value);
}
function isMessageFailed(
AddressResolver resolver,
bytes32 msgHash,
uint256 destChainId,
bytes calldata proof
) internal view returns (bool) {
if (destChainId == block.chainid) {
revert B_WRONG_CHAIN_ID();
}
if (msgHash == 0x0) {
revert B_MSG_HASH_NULL();
}
LibBridgeData.StatusProof memory sp = abi.decode(proof, (LibBridgeData.StatusProof));
bytes32 syncedHeaderHash = ICrossChainSync(resolver.resolve("taiko", false))
.getCrossChainBlockHash(sp.header.height);
if (syncedHeaderHash == 0 || syncedHeaderHash != sp.header.hashBlockHeader()) {
return false;
}
return LibTrieProof.verifyWithAccountProof({
stateRoot: sp.header.stateRoot,
addr: resolver.resolve(destChainId, "bridge", false),
slot: getMessageStatusSlot(msgHash),
value: bytes32(uint256(LibBridgeStatus.MessageStatus.FAILED)),
mkproof: sp.proof
});
}
function getMessageStatusSlot(bytes32 msgHash) internal pure returns (bytes32) {
return keccak256(bytes.concat(bytes("MESSAGE_STATUS"), msgHash));
}
function _setMessageStatus(bytes32 msgHash, MessageStatus status) private {
bytes32 slot = getMessageStatusSlot(msgHash);
uint256 value = uint256(status);
assembly {
sstore(slot, value)
}
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {
IERC20Upgradeable,
ERC20Upgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {Create2Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/Create2Upgradeable.sol";
import {EssentialContract} from "../common/EssentialContract.sol";
import {Proxied} from "../common/Proxied.sol";
import {TaikoToken} from "../L1/TaikoToken.sol";
import {BridgedERC20} from "./BridgedERC20.sol";
import {IBridge} from "./IBridge.sol";
import {Strings} from "lib/openzeppelin-contracts/contracts/utils/Strings.sol";
/**
* This vault holds all ERC20 tokens (but not Ether) that users have deposited.
* It also manages the mapping between canonical ERC20 tokens and their bridged
* tokens.
* @dev Ether is held by Bridges on L1 and by the EtherVault on L2,
* not TokenVaults.
* @custom:security-contact [email protected]
*/
contract TokenVault is EssentialContract {
using SafeERC20Upgradeable for ERC20Upgradeable;
/*//////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
struct CanonicalERC20 {
uint256 chainId;
address addr;
uint8 decimals;
string symbol;
string name;
}
struct MessageDeposit {
address token;
uint256 amount;
}
/*//////////////////////////////////////////////////////////////
STATE VARIABLES
//////////////////////////////////////////////////////////////*/
// Tracks if a token on the current chain is a canonical or bridged token.
mapping(address tokenAddress => bool isBridged) public isBridgedToken;
// Mappings from bridged tokens to their canonical tokens.
mapping(address bridgedAddress => CanonicalERC20 canonicalErc20) public bridgedToCanonical;
// Mappings from canonical tokens to their bridged tokens.
// Also storing chainId for tokens across other chains aside from Ethereum.
mapping(uint256 chainId => mapping(address canonicalAddress => address bridgedAddress)) public
canonicalToBridged;
// Tracks the token and amount associated with a message hash.
mapping(bytes32 msgHash => MessageDeposit messageDeposit) public messageDeposits;
uint256[47] private __gap;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event BridgedERC20Deployed(
uint256 indexed srcChainId,
address indexed canonicalToken,
address indexed bridgedToken,
string canonicalTokenSymbol,
string canonicalTokenName,
uint8 canonicalTokenDecimal
);
event EtherSent(
bytes32 indexed msgHash,
address indexed from,
address indexed to,
uint256 destChainId,
uint256 amount
);
event ERC20Sent(
bytes32 indexed msgHash,
address indexed from,
address indexed to,
uint256 destChainId,
address token,
uint256 amount
);
event ERC20Released(
bytes32 indexed msgHash, address indexed from, address token, uint256 amount
);
event ERC20Received(
bytes32 indexed msgHash,
address indexed from,
address indexed to,
uint256 srcChainId,
address token,
uint256 amount
);
/*//////////////////////////////////////////////////////////////
CUSTOM ERRORS
//////////////////////////////////////////////////////////////*/
error TOKENVAULT_INVALID_TO();
error TOKENVAULT_INVALID_VALUE();
error TOKENVAULT_INVALID_TOKEN();
error TOKENVAULT_INVALID_AMOUNT();
error TOKENVAULT_CANONICAL_TOKEN_NOT_FOUND();
error TOKENVAULT_INVALID_OWNER();
error TOKENVAULT_INVALID_SRC_CHAIN_ID();
error TOKENVAULT_MESSAGE_NOT_FAILED();
error TOKENVAULT_INVALID_SENDER();
/*//////////////////////////////////////////////////////////////
USER-FACING FUNCTIONS
//////////////////////////////////////////////////////////////*/
function init(address addressManager) external initializer {
EssentialContract._init(addressManager);
}
/**
* Transfers ERC20 tokens to this vault and sends a message to the
* destination chain so the user can receive the same amount of tokens
* by invoking the message call.
*
* @param destChainId @custom:see IBridge.Message
* @param to @custom:see IBridge.Message
* @param token The address of the token to be sent.
* @param amount The amount of token to be transferred.
* @param gasLimit @custom:see IBridge.Message
* @param processingFee @custom:see IBridge.Message
* @param refundAddress @custom:see IBridge.Message
* @param memo @custom:see IBridge.Message
*/
function sendERC20(
uint256 destChainId,
address to,
address token,
uint256 amount,
uint256 gasLimit,
uint256 processingFee,
address refundAddress,
string memory memo
) external payable nonReentrant {
if (to == address(0) || to == resolve(destChainId, "token_vault", false)) {
revert TOKENVAULT_INVALID_TO();
}
if (token == address(0)) revert TOKENVAULT_INVALID_TOKEN();
if (amount == 0) revert TOKENVAULT_INVALID_AMOUNT();
CanonicalERC20 memory canonicalToken;
uint256 _amount;
// is a bridged token, meaning, it does not live on this chain
if (isBridgedToken[token]) {
BridgedERC20(token).bridgeBurnFrom(msg.sender, amount);
canonicalToken = bridgedToCanonical[token];
if (canonicalToken.addr == address(0)) {
revert TOKENVAULT_CANONICAL_TOKEN_NOT_FOUND();
}
_amount = amount;
} else {
// is a canonical token, meaning, it lives on this chain
ERC20Upgradeable t = ERC20Upgradeable(token);
canonicalToken = CanonicalERC20({
chainId: block.chainid,
addr: token,
decimals: t.decimals(),
symbol: t.symbol(),
name: t.name()
});
uint256 _balance = t.balanceOf(address(this));
t.safeTransferFrom(msg.sender, address(this), amount);
_amount = t.balanceOf(address(this)) - _balance;
}
IBridge.Message memory message;
message.destChainId = destChainId;
message.owner = msg.sender;
message.to = resolve(destChainId, "token_vault", false);
message.data = abi.encodeWithSelector(
TokenVault.receiveERC20.selector, canonicalToken, message.owner, to, _amount
);
message.gasLimit = gasLimit;
message.processingFee = processingFee;
message.depositValue = msg.value - processingFee;
message.refundAddress = refundAddress;
message.memo = memo;
bytes32 msgHash = IBridge(resolve("bridge", false)).sendMessage{value: msg.value}(message);
// record the deposit for this message
messageDeposits[msgHash] = MessageDeposit(token, _amount);
emit ERC20Sent({
msgHash: msgHash,
from: message.owner,
to: to,
destChainId: destChainId,
token: token,
amount: _amount
});
}
/**
* Release deposited ERC20 back to the owner on the source TokenVault with
* a proof that the message processing on the destination Bridge has failed.
*
* @param message The message that corresponds the ERC20 deposit on the
* source chain.
* @param proof The proof from the destination chain to show the message
* has failed.
*/
function releaseERC20(IBridge.Message calldata message, bytes calldata proof)
external
nonReentrant
{
if (message.owner == address(0)) revert TOKENVAULT_INVALID_OWNER();
if (message.srcChainId != block.chainid) {
revert TOKENVAULT_INVALID_SRC_CHAIN_ID();
}
IBridge bridge = IBridge(resolve("bridge", false));
bytes32 msgHash = bridge.hashMessage(message);
address token = messageDeposits[msgHash].token;
uint256 amount = messageDeposits[msgHash].amount;
if (token == address(0)) revert TOKENVAULT_INVALID_TOKEN();
if (!bridge.isMessageFailed(msgHash, message.destChainId, proof)) {
revert TOKENVAULT_MESSAGE_NOT_FAILED();
}
messageDeposits[msgHash] = MessageDeposit(address(0), 0);
if (amount > 0) {
if (isBridgedToken[token]) {
BridgedERC20(token).bridgeMintTo(message.owner, amount);
} else {
ERC20Upgradeable(token).safeTransfer(message.owner, amount);
}
}
emit ERC20Released({msgHash: msgHash, from: message.owner, token: token, amount: amount});
}
/**
* @dev This function can only be called by the bridge contract while
* invoking a message call. See sendERC20, which sets the data to invoke
* this function.
* @param canonicalToken The canonical ERC20 token which may or may not
* live on this chain. If not, a BridgedERC20 contract will be
* deployed.
* @param from The source address.
* @param to The destination address.
* @param amount The amount of tokens to be sent. 0 is a valid value.
*/
function receiveERC20(
CanonicalERC20 calldata canonicalToken,
address from,
address to,
uint256 amount
) external nonReentrant onlyFromNamed("bridge") {
IBridge.Context memory ctx = IBridge(msg.sender).context();
if (ctx.sender != resolve(ctx.srcChainId, "token_vault", false)) {
revert TOKENVAULT_INVALID_SENDER();
}
address token;
if (canonicalToken.chainId == block.chainid) {
token = canonicalToken.addr;
ERC20Upgradeable(token).safeTransfer(to, amount);
} else {
token = _getOrDeployBridgedToken(canonicalToken);
BridgedERC20(token).bridgeMintTo(to, amount);
}
emit ERC20Received({
msgHash: ctx.msgHash,
from: from,
to: to,
srcChainId: ctx.srcChainId,
token: token,
amount: amount
});
}
/*//////////////////////////////////////////////////////////////
PRIVATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
function _getOrDeployBridgedToken(CanonicalERC20 calldata canonicalToken)
private
returns (address)
{
address token = canonicalToBridged[canonicalToken.chainId][canonicalToken.addr];
return token != address(0) ? token : _deployBridgedToken(canonicalToken);
}
/**
* @dev Deploys a new BridgedERC20 contract and initializes it. This must be
* called before the first time a bridged token is sent to this chain.
*/
function _deployBridgedToken(CanonicalERC20 calldata canonicalToken)
private
returns (address bridgedToken)
{
bridgedToken = Create2Upgradeable.deploy(
0, // amount of Ether to send
keccak256(
bytes.concat(
bytes32(canonicalToken.chainId), bytes32(uint256(uint160(canonicalToken.addr)))
)
),
type(BridgedERC20).creationCode
);
BridgedERC20(payable(bridgedToken)).init({
_addressManager: address(_addressManager),
_srcToken: canonicalToken.addr,
_srcChainId: canonicalToken.chainId,
_decimals: canonicalToken.decimals,
_symbol: canonicalToken.symbol,
_name: string.concat(
canonicalToken.name,
unicode"(bridged🌈",
Strings.toString(canonicalToken.chainId),
")"
)
});
isBridgedToken[bridgedToken] = true;
bridgedToCanonical[bridgedToken] = canonicalToken;
canonicalToBridged[canonicalToken.chainId][canonicalToken.addr] = bridgedToken;
emit BridgedERC20Deployed({
srcChainId: canonicalToken.chainId,
canonicalToken: canonicalToken.addr,
bridgedToken: bridgedToken,
canonicalTokenSymbol: canonicalToken.symbol,
canonicalTokenName: canonicalToken.name,
canonicalTokenDecimal: canonicalToken.decimals
});
}
}
contract ProxiedTokenVault is Proxied, TokenVault {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {Proxied} from "./Proxied.sol";
/**
* @notice Interface to set and get an address for a name.
*/
interface IAddressManager {
/**
* Changes the address associated with a particular name.
* @param domain Uint256 domain to assiciate an address with.
* @param name Name to associate an address with.
* @param newAddress Address to associate with the name.
*/
function setAddress(uint256 domain, bytes32 name, address newAddress) external;
/**
* Retrieves the address associated with a given name.
* @param domain Class to retrieve an address for.
* @param name Name to retrieve an address for.
* @return Address associated with the given name.
*/
function getAddress(uint256 domain, bytes32 name) external view returns (address);
}
/// @custom:security-contact [email protected]
contract AddressManager is OwnableUpgradeable, IAddressManager {
mapping(uint256 domain => mapping(bytes32 name => address addr)) private addresses;
event AddressSet(
uint256 indexed _domain, bytes32 indexed _name, address _newAddress, address _oldAddress
);
error EOAOwnerAddressNotAllowed();
/// @dev Initializer to be called after being deployed behind a proxy.
function init() external initializer {
OwnableUpgradeable.__Ownable_init();
}
function setAddress(uint256 domain, bytes32 name, address newAddress)
external
virtual
onlyOwner
{
// This is to prevent using the owner as named address
if (newAddress.code.length == 0 && newAddress == msg.sender) {
revert EOAOwnerAddressNotAllowed();
}
address oldAddress = addresses[domain][name];
addresses[domain][name] = newAddress;
emit AddressSet(domain, name, newAddress, oldAddress);
}
function getAddress(uint256 domain, bytes32 name)
external
view
virtual
returns (address addr)
{
addr = addresses[domain][name];
}
}
contract ProxiedAddressManager is Proxied, AddressManager {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {IAddressManager} from "./AddressManager.sol";
import {Strings} from "lib/openzeppelin-contracts/contracts/utils/Strings.sol";
/**
* This abstract contract provides a name-to-address lookup. Under the hood,
* it uses an AddressManager to manage the name-to-address mapping.
*
* @title AddressResolver
*/
abstract contract AddressResolver {
IAddressManager internal _addressManager;
uint256[49] private __gap;
error RESOLVER_DENIED();
error RESOLVER_INVALID_ADDR();
error RESOLVER_ZERO_ADDR(uint256 chainId, bytes32 name);
modifier onlyFromNamed(bytes32 name) {
if (msg.sender != resolve(name, false)) revert RESOLVER_DENIED();
_;
}
event AddressManagerChanged(address addressManager);
/**
* Resolves a name to an address on the current chain.
*
* @dev This function will throw if the resolved address is `address(0)`.
* @param name The name to resolve.
* @param allowZeroAddress True to allow zero address to be returned.
* @return The name's corresponding address.
*/
function resolve(bytes32 name, bool allowZeroAddress)
public
view
virtual
returns (address payable)
{
return _resolve(block.chainid, name, allowZeroAddress);
}
/**
* Resolves a name to an address on the specified chain.
*
* @dev This function will throw if the resolved address is `address(0)`.
* @param chainId The chainId.
* @param name The name to resolve.
* @param allowZeroAddress True to allow zero address to be returned.
* @return The name's corresponding address.
*/
function resolve(uint256 chainId, bytes32 name, bool allowZeroAddress)
public
view
virtual
returns (address payable)
{
return _resolve(chainId, name, allowZeroAddress);
}
/**
* Returns the AddressManager's address.
*
* @return The AddressManager's address.
*/
function addressManager() public view returns (address) {
return address(_addressManager);
}
function _init(address addressManager_) internal virtual {
if (addressManager_ == address(0)) revert RESOLVER_INVALID_ADDR();
_addressManager = IAddressManager(addressManager_);
}
function _resolve(uint256 chainId, bytes32 name, bool allowZeroAddress)
private
view
returns (address payable addr)
{
addr = payable(_addressManager.getAddress(chainId, name));
if (!allowZeroAddress && addr == address(0)) {
revert RESOLVER_ZERO_ADDR(chainId, name);
}
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {IAddressManager} from "./AddressManager.sol";
import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from
"@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {AddressResolver} from "./AddressResolver.sol";
/**
* @dev This abstract contract serves as the base contract for many core
* components in this package.
*/
abstract contract EssentialContract is
ReentrancyGuardUpgradeable,
OwnableUpgradeable,
AddressResolver
{
function _init(address _addressManager) internal virtual override {
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
OwnableUpgradeable.__Ownable_init();
AddressResolver._init(_addressManager);
}
/**
* Sets a new AddressManager's address.
*
* @param newAddressManager New address manager contract address
*/
function setAddressManager(address newAddressManager) external onlyOwner {
if (newAddressManager == address(0)) revert RESOLVER_INVALID_ADDR();
_addressManager = IAddressManager(newAddressManager);
emit AddressManagerChanged(newAddressManager);
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
/**
* Interface implemented by both the TaikoL1 and TaikoL2 contracts. It exposes
* the methods needed to access the block hashes of the other chain.
*/
interface ICrossChainSync {
event CrossChainSynced(uint256 indexed srcHeight, bytes32 blockHash, bytes32 signalRoot);
/**
* @notice Returns the cross-chain block hash at the given block number.
* @param number The block number. Use 0 for the latest block.
* @return The cross-chain block hash.
*/
function getCrossChainBlockHash(uint256 number) external view returns (bytes32);
/**
* @notice Returns the cross-chain signal service storage root at the given
* block number.
* @param number The block number. Use 0 for the latest block.
* @return The cross-chain signal service storage root.
*/
function getCrossChainSignalRoot(uint256 number) external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {IERC20Upgradeable} from
"@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
interface IMintableERC20 is IERC20Upgradeable {
function mint(address account, uint256 amount) external;
function burn(address account, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
abstract contract Proxied is Initializable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {
GovernorUpgradeable,
IGovernorUpgradeable
} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol";
import {GovernorSettingsUpgradeable} from
"@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol";
import {GovernorCountingSimpleUpgradeable} from
"@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol";
import {GovernorVotesUpgradeable} from
"@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol";
import {
GovernorVotesQuorumFractionUpgradeable,
IVotesUpgradeable
} from
"@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol";
import {
GovernorTimelockControlUpgradeable,
TimelockControllerUpgradeable
} from
"@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol";
import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {LibTaikoTokenConfig} from "../L1/TaikoToken.sol";
import {EssentialContract} from "../common/EssentialContract.sol";
import {Proxied} from "../common/Proxied.sol";
/// @custom:security-contact [email protected]
contract TaikoGovernor is
EssentialContract,
GovernorUpgradeable,
GovernorSettingsUpgradeable,
GovernorCountingSimpleUpgradeable,
GovernorVotesUpgradeable,
GovernorVotesQuorumFractionUpgradeable,
GovernorTimelockControlUpgradeable
{
/*//////////////////////////////////////////////////////////////
USER-FACING FUNCTIONS
//////////////////////////////////////////////////////////////*/
function init(
address _addressManager,
IVotesUpgradeable _token,
TimelockControllerUpgradeable _timelock
) public initializer {
EssentialContract._init(_addressManager);
__Governor_init("TaikoGovernor");
__GovernorSettings_init(1, /* 1 block */ 100800, /* 2 week */ LibTaikoTokenConfig.DECIMALS);
__GovernorCountingSimple_init();
__GovernorVotes_init(_token);
__GovernorVotesQuorumFraction_init(5);
__GovernorTimelockControl_init(_timelock);
}
// The following functions are overrides required by Solidity.
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override(GovernorUpgradeable, IGovernorUpgradeable) returns (uint256) {
return super.propose(targets, values, calldatas, description);
}
function votingDelay()
public
view
override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.votingDelay();
}
function votingPeriod()
public
view
override(IGovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.votingPeriod();
}
function quorum(uint256 blockNumber)
public
view
override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable)
returns (uint256)
{
return super.quorum(blockNumber);
}
function state(uint256 proposalId)
public
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (ProposalState)
{
return super.state(proposalId);
}
function proposalThreshold()
public
view
override(GovernorUpgradeable, GovernorSettingsUpgradeable)
returns (uint256)
{
return super.proposalThreshold();
}
function supportsInterface(bytes4 interfaceId)
public
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
/*//////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/
function _execute(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) {
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}
function _cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
)
internal
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (uint256)
{
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor()
internal
view
override(GovernorUpgradeable, GovernorTimelockControlUpgradeable)
returns (address)
{
return super._executor();
}
}
contract ProxiedTaikoGovernor is Proxied, TaikoGovernor {}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibMath} from "../../libs/LibMath.sol";
import {AddressResolver} from "../../common/AddressResolver.sol";
import {SafeCastUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import {TaikoData} from "../TaikoData.sol";
library LibEthDepositing {
using LibAddress for address;
using LibMath for uint256;
using SafeCastUpgradeable for uint256;
event EthDeposited(TaikoData.EthDeposit deposit);
error L1_INVALID_ETH_DEPOSIT();
function depositEtherToL2(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver
) internal {
if (msg.value < config.minEthDepositAmount || msg.value > config.maxEthDepositAmount) {
revert L1_INVALID_ETH_DEPOSIT();
}
TaikoData.EthDeposit memory deposit =
TaikoData.EthDeposit({recipient: msg.sender, amount: uint96(msg.value)});
address to = resolver.resolve("ether_vault", true);
if (to == address(0)) {
to = resolver.resolve("bridge", false);
}
to.sendEther(msg.value);
state.ethDeposits.push(deposit);
emit EthDeposited(deposit);
}
function processDeposits(
TaikoData.State storage state,
TaikoData.Config memory config,
address beneficiary
) internal returns (TaikoData.EthDeposit[] memory depositsProcessed) {
// Allocate one extra slot for collecting fees on L2
depositsProcessed = new TaikoData.EthDeposit[](
config.maxEthDepositsPerBlock + 1
);
uint256 j; // number of deposits to process on L2
if (
state.ethDeposits.length
>= state.nextEthDepositToProcess + config.minEthDepositsPerBlock
) {
unchecked {
// When maxEthDepositsPerBlock is 32, the average gas cost per
// EthDeposit is about 2700 gas. We use 21000 so the proposer may
// earn a small profit if there are 32 deposits included
// in the block; if there are less EthDeposit to process, the
// proposer may suffer a loss so the proposer should simply wait
// for more EthDeposit be become available.
uint96 feePerDeposit =
uint96(config.ethDepositMaxFee.min(block.basefee * config.ethDepositGas));
uint96 totalFee;
uint64 i = state.nextEthDepositToProcess;
while (
i < state.ethDeposits.length
&& i < state.nextEthDepositToProcess + config.maxEthDepositsPerBlock
) {
TaikoData.EthDeposit storage deposit = state.ethDeposits[i];
if (deposit.amount > feePerDeposit) {
totalFee += feePerDeposit;
depositsProcessed[j].recipient = deposit.recipient;
depositsProcessed[j].amount = deposit.amount - feePerDeposit;
++j;
} else {
totalFee += deposit.amount;
}
// delete the deposit
deposit.recipient = address(0);
deposit.amount = 0;
++i;
}
// Fee collecting deposit
if (totalFee > 0) {
depositsProcessed[j].recipient = beneficiary;
depositsProcessed[j].amount = totalFee;
++j;
}
// Advance cursor
state.nextEthDepositToProcess = i;
}
}
assembly {
mstore(depositsProcessed, j)
}
}
function hashEthDeposits(TaikoData.EthDeposit[] memory deposits)
internal
pure
returns (bytes32)
{
bytes memory buffer = new bytes(32 * deposits.length);
for (uint256 i; i < deposits.length;) {
uint256 encoded =
uint256(uint160(deposits[i].recipient)) << 96 | uint256(deposits[i].amount);
assembly {
mstore(add(buffer, mul(32, add(1, i))), encoded)
}
unchecked {
++i;
}
}
return keccak256(buffer);
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {LibAddress} from "../../libs/LibAddress.sol";
import {LibEthDepositing} from "./LibEthDepositing.sol";
import {LibUtils} from "./LibUtils.sol";
import {SafeCastUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import {TaikoData} from "../TaikoData.sol";
library LibProposing {
using SafeCastUpgradeable for uint256;
using LibAddress for address;
using LibAddress for address payable;
using LibUtils for TaikoData.State;
event BlockProposed(uint256 indexed id, TaikoData.BlockMetadata meta, uint64 blockFee);
error L1_BLOCK_ID();
error L1_INSUFFICIENT_TOKEN();
error L1_INVALID_METADATA();
error L1_TOO_MANY_BLOCKS();
error L1_TX_LIST_NOT_EXIST();
error L1_TX_LIST_HASH();
error L1_TX_LIST_RANGE();
error L1_TX_LIST();
function proposeBlock(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver,
TaikoData.BlockMetadataInput memory input,
bytes calldata txList
) internal returns (TaikoData.BlockMetadata memory meta) {
uint8 cacheTxListInfo =
_validateBlock({state: state, config: config, input: input, txList: txList});
if (cacheTxListInfo != 0) {
state.txListInfo[input.txListHash] = TaikoData.TxListInfo({
validSince: uint64(block.timestamp),
size: uint24(txList.length)
});
}
// After The Merge, L1 mixHash contains the prevrandao
// from the beacon chain. Since multiple Taiko blocks
// can be proposed in one Ethereum block, we need to
// add salt to this random number as L2 mixHash
meta.id = state.numBlocks;
meta.txListHash = input.txListHash;
meta.txListByteStart = input.txListByteStart;
meta.txListByteEnd = input.txListByteEnd;
meta.gasLimit = input.gasLimit;
meta.beneficiary = input.beneficiary;
meta.treasury = resolver.resolve(config.chainId, "treasury", false);
meta.depositsProcessed = LibEthDepositing.processDeposits(state, config, input.beneficiary);
unchecked {
meta.timestamp = uint64(block.timestamp);
meta.l1Height = uint64(block.number - 1);
meta.l1Hash = blockhash(block.number - 1);
meta.mixHash = bytes32(block.difficulty * state.numBlocks);
}
TaikoData.Block storage blk = state.blocks[state.numBlocks % config.ringBufferSize];
blk.blockId = state.numBlocks;
blk.proposedAt = meta.timestamp;
blk.nextForkChoiceId = 1;
blk.verifiedForkChoiceId = 0;
blk.metaHash = LibUtils.hashMetadata(meta);
blk.proposer = msg.sender;
uint64 blockFee = state.blockFee;
if (state.taikoTokenBalances[msg.sender] < blockFee) {
revert L1_INSUFFICIENT_TOKEN();
}
unchecked {
state.taikoTokenBalances[msg.sender] -= blockFee;
state.accBlockFees += blockFee;
state.accProposedAt += meta.timestamp;
}
emit BlockProposed(state.numBlocks, meta, blockFee);
unchecked {
++state.numBlocks;
}
}
function getBlock(
TaikoData.State storage state,
TaikoData.Config memory config,
uint256 blockId
) internal view returns (TaikoData.Block storage blk) {
blk = state.blocks[blockId % config.ringBufferSize];
if (blk.blockId != blockId) revert L1_BLOCK_ID();
}
function _validateBlock(
TaikoData.State storage state,
TaikoData.Config memory config,
TaikoData.BlockMetadataInput memory input,
bytes calldata txList
) private view returns (uint8 cacheTxListInfo) {
if (
input.beneficiary == address(0) || input.gasLimit == 0
|| input.gasLimit > config.blockMaxGasLimit
) revert L1_INVALID_METADATA();
if (state.numBlocks >= state.lastVerifiedBlockId + config.maxNumProposedBlocks + 1) {
revert L1_TOO_MANY_BLOCKS();
}
uint64 timeNow = uint64(block.timestamp);
// handling txList
{
uint24 size = uint24(txList.length);
if (size > config.maxBytesPerTxList) revert L1_TX_LIST();
if (input.txListByteStart > input.txListByteEnd) {
revert L1_TX_LIST_RANGE();
}
if (config.txListCacheExpiry == 0) {
// caching is disabled
if (input.txListByteStart != 0 || input.txListByteEnd != size) {
revert L1_TX_LIST_RANGE();
}
} else {
// caching is enabled
if (size == 0) {
// This blob shall have been submitted earlier
TaikoData.TxListInfo memory info = state.txListInfo[input.txListHash];
if (input.txListByteEnd > info.size) {
revert L1_TX_LIST_RANGE();
}
if (info.size == 0 || info.validSince + config.txListCacheExpiry < timeNow) {
revert L1_TX_LIST_NOT_EXIST();
}
} else {
if (input.txListByteEnd > size) revert L1_TX_LIST_RANGE();
if (input.txListHash != keccak256(txList)) {
revert L1_TX_LIST_HASH();
}
cacheTxListInfo = input.cacheTxListInfo;
}
}
}
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {LibMath} from "../../libs/LibMath.sol";
import {LibUtils} from "./LibUtils.sol";
import {TaikoData} from "../../L1/TaikoData.sol";
library LibProving {
using LibMath for uint256;
using LibUtils for TaikoData.State;
event BlockProven(
uint256 indexed id,
bytes32 parentHash,
bytes32 blockHash,
bytes32 signalRoot,
address prover,
uint32 parentGasUsed
);
error L1_ALREADY_PROVEN();
error L1_BLOCK_ID();
error L1_EVIDENCE_MISMATCH(bytes32 expected, bytes32 actual);
error L1_FORK_CHOICE_NOT_FOUND();
error L1_INVALID_EVIDENCE();
error L1_INVALID_PROOF();
error L1_INVALID_PROOF_OVERWRITE();
error L1_NOT_SPECIAL_PROVER();
error L1_ORACLE_PROVER_DISABLED();
error L1_SAME_PROOF();
error L1_SYSTEM_PROVER_DISABLED();
error L1_SYSTEM_PROVER_PROHIBITED();
function proveBlock(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver,
uint256 blockId,
TaikoData.BlockEvidence memory evidence
) internal {
if (
evidence.parentHash == 0 || evidence.blockHash == 0
|| evidence.blockHash == evidence.parentHash || evidence.signalRoot == 0
|| evidence.gasUsed == 0
) revert L1_INVALID_EVIDENCE();
if (blockId <= state.lastVerifiedBlockId || blockId >= state.numBlocks) {
revert L1_BLOCK_ID();
}
TaikoData.Block storage blk = state.blocks[blockId % config.ringBufferSize];
// Check the metadata hash matches the proposed block's. This is
// necessary to handle chain reorgs.
if (blk.metaHash != evidence.metaHash) {
revert L1_EVIDENCE_MISMATCH(blk.metaHash, evidence.metaHash);
}
// Separate between oracle proof (which needs to be overwritten)
// and non-oracle but system proofs
address specialProver;
if (evidence.prover == address(0)) {
specialProver = resolver.resolve("oracle_prover", true);
if (specialProver == address(0)) {
revert L1_ORACLE_PROVER_DISABLED();
}
} else if (evidence.prover == address(1)) {
specialProver = resolver.resolve("system_prover", true);
if (specialProver == address(0)) {
revert L1_SYSTEM_PROVER_DISABLED();
}
if (config.realProofSkipSize <= 1 || blockId % config.realProofSkipSize == 0) {
revert L1_SYSTEM_PROVER_PROHIBITED();
}
}
if (specialProver != address(0) && msg.sender != specialProver) {
if (evidence.proof.length != 64) {
revert L1_NOT_SPECIAL_PROVER();
} else {
uint8 v = uint8(evidence.verifierId);
bytes32 r;
bytes32 s;
bytes memory data = evidence.proof;
assembly {
r := mload(add(data, 32))
s := mload(add(data, 64))
}
// clear the proof before hashing evidence
evidence.verifierId = 0;
evidence.proof = new bytes(0);
if (specialProver != ecrecover(keccak256(abi.encode(evidence)), v, r, s)) {
revert L1_NOT_SPECIAL_PROVER();
}
}
}
TaikoData.ForkChoice storage fc;
uint256 fcId =
LibUtils.getForkChoiceId(state, blk, evidence.parentHash, evidence.parentGasUsed);
if (fcId == 0) {
fcId = blk.nextForkChoiceId;
unchecked {
++blk.nextForkChoiceId;
}
fc = blk.forkChoices[fcId];
if (fcId == 1) {
// We only write the key when fcId is 1.
fc.key = LibUtils.keyForForkChoice(evidence.parentHash, evidence.parentGasUsed);
} else {
state.forkChoiceIds[blk.blockId][evidence.parentHash][evidence.parentGasUsed] = fcId;
}
} else if (evidence.prover == address(0)) {
// This is the branch the oracle prover is trying to overwrite
fc = blk.forkChoices[fcId];
if (
fc.blockHash == evidence.blockHash && fc.signalRoot == evidence.signalRoot
&& fc.gasUsed == evidence.gasUsed
) revert L1_SAME_PROOF();
} else {
// This is the branch provers trying to overwrite
fc = blk.forkChoices[fcId];
if (fc.prover != address(0) && fc.prover != address(1)) {
revert L1_ALREADY_PROVEN();
}
if (
fc.blockHash != evidence.blockHash || fc.signalRoot != evidence.signalRoot
|| fc.gasUsed != evidence.gasUsed
) revert L1_INVALID_PROOF_OVERWRITE();
}
fc.blockHash = evidence.blockHash;
fc.signalRoot = evidence.signalRoot;
fc.gasUsed = evidence.gasUsed;
fc.prover = evidence.prover;
if (evidence.prover == address(1)) {
fc.provenAt = uint64(block.timestamp.max(blk.proposedAt + state.proofTimeTarget));
} else {
fc.provenAt = uint64(block.timestamp);
}
if (evidence.prover != address(0) && evidence.prover != address(1)) {
uint256[10] memory inputs;
inputs[0] = uint256(uint160(address(resolver.resolve("signal_service", false))));
inputs[1] =
uint256(uint160(address(resolver.resolve(config.chainId, "signal_service", false))));
inputs[2] = uint256(uint160(address(resolver.resolve(config.chainId, "taiko", false))));
inputs[3] = uint256(evidence.metaHash);
inputs[4] = uint256(evidence.parentHash);
inputs[5] = uint256(evidence.blockHash);
inputs[6] = uint256(evidence.signalRoot);
inputs[7] = uint256(evidence.graffiti);
inputs[8] = (uint256(uint160(evidence.prover)) << 96)
| (uint256(evidence.parentGasUsed) << 64) | (uint256(evidence.gasUsed) << 32);
// Also hash configs that will be used by circuits
inputs[9] = uint256(config.blockMaxGasLimit) << 192
| uint256(config.maxTransactionsPerBlock) << 128
| uint256(config.maxBytesPerTxList) << 64;
bytes32 instance;
assembly {
instance := keccak256(inputs, mul(32, 10))
}
(bool verified, bytes memory ret) = resolver.resolve(
LibUtils.getVerifierName(evidence.verifierId), false
).staticcall(
bytes.concat(
bytes16(0),
bytes16(instance), // left 16 bytes of the given instance
bytes16(0),
bytes16(uint128(uint256(instance))), // right 16 bytes of the given instance
evidence.proof
)
);
if (!verified || ret.length != 32 || bytes32(ret) != keccak256("taiko")) {
revert L1_INVALID_PROOF();
}
}
emit BlockProven({
id: blk.blockId,
parentHash: evidence.parentHash,
blockHash: evidence.blockHash,
signalRoot: evidence.signalRoot,
prover: evidence.prover,
parentGasUsed: evidence.parentGasUsed
});
}
function getForkChoice(
TaikoData.State storage state,
TaikoData.Config memory config,
uint256 blockId,
bytes32 parentHash,
uint32 parentGasUsed
) internal view returns (TaikoData.ForkChoice storage fc) {
TaikoData.Block storage blk = state.blocks[blockId % config.ringBufferSize];
if (blk.blockId != blockId) revert L1_BLOCK_ID();
uint256 fcId = LibUtils.getForkChoiceId(state, blk, parentHash, parentGasUsed);
if (fcId == 0) revert L1_FORK_CHOICE_NOT_FOUND();
fc = blk.forkChoices[fcId];
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {LibMath} from "../../libs/LibMath.sol";
import {SafeCastUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import {TaikoData} from "../TaikoData.sol";
import {TaikoToken} from "../TaikoToken.sol";
import {LibFixedPointMath as Math} from "../../thirdparty/LibFixedPointMath.sol";
library LibTokenomics {
using LibMath for uint256;
error L1_INSUFFICIENT_TOKEN();
function withdrawTaikoToken(
TaikoData.State storage state,
AddressResolver resolver,
uint256 amount
) internal {
uint256 balance = state.taikoTokenBalances[msg.sender];
if (balance < amount) revert L1_INSUFFICIENT_TOKEN();
unchecked {
state.taikoTokenBalances[msg.sender] -= amount;
}
TaikoToken(resolver.resolve("taiko_token", false)).mint(msg.sender, amount);
}
function depositTaikoToken(
TaikoData.State storage state,
AddressResolver resolver,
uint256 amount
) internal {
if (amount > 0) {
TaikoToken(resolver.resolve("taiko_token", false)).burn(msg.sender, amount);
state.taikoTokenBalances[msg.sender] += amount;
}
}
/**
* Get the block reward for a proof
*
* @param state The actual state data
* @param proofTime The actual proof time
* @return reward The reward given for the block proof
*/
function getProofReward(TaikoData.State storage state, uint64 proofTime)
internal
view
returns (uint64)
{
uint64 numBlocksUnverified = state.numBlocks - state.lastVerifiedBlockId - 1;
if (numBlocksUnverified == 0) {
return 0;
} else {
uint64 totalNumProvingSeconds =
uint64(uint256(numBlocksUnverified) * block.timestamp - state.accProposedAt);
// If block timestamp is equal to state.accProposedAt (not really,
// but theoretically possible) there will be division by 0 error
if (totalNumProvingSeconds == 0) {
totalNumProvingSeconds = 1;
}
return uint64((uint256(state.accBlockFees) * proofTime) / totalNumProvingSeconds);
}
}
/**
* Calculate the newProofTimeIssued and blockFee
*
* @param state The actual state data
* @param proofTime The actual proof time
* @return newProofTimeIssued Accumulated proof time
* @return blockFee New block fee
*/
function getNewBlockFeeAndProofTimeIssued(TaikoData.State storage state, uint64 proofTime)
internal
view
returns (uint64 newProofTimeIssued, uint64 blockFee)
{
newProofTimeIssued = (state.proofTimeIssued > state.proofTimeTarget)
? state.proofTimeIssued - state.proofTimeTarget
: uint64(0);
newProofTimeIssued += proofTime;
uint256 x = (newProofTimeIssued * Math.SCALING_FACTOR_1E18)
/ (state.proofTimeTarget * state.adjustmentQuotient);
if (Math.MAX_EXP_INPUT <= x) {
x = Math.MAX_EXP_INPUT;
}
uint256 result = (uint256(Math.exp(int256(x))) / Math.SCALING_FACTOR_1E18)
/ (state.proofTimeTarget * state.adjustmentQuotient);
blockFee = uint64(result.min(type(uint64).max));
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {LibMath} from "../../libs/LibMath.sol";
import {LibEthDepositing} from "./LibEthDepositing.sol";
import {SafeCastUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import {TaikoData} from "../TaikoData.sol";
library LibUtils {
using LibMath for uint256;
error L1_BLOCK_ID();
function getL2ChainData(
TaikoData.State storage state,
TaikoData.Config memory config,
uint256 blockId
) internal view returns (bool found, TaikoData.Block storage blk) {
uint256 id = blockId == 0 ? state.lastVerifiedBlockId : blockId;
blk = state.blocks[id % config.ringBufferSize];
found = (blk.blockId == id && blk.verifiedForkChoiceId != 0);
}
function getForkChoiceId(
TaikoData.State storage state,
TaikoData.Block storage blk,
bytes32 parentHash,
uint32 parentGasUsed
) internal view returns (uint256 fcId) {
if (blk.forkChoices[1].key == keyForForkChoice(parentHash, parentGasUsed)) {
fcId = 1;
} else {
fcId = state.forkChoiceIds[blk.blockId][parentHash][parentGasUsed];
}
if (fcId >= blk.nextForkChoiceId) {
fcId = 0;
}
}
function getStateVariables(TaikoData.State storage state)
internal
view
returns (TaikoData.StateVariables memory)
{
return TaikoData.StateVariables({
blockFee: state.blockFee,
accBlockFees: state.accBlockFees,
genesisHeight: state.genesisHeight,
genesisTimestamp: state.genesisTimestamp,
numBlocks: state.numBlocks,
proofTimeIssued: state.proofTimeIssued,
proofTimeTarget: state.proofTimeTarget,
lastVerifiedBlockId: state.lastVerifiedBlockId,
accProposedAt: state.accProposedAt,
nextEthDepositToProcess: state.nextEthDepositToProcess,
numEthDeposits: uint64(state.ethDeposits.length)
});
}
function movingAverage(uint256 maValue, uint256 newValue, uint256 maf)
internal
pure
returns (uint256)
{
if (maValue == 0) {
return newValue;
}
uint256 _ma = (maValue * (maf - 1) + newValue) / maf;
return _ma > 0 ? _ma : maValue;
}
/// @dev Hashing the block metadata.
function hashMetadata(TaikoData.BlockMetadata memory meta)
internal
pure
returns (bytes32 hash)
{
uint256[7] memory inputs;
inputs[0] = (uint256(meta.id) << 192) | (uint256(meta.timestamp) << 128)
| (uint256(meta.l1Height) << 64);
inputs[1] = uint256(meta.l1Hash);
inputs[2] = uint256(meta.mixHash);
inputs[3] = uint256(LibEthDepositing.hashEthDeposits(meta.depositsProcessed));
inputs[4] = uint256(meta.txListHash);
inputs[5] = (uint256(meta.txListByteStart) << 232) | (uint256(meta.txListByteEnd) << 208)
| (uint256(meta.gasLimit) << 176) | (uint256(uint160(meta.beneficiary)) << 16);
inputs[6] = (uint256(uint160(meta.treasury)) << 96);
assembly {
hash := keccak256(inputs, mul(7, 32))
}
}
function keyForForkChoice(bytes32 parentHash, uint32 parentGasUsed)
internal
pure
returns (bytes32 key)
{
assembly {
let ptr := mload(0x40)
mstore(ptr, parentGasUsed)
mstore(add(ptr, 32), parentHash)
key := keccak256(add(ptr, 28), 36)
mstore(0x40, add(ptr, 64))
}
}
function getVerifierName(uint16 id) internal pure returns (bytes32) {
return bytes32(uint256(0x1000000) + id);
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {AddressResolver} from "../../common/AddressResolver.sol";
import {ISignalService} from "../../signal/ISignalService.sol";
import {LibTokenomics} from "./LibTokenomics.sol";
import {LibUtils} from "./LibUtils.sol";
import {SafeCastUpgradeable} from
"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import {TaikoData} from "../../L1/TaikoData.sol";
library LibVerifying {
using SafeCastUpgradeable for uint256;
using LibUtils for TaikoData.State;
event BlockVerified(uint256 indexed id, bytes32 blockHash, uint64 reward);
event CrossChainSynced(uint256 indexed srcHeight, bytes32 blockHash, bytes32 signalRoot);
error L1_INVALID_CONFIG();
function init(
TaikoData.State storage state,
TaikoData.Config memory config,
bytes32 genesisBlockHash,
uint64 initBlockFee,
uint64 initProofTimeTarget,
uint64 initProofTimeIssued,
uint16 adjustmentQuotient
) internal {
if (
config.chainId <= 1 || config.maxNumProposedBlocks == 1
|| config.ringBufferSize <= config.maxNumProposedBlocks + 1
|| config.blockMaxGasLimit == 0 || config.maxTransactionsPerBlock == 0
|| config.maxBytesPerTxList == 0
// EIP-4844 blob size up to 128K
|| config.maxBytesPerTxList > 128 * 1024 || config.maxEthDepositsPerBlock == 0
|| config.maxEthDepositsPerBlock < config.minEthDepositsPerBlock
// EIP-4844 blob deleted after 30 days
|| config.txListCacheExpiry > 30 * 24 hours || config.ethDepositGas == 0
|| config.ethDepositMaxFee == 0 || config.ethDepositMaxFee >= type(uint96).max
|| adjustmentQuotient == 0 || initProofTimeTarget == 0 || initProofTimeIssued == 0
) revert L1_INVALID_CONFIG();
uint64 timeNow = uint64(block.timestamp);
state.genesisHeight = uint64(block.number);
state.genesisTimestamp = timeNow;
state.blockFee = initBlockFee;
state.proofTimeIssued = initProofTimeIssued;
state.proofTimeTarget = initProofTimeTarget;
state.adjustmentQuotient = adjustmentQuotient;
state.numBlocks = 1;
TaikoData.Block storage blk = state.blocks[0];
blk.proposedAt = timeNow;
blk.nextForkChoiceId = 2;
blk.verifiedForkChoiceId = 1;
TaikoData.ForkChoice storage fc = state.blocks[0].forkChoices[1];
fc.blockHash = genesisBlockHash;
fc.provenAt = timeNow;
emit BlockVerified(0, genesisBlockHash, 0);
}
function verifyBlocks(
TaikoData.State storage state,
TaikoData.Config memory config,
AddressResolver resolver,
uint256 maxBlocks
) internal {
uint256 i = state.lastVerifiedBlockId;
TaikoData.Block storage blk = state.blocks[i % config.ringBufferSize];
uint256 fcId = blk.verifiedForkChoiceId;
assert(fcId > 0);
bytes32 blockHash = blk.forkChoices[fcId].blockHash;
uint32 gasUsed = blk.forkChoices[fcId].gasUsed;
bytes32 signalRoot;
uint64 processed;
unchecked {
++i;
}
address systemProver = resolver.resolve("system_prover", true);
while (i < state.numBlocks && processed < maxBlocks) {
blk = state.blocks[i % config.ringBufferSize];
assert(blk.blockId == i);
fcId = LibUtils.getForkChoiceId(state, blk, blockHash, gasUsed);
if (fcId == 0) break;
TaikoData.ForkChoice storage fc = blk.forkChoices[fcId];
if (fc.prover == address(0)) break;
uint256 proofCooldownPeriod = fc.prover == address(1)
? config.systemProofCooldownPeriod
: config.proofCooldownPeriod;
if (block.timestamp < fc.provenAt + proofCooldownPeriod) break;
blockHash = fc.blockHash;
gasUsed = fc.gasUsed;
signalRoot = fc.signalRoot;
_markBlockVerified({
state: state,
blk: blk,
fcId: uint24(fcId),
fc: fc,
systemProver: systemProver
});
unchecked {
++i;
++processed;
}
}
if (processed > 0) {
unchecked {
state.lastVerifiedBlockId += processed;
}
if (config.relaySignalRoot) {
// Send the L2's signal root to the signal service so other TaikoL1
// deployments, if they share the same signal service, can relay the
// signal to their corresponding TaikoL2 contract.
ISignalService(resolver.resolve("signal_service", false)).sendSignal(signalRoot);
}
emit CrossChainSynced(state.lastVerifiedBlockId, blockHash, signalRoot);
}
}
function _markBlockVerified(
TaikoData.State storage state,
TaikoData.Block storage blk,
TaikoData.ForkChoice storage fc,
uint24 fcId,
address systemProver
) private {
uint64 proofTime;
unchecked {
proofTime = uint64(fc.provenAt - blk.proposedAt);
}
uint64 reward = LibTokenomics.getProofReward(state, proofTime);
(state.proofTimeIssued, state.blockFee) =
LibTokenomics.getNewBlockFeeAndProofTimeIssued(state, proofTime);
unchecked {
state.accBlockFees -= reward;
state.accProposedAt -= blk.proposedAt;
}
// reward the prover
if (reward != 0) {
address prover = fc.prover != address(1) ? fc.prover : systemProver;
// systemProver may become address(0) after a block is proven
if (prover != address(0)) {
if (state.taikoTokenBalances[prover] == 0) {
// Reduce refund to 1 wei as a penalty if the proposer
// has 0 TKO outstanding balance.
state.taikoTokenBalances[prover] = 1;
} else {
state.taikoTokenBalances[prover] += reward;
}
}
}
blk.nextForkChoiceId = 1;
blk.verifiedForkChoiceId = fcId;
emit BlockVerified(blk.blockId, fc.blockHash, reward);
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
import {TaikoData} from "../L1/TaikoData.sol";
library TaikoConfig {
function getConfig() internal pure returns (TaikoData.Config memory) {
return TaikoData.Config({
chainId: 167005,
// Two weeks if avg block time is 3 seconds
maxNumProposedBlocks: 403200,
ringBufferSize: 403200 + 10,
// Each time one more block is verified, there will be ~20k
// more gas cost.
maxVerificationsPerTx: 10,
// Set it to 6M, since its the upper limit of the Alpha-3
// testnet's circuits.
blockMaxGasLimit: 6000000,
// Set it to 79 (+1 TaikoL2.anchor transaction = 80),
// and 80 is the upper limit of the Alpha-3 testnet's circuits.
maxTransactionsPerBlock: 79,
minEthDepositsPerBlock: 1,
maxEthDepositsPerBlock: 32,
maxEthDepositAmount: 10000 ether,
minEthDepositAmount: 0.1 ether,
// Set it to 120KB, since 128KB is the upper size limit
// of a geth transaction, so using 120KB for the proposed
// transactions list calldata, 8K for the remaining tx fields.
maxBytesPerTxList: 120000,
proofCooldownPeriod: 30 minutes,
systemProofCooldownPeriod: 15 minutes,
// Only need 1 real zkp per 10 blocks.
// If block number is N, then only when N % 10 == 0, the real ZKP
// is needed. For mainnet, this must be 0 or 1.
realProofSkipSize: 10,
ethDepositGas: 21000,
ethDepositMaxFee: 1 ether / 10,
txListCacheExpiry: 0,
relaySignalRoot: false
});
}
}
// SPDX-License-Identifier: MIT
// _____ _ _ _ _
// |_ _|_ _(_) |_____ | | __ _| |__ ___
// | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
pragma solidity ^0.8.18;
library TaikoData {
struct Config {
uint256 chainId;
uint256 maxNumProposedBlocks;
uint256 ringBufferSize;
// This number is calculated from maxNumProposedBlocks to make
// the 'the maximum value of the multiplier' close to 20.0
uint256 maxVerificationsPerTx;
uint64 blockMaxGasLimit;
uint64 maxTransactionsPerBlock;
uint64 maxBytesPerTxList;
uint256 txListCacheExpiry;
uint256 proofCooldownPeriod;
uint256 systemProofCooldownPeriod;
uint256 realProofSkipSize;
uint256 ethDepositGas;
uint256 ethDepositMaxFee;
uint64 minEthDepositsPerBlock;
uint64 maxEthDepositsPerBlock;
uint96 maxEthDepositAmount;
uint96 minEthDepositAmount;
bool relaySignalRoot;
}
struct StateVariables {
uint64 blockFee;
uint64 accBlockFees;
uint64 genesisHeight;
uint64 genesisTimestamp;
uint64 numBlocks;
uint64 proofTimeIssued;
uint64 proofTimeTarget;
uint64 lastVerifiedBlockId;
uint64 accProposedAt;
uint64 nextEthDepositToProcess;
uint64 numEthDeposits;
}
// 3 slots
struct BlockMetadataInput {
bytes32 txListHash;
address beneficiary;
uint32 gasLimit;
uint24 txListByteStart; // byte-wise start index (inclusive)
uint24 txListByteEnd; // byte-wise end index (exclusive)
uint8 cacheTxListInfo; // non-zero = True
}
// Changing this struct requires changing LibUtils.hashMetadata accordingly.
struct BlockMetadata {
uint64 id;
uint64 timestamp;
uint64 l1Height;
bytes32 l1Hash;
bytes32 mixHash;
bytes32 txListHash;
uint24 txListByteStart;
uint24 txListByteEnd;
uint32 gasLimit;
address beneficiary;
address treasury;
TaikoData.EthDeposit[] depositsProcessed;
}
struct BlockEvidence {
bytes32 metaHash;
bytes32 parentHash;
bytes32 blockHash;
bytes32 signalRoot;
bytes32 graffiti;
address prover;
uint32 parentGasUsed;
uint32 gasUsed;
uint16 verifierId;
bytes proof;
}
// 4 slots
struct ForkChoice {
// Key is only written/read for the 1st fork choice.
bytes32 key;
bytes32 blockHash;
bytes32 signalRoot;
uint64 provenAt;
address prover;
uint32 gasUsed;
}
// 4 slots
struct Block {
// ForkChoice storage are reusable
mapping(uint256 forkChoiceId => ForkChoice) forkChoices;
uint64 blockId;
uint64 proposedAt;
uint24 nextForkChoiceId;
uint24 verifiedForkChoiceId;
bytes32 metaHash;
address proposer;
}
// This struct takes 9 slots.
struct TxListInfo {
uint64 validSince;
uint24 size;
}
// 1 slot
struct EthDeposit {
address recipient;
uint96 amount;
}
struct State {
// Ring buffer for proposed blocks and a some recent verified blocks.
mapping(uint256 blockId_mode_ringBufferSize => Block) blocks;
// solhint-disable-next-line max-line-length
mapping(
uint256 blockId
=> mapping(
bytes32 parentHash => mapping(uint32 parentGasUsed => uint256 forkChoiceId)
)
) forkChoiceIds;
mapping(address account => uint256 balance) taikoTokenBalances;
mapping(bytes32 txListHash => TxListInfo) txListInfo;
EthDeposit[] ethDeposits;
// Never or rarely changed
// Slot 7: never or rarely changed
uint64 genesisHeight;
uint64 genesisTimestamp;
uint16 adjustmentQuotient;
uint48 __reserved71;
uint64 __reserved72;
// Slot 8
uint64 accProposedAt;
uint64 accBlockFees;
uint64 numBlocks;
uint64 nextEthDepositToProcess;
// Slot 9
uint64 blockFee;
uint64 proofTimeIssued;
uint64 lastVerifiedBlockId;
uint64 proofTimeTarget;
// Reserved
uint256[42] __gap;
}
}