在私链上部署uniswap

准备工作

以UniSwap v2 版本为例,部署到自己的私链上。

首先,下载并安装好Node.js,然后执行命令npm i yarn -g全局安装yarn。

然后,下载需要的两个合约

git clone https://github.com/Uniswap/v2-core.git
git clone https://github.com/Uniswap/v2-periphery.git

最后,下载需要的前端项目。由于前端v1,v2,v3版本都在同一个项目,所以我们需要切换到最新的v2版本的标签v2.6.5。由于后续需要修改这个前端项目使之能在私链部署,所以为了方便管理直接以v2.6.5创建一个新的分支master,执行如下命令

git clone https://github.com/Uniswap/interface.git
cd interface && git checkout -b master v2.6.5

由于需要multicall 执行批量查询工具,我们在目录下面放上 v2-core/contracts/Multicall.sol 合约。合约内容点击上面的链接获取。为了方便,我直接贴在下面

// SPDX-License-Identifier: MIT

pragma solidity >=0.5.0;
pragma experimental ABIEncoderV2;

/// @title Multicall - Aggregate results from multiple read-only function calls
/// @author Michael Elliot <mike@makerdao.com>
/// @author Joshua Levine <joshua@makerdao.com>
/// @author Nick Johnson <arachnid@notdot.net>

contract Multicall {
    struct Call {
        address target;
        bytes callData;
    }
    function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
        blockNumber = block.number;
        returnData = new bytes[](calls.length);
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
            require(success);
            returnData[i] = ret;
        }
    }
    // Helper functions
    function getEthBalance(address addr) public view returns (uint256 balance) {
        balance = addr.balance;
    }
    function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
        blockHash = blockhash(blockNumber);
    }
    function getLastBlockHash() public view returns (bytes32 blockHash) {
        blockHash = blockhash(block.number - 1);
    }
    function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
        timestamp = block.timestamp;
    }
    function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
        difficulty = block.difficulty;
    }
    function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
        gaslimit = block.gaslimit;
    }
    function getCurrentBlockCoinbase() public view returns (address coinbase) {
        coinbase = block.coinbase;
    }
}

由于后续演示兑换需要两个ERC20合约,在目录下面放上 v2-core/contracts/MyToken.sol 合约内容如下所示:

pragma solidity >=0.5.0;

contract ERC20 {
    function totalSupply() public view returns (uint supply); // 总供应量
    function balanceOf( address who ) public view returns (uint value); // 指定账号余额
    function allowance( address owner, address spender ) public view returns (uint _allowance); // 限额

    function transfer( address to, uint value) public returns (bool ok); // 转账
    function transferFrom( address from, address to, uint value) public returns (bool ok); // 从他人处转账
    function approve( address spender, uint value ) public returns (bool ok); // 设置允许量值

    event Transfer( address indexed from, address indexed to, uint value); // 转账事件
    event Approval( address indexed owner, address indexed spender, uint value); // 允许事件
}

contract MyToken is ERC20 {
    string public name;  // 代币名称
    string public symbol; // 代币符号
    uint8 public decimals; // 小数点位

    address _cfo;
    mapping (address => uint256) _balances;
    uint256 _supply;
    mapping (address => mapping (address => uint256)) _approvals;

    constructor (string memory n, string memory s, uint8 d, uint256 supply) public {
        name = n;
        symbol = s;
        decimals = d;
        _cfo = msg.sender;
        _supply = supply * 10 ** uint256(d);  
        _balances[_cfo] = _supply;
    }

    modifier onlyCFO() {
        require(msg.sender == _cfo);
        _;
    }

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

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

    function allowance(address src, address guy) public view returns (uint256) {
        return _approvals[src][guy];
    }

    function transfer(address dst, uint wad) public returns (bool) {
        assert(_balances[msg.sender] >= wad);

        _balances[msg.sender] = _balances[msg.sender] - wad;
        _balances[dst] = _balances[dst] + wad;

        emit Transfer(msg.sender, dst, wad);

        return true;
    }

    function transferFrom(address src, address dst, uint wad) public returns (bool) {
        assert(_balances[src] >= wad);
        assert(_approvals[src][msg.sender] >= wad);

        _approvals[src][msg.sender] = _approvals[src][msg.sender] - wad;
        _balances[src] = _balances[src] - wad;
        _balances[dst] = _balances[dst] + wad;

        emit Transfer(src, dst, wad);

        return true;
    }

    function approve(address guy, uint256 wad) public returns (bool) {
        _approvals[msg.sender][guy] = wad;

        emit Approval(msg.sender, guy, wad);

        return true;
    }
}

编译合约

执行命令cd v2-core && yarn && yarn compile编译v2-core里面的合约。在执行安装的过程中,有可能出现一些安装错误比如error D:\Code\swap\v2-core\node_modules\sha3: Command failed.。这个不要紧,可以忽略。如果yarn出现这些错误不会进行yarn compile。那么你重新执行以下编译命令即可。编译完成之后,会在合约目录下面生成一个build目录,里面有很多JSON文件,那么就证明编译成功了。

执行命令cd v2-periphery && yarn && yarn compile编译v2-periphery里面的合约。

修改 init code hash

执行编译之后,还需要修改 v2-periphery/contracts/libraries/UniswapV2Library.sol 里面的 init code hash。系统默认为 96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f。我们要使用编译后的文件v2-core/build/UniswapV2Pair.json里面的evm.bytecode.object字段计算哈希。打开网站http://eth.lucq.fun/#/hash,点击keeack256同时勾选输入数据16进制转uint8,将evm.bytecode.object内容复制到输入框,将下面计算出来的哈希替换掉 v2-periphery/contracts/libraries/UniswapV2Library.sol 中的 96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f 字符串。如果计算出来的哈希值跟系统的不一样,那么需要重新编译一下合约!

部署合约

我们需要部署如下几个合约

  • WETH9 用于将原生的ETH包裹为WETH
  • UniswapV2Pair 配对合约
  • UniswapV2Factory 工厂合约
  • UniswapV2Router01 路由合约
  • UniswapV2Router02 路由合约
  • multicall 批量查询工具。
  • MyToken ERC20合约,为了后续演示兑换,这里会使用不同的入参部署2遍合约。

为了部署方便,在v2-core与v2-periphery同级目录创建一个deploy目录,用于执行合约的部署。

mkdir deploy && cd deploy && npm init -f && npm i web3 && touch index.js 

部署脚本index.js内容如下所示:

const Web3 = require('web3')
const WETH9 = require('../v2-periphery/build/WETH9.json')
const UniswapV2Pair = require('../v2-core/build/UniswapV2Pair.json')
const UniswapV2Factory = require('../v2-core/build/UniswapV2Factory.json')
const UniswapV2Router01 = require('../v2-periphery/build/UniswapV2Router01.json')
const UniswapV2Router02 = require('../v2-periphery/build/UniswapV2Router02.json')
const Multicall = require('../v2-core/build/Multicall.json')
const MyToken = require('../v2-core/build/MyToken.json')

const endpoint = 'http://node.lucq.fun';
const hexPrivateKey = '0xf78a036930ce63791ea6ea20072986d8c3f16a6811f6a2583b0787c45086f769';

async function sendTransaction(web3, chainId, account, data, nonce, gasPrice, to, value) {
  const message = {
    from: account.address,
    gas: 5000000,
    gasPrice: gasPrice,
    data: data.startsWith('0x') ? data : '0x' + data,
    nonce: nonce,
    chainId: chainId
  }
  if (to) {
    message.to = to
  }
  if (value) {
    message.value = value
  }
  const transaction = await account.signTransaction(message)
  return web3.eth.sendSignedTransaction(transaction.rawTransaction)
}

(async () => {
  const options = { timeout: 1000 * 30 }
  const web3 = new Web3(new Web3.providers.HttpProvider(endpoint, options))
  const account = web3.eth.accounts.privateKeyToAccount(hexPrivateKey)

  const chainId = await web3.eth.getChainId()
  const gasPrice = await web3.eth.getGasPrice()
  let nonce = await web3.eth.getTransactionCount(account.address)

  // deploy WETH contract
  let weth = null
  {
    const contract = new web3.eth.Contract(WETH9.abi)
    const data = contract.deploy({ data: WETH9.bytecode }).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('WETH:', weth = receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy UniswapV2Factory contract
  let factory = null
  {
    const contract = new web3.eth.Contract(UniswapV2Factory.abi)
    const options = { data: UniswapV2Factory.bytecode, arguments: [account.address] }
    const data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('UniswapV2Factory:', factory = receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy UniswapV2Router01 contract
  {
    const contract = new web3.eth.Contract(UniswapV2Router01.abi)
    const options = { data: UniswapV2Router01.bytecode, arguments: [factory, weth] }
    const data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('UniswapV2Router01:', receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy UniswapV2Router02 contract
  {
    const contract = new web3.eth.Contract(UniswapV2Router02.abi)
    const options = { data: UniswapV2Router02.bytecode, arguments: [factory, weth] }
    const data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('UniswapV2Router02:', receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy Multicall contract
  {
    const contract = new web3.eth.Contract(Multicall.abi)
    const options = { data: Multicall.bytecode, arguments: [] }
    const data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('Multicall:', receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy MyToken contract
  {
    const contract = new web3.eth.Contract(MyToken.abi)
    const options = { data: MyToken.bytecode, arguments: ["Probe", "Pro", 18, "1000000000000000000000000"] }
    const data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('MyToken Probe:', receipt.contractAddress)
    nonce = nonce + 1
  }

  // deploy MyToken contract
  {
    let contract = new web3.eth.Contract(MyToken.abi)
    const options = { data: MyToken.bytecode, arguments: ["Tether USD", "USDT", 18, "1000000000000000000000000"] }
    let data = contract.deploy(options).encodeABI()
    const receipt = await sendTransaction(web3, chainId, account, data, nonce, gasPrice)
    console.info('MyToken USDT:', receipt.contractAddress)
    nonce = nonce + 1

    // transfer probe to 0x1111102dd32160b064f2a512cdef74bfdb6a9f96
    // 0x1111102dd32160b064f2a512cdef74bfdb6a9f96 privatekey is 95e06fa1a8411d7f6693f486f0f450b122c58feadbcee43fbd02e13da59395d5
    const to = "0x1111102dd32160b064f2a512cdef74bfdb6a9f96"
    contract.options.address = receipt.contractAddress
    data = contract.methods["transfer"].apply(contract.methods, [to, "10000000000000000000"]).encodeABI();
    await sendTransaction(web3, chainId, account, data, nonce, gasPrice, receipt.contractAddress)
    nonce = nonce + 1
  }

  // deposit ETH to WETH contract
  {
    const contract = new web3.eth.Contract(WETH9.abi, weth)
    const value = "1000000000000000000000"
    const data = contract.methods["deposit"].apply(contract.methods, []).encodeABI();
    await sendTransaction(web3, chainId, account, data, nonce, gasPrice, weth, value)
    nonce = nonce + 1
  }

  // calc code hash
  {
    let data = UniswapV2Pair.bytecode
    if (!data.startsWith('0x')) data = '0x' + data
    const hash = web3.utils.keccak256(data)
    console.info('INIT_CODE_HASH:', hash)
    if (hash != "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f") {
      console.warn("计算出来的init code hash为 " + hash + " ,与系统默认的 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f 不一致,请确保你已经更新合约v2-periphery/contracts/libraries/UniswapV2Library.sol里面的init code hash并重新编译过合约了。")
    }
  }
})()

在deploy执行命令node index.js部署合约,如果部署成功,你将得到如下输出:

WETH: 0x546bc6E008689577C69C42b9C1f6b4C923f59B5d
UniswapV2Factory: 0xDad56A6B5eed8567Fc4395d05b59D15077c2c888
UniswapV2Router01: 0x33Add53fb1CDeF4A10BeE7249b66a685200DDd2f
UniswapV2Router02: 0xb4936c57f5b6B5a247aD6089C56064DF98fFf470
Multicall: 0x6e355de3fbc1290c5A72D9A9400A0304E2b8a756
MyToken Probe: 0x4BD9051a87E8d731E452eD84D22AA6E33b608E25
MyToken Tether USD: 0x67a2de7C64F04C1c8E894674acB2A2F99710bDDE
INIT_CODE_HASH: 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f

保留这些信息,后续你将会使用到。

部署前端

执行命令cd interface && yarn安装前端所需要额依赖。由于前端的合约都是使用部署在以太坊上面的合约,我们需要将我们部署的合约地址替换掉以太坊上面的合约地址。需要替换的地方如下所,注意:以下均都是对interface目录下面的文件进行的修改:

  • src/constants/index.ts 文件中 ROUTER_ADDRESS 的值替换为 UniswapV2Router02
  • src/state/swap/hooks.ts 文件中 BAD_RECIPIENT_ADDRESSES 数组的值修改为 [UniswapV2Factory, UniswapV2Router01, UniswapV2Router02]
  • node_modules/@uniswap/sdk/dist/sdk.cjs.development.js 和 node_modules/@uniswap/sdk/dist/sdk.esm.js 文件中 FACTORY_ADDRESS 为 UniswapV2Factory
  • node_modules/@uniswap/sdk/dist/sdk.cjs.development.js 和 node_modules/@uniswap/sdk/dist/sdk.esm.js 文件中 INIT_CODE_HASH 为 INIT_CODE_HASH

由于我们是部署在私链上,我们需要将私链的信息配置好。假设我的私链名称为PRIVNETChainId 是 1024,需要替换的地方或者新增的有这些方面:

  • node_modules/@uniswap/sdk/dist/constants.d.ts 中ChainId添加 PRIVNET = 1024
  • node_modules/@uniswap/sdk/dist/sdk.cjs.development.js 中的大概第22行函数 function (ChainId),需要添加 ChainId[ChainId["PRIVNET"] = 1024] = "PRIVNET";
  • node_modules/@uniswap/sdk/dist/sdk.esm.js 中的大概第18行函数 function (ChainId),需要添加 ChainId[ChainId["PRIVNET"] = 1024] = "PRIVNET";
  • node_modules/@uniswap/sdk/dist/sdk.cjs.development.js 中全局变量大概第422行 WETH,添加_WETH[exports.ChainId.PRIVNET] = /*#__PURE__*/new Token(exports.ChainId.PRIVNET, '0x546bc6E008689577C69C42b9C1f6b4C923f59B5d', 18, 'WETH', 'Wrapped Ether')
  • node_modules/@uniswap/sdk/dist/sdk.esm.js 中全局变量大概第422行 WETH,添加_WETH[ChainId.PRIVNET] = /*#__PURE__*/new Token(ChainId.PRIVNET, '0x546bc6E008689577C69C42b9C1f6b4C923f59B5d', 18, 'WETH', 'Wrapped Ether')
  • /node_modules/@uniswap/sdk/dist/entities/token.d.ts 中 WETH,添加 1024: Token;
  • src/components/Header/index.tsx 文件,在全局对象 NETWORK_LABELS 里面添加 [ChainId.PRIVNET]: 'Privnet'
  • src/connectors/index.ts 文件,在 supportedChainIds 数组里面添加你自己的chainId 1024
  • src/constants/index.ts 文件,在全局对象 WETH_ONLY 里面添加 [ChainId.PRIVNET]: [WETH[ChainId.PRIVNET]]
  • src/constants/multicall/index.ts 文件,在全局对象 MULTICALL_NETWORKS 里面添加 [ChainId.PRIVNET]: '0x6e355de3fbc1290c5A72D9A9400A0304E2b8a756',里面的地址是之前我们部署的 Multicall 合约地址。
  • src/constants/v1/index.ts 文件,在全局对象 V1_FACTORY_ADDRESSES 里面添加 [ChainId.PRIVNET]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',地址是随便填的。因为用不到。但是不填会报错。
  • src/state/lists/hooks.ts 文件,在全局对象 EMPTY_LIST 里面添加 [ChainId.PRIVNET]: {}
  • src/utils/index.ts 文件,在全局对象 ETHERSCAN_PREFIXES 里面添加 1024: ''。其中 1024 是私链的 ChainId

添加代币列表

经过上面的修改,前端成功的运行起来了,但是在添加流动性进入Manage Lists页面的时候,是系统默认的代币列表。当然,这个页面你也可以添加自己token列表。这个列表在文件 interface/src/constants/lists.ts 中的 DEFAULT_LIST_OF_LISTS 常量数组。将数组中的列表中返回自己的列表即可。列表内容参照如下所示。我将上面部署出来的代币写成了一个JSON对象,并在我自己的服务器上提供了一个http地址返回。JSON对象内容如下:

{
  "name": "MyPrivateChainTokens",
  "logoURI": "https://umaproject.org/assets/images/UMA_square_red_logo_circle.png",
  "keywords": ["private"],
  "timestamp": "2021-12-08T01:40:34.305Z",
  "tokens": [
    {
      "chainId": 1024,
      "address": "0x546bc6E008689577C69C42b9C1f6b4C923f59B5d",
      "name": "WETH",
      "symbol": "WETH",
      "decimals": 18
    },
    {
      "chainId": 1024,
      "address": "0x4BD9051a87E8d731E452eD84D22AA6E33b608E25",
      "name": "Probe",
      "symbol": "Pro",
      "decimals": 18
    },
    {
      "chainId": 1024,
      "address": "0x67a2de7C64F04C1c8E894674acB2A2F99710bDDE",
      "name": "USDT",
      "symbol": "USDT",
      "decimals": 18
    }
  ],
  "version": { "major": 1, "minor": 0, "patch": 0 }
}

相应的,lists.ts内容改为如下:

// the Uniswap Default token list lives here
export const DEFAULT_TOKEN_LIST_URL = 'https://b.lucq.fun/api/noteShare/?id=24&json=true'

export const DEFAULT_LIST_OF_LISTS: string[] = [
  DEFAULT_TOKEN_LIST_URL,
]

如果你没有自己的服务器部署自己的tokens列表。那么你可以在我的网站https://b.lucq.fun使用test账号登陆进去,在备忘页面将JSON内容作为一个备忘添加到我系统。然后查看该备忘内容,点击复制分享按钮,将复制到的地址在后面添加&json=true即可做为你自己的tokens列表https地址。

当然,如果你不想这么麻烦,那么可以在Select a token页面,将你的ERC20地址输进去一个一个添加也是可以的。

修改原生代币名称

如果我们需要将原生代币名称由 ETH 改为其它的,例如 PRO,还需要作出以下修改。

  • node_modules/@uniswap/sdk/dist/sdk.cjs.development.js 中 Currency.ETHE 将值改为 new Currency(18, 'PRO', 'PRO Token');
  • node_modules/@uniswap/sdk/dist/sdk.esm.js 中 Currency.ETHER,将值改为 new Currency(18, 'PRO', 'PRO Token');
  • src/components/Header/index.tsx 中将文本 ETH 修改为 PRO
  • src/components/SearchModal/CommonBases.tsx 中将文本 修改为 PRO

一个例子

根据上面的步骤,我已经基于我的私链chainId:1024,RPC: http://node.lucq.fun 部署了相应的uniswap。访问地址为:http://uniswap.lucq.fun/ 。在这个环境上,我已经创建了如下2个配对:

  • WETH 与 USDT 的配对,大概 1 WETH == 100 USDT
  • WETH 与 Pro 的配对,大概 1 WETH == 1000 Pro

有一个私钥为 95e06fa1a8411d7f6693f486f0f450b122c58feadbcee43fbd02e13da59395d5 的地址我已经内置了大量 USDT。你可以测试用USDT兑换Pro,或者用USDT兑换WETH,然后调用WETH合约将WETH提取出来换成原生的ETH等操作。

在测试的过程中,发现读取tokens list有点问题,你可以利用地址直接将Token添加进去,三个ERC20合约相对应的地址见上面部署里面的合约地址信息。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇