Links

DEX tutorial

This tutorial utilizes the predeployed DEX smart contract to swap the ERC20 tokens of the predeployed Token smart contracts, which we instantiate with the help of the ADDRESS utility.

Table of contents

About

This example introduces the use of Acala EVM+ predeployed DEX that is present on every network at a fixed address (the address of a predeployed contract is the same on a local development network, public test network as well as the production network). As this example focuses on showcasing the interactions with the predeployed DEX, it doesn't have its own smart contract. We will get all of the required imports from the @acala-network/contracts dependency. The precompiles and predeploys are a specific feature of the Acala EVM+, so this tutorial is no longer compatible with traditional EVM development networks (like Ganache).
Let's take a look!
NOTE: You can refer to the complete code of this tutorial https://github.com/AcalaNetwork/truffle-tutorials/tree/master/DEX

Smart contract

The smart contract in this tutorial is only used to satisfy the Truffle's requirement to have a smart contract to compile. For this, we will create an empty smart contract that will inherit the DEX from @acala-network/contracts dependency. Your skeleton smart contract should look like this:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.9;
contract PrecompiledDEX {
}
Import of the DEX from @acala-network/contracts is done between the pragma definition and the start od the contract block:
import "@acala-network/contracts/dex/DEX.sol";
As we now have access to DEX.sol from @acala-network/contracts, we can set the inheritance of our PrecompiledDEX contract:
contract PrecompiledDEX is DEX {
This concludes our PrecompiledDEX smart contract.
Your contracts/PrecompiledDEX.sol should look like this:
// SPDX-License-Identifier: MIT
pragma solidity =0.8.9;
import "@acala-network/contracts/dex/DEX.sol";
contract PrecompiledDEX is DEX {
}
NOTE: in order for Truffle to properly use the Token precompiles (used in the following section), we need to add the PrecompiledToken.sol smart contract as well.

Test

Tests for this tutorial will validate the expected values returned and expected behaviour of DEX predeployed smart contract. The test file in our case is called DEX.js. Within it we import the DEX and Token from @acala-network/contracts dependency and assign it to PrecompiledDEX and PrecompiledToken variables. The ACA, AUSD, LP_ACA_AUSD, DOT, RENBTC and DEX, which are the exports from the ADDRESS utility of @acala-network/contracts dependency, are imported and they hold the values of the addresses of the predeployed smart contracts. The MandalaAddress utility holds the values of the predeployed smart contracts in the local development and Mandala network and we will be using this one. There are also AcalaNetwork and KaruraNetwork, that hold the addresses of their respective networks. We are also importing truffleAssert and parseUnits in order to ease our verification of the expected values and we are defining the NULL_ADDRESS constant, so we don't have to copy-paste the value when needed.
The test file with import statements and an empty test should look like this:
const PrecompiledDEX = artifacts.require("@acala-network/contracts/build/contracts/DEX");
const PrecompiledToken = artifacts.require("@acala-network/contracts/build/contracts/Token");
const truffleAssert = require("truffle-assertions");
const { parseUnits } = require("ethers/lib/utils");
const { ACA, AUSD, LP_ACA_AUSD, DOT, RENBTC, DEX } = require("@acala-network/contracts/utils/MandalaAddress");
const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
/*
* uncomment accounts to access the test accounts made available by the
* Ethereum client
* See docs: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript
*/
contract("PrecompiledDEX", function (accounts) {
});
To prepare for the testing, we have to define four global variables, instance, AUSDinstance, ACAinstance and deployer. The instance will store the predeployed DEX smart contract instance. AUSDinstance and ACAinstance will store the values of the token predeployed smart contracts. he deployer will store the account that we will be using in our tests. Let's assign them values in the beforeEach action:
let instance;
let ACAinstance;
let AUSDinstance;
let deployer;
beforeEach("setup development environment", async function () {
deployer = accounts[0];
instance = await PrecompiledDEX.at(DEX);
ACAinstance = await PrecompiledToken.at(ACA);
AUSDinstance = await PrecompiledToken.at(AUSD);
});
You can see how we used the DEX, ACA and AUSD from the ADDRESS utility in order to set the addresses of our predeployed smart contract.
Our test will only contain one top-level section called Operation in which we will be checking the following functions (which will each be tested in its own section):
  1. 1.
    getLiquidityPool function to get the liquidity pool of the desired pair.
  2. 2.
    getLiquidityTokenAddress function to get the address of the liquidity token for the desired pair.
  3. 3.
    getSwapTargetAddress function to get the informative amount of the swap egress token based on set supply of the ingress token.
  4. 4.
    getSwapSupplyAmount function to get the informative amount of the swap supply based on the set target amount of egress token.
  5. 5.
    swapWithExactSupply function to swap tokens based on the set supply of the ingress token.
  6. 6.
    swapWithExactTarget function to swap tokens based on the set target of the egress token.
  7. 7.
    addLiquidity function that adds liquidity of the desired pair.
  8. 8.
    removeLiquidity function that removes liquidity of the desired pair.
The structure described above without the checks, should look like this:
describe("Operation", function () {
describe("getLiquidityPool", function () {
});
describe("getLiquidityTokenAddress", function () {
});
describe("getSwapTargetAddress", function () {
});
describe("getSwapSupplyAmount", function () {
});
describe("swapWithExactSupply", function () {
});
describe("swapWithExactTarget", function () {
});
describe("addLiquidity", function () {
});
describe("removeLiquidity", function () {
});
});
When validating the getLiquidityPool function, we will check for the following examples:
  1. 1.
    tokenA should not be a 0x0 address.
  2. 2.
    tokenB should not be a 0x0 address.
  3. 3.
    Liquidity of the non-existent pair should be 0.
  4. 4.
    Liquidity should be returned for the existing pairs.
The section should look like this:
it("should not allow tokenA to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getLiquidityPool(NULL_ADDRESS, ACA),
"DEX: tokenA is zero address"
);
});
it("should not allow tokenB to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getLiquidityPool(ACA, NULL_ADDRESS),
"DEX: tokenB is zero address"
);
});
it("should return 0 liquidity for nonexistent pair", async function () {
const response = await instance.getLiquidityPool(ACA, DOT);
const liquidityA = response[0];
const liquidityB = response[1];
expect(liquidityA.isZero()).to.be.true;
expect(liquidityB.isZero()).to.be.true;
});
it("should return liquidity for existing pairs", async function () {
const response = await instance.getLiquidityPool(ACA, AUSD);
const liquidityA = response[0];
const liquidityB = response[1];
expect(liquidityA.gt(web3.utils.toBN("0"))).to.be.true;
expect(liquidityB.gt(web3.utils.toBN("0"))).to.be.true;
});
When validating the getLiquidityTokenAddress function, we will check for the following examples:
  1. 1.
    tokenA should not be a 0x0 address.
  2. 2.
    tokenB should not be a 0x0 address.
  3. 3.
    Liquidity token address should be returned for the existing pairs.
The section should look like this:
it("should not allow tokenA to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getLiquidityTokenAddress(NULL_ADDRESS, ACA),
"DEX: tokenA is zero address"
);
});
it("should not allow tokenB to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getLiquidityTokenAddress(ACA, NULL_ADDRESS),
"DEX: tokenB is zero address"
);
});
it("should return liquidity token address for an existing pair", async function () {
const response = await instance.getLiquidityTokenAddress(ACA, AUSD);
expect(response).to.equal(LP_ACA_AUSD);
});
When validating the getSwapTargetAddress function, we will check for the following examples:
  1. 1.
    path should not include the 0x0 address.
  2. 2.
    supplyAmount should not be 0.
  3. 3.
    Getting swap target amount should return 0 for an incompatible path.
  4. 4.
    Swap target amount should be returned when all parameters are correct.
The section should look like this:
it("should not allow for the path to include a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getSwapTargetAmount([NULL_ADDRESS, ACA, DOT, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, NULL_ADDRESS, DOT, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, DOT, NULL_ADDRESS, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, DOT, RENBTC, NULL_ADDRESS], 12345678990),
"DEX: token is zero address"
);
});
it("should not allow supplyAmount to be 0", async function () {
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, AUSD], 0),
"DEX: supplyAmount is zero"
);
});
it("should return 0 for an incompatible path", async function () {
const expected_target = await instance.getSwapTargetAmount([ACA, DOT], 100);
expect(expected_target).to.deep.equal(web3.utils.toBN('0'));
});
it("should return a swap target amount", async function () {
const response = await instance.getSwapTargetAmount([ACA, AUSD], 100);
expect(response.gt(web3.utils.toBN("0"))).to.be.true;
});
When validating the getSwapSupplyAmount function, we will check for the following examples:
  1. 1.
    path should not include the 0x0 address.
  2. 2.
    targetAmount should not be 0.
  3. 3.
    Getting swap supply amount should return 0 for an incompatible path.
  4. 4.
    Swap supply amount should be returned when all parameters are correct.
The section should look like this:
it("should not allow an address in the path to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.getSwapSupplyAmount([NULL_ADDRESS, ACA, DOT, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapSupplyAmount([ACA, NULL_ADDRESS, DOT, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapSupplyAmount([ACA, DOT, NULL_ADDRESS, RENBTC], 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.getSwapSupplyAmount([ACA, DOT, RENBTC, NULL_ADDRESS], 12345678990),
"DEX: token is zero address"
);
});
it("should not allow targetAmount to be 0", async function () {
await truffleAssert.reverts(
instance.getSwapSupplyAmount([ACA, AUSD], 0),
"DEX: targetAmount is zero"
);
});
it("should return 0 for an incompatible path", async function () {
const expected_supply = await instance.getSwapSupplyAmount([ACA, DOT], 100);
expect(expected_supply).to.deep.equal(web3.utils.toBN('0'));
});
it("should return the supply amount", async function () {
const response = await instance.getSwapSupplyAmount([ACA, AUSD], 100);
expect(response.gt(web3.utils.toBN("0"))).to.be.true;
});
When validating the swapWithExactSupply function, we will check for the following examples:
  1. 1.
    path should not include the 0x0 address.
  2. 2.
    supplyAmount should not be 0.
  3. 3.
    Egress token balance of the caller should increase.
  4. 4.
    Successful execution should emit a Swaped event.
The section should look like this:
it("should not allow path to contain a 0x0 address", async function () {
await truffleAssert.reverts(
instance.swapWithExactSupply([NULL_ADDRESS, ACA, DOT, RENBTC], 12345678990, 1),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactSupply([ACA, NULL_ADDRESS, DOT, RENBTC], 12345678990, 1),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactSupply([ACA, DOT, NULL_ADDRESS, RENBTC], 12345678990, 1),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactSupply([ACA, DOT, RENBTC, NULL_ADDRESS], 12345678990, 1),
"DEX: token is zero address"
);
});
it("should not allow supplyAmount to be 0", async function () {
await truffleAssert.reverts(
instance.swapWithExactSupply([ACA, AUSD], 0, 1),
"DEX: supplyAmount is zero"
);
});
it("should allocate the tokens to the caller", async function () {
const initalBalance = await ACAinstance.balanceOf(deployer);
const initBal = await AUSDinstance.balanceOf(deployer);
const path = [ACA, AUSD];
const expected_target = await instance.getSwapTargetAmount(path, 100);
await instance.swapWithExactSupply(path, 100, 1, { from: deployer });
const finalBalance = await ACAinstance.balanceOf(deployer);
const finBal = await AUSDinstance.balanceOf(deployer);
// The following assertion needs to check for the balance to be below the initialBalance - 100, because some of the ACA balance is used to pay for the transaction fee.
expect(finalBalance.lt(initalBalance.sub(web3.utils.toBN(100)))).to.be.true;
expect(finBal.eq(initBal.add(expected_target))).to.be.true;
});
it("should emit a Swaped event", async function () {
const path = [ACA, AUSD];
const expected_target = await instance.getSwapTargetAmount(path, 100);
const tx = await instance.swapWithExactSupply(path, 100, 1, { from: deployer });
const event = tx.logs[0].event;
const sender = tx.logs[0].args.sender;
const event_path = tx.logs[0].args.path;
const supplyAmount = tx.logs[0].args.supplyAmount;
const targetAmount = tx.logs[0].args.targetAmount;
expect(event).to.equal("Swaped");
expect(sender).to.equal(deployer);
expect(event_path).to.deep.equal(path);
expect(supplyAmount).to.deep.equal(web3.utils.toBN(100));
expect(targetAmount).to.deep.equal(expected_target);
});
When validating the swapWithExactTarget function, we will check for the following examples:
  1. 1.
    path should not include the 0x0 address.
  2. 2.
    targetAmount should not be 0.
  3. 3.
    Egress token balance of the caller should increase.
  4. 4.
    Successful execution should emit a Swaped event.
The section should look like this:
it("should not allow a token in a path to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.swapWithExactTarget([NULL_ADDRESS, ACA, DOT, RENBTC], 1, 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactTarget([ACA, NULL_ADDRESS, DOT, RENBTC], 1, 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactTarget([ACA, DOT, NULL_ADDRESS, RENBTC], 1, 12345678990),
"DEX: token is zero address"
);
await truffleAssert.reverts(
instance.swapWithExactTarget([ACA, DOT, RENBTC, NULL_ADDRESS], 1, 12345678990),
"DEX: token is zero address"
);
});
it("should not allow targetAmount to be 0", async function () {
await truffleAssert.reverts(
instance.swapWithExactTarget([ACA, AUSD], 0, 1234567890),
"DEX: targetAmount is zero"
);
});
it("should allocate tokens to the caller", async function () {
const initalBalance = await ACAinstance.balanceOf(deployer);
const initBal = await AUSDinstance.balanceOf(deployer);
const path = [ACA, AUSD];
const expected_supply = await instance.getSwapSupplyAmount(path, 100);
await instance.swapWithExactTarget(path, 100, 1234567890, { from: deployer });
const finalBalance = await ACAinstance.balanceOf(deployer);
const finBal = await AUSDinstance.balanceOf(deployer);
// The following assertion needs to check for the balance to be below the initialBalance - 100, because some of the ACA balance is used to pay for the transaction fee.
expect(finalBalance.lt(initalBalance.sub(expected_supply))).to.be.true;
expect(finBal.eq(initBal.add(web3.utils.toBN(100)))).to.be.true;
});
it("should emit Swaped event", async function () {
const path = [ACA, AUSD];
const expected_supply = await instance.getSwapSupplyAmount(path, 100);
const tx = await instance.swapWithExactTarget(path, 100, 1234567890, { from: deployer });
const event = tx.logs[0].event;
const sender = tx.logs[0].args.sender;
const event_path = tx.logs[0].args.path;
const supplyAmount = tx.logs[0].args.supplyAmount;
const targetAmount = tx.logs[0].args.targetAmount;
expect(event).to.equal("Swaped");
expect(sender).to.equal(deployer);
expect(event_path).to.deep.equal(path);
expect(supplyAmount).to.deep.equal(expected_supply);
expect(targetAmount).to.deep.equal(web3.utils.toBN(100));
});
When validating the addLiquidity function, we will check for the following examples:
  1. 1.
    tokenA should not be a 0x0 address.
  2. 2.
    tokenB should not be a 0x0 address.
  3. 3.
    maxAmountA should not be 0.
  4. 4.
    maxAmountB should not be 0.
  5. 5.
    Successfull execution should increase the liquidity of the pair.
  6. 6.
    Successfull execution should emit AddedLiquidity event.
The section should look like this:
it("should not allow tokenA to be 0x0 address", async function () {
await truffleAssert.reverts(
instance.addLiquidity(NULL_ADDRESS, AUSD, 1000, 1000, 1),
"DEX: tokenA is zero address"
);
});
it("should not allow tokenB to be 0x0 address", async function () {
await truffleAssert.reverts(
instance.addLiquidity(ACA, NULL_ADDRESS, 1000, 1000, 1),
"DEX: tokenB is zero address"
);
});
it("should not allow maxAmountA to be 0", async function () {
await truffleAssert.reverts(
instance.addLiquidity(ACA, AUSD, 0, 1000, 1),
"DEX: maxAmountA is zero"
);
});
it("should not allow maxAmountB to be 0", async function () {
await truffleAssert.reverts(
instance.addLiquidity(ACA, AUSD, 1000, 0, 1),
"DEX: maxAmountB is zero"
);
});
it("should increase liquidity", async function () {
const intialLiquidity = await instance.getLiquidityPool(ACA, AUSD);
await instance.addLiquidity(ACA, AUSD, parseUnits("2", 12), parseUnits("2", 12), 1);
const finalLiquidity = await instance.getLiquidityPool(ACA, AUSD);
expect(finalLiquidity[0].gt(intialLiquidity[0])).to.be.true;
expect(finalLiquidity[1].gt(intialLiquidity[1])).to.be.true;
});
it("should emit AddedLiquidity event", async function () {
const tx = await instance.addLiquidity(ACA, AUSD, 1000, 1000, 1, { from: deployer });
const event = tx.logs[0].event;
const sender = tx.logs[0].args.sender;
const tokenA = tx.logs[0].args.tokenA;
const tokenB = tx.logs[0].args.tokenB;
const maxAmountA = tx.logs[0].args.maxAmountA;
const maxAmountB = tx.logs[0].args.maxAmountB;
expect(event).to.equal("AddedLiquidity");
expect(sender).to.equal(deployer);
expect(tokenA).to.deep.equal(ACA);
expect(tokenB).to.deep.equal(AUSD);
expect(maxAmountA).to.deep.equal(web3.utils.toBN(1000));
expect(maxAmountB).to.deep.equal(web3.utils.toBN(1000));
});
When validating the removeLiquidity function, we will check for the following examples:
  1. 1.
    tokenA should not be a 0x0 address.
  2. 2.
    tokenB should not be a 0x0 address.
  3. 3.
    removeShare should not be 0.
  4. 4.
    Successfull execution should reduce the liquidity of the pair.
  5. 5.
    Successfull execution should emit RemovedLiquidity event.
The section should look like this:
it("should not allow tokenA to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.removeLiquidity(NULL_ADDRESS, AUSD, 1, 0, 0),
"DEX: tokenA is zero address"
);
});
it("should not allow tokenB to be a 0x0 address", async function () {
await truffleAssert.reverts(
instance.removeLiquidity(ACA, NULL_ADDRESS, 1, 0, 0),
"DEX: tokenB is zero address"
);
});
it("should not allow removeShare to be 0", async function () {
await truffleAssert.reverts(
instance.removeLiquidity(ACA, AUSD, 0, 0, 0),
"DEX: removeShare is zero"
);
});
it("should reduce the liquidity", async function () {
const intialLiquidity = await instance.getLiquidityPool(ACA, AUSD);
await instance.removeLiquidity(ACA, AUSD, 10, 1, 1);
const finalLiquidity = await instance.getLiquidityPool(ACA, AUSD);
expect(intialLiquidity[0].gt(finalLiquidity[0])).to.be.true;
expect(intialLiquidity[1].gt(finalLiquidity[1])).to.be.true;
});
it("should emit RemovedLiquidity event", async function () {
const tx = await instance.removeLiquidity(ACA, AUSD, 1, 0, 0, { from: deployer });
const event = tx.logs[0].event;
const sender = tx.logs[0].args.sender;
const tokenA = tx.logs[0].args.tokenA;
const tokenB = tx.logs[0].args.tokenB;
const removeShare = tx.logs[0].args.removeShare;
expect(event).to.equal("RemovedLiquidity");
expect(sender).to.equal(deployer);
expect(tokenA).to.deep.equal(ACA);
expect(tokenB).to.deep.equal(AUSD);
expect(removeShare).to.deep.equal(web3.utils.toBN(1));
});
With that, our test is ready to be run.
Your test/DEX.js should look like this:
const PrecompiledDEX = artifacts.require('@acala-network/contracts/build/contracts/DEX');
const PrecompiledToken = artifacts.require('@acala-network/contracts/build/contracts/Token');
const truffleAssert = require('truffle-assertions');
const { parseUnits } = require('ethers/lib/utils');
const { ACA, AUSD, LP_ACA_AUSD, DOT, RENBTC, DEX } = require('@acala-network/contracts/utils/MandalaAddress');
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
/*
* uncomment accounts to access the test accounts made available by the
* Ethereum client
* See docs: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript
*/
contract('PrecompiledDEX', function (accounts) {
let instance;
let ACAinstance;
let AUSDinstance;
let deployer;
beforeEach('setup development environment', async function () {
deployer = accounts[0];
instance = await PrecompiledDEX.at(DEX);
ACAinstance = await PrecompiledToken.at(ACA);
AUSDinstance = await PrecompiledToken.at(AUSD);
});
describe('Operation', function () {
describe('getLiquidityPool', function () {
it('should not allow tokenA to be a 0x0 address', async function () {
await truffleAssert.reverts(instance.getLiquidityPool(NULL_ADDRESS, ACA), 'DEX: tokenA is zero address');
});
it('should not allow tokenB to be a 0x0 address', async function () {
await truffleAssert.reverts(instance.getLiquidityPool(ACA, NULL_ADDRESS), 'DEX: tokenB is zero address');
});
it('should return 0 liquidity for nonexistent pair', async function () {
const response = await instance.getLiquidityPool(ACA, DOT);
const liquidityA = response[0];
const liquidityB = response[1];
expect(liquidityA.isZero()).to.be.true;
expect(liquidityB.isZero()).to.be.true;
});
it('should return liquidity for existing pairs', async function () {
const response = await instance.getLiquidityPool(ACA, AUSD);
const liquidityA = response[0];
const liquidityB = response[1];
expect(liquidityA.gt(web3.utils.toBN('0'))).to.be.true;
expect(liquidityB.gt(web3.utils.toBN('0'))).to.be.true;
});
});
describe('getLiquidityTokenAddress', function () {
it('should not allow tokenA to be a 0x0 address', async function () {
await truffleAssert.reverts(
instance.getLiquidityTokenAddress(NULL_ADDRESS, ACA),
'DEX: tokenA is zero address'
);
});
it('should not allow tokenB to be a 0x0 address', async function () {
await truffleAssert.reverts(
instance.getLiquidityTokenAddress(ACA, NULL_ADDRESS),
'DEX: tokenB is zero address'
);
});
it('should return liquidity token address for an existing pair', async function () {
const response = await instance.getLiquidityTokenAddress(ACA, AUSD);
expect(response).to.equal(LP_ACA_AUSD);
});
});
describe('getSwapTargetAddress', function () {
it('should not allow for the path to include a 0x0 address', async function () {
await truffleAssert.reverts(
instance.getSwapTargetAmount([NULL_ADDRESS, ACA, DOT, RENBTC], 12345678990),
'DEX: token is zero address'
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, NULL_ADDRESS, DOT, RENBTC], 12345678990),
'DEX: token is zero address'
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, DOT, NULL_ADDRESS, RENBTC], 12345678990),
'DEX: token is zero address'
);
await truffleAssert.reverts(
instance.getSwapTargetAmount([ACA, DOT, RENBTC, NULL_ADDRESS],