// File: src/contracts/Ownable.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * Abstract contract implementing ownership handling and verification.
 *
 * The owner address gets stored in the contract's _owner slot and is
 * used to verify both implicit (direct method calls) and explicit
 * (passed in as an argument from arbitrary address, on owner's
 * behalf) signatures.
 *
 * The proposed owner address gets stored in _nextOwner slot and is only
 * promoted to the _owner slot upon receiving a signed approval.
 *
 * The reusable modifiers are utilized to validate that certain functions get
 * invoked by the current owner of the contract.
 */
abstract contract Ownable {
  /**
   * A struct holding the ECDSA explicit signature and the nonce used to
   * produce it.  Nonce is strictly increasing to prevent replay attacks.
   *
   * The recovery id "v" should be 27 or 28.
   */
  struct Signature {
    uint8 v;
    bytes32 r;
    bytes32 s;
    uint96 nonce;
  }

  // Current owner of the contract
  address public _owner;
  // Last explicit signature's nonce
  uint96 public _lastNonce;

  // Proposed owner of the contract
  address public _nextOwner;

  /**
   * Validates that the caller of the method matches the requiredOwner parameter.
   *
   * @param requiredOwner the required address
   */
  modifier onlyBy(address requiredOwner) {
    require(msg.sender == requiredOwner, "Only owner can do that");
    _;
  }

  /**
   * Validates that the provided signature was signed by an address matching requiredOwner parameter.
   *
   * @param requiredOwner the required address for the explicit signature
   * @param argsHash an opaque digest of whatever parameters are authorized by the explicit signature
   * @param signature an explicit ECDSA signature over argHash
   */
  modifier onlySignedBy(address requiredOwner, bytes32 argsHash, Signature calldata signature) {
    // Pack the hash of the arguments along with the nonce and contract address to prevent replay attacks.
    bytes32 hash = keccak256(abi.encodePacked(address(this), argsHash, signature.nonce));
    // Check the explicit ECDSA signature by recovering the signer address
    address signer = ecrecover(hash, signature.v, signature.r, signature.s);

    // Make sure the nonce is strictly greater to avoid re-use
    require(signature.nonce > _lastNonce, "The nonce is too old");
    // Make sure the signer matches the one requested
    require(signer == requiredOwner, "Only owner can do that");

    // Update the nonce
    _lastNonce = signature.nonce;

    _;
  }

  /**
   * Updates the proposed owner to a new address. To be called directly by the Owner.
   *
   * Requires the transaction to be signed by current owner.
   *
   * @param nextOwner the proposed address of the new owner
   */
  function setNextOwner(address nextOwner) public onlyBy(_owner) {
    _nextOwner = nextOwner;
  }

  /**
   * Updates the proposed owner to a new address. To be called on Owner's behalf.
   *
   * Requires the signature from the current owner to be passed.
   *
   * @param nextOwner the proposed address of the new owner
   * @param signature an explicit ECDSA signature of the authorization with the Owner's public key
   */
  function setNextOwner(address nextOwner, Signature calldata signature) public onlySignedBy(_owner, keccak256(abi.encodePacked(nextOwner)), signature) {
    _nextOwner = nextOwner;
  }

  /**
   * Accepts the new owner. To be called by the proposed Owner directly.
   */
  function acceptNextOwner() public onlyBy(_nextOwner) {
    _owner = _nextOwner;
  }

  /**
   * Accepts the new owner. To be called on the proposed Owner's behalf.
   *
   * @param signature an explicit ECDSA signature of the authorization with the Owner's public key
   */
  function acceptNextOwner(Signature calldata signature) public onlySignedBy(_nextOwner, keccak256(abi.encodePacked()), signature) {
    _owner = _nextOwner;
  }
}

// File: src/interfaces/ISlickSwapRegistryV1.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

/**
 * This interface defines accessors to be present on the Registry contract.
 */
interface ISlickSwapRegistryV1 {
  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getTradingFeeRecipient() external view returns (address);

  /**
   * Returns the address that should receive fees from withdrawal operations.
   */
  function getWithdrawalFeeRecipient() external view returns (address);

  /**
   * Returns the address of the most up-to-date SlickSwap implementation contract.
   */
  function getNextImplementation() external view returns (address);

  /**
   * Reverts unless the provided address is allowed to trade.
   */
  function verifyTrader(address sender) external;
}

// File: src/SlickSwapRegistryV1.sol



// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
pragma abicoder v2;

// Imports
import './contracts/Ownable.sol';
import './interfaces/ISlickSwapRegistryV1.sol';

/**
 * SlickSwapRegistryV1 - this contract stores various system settings used by
 * the individual SlickSwap wallet contracts.
 *
 * This contract is immutable (unlike wallet contracts, which are individually upgradeable).
 * Newer SlickSwap versions may introduce additional Registry contracts for new settings.
 */
contract SlickSwapRegistryV1 is Ownable, ISlickSwapRegistryV1 {
  // Storage variables
  mapping(address => bool) public _traderAddresses;
  address public _tradingFeeRecipient;
  address public _withdrawalFeeRecipient;
  address public _nextImplementation;

  /**
   * Initializes the contract and assigns the owner to the signer
   * of the deployment transaction.
   */
  constructor() {
    _owner = msg.sender;
  }

  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getTradingFeeRecipient() external view returns (address) {
    return _tradingFeeRecipient;
  }

  /**
   * Returns the address that should receive fees from trading operations.
   */
  function getWithdrawalFeeRecipient() external view returns (address) {
    return _withdrawalFeeRecipient;
  }

  /**
   * Returns the address of the most up-to-date SlickSwap implementation contract.
   */
  function getNextImplementation() external view returns (address) {
    return _nextImplementation;
  }

  /**
   * Validates that the address is indeed authorized to settle SlickSwap trades.
   *
   * @param sender the address calling the trade method
   */
  function verifyTrader(address sender) external view {
    require(_traderAddresses[sender], "This address is not allowed to trade");
  }

  /**
   * Updates the address that should receive fees from trading operations.
   * Called by Registry owner.
   *
   * @param tradingFeeRecipient new trading fee recipient.
   */
  function setTradingFeeRecipient(address tradingFeeRecipient) external onlyBy(_owner) {
    _tradingFeeRecipient = tradingFeeRecipient;
  }

  /**
   * Updates the address that should receive fees from withdrawal operations.
   * Called by Registry owner.
   *
   * @param withdrawalFeeRecipient new withdrawal fee recipient
   */
  function setWithdrawalFeeRecipient(address withdrawalFeeRecipient) external onlyBy(_owner) {
    _withdrawalFeeRecipient = withdrawalFeeRecipient;
  }

  /**
   * Updates the address of the most up-to-date SlickSwap implementation contract.
   * Called by Registry owner.
   *
   * @param nextImplementation new address of the SlickSwap logic contract.
   */
  function setNextImplementation(address nextImplementation) external onlyBy(_owner) {
    _nextImplementation = nextImplementation;
  }

  /**
   * Adds/removes the address of the account authorized to perform the trades.
   * Called by Registry owner.
   *
   * @param traderAddress address in consideration
   * @param enable adding represented by true, removing by false
   */
  function enableTradingFor(address traderAddress, bool enable) external onlyBy(_owner) {
    _traderAddresses[traderAddress] = enable;
  }
}