// SPDX-License-Identifier: MIT // OpenZeppelin Tron Contracts (last updated v5.6.0) (crosschain/bridges/abstract/BridgeFungible.sol) pragma solidity ^0.8.26; import {InteroperableAddress} from "../../../utils/draft-InteroperableAddress.sol"; import {Context} from "../../../utils/Context.sol"; import {TRC7786Recipient} from "../../TRC7786Recipient.sol"; import {CrosschainLinked} from "../../CrosschainLinked.sol"; /** * @dev Base contract for bridging TRC-20 between chains using a TRC-7786 gateway. * * In order to use this contract, two functions must be implemented to link it to the token: * * {_onSend}: called when a crosschain transfer is going out. Must take the sender tokens or revert. * * {_onReceive}: called when a crosschain transfer is coming in. Must give tokens to the receiver. * * This base contract is used by the {BridgeTRC20}, which interfaces with legacy TRC-20 tokens, and {BridgeTRC7802}, * which interface with TRC-7802 to provide an approve-free user experience. It is also used by the {TRC20Crosschain} * extension, which embeds the bridge logic directly in the token contract. */ abstract contract BridgeFungible is Context, CrosschainLinked { using InteroperableAddress for bytes; event CrosschainFungibleTransferSent(bytes32 indexed sendId, address indexed from, bytes to, uint256 amount); event CrosschainFungibleTransferReceived(bytes32 indexed receiveId, bytes from, address indexed to, uint256 amount); /// @dev The `recipient` of the crosschain transfer is not a valid address. error BridgeInvalidRecipient(bytes recipient); /// @dev Revert reason when the address part of the interoperable address is empty. error CrosschainFungibleEmptyAddress(); /** * @dev Transfer `amount` tokens to a crosschain receiver. * * Note: The `to` parameter is the full InteroperableAddress (chain ref + address). */ function crosschainTransfer(bytes memory to, uint256 amount) public virtual returns (bytes32) { return _crosschainTransfer(_msgSender(), to, amount); } /** * @dev Internal crosschain transfer function. * * Note: The `to` parameter is the full InteroperableAddress (chain ref + address). */ function _crosschainTransfer(address from, bytes memory to, uint256 amount) internal virtual returns (bytes32) { _onSend(from, amount); (bytes2 chainType, bytes memory chainReference, bytes memory addr) = to.parseV1(); require(addr.length > 0, CrosschainFungibleEmptyAddress()); bytes32 sendId = _sendMessageToCounterpart( InteroperableAddress.formatV1(chainType, chainReference, hex""), abi.encode(InteroperableAddress.formatEvmV1(block.chainid, from), addr, amount), new bytes[](0) ); emit CrosschainFungibleTransferSent(sendId, from, to, amount); return sendId; } /// @inheritdoc TRC7786Recipient function _processMessage( address /*gateway*/, bytes32 receiveId, bytes calldata /*sender*/, bytes calldata payload ) internal virtual override { // split payload (bytes memory from, bytes memory toBinary, uint256 amount) = abi.decode(payload, (bytes, bytes, uint256)); // An address is 20 bytes; reject any other length instead of letting `bytes20` truncate or zero-pad it. if (toBinary.length != 20) revert BridgeInvalidRecipient(toBinary); address to = address(bytes20(toBinary)); _onReceive(to, amount); emit CrosschainFungibleTransferReceived(receiveId, from, to, amount); } /// @dev Virtual function: implementation is required to handle token being burnt or locked on the source chain. function _onSend(address from, uint256 amount) internal virtual; /// @dev Virtual function: implementation is required to handle token being minted or unlocked on the destination chain. function _onReceive(address to, uint256 amount) internal virtual; }