{ "language": "Solidity", "sources": { "@openzeppelin/contracts/token/ERC20/IERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" }, "contracts/DEX.sol": { "content": "// SPDX-License-Identifier: MIT\n\npragma solidity >=0.8.0 <0.9.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/**\n * @title DEX Template\n * @author stevepham.eth and m00npapi.eth\n * @notice Empty DEX.sol that just outlines what features could be part of the challenge (up to you!)\n * @dev We want to create an automatic market where our contract will hold reserves of both ETH and 🎈 Balloons. These reserves will provide liquidity that allows anyone to swap between the assets.\n * NOTE: functions outlined here are what work with the front end of this challenge. Also return variable names need to be specified exactly may be referenced (It may be helpful to cross reference with front-end code function calls).\n */\ncontract DEX {\n /* ========== GLOBAL VARIABLES ========== */\n\n IERC20 token; //instantiates the imported contract\n uint256 public totalLiquidity;\n mapping (address => uint256) public liquidity;\n\n /* ========== EVENTS ========== */\n\n /**\n * @notice Emitted when ethToToken() swap transacted\n */\n event EthToTokenSwap(address swapper, uint256 tokenOutput, uint256 ethInput);\n\n /**\n * @notice Emitted when tokenToEth() swap transacted\n */\n event TokenToEthSwap(address swapper, uint256 tokensInput, uint256 ethOutput);\n\n /**\n * @notice Emitted when liquidity provided to DEX and mints LPTs.\n */\n event LiquidityProvided(address liquidityProvider, uint256 liquidityMinted, uint256 ethInput, uint256 tokensInput);\n\n /**\n * @notice Emitted when liquidity removed from DEX and decreases LPT count within DEX.\n */\n event LiquidityRemoved(\n address liquidityRemover,\n uint256 liquidityWithdrawn,\n uint256 tokensOutput,\n uint256 ethOutput\n );\n\n /* ========== CONSTRUCTOR ========== */\n\n constructor(address tokenAddr) {\n token = IERC20(tokenAddr); //specifies the token address that will hook into the interface and be used through the variable 'token'\n }\n\n /* ========== MUTATIVE FUNCTIONS ========== */\n\n /**\n * @notice initializes amount of tokens that will be transferred to the DEX itself from the erc20 contract mintee (and only them based on how Balloons.sol is written). Loads contract up with both ETH and Balloons.\n * @param tokens amount to be transferred to DEX\n * @return totalLiquidity is the number of LPTs minting as a result of deposits made to DEX contract\n * NOTE: since ratio is 1:1, this is fine to initialize the totalLiquidity (wrt to balloons) as equal to eth balance of contract.\n */\n function init(uint256 tokens) public payable returns (uint256) {\n require(totalLiquidity == 0, \"DEX: init - already has liquidity\");\n totalLiquidity = address(this).balance;\n liquidity[msg.sender] = totalLiquidity;\n require(token.transferFrom(msg.sender, address(this), tokens), \"DEX: init - transfer did not transact\");\n return totalLiquidity;\n }\n\n /**\n * @notice returns yOutput, or yDelta for xInput (or xDelta)\n * @dev Follow along with the [original tutorial](https://medium.com/@austin_48503/%EF%B8%8F-minimum-viable-exchange-d84f30bd0c90) Price section for an understanding of the DEX's pricing model and for a price function to add to your contract. You may need to update the Solidity syntax (e.g. use + instead of .add, * instead of .mul, etc). Deploy when you are done.\n */\n function price(uint256 xInput, uint256 xReserves, uint256 yReserves) public pure returns (uint256 yOutput) {\n uint256 feePercentage = 997;\n uint256 feeDenominator = 1000;\n uint256 xInputAfterFee = xInput * feePercentage;\n\n uint256 numerator = xInputAfterFee * yReserves;\n uint256 denominator = (xReserves * feeDenominator) + xInputAfterFee;\n return (numerator / denominator);\n }\n\n /**\n * @notice returns liquidity for a user.\n * NOTE: this is not needed typically due to the `liquidity()` mapping variable being public and having a getter as a result. This is left though as it is used within the front end code (App.jsx).\n * NOTE: if you are using a mapping liquidity, then you can use `return liquidity[lp]` to get the liquidity for a user.\n * NOTE: if you will be submitting the challenge make sure to implement this function as it is used in the tests.\n */\n function getLiquidity(address lp) public view returns (uint256) {\n return liquidity[lp];\n }\n\n /**\n * @notice sends Ether to DEX in exchange for $BAL\n */\n function ethToToken() public payable returns (uint256 tokenOutput) {\n require(msg.value > 0, \"cannot swap 0 ETH\");\n uint256 ethReserve = address(this).balance - msg.value;\n uint256 tokenReserve = token.balanceOf(address(this));\n tokenOutput = price(msg.value, ethReserve, tokenReserve);\n\n require(token.transfer(msg.sender, tokenOutput), \"ethToToken(): reverted swap.\");\n emit EthToTokenSwap(msg.sender, tokenOutput, msg.value);\n return tokenOutput;\n }\n\n /**\n * @notice sends $BAL tokens to DEX in exchange for Ether\n */\n function tokenToEth(uint256 tokenInput) public returns (uint256 ethOutput) {\n require(tokenInput > 0, \"cannot swap 0 tokens\");\n require(token.balanceOf(msg.sender) >= tokenInput, \"insufficient token balance\");\n require(token.allowance(msg.sender, address(this)) >= tokenInput, \"insufficient allowance\");\n uint256 ethReserve = address(this).balance;\n uint256 tokenReserve = token.balanceOf(address(this));\n ethOutput = price(tokenInput, tokenReserve, ethReserve);\n\n require(token.transferFrom(msg.sender, address(this), tokenInput), \"tokenToEth(): reverted swap.\");\n (bool success,) = msg.sender.call{value: ethOutput}(\"\");\n require(success, \"tokenToEth: reverted in transferring eth to you!\");\n emit TokenToEthSwap(msg.sender, tokenInput, ethOutput);\n return ethOutput;\n }\n\n /**\n * @notice allows deposits of $BAL and $ETH to liquidity pool\n * NOTE: parameter is the msg.value sent with this function call. That amount is used to determine the amount of $BAL needed as well and taken from the depositor.\n * NOTE: user has to make sure to give DEX approval to spend their tokens on their behalf by calling approve function prior to this function call.\n * NOTE: Equal parts of both assets will be removed from the user's wallet with respect to the price outlined by the AMM.\n */\n function deposit() public payable returns (uint256 tokensDeposited) {\n require(msg.value > 0, \"Must send value when depositing\");\n uint256 ethReserve = address(this).balance - msg.value;\n uint256 tokenReserve = token.balanceOf(address(this));\n uint256 tokenDeposit;\n\n tokenDeposit = (msg.value * tokenReserve / ethReserve) + 1;\n\n require(token.balanceOf(msg.sender) >= tokenDeposit, \"insufficient token balance\");\n require(token.allowance(msg.sender, address(this)) >= tokenDeposit, \"insufficient allowance\");\n\n uint256 liquidityMinted = msg.value * totalLiquidity / ethReserve;\n liquidity[msg.sender] += liquidityMinted;\n totalLiquidity += liquidityMinted;\n\n require(token.transferFrom(msg.sender, address(this), tokenDeposit));\n emit LiquidityProvided(msg.sender, liquidityMinted, msg.value, tokenDeposit);\n return tokenDeposit;\n }\n\n /**\n * @notice allows withdrawal of $BAL and $ETH from liquidity pool\n * NOTE: with this current code, the msg caller could end up getting very little back if the liquidity is super low in the pool. I guess they could see that with the UI.\n */\n function withdraw(uint256 amount) public returns (uint256 ethAmount, uint256 tokenAmount) {\n require(liquidity[msg.sender] >= amount, \"withdraw: sender does not have enough liquidity to withdraw.\");\n uint256 ethReserve = address(this).balance;\n uint256 tokenReserve = token.balanceOf(address(this));\n uint256 ethWithdrawn;\n\n ethWithdrawn = amount * ethReserve / totalLiquidity;\n\n uint256 tokenAmount = amount * tokenReserve / totalLiquidity;\n liquidity[msg.sender] -= amount;\n totalLiquidity -= amount;\n (bool sent, ) = payable(msg.sender).call{value: ethWithdrawn}(\"\");\n require(sent, \"withdraw(): revert in transferring eth to you!\");\n require(token.transfer(msg.sender, tokenAmount));\n emit LiquidityRemoved(msg.sender, amount, tokenAmount, ethWithdrawn);\n return (ethWithdrawn, tokenAmount);\n }\n}\n" } }, "settings": { "optimizer": { "enabled": true, "runs": 200 }, "evmVersion": "paris", "outputSelection": { "*": { "*": [ "abi", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "metadata", "devdoc", "userdoc", "storageLayout", "evm.gasEstimates" ], "": [ "ast" ] } }, "metadata": { "useLiteralContent": true } } }