본문 바로가기

블록체인/이더리움

[SpeedRunEthereum] Challenge 3 [Dex]

챌린지 3부터는 프로필에 Builder라는 태그가 부착되고 개발자들 풀에 참여해 팀원을 찾거나 서로 정보를 공유하는 커뮤니티에 접근할 수 있게 됩니다.

Checkpoint 0: install

템플릿 프로젝트 파일을 설치해 주겠습니다.

git clone https://github.com/squirtleDevs/scaffold-eth.git challenge-3-single-pool-dex
cd scaffold-eth
git checkout challenge-3-single-pool-dex
yarn install

Checkpoint 1: Environment

리액트, 하드햇 로컬 체인, 배포를 진행해줍니다.

yarn start
yarn chain
yarn deploy

Checkpoint 2: Reserves

만들고 있는 이 Dex는 간단하게 ETH와 Balloons라는 2개의 코인만의 스왑을 할 것입니다.
DEX.sol 에서 유동성을 관리하기 위한 변수를 선언합니다.

uint256 public totalLiquidity; 
mapping(address => uint256) liquidity;


초기 상태를 지정하기 위한 init 함수를 작성합니다.

  function init(uint256 tokens) public payable returns (uint256) {
        require(totalLiquidity == 0, "initialize already initiated");
        totalLiquidity = msg.value;
        liquidity[msg.sender] = msg.value;

        bool sent = token.transferFrom(msg.sender, address(this), tokens);
        require(sent, "transaction failed.");
        return totalLiquidity;
    }

코드를 보면 앞서 선언한 totalLiquidity와 liquidity에 msg.value를 사용하는데 이는 이더리움만을 저장합니다.
Ballons 토큰에 대한 liquidity는 Balloons token 객체가 erc20 인터페이스를 상속하기 때문에. balanceOf로 소유량을 확인할 수 있습니다.

deploy 스크립트를 수정합니다.

// uncomment to init DEX on deploy:
console.log("Approving DEX ("+dex.address+") to take Balloons from main account...")
// If you are going to the testnet make sure your deployer account has enough ETH
await balloons.approve(dex.address,ethers.utils.parseEther('100'));
console.log("INIT exchange...")
await dex.init(""+(3*10**18),{value:ethers.utils.parseEther('3'),gasLimit:200000})

Checkpoint 3: Price

스왑 할 가격을 정하는 price 함수를 작성합니다.

    function price(
        uint256 xInput,
        uint256 xReserves,
        uint256 yReserves
    ) public view returns (uint256 yOutput) {
        uint256 xInput_with_fee = xInput.mul(997);
        uint256 numerator = xInput_with_fee.mul(yReserves);
        uint256 denominator = xReserves.mul(1000).add(xInput_with_fee);
        return numerator / denominator;

    }

price 함수의 가격 공식은 설명을 참조하시면 됩니다.

Checkpoint 4: Trading


swap을 수행하는 함수를 작성합니다.

     
    function ethToToken() public payable returns (uint256 tokenOutput) {
        uint256 token_reserve = token.balanceOf(address(this));
        uint256 token_bought = price(msg.value, address(this).balance.sub(msg.value), token_reserve);

        require(token.transfer(msg.sender, token_bought));
        emit EthToTokenSwap();
        return token_bought;
    }


    function tokenToEth(uint256 tokenInput) public returns (uint256 ethOutput) {
        uint256 token_reserve = token.balanceOf((address(this)));
        uint256 eth_bought = price(tokenInput, token_reserve, address(this).balance);
        (bool sent, ) = msg.sender.call{value: eth_bought}("");

        require(sent, "Failed to send user eth.");
        require(token.transferFrom(msg.sender, address(this), tokenInput));
        emit TokenToEthSwap();

        return eth_bought;

    }

Checkpoint 5: Liquidity

    function deposit() public payable returns (uint256 tokensDeposited) {
        uint256 eth_reserve = address(this).balance.sub(msg.value);
        uint256 token_reserve = token.balanceOf(address(this));
        uint256 token_amount = (msg.value.mul(totalLiquidity)/ eth_reserve).add(1);
        uint256 liquidity_minted = msg.value.mul(totalLiquidity) / eth_reserve;

        liquidity[msg.sender] = liquidity[msg.sender].add(liquidity_minted);
        totalLiquidity = totalLiquidity.add(liquidity_minted);

        require(token.transferFrom(msg.sender, address(this), token_amount));
        // emit LiquidityProvided();
        return liquidity_minted;
    }

    /**
     * @notice allows withdrawal of $BAL and $ETH from liquidity pool
     * 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.
     */
    function withdraw(uint256 amount) public returns (uint256 eth_amount, uint256 token_amount) {
        uint256 token_reserve = token.balanceOf(address(this));
        uint256 eth_amount = amount.mul(address(this).balance) / totalLiquidity;
        uint256 token_amount = amount.mul(token_reserve) / totalLiquidity;
        liquidity[msg.sender] = liquidity[msg.sender].sub(eth_amount);
        totalLiquidity = totalLiquidity.sub(eth_amount);
        
        (bool sent, ) = msg.sender.call{value : eth_amount}("");
        require(sent, "Failed to send user eth.");
        require(token.transfer(msg.sender, token_amount));

        // emit LiquidityRemoved();
        
        return (eth_amount, token_amount);

    }



추가적으로 위에 작성한 함수들의 이벤트와 approve 또한 events를 프론트에 표시해주시면 기본 챌린지는 완료입니다.