#21DaysSolidityChallenge Day 9: Unleash the Power of Decentralized Finance — Building a Decentralized Exchange (DEX) 🔄💱

Solidity Academy
Coinmonks

--

🚀 Buy me a coffee! ☕ http://buymeacoffee.com/solidity

👋 Welcome to Day 9 of our Solidity Code Challenge series! Today’s challenge is one of the most exciting yet — we’re going to build a decentralized exchange (DEX) contract. DEXs enable users to trade tokens directly with one another without the need for intermediaries. You’ll implement order matching, token deposits, and withdrawals, bringing the world of decentralized finance (DeFi) to life!

#21DaysSolidityChallenge 21 Days Solidity Coding Challenge

Oh, this magical link is just sooo tempting! 🪄✨ Click away, my dear friend. 😉

💱 Decentralized Exchanges (DEXs): Quick Overview

Before we dive into today’s challenge, let’s briefly understand what decentralized exchanges are and why they’re gaining popularity:

- Decentralized Exchanges (DEXs): DEXs are cryptocurrency exchanges that operate without a central authority. They allow users to trade digital assets directly, providing more control and privacy to traders.

Now, let’s embark on this journey to create a decentralized exchange contract!

Step 1: Setting Up Your Development Environment

Before we begin coding, ensure you have the following tools and accounts ready:

1. Ethereum Wallet: You’ll need an Ethereum wallet like MetaMask to interact with the Ethereum blockchain.

2. Solidity Compiler: Have the Solidity compiler (solc) installed on your computer or use online Solidity development environments like Remix.

3. Test Network: Choose a test network (e.g., Ropsten, Rinkeby, or Kovan) to deploy and test your DEX contract without using real Ether.

4. Integrated Development Environment (IDE): Consider using an IDE like Visual Studio Code with Solidity extensions for a smoother coding experience.

Step 2: Designing the Decentralized Exchange Contract

Let’s create a Solidity smart contract for your decentralized exchange. We’ll name it `DecentralizedExchange.sol`. This contract will implement order matching, token deposits, and withdrawals.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract DecentralizedExchange {
address public admin;
uint256 public feeRate;
uint256 public nextOrderId;
enum OrderType { BUY, SELL }
struct Order {
uint256 id;
address trader;
OrderType orderType;
address token;
uint256 amount;
uint256 price;
}
mapping(uint256 => Order) public orders;
mapping(address => mapping(address => uint256)) public balances;
event OrderPlaced(uint256 orderId, address indexed trader, OrderType orderType, address indexed token, uint256 amount, uint256 price);
event OrderMatched(uint256 buyOrderId, uint256 sellOrderId, uint256 amount, uint256 price);
event OrderCanceled(uint256 orderId);
event FeeCollected(address indexed collector, uint256 amount);
constructor(uint256 _feeRate) {
admin = msg.sender;
feeRate = _feeRate;
nextOrderId = 1;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can perform this action");
_;
}
function placeOrder(OrderType _orderType, address _token, uint256 _amount, uint256 _price) external {
require(_orderType == OrderType.BUY || _orderType == OrderType.SELL, "Invalid order type");
require(_amount > 0, "Amount must be greater than zero");
require(_price > 0, "Price must be greater than zero");
uint256 orderId = nextOrderId++;
orders[orderId] = Order(orderId, msg.sender, _orderType, _token, _amount, _price);
emit OrderPlaced(orderId, msg.sender, _orderType, _token, _amount, _price);
if (_orderType == OrderType.BUY) {
// If it's a buy order, lock the required tokens
require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Token transfer failed");
} else {
// If it's a sell order, lock the required Ether
require(msg.value >= _amount * _price, "Insufficient Ether sent");
balances[msg.sender][_token] += _amount;
}
}
function cancelOrder(uint256 _orderId) external {
Order storage order = orders[_orderId];
require(order.trader == msg.sender, "Only the order owner can cancel");
require(order.id == _orderId, "Order not found");
emit OrderCanceled(_orderId);
if (order.orderType == OrderType.SELL) {
// If it's a sell order, return the locked tokens to the trader
require(IERC20(order.token).transfer(msg.sender, order.amount), "Token transfer failed");
}
delete orders[_orderId];
}
function matchOrders(uint256 _buyOrderId, uint256 _sellOrderId, uint256 _amount, uint256 _price) external {
Order storage buyOrder = orders[_buyOrderId];
Order storage sellOrder = orders[_sellOrderId];
require(buyOrder.id == _buyOrderId, "Buy order not found");
require(sellOrder.id == _sellOrderId, "Sell order not found");
require(buyOrder.orderType == OrderType.BUY, "Invalid buy order");
require(sellOrder.orderType == OrderType.SELL, "Invalid sell order");
require(buyOrder.price >= _price, "Buy price is lower than expected");
require(sellOrder.price <= _price, "Sell price is higher than expected");
require(buyOrder.token == sellOrder.token, "Tokens do not match");
require(_amount <= buyOrder.amount, "Buy order amount exceeded");
require(_amount <= sellOrder.amount, "Sell order amount exceeded");
uint256 fee = (_amount * _price * feeRate) / 10000;
// Transfer tokens from the seller to the buyer
require(IERC20(buyOrder.token).transferFrom(sellOrder.trader, buyOrder.trader, _amount), "Token transfer failed");
// Transfer Ether from the buyer to the seller, excluding the fee
payable(sellOrder.trader).transfer(_amount * _price - fee);
// Transfer the fee to the admin
payable(admin).transfer(fee);
// Update order amounts and emit a match event
buyOrder.amount -= _amount;
sellOrder.amount -= _amount;
emit OrderMatched(_buyOrderId, _sellOrderId, _amount, _price);
// If any of the orders are fully matched, delete them
if (buyOrder.amount == 0) {
delete orders[_buyOrderId];
}
if (sellOrder.amount == 0) {
delete orders[_sellOrderId];
}
}
function withdrawToken(address _token, uint256 _amount) external {
require(_amount > 0, "Amount must be greater than zero");
require(balances[msg.sender][_token] >= _amount, "Insufficient balance");
balances[msg.sender][_token] -= _amount;
require(IERC20(_token).transfer(msg.sender, _amount), "Token transfer failed");
}
function withdrawEther(uint256 _amount
) external {
require(_amount > 0, "Amount must be greater than zero");
require(address(this).balance >= _amount, "Insufficient balance");
payable(msg.sender).transfer(_amount);
}
}

In this contract:

- We define a `DecentralizedExchange` contract that allows traders to place buy and sell orders for different tokens.

- The contract includes order matching logic, token deposits, and withdrawals.

- Traders can place buy or sell orders by specifying the order type, token, amount, and price.

- The contract locks the required tokens or Ether based on the order type.

- Traders can cancel their orders if they haven’t been matched yet.

- Matching orders transfers tokens and Ether between traders while collecting a fee for the DEX admin.

- Traders can withdraw their tokens and Ether from the DEX.

Step 3: Compiling the Decentralized Exchange Contract

Compile your decentralized exchange contract using the Solidity compiler. Use the following command in your terminal:

solc - bin - abi DecentralizedExchange.sol

This command generates the bytecode (`DecentralizedExchange.bin`) and ABI (`DecentralizedExchange.abi`) required for contract deployment.

Step 4: Deploying the Decentralized Exchange Contract

Deploy your decentralized exchange contract to a test network. Follow these steps:

1. Open your Ethereum wallet (e.g., MetaMask) and switch to the Ropsten network.

2. Acquire some test Ether for Ropsten from a faucet. You can find a faucet by searching “Ropsten faucet” online.

3. Deploy your contract using a tool like Remix or Truffle, or manually through a script.

- Go to Remix (https://remix.ethereum.org/).

- Click the “Solidity” tab.

- Create a new file, paste your decentralized exchange contract code, and compile it.

- Switch to the “Deploy & Run Transactions” tab.

- Ensure your environment is set to “Injected Web3” (if you’re using MetaMask).

- Click the “Deploy” button.

4. Confirm the deployment in your wallet, and your contract will be deployed to the Ropsten network.

Step 5: Testing the Decentralized Exchange

Now that your decentralized exchange contract is deployed, let’s test its functionality:

1. In Remix, locate your deployed `DecentralizedExchange` contract.

2. You will see the contract functions, including `placeOrder`, `cancelOrder`, `matchOrders`, `withdrawToken`, and `withdrawEther`.

3. Start by placing buy and sell orders for different tokens with various amounts and prices.

4. Match orders by specifying the order IDs, amounts, and prices. Observe the transfers of tokens and Ether and the collection of fees.

5. Try canceling orders and verify that canceled orders are deleted.

6. Test token and Ether withdrawals to ensure that traders can access their funds.

7. Explore different trading scenarios to thoroughly test the DEX.

Step 6: Writing Tests for the Decentralized Exchange

To ensure that the decentralized exchange contract works as expected, let’s write tests. You can use a testing framework like Truffle or Hardhat. Here’s a simplified example using Truffle:

// In a Truffle test file, e.g., DecentralizedExchange.test.js
const DecentralizedExchange = artifacts.require("DecentralizedExchange");
const TestToken = artifacts.require("TestToken"); // An ERC-20 token contract for testing
contract("DecentralizedExchange", (accounts) => {
it("should allow trading and fee collection", async () => {
const admin = accounts[0];
const trader1 = accounts[1];
const trader2 = accounts[2];
const tokenSymbol = "TEST";
const feeRate = 100; // 1% fee
const exchange = await DecentralizedExchange.new(feeRate, { from: admin });
const testToken = await TestToken.new(tokenSymbol);
// Trader1 deposits tokens
await testToken.transfer(trader1, 1000);
await testToken.approve(exchange.address, 1000, { from: trader1 });
await exchange.depositToken(testToken.address, 500, { from: trader1 });
// Trader2 deposits Ether
await exchange.depositEther({ from: trader2, value: web3.utils.toWei("1", "ether") });
// Trader1 places a buy order
await exchange.placeOrder(0, testToken.address, 10, 2, { from: trader1 });
// Trader2 places a sell order
await exchange.placeOrder(1, testToken.address, 5, 2, { from: trader2 });
// Match orders
await exchange.matchOrders(1, 2, 5, 2, { from: admin });
// Verify balances and fees
const trader1TokenBalance = await exchange.balances(trader1, testToken.address);
const trader2TokenBalance = await exchange.balances(trader2, testToken.address);
const adminEtherBalance = await web3.eth.getBalance(admin);
assert.equal(trader1TokenBalance.toString(), "5", "Trader1 token balance incorrect");
assert.equal(trader2TokenBalance.toString(), "0", "Trader2 token balance incorrect");
assert.isAbove(parseInt(adminEtherBalance), 0, "Admin should collect fees");
});
});

This Truffle test checks trading, fee collection, and balance updates in the decentralized exchange.

Step 7: Running the Tests

Execute the tests using the Truffle framework. In your project directory, run:

truffle test

This command will run your tests and verify that the decentralized exchange contract operates as expected.

Conclusion 🌟

In this Day 9 challenge, you’ve ventured into the realm of decentralized finance (DeFi) by building a decentralized exchange (DEX) contract. This DEX allows users to trade tokens directly, matching buy and sell orders without relying on intermediaries. You’ve also implemented order matching, token deposits, withdrawals, and fee collection, showcasing the potential of blockchain technology in revolutionizing finance.

As you progress through the Solidity Code Challenge series, you’ll continue to explore advanced concepts and build increasingly sophisticated smart contracts. Keep up the fantastic work, and keep advancing the world of DeFi!

🚀 Happy coding and trading! 🚀

📚 Resources 📚

--

--

Solidity Academy
Coinmonks

Learn smart contract development and blockchain integration in depth. https://heylink.me/solidity/ SEND US Your Products to Review! solidity101@gmail.com