ETH Price: $1,940.84 (-2.55%)

Contract Diff Checker

Contract Name:
BridgeToken

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IBridgeToken} from "./interfaces/IBridgeToken.sol";

/// @title BridgeToken
/// @notice ERC20 token with mint/burn capabilities for cross-chain bridges
/// @dev Design decisions and tradeoffs:
///
/// TRUST ASSUMPTIONS:
/// - Minters (bridge contracts) are trusted to only mint when valid burns occur
/// - If a minter is compromised, they can mint unlimited tokens
/// - This is why minter management is critical (see setMinter)
///
/// WHY MINT/BURN vs LOCK/UNLOCK?
/// Mint/Burn:
///   + No liquidity fragmentation (tokens exist on one chain at a time)
///   + Total supply remains constant across all chains
///   + Simpler accounting
///   - Requires token to have mint authority (can't use for existing tokens like USDC)
///   - Trust in bridge's mint/burn logic
///
/// Lock/Unlock:
///   + Works with any existing token
///   + No special token permissions needed
///   - Liquidity locked on source chain (capital inefficient)
///   - Wrapped tokens on destination (not fungible with native)
///   - TVL in bridge contract = honeypot for hackers
contract BridgeToken is IBridgeToken {
    string public name;
    string public symbol;
    uint8 public constant decimals = 18;
    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    /// @notice Addresses authorized to mint tokens
    /// @dev Critical security surface - compromise = unlimited minting
    mapping(address => bool) public isMinter;

    /// @notice Contract owner who can manage minters
    address public owner;

    /// @notice Pending owner for 2-step ownership transfer
    /// @dev 2-step prevents accidentally transferring to wrong address
    address public pendingOwner;

    event MinterSet(address indexed minter, bool authorized);
    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    error Unauthorized();
    error ZeroAddress();
    error InsufficientBalance();
    error InsufficientAllowance();

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    modifier onlyMinter() {
        if (!isMinter[msg.sender]) revert Unauthorized();
        _;
    }

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
        owner = msg.sender;
    }

    // ============ Minter Management ============

    /// @notice Authorize or revoke a minter
    /// @dev SECURITY: This is the most critical function
    ///      - Should be behind timelock in production
    ///      - Consider multisig ownership
    ///      - Monitor for unexpected minter additions
    function setMinter(address minter, bool authorized) external onlyOwner {
        if (minter == address(0)) revert ZeroAddress();
        isMinter[minter] = authorized;
        emit MinterSet(minter, authorized);
    }

    // ============ Ownership Transfer ============

    /// @notice Start ownership transfer (2-step process)
    /// @dev Why 2-step? Prevents losing ownership to typos or wrong addresses
    function transferOwnership(address newOwner) external onlyOwner {
        pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner, newOwner);
    }

    /// @notice Accept ownership transfer
    function acceptOwnership() external {
        if (msg.sender != pendingOwner) revert Unauthorized();
        emit OwnershipTransferred(owner, msg.sender);
        owner = msg.sender;
        pendingOwner = address(0);
    }

    // ============ Mint/Burn (Bridge Operations) ============

    /// @notice Mint tokens to recipient
    /// @dev Called by bridge when tokens are burned on source chain
    ///      TRUST: We trust the bridge verified the burn before calling
    function mint(address to, uint256 amount) external onlyMinter {
        if (to == address(0)) revert ZeroAddress();
        totalSupply += amount;
        balanceOf[to] += amount;
        emit Transfer(address(0), to, amount);
    }

    /// @notice Burn tokens from caller
    /// @dev User initiates bridge by burning their tokens
    function burn(uint256 amount) external {
        if (balanceOf[msg.sender] < amount) revert InsufficientBalance();
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }

    /// @notice Burn tokens from address (requires approval)
    /// @dev Bridge contract can burn on behalf of user
    function burnFrom(address from, uint256 amount) external {
        uint256 currentAllowance = allowance[from][msg.sender];
        if (currentAllowance < amount) revert InsufficientAllowance();
        if (balanceOf[from] < amount) revert InsufficientBalance();

        allowance[from][msg.sender] = currentAllowance - amount;
        balanceOf[from] -= amount;
        totalSupply -= amount;
        emit Transfer(from, address(0), amount);
    }

    // ============ Standard ERC20 ============

    function transfer(address to, uint256 amount) external returns (bool) {
        if (to == address(0)) revert ZeroAddress();
        if (balanceOf[msg.sender] < amount) revert InsufficientBalance();

        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        if (to == address(0)) revert ZeroAddress();
        uint256 currentAllowance = allowance[from][msg.sender];
        if (currentAllowance < amount) revert InsufficientAllowance();
        if (balanceOf[from] < amount) revert InsufficientBalance();

        allowance[from][msg.sender] = currentAllowance - amount;
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IERC20} from "forge-std/interfaces/IERC20.sol";

/// @title IBridgeToken
/// @notice Interface for tokens that can be minted/burned by a bridge
/// @dev Key design decision: Who has mint/burn authority?
///      - Option 1: Single bridge contract (simpler, but single point of failure)
///      - Option 2: Multiple authorized bridges (more flexible, but harder access control)
///      - Option 3: Governance-controlled minter list (most decentralized, but slower to update)
interface IBridgeToken is IERC20 {
    /// @notice Mints tokens to a recipient
    /// @dev Only callable by authorized minters (bridge contracts)
    /// @param to The recipient address
    /// @param amount The amount to mint
    function mint(address to, uint256 amount) external;

    /// @notice Burns tokens from the caller
    /// @dev Anyone can burn their own tokens
    /// @param amount The amount to burn
    function burn(uint256 amount) external;

    /// @notice Burns tokens from an address (requires approval)
    /// @dev Used by bridge contracts to burn tokens on behalf of users
    /// @param from The address to burn from
    /// @param amount The amount to burn
    function burnFrom(address from, uint256 amount) external;
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.13 <0.9.0;

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Returns the amount of tokens in existence.
    function totalSupply() external view returns (uint256);

    /// @notice Returns the amount of tokens owned by `account`.
    function balanceOf(address account) external view returns (uint256);

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Returns the name of the token.
    function name() external view returns (string memory);

    /// @notice Returns the symbol of the token.
    function symbol() external view returns (string memory);

    /// @notice Returns the decimals places of the token.
    function decimals() external view returns (uint8);
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):