---
# System prepended metadata

title: Workshop 1

---

# Workshop 1 

```// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

import "@openzeppelin/token/ERC20/IERC20.sol";

contract DEX {
    address public owner;
    bool    public paused;

    IERC20  public  token1;
    IERC20  public  token2;
    uint256 private k;

    uint256 public totalShares;
    mapping (address => uint256) public shares;

    constructor(IERC20 _token1, IERC20 _token2, address _owner) {
        token1 = _token1;
        token2 = _token2;
        owner = _owner;
    }

    function init(uint256 amount1, uint256 amount2) external {
        require(token1.balanceOf(address(this)) == 0 && token2.balanceOf(address(this)) == 0);
        require(token1.transferFrom(msg.sender, address(this), amount1));
        require(token2.transferFrom(msg.sender, address(this), amount2));
        totalShares = shares[msg.sender] = amount1;
        _sync();
    }
    
    modifier notPaused() {
        require(!paused);
        _;
    }

    function pause() external {
        require(msg.sender == owner);
        paused = true;
    }

    function unpause() external {
        require(msg.sender == owner);
        paused = false;
        _sync();
    }

    function swap(IERC20 tokenIn, uint256 amountIn, IERC20 tokenOut, uint256 amountOut) external notPaused() {
        require((tokenIn == token1 && tokenOut == token2) || (tokenIn == token2 && tokenOut == token1));
        require(tokenIn.transferFrom(msg.sender, address(this), amountIn));
        require(tokenOut.transfer(msg.sender, amountOut));
        uint256 x = tokenIn .balanceOf(address(this));
        uint256 y = tokenOut.balanceOf(address(this));
        require(1000 * x * y - amountIn * y * 5 >= 1000 * k, "bad swap"); // charge fee, (x - 0.005 * amountIn) * y >= k
        _sync();
    }

    function addLiquidity(uint256 sharesToMint) external notPaused() {
        // amount / balanceOf == sharesToMint / totalShares
        uint256 amount1 = token1.balanceOf(address(this)) * sharesToMint / totalShares;
        uint256 amount2 = token2.balanceOf(address(this)) * sharesToMint / totalShares;
        shares[msg.sender] += sharesToMint;
        totalShares += sharesToMint;
        require(token1.transferFrom(msg.sender, address(this), amount1));
        require(token2.transferFrom(msg.sender, address(this), amount2));
        _sync();
    }

    function removeLiquidity(uint256 sharesToBurn) external {
        uint256 amount1 = token1.balanceOf(address(this)) * sharesToBurn / totalShares;
        uint256 amount2 = token2.balanceOf(address(this)) * sharesToBurn / totalShares;
        shares[msg.sender] -= sharesToBurn;
        totalShares -= sharesToBurn;
        require(token1.transfer(msg.sender, amount1));
        require(token2.transfer(msg.sender, amount2));
        _sync();
    }

    function flashLoan(IERC3156FlashBorrower receiver, IERC20 token, uint256 amount, bytes calldata data) external notPaused() returns (bool) {
        require(token == token1 || token == token2);
        uint256 fee = amount * 5 / 1000; // 0.5 %
        require(token.transfer(address(receiver), amount));
        require(receiver.onFlashLoan(msg.sender, address(token), amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"));
        require(IERC20(token).transferFrom(address(receiver), address(this), amount + fee));
        _sync();
        return true;
    }
    
    function _sync() internal {
        k = token1.balanceOf(address(this)) * token2.balanceOf(address(this));
    }
}

interface IERC3156FlashBorrower {
    function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns (bytes32);
}```