分散型取引所DEXを実装してみる

分散型取引所(DEX)を実装するためのソースコードの基本的な例を提供します。この例では、シンプルなERC-20トークンのスワップ機能を持つDEXを作成します。Solidityを使用してスマートコントラクトを記述し、Truffleフレームワークを使用してデプロイします。

前提条件

  • Node.jsとnpmがインストールされていること

  • TruffleとGanacheがインストールされていること

  • Solidityの基本的な知識

ステップ1: プロジェクトのセットアップ

  1. プロジェクトのディレクトリを作成し、Truffleプロジェクトを初期化します。

mkdir dex-project cd dex-project truffle init
  1. ERC-20トークンのインターフェースを作成します。contractsディレクトリにIERC20.solを作成します。

// contracts/IERC20.sol
pragma solidity ^0.8.0;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

ステップ2: ERC-20トークンコントラクトの実装

次に、シンプルなERC-20トークンを実装します。contractsディレクトリにToken.solを作成します。

// contracts/Token.sol
pragma solidity ^0.8.0;

import "./IERC20.sol";

contract Token is IERC20 {
    string public constant name = "Example Token";
    string public constant symbol = "EXT";
    uint8 public constant decimals = 18;
    uint256 private _totalSupply;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    constructor(uint256 initialSupply) {
        _totalSupply = initialSupply;
        _balances[msg.sender] = initialSupply;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, msg.sender, _allowances[sender][msg.sender] - amount);
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "Transfer from the zero address");
        require(recipient != address(0), "Transfer to the zero address");
        require(_balances[sender] >= amount, "Transfer amount exceeds balance");

        _balances[sender] -= amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }

    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "Approve from the zero address");
        require(spender != address(0), "Approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
}

ステップ3: DEXコントラクトの実装

次に、DEXのコントラクトを作成します。contractsディレクトリにDEX.solを作成します。

// contracts/DEX.sol
pragma solidity ^0.8.0;

import "./IERC20.sol";

contract DEX {
    address public tokenAddress;

    constructor(address _tokenAddress) {
        tokenAddress = _tokenAddress;
    }

    function swap(uint256 tokenAmount) public payable {
        require(msg.value > 0, "Must send ETH to swap for tokens");
        require(tokenAmount > 0, "Must specify token amount to swap");

        IERC20 token = IERC20(tokenAddress);
        require(token.balanceOf(address(this)) >= tokenAmount, "DEX does not have enough tokens");

        // Transfer ETH to DEX
        payable(address(this)).transfer(msg.value);

        // Transfer tokens to sender
        token.transfer(msg.sender, tokenAmount);
    }

    function depositTokens(uint256 tokenAmount) public {
        IERC20 token = IERC20(tokenAddress);
        require(token.transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed");
    }

    function withdrawTokens(uint256 tokenAmount) public {
        IERC20 token = IERC20(tokenAddress);
        require(token.transfer(msg.sender, tokenAmount), "Token transfer failed");
    }

    function getTokenBalance() public view returns (uint256) {
        IERC20 token = IERC20(tokenAddress);
        return token.balanceOf(address(this));
    }

    function getETHBalance() public view returns (uint256) {
        return address(this).balance;
    }
}

ステップ4: マイグレーションスクリプトの作成

migrationsディレクトリに新しいマイグレーションスクリプトを作成します。例えば、2_deploy_contracts.jsという名前のファイルを作成します。

// migrations/2_deploy_contracts.js
const Token = artifacts.require("Token");
const DEX = artifacts.require("DEX");

module.exports = async function (deployer) {
  // トークンの初期供給量を設定
  const initialSupply = web3.utils.toWei('1000', 'ether');

  // Tokenコントラクトをデプロイ
  await deployer.deploy(Token, initialSupply);
  const token = await Token.deployed();

  // DEXコントラクトをデプロイ
  await deployer.deploy(DEX, token.address);
  const dex = await DEX.deployed();

  // DEXにトークンをデポジット
  await token.approve(dex.address, initialSupply);
  await dex.depositTokens(initialSupply);
};

ステップ5: コントラクトのデプロイ

Ganacheを起動してローカルのEthereumネットワークを開始します。その後、Truffleを使用してコントラクトをデプロイします。

ganache-cli truffle migrate --network development

ステップ6: テストの実行

testディレクトリにテストスクリプトを作成して、コントラクトの機能をテストします。例えば、dex_test.jsという名前のファイルを作成します。

// test/dex_test.js
const Token = artifacts.require("Token");
const DEX = artifacts.require("DEX");

contract("DEX", (accounts) => {
  let token, dex;
  const initialSupply = web3.utils.toWei('1000', 'ether');

  before(async () => {
    token = await Token.deployed();
    dex = await DEX.deployed();
  });

  it("should have initial token balance in DEX", async () => {
    const balance = await dex.getTokenBalance();
    assert.equal(balance.toString(), initialSupply, "Initial balance is incorrect");
  });

  it("should allow token swaps", async () => {
    const ethAmount = web3.utils.toWei('1', 'ether');
    const tokenAmount = web3.utils.toWei('10', 'ether');

    // スワップ前のバランスを取得
    const initialTokenBalance = await token.balanceOf(accounts[0]);
    const initialEthBalance = await web3.eth.getBalance(accounts[0]);

    // スワップを実行
    await dex.swap(tokenAmount, { from: accounts[0], value: ethAmount });

    // スワップ後のバランスを取得
    const finalTokenBalance = await token.balanceOf(accounts[0]);
    const finalEthBalance = await web3.eth.getBalance(accounts[0]);

    // バランスの変化を確認
    assert.equal(finalTokenBalance.sub(initialTokenBalance).toString(), tokenAmount, "Token swap failed");
    assert(finalEthBalance < initialEthBalance, "ETH swap failed");
  });
});

テストを実行して、コントラクトの機能が期待通りに動作することを確認します。

truffle test

まとめ

これで、基本的なERC-20トークンのスワップ機能を持つ分散型取引所(DEX)の実装が完了です。この例では、非常にシンプルな機能のみを実装していますが、これを基にさらに高度な機能(例えば、オーダーブックやプールベースのアプローチなど)を追加することが可能です。各種セキュリティ対策や最適化も考慮しながら、実際のプロジェクトに応じた拡張を行ってください。

この記事が気に入ったらサポートをしてみませんか?