// File: ManualBridge.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "IManualBridge.sol";
import "Ownable.sol";
import "IERC20.sol";

// This is a totally centralized (ie not trustless) manual bridge contract for DFP2
// bridging between Ethereum and Radix. Requires trust in contract owner.
contract ManualBridge is IManualBridge, Ownable {
  IERC20 constant token = IERC20(0x2F57430a6ceDA85a67121757785877b4a71b8E6D);

  uint16 constant UNLOCKED = 0x0000;
  uint16 constant LOCKED = 0x0001;
  address admin;

  Config public bridgeConfig = Config({
    locked: LOCKED,                     // 2nd bit is admin lock
    forwardBaseFee: 100,                // Base fee
    forwardFeeFraction: 655,            // Approximately 1%
    backwardBaseFee: 500,               // Base fee
    backwardFeeFraction: 655,           // Approximately 1%
    totalFees: 0                        // Total fees collected
  });

  constructor(){
    admin = 0x3C7791728AdCA8C8ca5A46d6170a9c1fd24408e2;
  }

  // used for bridge (un)lock functions which may only be called by admin or owner
  modifier onlyAdmin() {
    if (msg.sender != admin && msg.sender != owner()) { revert AdminRightsRequired(); }
    _;
  }

  /**
   * @inheritdoc IManualBridge
   */
  function bridge(
    uint256 inputAmount,
    bytes32[2] calldata radixAddress
  ) external override
  {
    Config storage c = bridgeConfig;
    if (c.locked == LOCKED) { revert BridgeLocked(); }
    if (inputAmount <= c.forwardBaseFee) { revert InsufficientInput(); }
    if (token.transferFrom(msg.sender, address(this), inputAmount) != true) { revert TokenTransferFailure(); }

    uint256 baseFee = uint256(c.forwardBaseFee) * 10**18;
    uint256 fractionalFee = (inputAmount * c.forwardFeeFraction) >> 16;
    uint256 fee = baseFee > fractionalFee ? baseFee : fractionalFee;
    c.totalFees += uint96(fee);

    emit Bridged(msg.sender, inputAmount - fee, fee, radixAddress);
  }

  /**
   * @notice Bridge tokens back from Radix to Ethereum. Release these tokens to the user.
   * @param inputAmount Amount of tokens bridged back to Ethereum.
   * @param destination Address of the user to receive the funds.
   * @param radixHash Transaction on the Radix network removing supply on Radix.
   */
  function release(
    uint256 inputAmount,
    address destination,
    bytes32[2] calldata radixHash
  ) external onlyOwner()
  {
    Config storage c = bridgeConfig;
    if (c.locked == LOCKED) { revert BridgeLocked(); }
    if (inputAmount <= c.backwardBaseFee) { revert InsufficientInput(); }

    uint256 baseFee = uint256(c.backwardBaseFee) * 10**18;
    uint256 fractionalFee = (inputAmount * c.backwardFeeFraction) >> 16;
    uint256 fee = baseFee > fractionalFee ? baseFee : fractionalFee;
    c.totalFees += uint96(fee);

    if (token.transfer(destination, inputAmount - fee) != true) { revert TokenTransferFailure(); }
    emit Released(destination, inputAmount - fee, fee, radixHash);
  }

  /**
   * @notice Sets admin address for emergency exchange locking.
   * @dev Can only be called by the owner of the contract.
   * @param adminAddress Address of the admin to set
   */
  function setAdmin(address adminAddress) external onlyOwner() {
    admin = adminAddress;
    emit AdminChanged(adminAddress);
  }

  /**
   * @notice Sets exchange lock, under which swap and liquidity add (but not remove) are disabled.
   * @dev Can only be called by the admin of the contract.
   */
  function lockBridge() external onlyAdmin() {
    bridgeConfig.locked = LOCKED;
    emit LockChanged(msg.sender, bridgeConfig.locked);
  }

  /**
   * @notice Resets exchange lock.
   * @dev Can only be called by the admin of the contract.
   */
  function unlockBridge() external onlyAdmin() {
    bridgeConfig.locked = UNLOCKED;
    emit LockChanged(msg.sender, bridgeConfig.locked);
  }

  /**
   * @notice Updates bridge fees.
   * @param newForwardBaseFee New forward base fee level in whole tokens
   * @param newForwardFeeFraction New forward fractional fee (times 2^-16)
   * @param newBackwardBaseFee New backward base fee level in whole tokens
   * @param newBackwardFeeFraction New backward fractional fee (times 2^-16)
   */
  function setFees(
    uint16 newForwardBaseFee,
    uint16 newForwardFeeFraction,
    uint16 newBackwardBaseFee,
    uint16 newBackwardFeeFraction
  ) external onlyOwner()
  {
    Config storage c = bridgeConfig;
    c.forwardBaseFee = newForwardBaseFee;
    c.forwardFeeFraction = newForwardFeeFraction;
    c.backwardBaseFee = newBackwardBaseFee;
    c.backwardFeeFraction = newBackwardFeeFraction;

    emit ConfigUpdated(c);
  }

  /**
   * @notice Claims fees collected by the bridge and transfers to selected address
   * @param destination Address that the fees will be transferred to
   */
  function claimFees(address destination) external onlyOwner() {
    Config storage c = bridgeConfig;
    uint256 totalFees = uint256(c.totalFees);
    if (token.transfer(destination, totalFees) != true) { revert TokenTransferFailure(); }
    c.totalFees = 0;

    emit FeesClaimed(totalFees);
  }
}

// File: IManualBridge.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0;

/**
 * @title ManualBridge interface
 * @author Jazzer9F
 */
interface IManualBridge {
  error AdminRightsRequired();
  error TokenTransferFailure();
  error InsufficientInput();
  error BridgeLocked();

  // bridge configuration
  struct Config {
    uint16 locked;                    // variable to keep track of whether the exchnage is locked
    uint16 forwardBaseFee;            // base fee level in # of whole tokens
    uint16 forwardFeeFraction;        // variable part of fee
    uint16 backwardBaseFee;           // base fee level in # of whole tokens
    uint16 backwardFeeFraction;       // variable part of fee
    uint96 totalFees;                 // total fees currently held by the contract
  }

 /**
  * @notice Bridge the token towards Radix. Forwards the tokens to the team address.
  * @param amountToBridge Amount of tokens to bridge to Radix
  * @param radixAddress Address on the Radix public network that tokens should be sent to
  */
  function bridge(
    uint256 amountToBridge,
    bytes32[2] calldata radixAddress
  ) external;

 /**
  * @notice Emit Bridged event when tokens are bridged to the Radix network
  * @param source Address of the caller
  * @param netAmountBridged Amount of tokens sent to the Radix network (held in team wallet)
  * @param feePayed Fee withheld from the bridged amount
  * @param radixAddress The address on the Radix network that the tokens are to be sent to
  */
  event Bridged(
    address source,
    uint256 netAmountBridged,
    uint256 feePayed,
    bytes32[2] radixAddress
  );

  /**
   * @notice Emit Released event when tokens are bridged back from the Radix network
   * @param destination Address of the bridge user
   * @param radixHash Transaction on Radix network paying for this release
   * @param feePayed Amount of fee payed
   * @param amountReleased Amount of DFP2 released to the user
   */
  event Released(
    address destination,
    uint256 amountReleased,
    uint256 feePayed,
    bytes32[2] radixHash
  );

  /**
   * @notice Emit adminChanged event when the bridge config (ie fees) is updated.
   * @param newConfig The updated config struct
   */
  event ConfigUpdated(
    Config newConfig
  );

  /**
   * @notice Emit adminChanged event when the exchange admin address is changed
   * @param newAdmin Address of new admin, who can (un)lock the exchange
   */
  event AdminChanged(
    address newAdmin
  );

  /**
   * @notice Emit LockChanged event when the exchange is (un)locked by an admin
   * @param exchangeAdmin Address of the admin making the change
   * @param newLockValue The updated value of the lock variable
   */
  event LockChanged(
    address exchangeAdmin,
    uint256 newLockValue
  );

  /**
   * @notice Emit FeesClaimed when the fees collected by the bridge are claimed by the owner
   * @param amountClaimed Amount of fees withdrawn. This is always the total amount of fees collected until now.
   */
  event FeesClaimed(
    uint256 amountClaimed
  );
}

// File: Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}