├── test └── .gitkeep ├── migrations ├── 1_initial_migration.js └── 2_deploy_MyTokenAndFarmToken.js ├── contracts ├── MyToken.sol ├── Migrations.sol └── FarmToken.sol ├── scripts ├── getMyTokenBalance.js ├── transferMyTokenFrom2Addresses.js ├── withdrawMyTokenFromTokenFarm.js └── transferMyTokenToFarmToken.js ├── .gitignore ├── package.json ├── truffle-config.js └── README.md /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /contracts/MyToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.2; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MyToken is ERC20 { 7 | constructor() ERC20("MyToken", "MTKN"){ 8 | _mint(msg.sender, 1000000000000000000000000); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /scripts/getMyTokenBalance.js: -------------------------------------------------------------------------------- 1 | const MyToken = artifacts.require('MyToken'); 2 | const FarmToken = artifacts.require("FarmToken"); 3 | 4 | module.exports = async function(callback) { 5 | myToken = await MyToken.deployed() 6 | farmToken = await FarmToken.deployed() 7 | balance = await myToken.balanceOf(farmToken.address) 8 | console.log(web3.utils.fromWei(balance.toString())) 9 | callback(); 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* -------------------------------------------------------------------------------- /migrations/2_deploy_MyTokenAndFarmToken.js: -------------------------------------------------------------------------------- 1 | const MyToken = artifacts.require('MyToken') 2 | const FarmToken = artifacts.require('FarmToken') 3 | 4 | module.exports = async function(deployer, network, accounts) { 5 | // Deploy MyToken 6 | await deployer.deploy(MyToken) 7 | const myToken = await MyToken.deployed() 8 | 9 | // Deploy Farm Token 10 | await deployer.deploy(FarmToken, myToken.address) 11 | const farmToken = await FarmToken.deployed() 12 | } -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "defi-token-farm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/strykerin/DeFi-Token-Farm.git" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/strykerin/DeFi-Token-Farm/issues" 22 | }, 23 | "homepage": "https://github.com/strykerin/DeFi-Token-Farm#readme" 24 | } 25 | -------------------------------------------------------------------------------- /scripts/transferMyTokenFrom2Addresses.js: -------------------------------------------------------------------------------- 1 | const MyToken = artifacts.require("MyToken"); 2 | 3 | module.exports = async function(callback) { 4 | const accounts = await new web3.eth.getAccounts(); 5 | const myToken = await MyToken.deployed(); 6 | 7 | // Transfer MyToken from account[0] to account[1] 8 | await myToken.transfer(accounts[1], '1000000'); 9 | 10 | // Check balance for accounts[0] and accounts[1] 11 | balance0 = await myToken.balanceOf(accounts[0]); 12 | balance1 = await myToken.balanceOf(accounts[1]); 13 | console.log('Balance MyToken accounts[0] ' + web3.utils.fromWei(balance0.toString())) 14 | console.log('Balance MyToken accounts[1] ' + web3.utils.fromWei(balance1.toString())) 15 | 16 | callback(); 17 | } -------------------------------------------------------------------------------- /contracts/FarmToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.10; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/utils/Address.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 9 | 10 | 11 | contract FarmToken is ERC20 { 12 | using Address for address; 13 | using SafeMath for uint256; 14 | using SafeERC20 for IERC20; 15 | 16 | IERC20 public token; 17 | 18 | constructor(address _token) ERC20("FarmToken", "FRM") { 19 | token = IERC20(_token); 20 | } 21 | 22 | function balance() public view returns (uint256) { 23 | return token.balanceOf(address(this)); 24 | } 25 | 26 | function deposit(uint256 _amount) public { 27 | // Amount must be greater than zero 28 | require(_amount > 0, "amount cannot be 0"); 29 | 30 | // Transfer MyToken to smart contract 31 | token.safeTransferFrom(msg.sender, address(this), _amount); 32 | 33 | // Mint FarmToken to msg sender 34 | _mint(msg.sender, _amount); 35 | } 36 | 37 | function depositAll() public { 38 | // Deposit all of MyTokens from msg sender 39 | deposit(token.balanceOf(msg.sender)); 40 | } 41 | 42 | function withdraw(uint256 _amount) public { 43 | // Burn FarmTokens from msg sender 44 | _burn(msg.sender, _amount); 45 | 46 | // Transfer MyTokens from this smart contract to msg sender 47 | token.safeTransfer(msg.sender, _amount); 48 | } 49 | } -------------------------------------------------------------------------------- /scripts/withdrawMyTokenFromTokenFarm.js: -------------------------------------------------------------------------------- 1 | const MyToken = artifacts.require("MyToken"); 2 | const FarmToken = artifacts.require("FarmToken"); 3 | 4 | module.exports = async function(callback) { 5 | const accounts = await new web3.eth.getAccounts(); 6 | const myToken = await MyToken.deployed(); 7 | const farmToken = await FarmToken.deployed(); 8 | 9 | // Verify accounts[0] and farmToken balance of MyToken before and after the transfer 10 | balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0]); 11 | balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address); 12 | console.log('*** My Token ***') 13 | console.log('Balance MyToken Before accounts[0] ' + web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())) 14 | console.log('Balance MyToken Before TokenFarm ' + web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())) 15 | 16 | console.log('*** Farm Token ***') 17 | balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0]); 18 | balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address); 19 | console.log('Balance FarmToken Before accounts[0] ' + web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())) 20 | console.log('Balance FarmToken Before TokenFarm ' + web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())) 21 | 22 | // Call Deposit function from FarmToken 23 | console.log('Call Withdraw Function') 24 | await farmToken.withdraw(web3.utils.toWei('100', 'ether')); 25 | 26 | console.log('*** My Token ***') 27 | balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0]); 28 | balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address); 29 | console.log('Balance MyToken After accounts[0] ' + web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())) 30 | console.log('Balance MyToken After TokenFarm ' + web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())) 31 | 32 | console.log('*** Farm Token ***') 33 | balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0]); 34 | balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address); 35 | console.log('Balance FarmToken After accounts[0] ' + web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())) 36 | console.log('Balance FarmToken After TokenFarm ' + web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())) 37 | 38 | // End function 39 | callback(); 40 | } -------------------------------------------------------------------------------- /scripts/transferMyTokenToFarmToken.js: -------------------------------------------------------------------------------- 1 | const MyToken = artifacts.require("MyToken"); 2 | const FarmToken = artifacts.require("FarmToken"); 3 | 4 | module.exports = async function(callback) { 5 | const accounts = await new web3.eth.getAccounts(); 6 | const myToken = await MyToken.deployed(); 7 | const farmToken = await FarmToken.deployed(); 8 | 9 | // Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom. 10 | // This is zero by default. 11 | const allowanceBefore = await myToken.allowance(accounts[0], farmToken.address); 12 | console.log('Amount of MyToken FarmToken is allowed to transfer on our behalf Before: ' + allowanceBefore.toString()); 13 | 14 | // In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf, 15 | // we must explicitly allow it. 16 | // We allow farmToken to transfer x amount of MyToken on our behalf 17 | await myToken.approve(farmToken.address, web3.utils.toWei('100', 'ether')); 18 | 19 | // Validate that the farmToken can now move x amount of MyToken on our behalf 20 | const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address); 21 | console.log('Amount of MyToken FarmToken is allowed to transfer on our behalf After: ' + allowanceAfter.toString()); 22 | 23 | 24 | // Verify accounts[0] and farmToken balance of MyToken before and after the transfer 25 | balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0]); 26 | balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address); 27 | console.log('*** My Token ***') 28 | console.log('Balance MyToken Before accounts[0] ' + web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())) 29 | console.log('Balance MyToken Before TokenFarm ' + web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())) 30 | 31 | console.log('*** Farm Token ***') 32 | balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0]); 33 | balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address); 34 | console.log('Balance FarmToken Before accounts[0] ' + web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())) 35 | console.log('Balance FarmToken Before TokenFarm ' + web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())) 36 | // Call Deposit function from FarmToken 37 | console.log('Call Deposit Function') 38 | await farmToken.deposit(web3.utils.toWei('100', 'ether')); 39 | console.log('*** My Token ***') 40 | balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0]); 41 | balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address); 42 | console.log('Balance MyToken After accounts[0] ' + web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())) 43 | console.log('Balance MyToken After TokenFarm ' + web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())) 44 | 45 | console.log('*** Farm Token ***') 46 | balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0]); 47 | balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address); 48 | console.log('Balance FarmToken After accounts[0] ' + web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())) 49 | console.log('Balance FarmToken After TokenFarm ' + web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())) 50 | 51 | // End function 52 | callback(); 53 | } -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * trufflesuite.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // 23 | // const fs = require('fs'); 24 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 25 | 26 | module.exports = { 27 | /** 28 | * Networks define how you connect to your ethereum client and let you set the 29 | * defaults web3 uses to send transactions. If you don't specify one truffle 30 | * will spin up a development blockchain for you on port 9545 when you 31 | * run `develop` or `test`. You can ask a truffle command to use a specific 32 | * network from the command line, e.g 33 | * 34 | * $ truffle test --network 35 | */ 36 | 37 | networks: { 38 | // Useful for testing. The `development` name is special - truffle uses it by default 39 | // if it's defined here and no other network is specified at the command line. 40 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 41 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 42 | // options below to some value. 43 | // 44 | // development: { 45 | // host: "127.0.0.1", // Localhost (default: none) 46 | // port: 8545, // Standard Ethereum port (default: none) 47 | // network_id: "*", // Any network (default: none) 48 | // }, 49 | // Another network with more advanced options... 50 | // advanced: { 51 | // port: 8777, // Custom port 52 | // network_id: 1342, // Custom network 53 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 54 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 55 | // from:
, // Account to send txs from (default: accounts[0]) 56 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 57 | // }, 58 | // Useful for deploying to a public network. 59 | // NB: It's important to wrap the provider as a function. 60 | // ropsten: { 61 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 62 | // network_id: 3, // Ropsten's id 63 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 64 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 65 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 66 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 67 | // }, 68 | // Useful for private networks 69 | // private: { 70 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 71 | // network_id: 2111, // This network is yours, in the cloud. 72 | // production: true // Treats this network as if it was a public net. (default: false) 73 | // } 74 | }, 75 | 76 | // Set default mocha options here, use special reporters etc. 77 | mocha: { 78 | // timeout: 100000 79 | }, 80 | 81 | // Configure your compilers 82 | compilers: { 83 | solc: { 84 | version: "0.8.10", 85 | //version: "0.6.2", 86 | // version: "0.5.1", // Fetch exact version from solc-bin (default: truffle's version) 87 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 88 | // settings: { // See the solidity docs for advice about optimization and evmVersion 89 | // optimizer: { 90 | // enabled: false, 91 | // runs: 200 92 | // }, 93 | // evmVersion: "byzantium" 94 | // } 95 | } 96 | }, 97 | 98 | // Truffle DB is currently disabled by default; to enable it, change enabled: 99 | // false to enabled: true. The default storage location can also be 100 | // overridden by specifying the adapter settings, as shown in the commented code below. 101 | // 102 | // NOTE: It is not possible to migrate your contracts to truffle DB and you should 103 | // make a backup of your artifacts to a safe location before enabling this feature. 104 | // 105 | // After you backed up your artifacts you can utilize db by running migrate as follows: 106 | // $ truffle migrate --reset --compile-all 107 | // 108 | // db: { 109 | // enabled: false, 110 | // host: "127.0.0.1", 111 | // adapter: { 112 | // name: "sqlite", 113 | // settings: { 114 | // directory: ".db" 115 | // } 116 | // } 117 | // } 118 | }; 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeFi Token Farm 2 | 3 | Create and deploy a DeFi App to Ethereum 4 | 5 | ### Deposit ERC20 tokens to the smart contract and mint Farm Tokens 6 | 7 | In this repo we will build a DeFi Application with Solidity where users can deposit an ERC20 token to the smart contract and it will mint and transfer Farm Tokens to them. The users can later withdraw their ERC20 tokens by burning their Farm Token on smart contract and the ERC20 tokens will be transferred back to them. 8 | 9 | ## Install Truffle and Ganache 10 | ``` 11 | brew install truffle 12 | ``` 13 | or 14 | ``` 15 | npm install truffle 16 | ``` 17 | 18 | If this is the first time you are writing a smart contract, you will need to set up your environment. We are going to use two tools: [Truffle](https://www.trufflesuite.com/) and [Ganache](https://www.trufflesuite.com/ganache). 19 | 20 | Truffle is a development environment and testing framework for developing smart contracts for Ethereum. With Truffle it is easy to build and deploy smart contracts to the blockchain. Ganache allow us to create a local Ethereum blockchain in order to test smart contracts. It simulates the features of the real network and the first 10 accounts are funded with 100 test Ether, thus making the smart contract deployment and testing free and easy. Ganache is available as a desktop application and a command-line tool. For this article we will be using the UI desktop application. 21 | 22 | ![Ganache UI desktop application](https://cdn-images-1.medium.com/max/2360/1*V1iQ5onbLbT5Ib2QaiOSyg.png)*Ganache UI desktop application* 23 | 24 | To create the project, run the following commands 25 | 26 | ```Powershell 27 | mkdir YourProjectName 28 | cd YourProjectName 29 | truffle init 30 | ``` 31 | 32 | This will create a blank project for the development and deployment of our smart contracts. The created project structure is the following: 33 | 34 | * `contracts`: Folder for the solidity smart contracts 35 | 36 | * `migrations`: Folder for the deployment scripts 37 | 38 | * `test`: Folder for testing our smart contracts 39 | 40 | * `truffle-config.js`: Truffle configuration file 41 | 42 | ## Update Solidity Version 43 | 44 | To compile our smart contract, we must first check our solidity compiler version. You can check that by running the command: 45 | 46 | ``` 47 | truffle version 48 | ``` 49 | 50 | The default version is the `Solidity v0.5.16`. Since our token is written using the solidity version `0.8.10`, if we run the command to compile our contracts we will get a compiler error. In order to specify which solidity compiler version to use, go to the file `truffle-config.js` and set to the desired compiler version as seen below: 51 | 52 | ```javascript 53 | // Configure your compilers 54 | compilers: { 55 | solc: { 56 | version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) 57 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 58 | // settings: { // See the solidity docs for advice about optimization and evmVersion 59 | // optimizer: { 60 | // enabled: false, 61 | // runs: 200 62 | // }, 63 | // evmVersion: "byzantium" 64 | // } 65 | } 66 | } 67 | ``` 68 | 69 | ## Create the ERC20 Token 70 | 71 | First we need to create our ERC20 token that we will use to stake on the smart contract. To create our fungible token, we will first need to install the OpenZeppelin library. This library contains the implementations of standards such as the ERC20 and the ERC721. To install it, run the command: 72 | 73 | ``` 74 | npm install @openzeppelin/contracts 75 | ``` 76 | 77 | Using the openzeppelin library we can create our ERC20 token called `MyToken` with the following solidity code: 78 | 79 | ```solidity 80 | // SPDX-License-Identifier: UNLICENSED 81 | pragma solidity ^0.8.2; 82 | 83 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 84 | 85 | contract MyToken is ERC20 { 86 | constructor() ERC20("MyToken", "MTKN"){ 87 | _mint(msg.sender, 1000000000000000000000000); 88 | } 89 | 90 | } 91 | ``` 92 | 93 | In the code above on: 94 | 95 | * Line 3: We import the contract ERC20.sol from openzeppelin that contains the implementation for this token standard. 96 | 97 | * Line 5: We inherit from the ERC20.sol contract. 98 | 99 | * Line 6: We are calling the ERC20.sol constructor and passing for the name and symbol parameters as `"MyToken"` and `"MTKN"` respectively. 100 | 101 | * Line 7: We are minting and transferring 1 million tokens for the account that is deploying the smart contract (we are using the default 18 decimals for the ERC20 token, that means that if we want to mint 1 token, you will represent it as 1000000000000000000, 1 with 18 zeros). 102 | 103 | We can see below the ERC20.sol constructor implementation where the `_decimals` field is set to 18: 104 | 105 | ```solidity 106 | string private _name; 107 | string private _symbol; 108 | uint8 private _decimals; 109 | 110 | constructor (string memory name_, string memory symbol_) public { 111 | _name = name_; 112 | _symbol = symbol_; 113 | _decimals = 18; 114 | } 115 | ``` 116 | 117 | ## Deploy ERC20 Token 118 | 119 | On the `migrations` folder, create a file called `2_deploy_Tokens.js`. This file is where we will deploy both our ERC20 Token and our FarmToken smart contract. The code below is used to deploy our MyToken.sol contract: 120 | 121 | ```javascript 122 | const MyToken = artifacts.require('MyToken') 123 | 124 | module.exports = async function(deployer, network, accounts) { 125 | // Deploy MyToken 126 | await deployer.deploy(MyToken) 127 | const myToken = await MyToken.deployed() 128 | } 129 | ``` 130 | 131 | Now we can compile our smart contract by running the following command: 132 | 133 | ``` 134 | truffle compile 135 | ``` 136 | 137 | After compiling, we can now deploy our token. For this open Ganache and select the option "Quickstart" to start a local Ethereum blockchain. To deploy our contract, run: 138 | 139 | ``` 140 | truffle migrate 141 | ``` 142 | 143 | The address used to deploy our contracts is the first one from the list of addresses that Ganache shows us. To verify that, we can open the Ganache desktop application and we can verify that the balance of Ether for the first account has been reduced due to the cost of Ether to deploy our smart contracts: 144 | 145 | ![Ganache desktop application](https://cdn-images-1.medium.com/max/2346/1*1iJ9VRlyLuza58HL3DLfpg.png)*Ganache desktop application* 146 | 147 | To verify that 1 million MyToken tokens have been sent to the deployer address, we can use the Truffle Console to interact with our deployed smart contract. 148 | > [Truffle Console is a a basic interactive console connecting to any Ethereum client.](https://www.trufflesuite.com/docs/truffle/getting-started/using-truffle-develop-and-the-console) 149 | 150 | In order to interact with our smart contract, run the following command: 151 | 152 | ``` 153 | truffle console 154 | ``` 155 | 156 | Now we can write the following commands in the terminal: 157 | 158 | * Get the smart contract: `myToken = await MyToken.deployed()` 159 | 160 | * Get the array of accounts from Ganache: `accounts = await web3.eth.getAccounts()` 161 | 162 | * Get the balance for the first account: `balance = await myToken.balanceOf(accounts[0])` 163 | 164 | * Format the balance from 18 decimals: `web3.utils.fromWei(balance.toString())` 165 | 166 | By running the commands above, we will see that the first address has in fact 1 million MyTokens: 167 | 168 | ![First address has 1000000 MyTokens](https://cdn-images-1.medium.com/max/2000/1*AQlj9A7dw-qtY4QAD3Bpxw.png)*First address has 1000000 MyTokens* 169 | 170 | ## Create FarmToken Smart Contract 171 | 172 | The FarmToken smart contract will have 3 functions: 173 | 174 | * `balance()`: Get the MyToken balance on the FarmToken smart contract. 175 | 176 | * `deposit(uint256 _amount)`: Transfer MyToken on behalf of the user to the FarmToken smart contract then mint and transfer FarmToken to the user. 177 | 178 | * `withdraw(uint256 _amount)`: Burn user's FarmTokens and transfer MyTokens to the user's address. 179 | 180 | Let's look at the FarmToken constructor: 181 | 182 | ```solidity 183 | // SPDX-License-Identifier: UNLICENSED 184 | pragma solidity 0.8.10; 185 | 186 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 187 | import "@openzeppelin/contracts/utils/Address.sol"; 188 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 189 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 190 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 191 | 192 | 193 | contract FarmToken is ERC20 { 194 | using Address for address; 195 | using SafeMath for uint256; 196 | using SafeERC20 for IERC20; 197 | 198 | IERC20 public token; 199 | 200 | constructor(address _token) ERC20("FarmToken", "FRM") { 201 | token = IERC20(_token); 202 | } 203 | } 204 | ``` 205 | 206 | * Lines 3-6: We are importing the following contracts from openzeppelin: IERC20.sol, Address.sol, SafeERC20.sol and ERC20.sol. 207 | 208 | * Line 8: The FarmToken will inherit from the ERC20 contract. 209 | 210 | * Lines 14-19: The FarmToken constructor will receive as parameter the address of MyToken contract and we will assign its contract to our public variable called `token`. 211 | 212 | Let's implement the `balance()` function. It will receive no parameters and it will return the balance of MyToken on this smart contract. It is implemented as shown below: 213 | 214 | ```solidity 215 | function balance() public view returns (uint256) { 216 | return token.balanceOf(address(this)); 217 | } 218 | ``` 219 | 220 | For the `deposit(uint256 _amount)` function, it will receive as parameter the amount the user wants to deposit and it will mint and transfer FarmTokens to the user: 221 | 222 | ```solidity 223 | function deposit(uint256 _amount) public { 224 | // Amount must be greater than zero 225 | require(_amount > 0, "amount cannot be 0"); 226 | 227 | // Transfer MyToken to smart contract 228 | token.safeTransferFrom(msg.sender, address(this), _amount); 229 | 230 | // Mint FarmToken to msg sender 231 | _mint(msg.sender, _amount); 232 | } 233 | ``` 234 | 235 | For the `withdraw(uint256 _amount)` function, we will receive as parameter the amount of FarmTokens the user wants to burn and then transfer the same amount of MyTokens back to the user: 236 | 237 | ```solidity 238 | function withdraw(uint256 _amount) public { 239 | // Burn FarmTokens from msg sender 240 | _burn(msg.sender, _amount); 241 | 242 | // Transfer MyTokens from this smart contract to msg sender 243 | token.safeTransfer(msg.sender, _amount); 244 | } 245 | ``` 246 | 247 | Now we will deploy our smart contract. To do so, we will go back to the file `2_deploy_Tokens.js` and add the new contract to be deployed: 248 | 249 | ```javascript 250 | const MyToken = artifacts.require('MyToken') 251 | const FarmToken = artifacts.require('FarmToken') 252 | 253 | module.exports = async function(deployer, network, accounts) { 254 | // Deploy MyToken 255 | await deployer.deploy(MyToken) 256 | const myToken = await MyToken.deployed() 257 | 258 | // Deploy Farm Token 259 | await deployer.deploy(FarmToken, myToken.address) 260 | const farmToken = await FarmToken.deployed() 261 | } 262 | ``` 263 | 264 | Note that when deploying the FarmToken, we pass as parameter the address of the deployed MyToken contract. 265 | 266 | Now, run `truffle compile` and `truffle migrate` to deploy our contracts. 267 | 268 | Let's test our smart contract. Instead of using the `truffle console` to interact with our smart contract, we will create a script to automate this process. Create a folder called `scripts` and add the following file `getMyTokenBalance.js`. It will check the balance of MyTokens on the FarmToken smart contract: 269 | 270 | ```javascript 271 | const MyToken = artifacts.require('MyToken'); 272 | const FarmToken = artifacts.require("FarmToken"); 273 | 274 | module.exports = async function(callback) { 275 | myToken = await MyToken.deployed() 276 | farmToken = await FarmToken.deployed() 277 | balance = await myToken.balanceOf(farmToken.address) 278 | console.log(web3.utils.fromWei(balance.toString())) 279 | callback(); 280 | } 281 | ``` 282 | 283 | To execute this script, run the following cli command: 284 | 285 | ```powershell 286 | truffle exec .\scripts\getMyTokenBalance.js 287 | ``` 288 | 289 | We will get the expected result that is 0. 290 | 291 | Now, let's stake the MyToken to the smart contract. Since the function `deposit(uint256 _amount)` calls the function `safeTransferFrom` from the ERC20, the user must first approve the smart contract to transfer MyToken on the user's behalf. So on the script below, we will first approve this step then we will call the function: 292 | 293 | ```javascript 294 | const MyToken = artifacts.require("MyToken"); 295 | const FarmToken = artifacts.require("FarmToken"); 296 | 297 | module.exports = async function(callback) { 298 | const accounts = await new web3.eth.getAccounts(); 299 | const myToken = await MyToken.deployed(); 300 | const farmToken = await FarmToken.deployed(); 301 | 302 | // Returns the remaining number of tokens that spender will be allowed to spend on behalf of owner through transferFrom. 303 | // This is zero by default. 304 | const allowanceBefore = await myToken.allowance(accounts[0], farmToken.address); 305 | console.log('Amount of MyToken FarmToken is allowed to transfer on our behalf Before: ' + allowanceBefore.toString()); 306 | 307 | // In order to allow the Smart Contract to transfer to MyToken (ERC-20) on the accounts[0] behalf, 308 | // we must explicitly allow it. 309 | // We allow farmToken to transfer x amount of MyToken on our behalf 310 | await myToken.approve(farmToken.address, web3.utils.toWei('100', 'ether')); 311 | 312 | // Validate that the farmToken can now move x amount of MyToken on our behalf 313 | const allowanceAfter = await myToken.allowance(accounts[0], farmToken.address); 314 | console.log('Amount of MyToken FarmToken is allowed to transfer on our behalf After: ' + allowanceAfter.toString()); 315 | 316 | 317 | // Verify accounts[0] and farmToken balance of MyToken before and after the transfer 318 | balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0]); 319 | balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address); 320 | console.log('*** My Token ***') 321 | console.log('Balance MyToken Before accounts[0] ' + web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())) 322 | console.log('Balance MyToken Before TokenFarm ' + web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())) 323 | 324 | console.log('*** Farm Token ***') 325 | balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0]); 326 | balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address); 327 | console.log('Balance FarmToken Before accounts[0] ' + web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())) 328 | console.log('Balance FarmToken Before TokenFarm ' + web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())) 329 | // Call Deposit function from FarmToken 330 | console.log('Call Deposit Function') 331 | await farmToken.deposit(web3.utils.toWei('100', 'ether')); 332 | console.log('*** My Token ***') 333 | balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0]); 334 | balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address); 335 | console.log('Balance MyToken After accounts[0] ' + web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())) 336 | console.log('Balance MyToken After TokenFarm ' + web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())) 337 | 338 | console.log('*** Farm Token ***') 339 | balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0]); 340 | balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address); 341 | console.log('Balance FarmToken After accounts[0] ' + web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())) 342 | console.log('Balance FarmToken After TokenFarm ' + web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())) 343 | 344 | // End function 345 | callback(); 346 | } 347 | ``` 348 | 349 | To run this script: `truffle exec .\scripts\transferMyTokenToFarmToken.js`. You should see on your console: 350 | 351 | ![output of transferMyTokenToFarmToken.js](https://cdn-images-1.medium.com/max/2000/1*MoekE2QCw7vB98u5dl7ang.png)*output of transferMyTokenToFarmToken.js* 352 | 353 | As we can see, we have successfully deposited MyTokens to the smart contract as the first account has now FarmTokens. 354 | 355 | In order to withdraw: 356 | 357 | ```javascript 358 | const MyToken = artifacts.require("MyToken"); 359 | const FarmToken = artifacts.require("FarmToken"); 360 | 361 | module.exports = async function(callback) { 362 | const accounts = await new web3.eth.getAccounts(); 363 | const myToken = await MyToken.deployed(); 364 | const farmToken = await FarmToken.deployed(); 365 | 366 | // Verify accounts[0] and farmToken balance of MyToken before and after the transfer 367 | balanceMyTokenBeforeAccounts0 = await myToken.balanceOf(accounts[0]); 368 | balanceMyTokenBeforeFarmToken = await myToken.balanceOf(farmToken.address); 369 | console.log('*** My Token ***') 370 | console.log('Balance MyToken Before accounts[0] ' + web3.utils.fromWei(balanceMyTokenBeforeAccounts0.toString())) 371 | console.log('Balance MyToken Before TokenFarm ' + web3.utils.fromWei(balanceMyTokenBeforeFarmToken.toString())) 372 | 373 | console.log('*** Farm Token ***') 374 | balanceFarmTokenBeforeAccounts0 = await farmToken.balanceOf(accounts[0]); 375 | balanceFarmTokenBeforeFarmToken = await farmToken.balanceOf(farmToken.address); 376 | console.log('Balance FarmToken Before accounts[0] ' + web3.utils.fromWei(balanceFarmTokenBeforeAccounts0.toString())) 377 | console.log('Balance FarmToken Before TokenFarm ' + web3.utils.fromWei(balanceFarmTokenBeforeFarmToken.toString())) 378 | 379 | // Call Deposit function from FarmToken 380 | console.log('Call Withdraw Function') 381 | await farmToken.withdraw(web3.utils.toWei('100', 'ether')); 382 | 383 | console.log('*** My Token ***') 384 | balanceMyTokenAfterAccounts0 = await myToken.balanceOf(accounts[0]); 385 | balanceMyTokenAfterFarmToken = await myToken.balanceOf(farmToken.address); 386 | console.log('Balance MyToken After accounts[0] ' + web3.utils.fromWei(balanceMyTokenAfterAccounts0.toString())) 387 | console.log('Balance MyToken After TokenFarm ' + web3.utils.fromWei(balanceMyTokenAfterFarmToken.toString())) 388 | 389 | console.log('*** Farm Token ***') 390 | balanceFarmTokenAfterAccounts0 = await farmToken.balanceOf(accounts[0]); 391 | balanceFarmTokenAfterFarmToken = await farmToken.balanceOf(farmToken.address); 392 | console.log('Balance FarmToken After accounts[0] ' + web3.utils.fromWei(balanceFarmTokenAfterAccounts0.toString())) 393 | console.log('Balance FarmToken After TokenFarm ' + web3.utils.fromWei(balanceFarmTokenAfterFarmToken.toString())) 394 | 395 | // End function 396 | callback(); 397 | } 398 | ``` 399 | 400 | To run this script: `truffle exec .\scripts\withdrawMyTokenFromTokenFarm.js`. As we can see on the output below, we have successfully got the MyTokens back and we have burned the FarmTokens: 401 | 402 | ![output of withdrawMyTokenFromTokenFarm.js](https://cdn-images-1.medium.com/max/2000/1*jHYlTFg0NgGbhASpsRvc0w.png)*output of withdrawMyTokenFromTokenFarm.js* 403 | 404 | ## References 405 | 406 | [Contracts - OpenZeppelin Docs](https://docs.openzeppelin.com/contracts/3.x/) 407 | 408 | [Sweet Tools for Smart Contracts | Truffle Suite](https://www.trufflesuite.com/) 409 | 410 | [Ganache | Truffle Suite](https://www.trufflesuite.com/ganache) 411 | 412 | [What is DeFi? A Begginer's Guide (2021 Updated) (99bitcoins.com)](https://99bitcoins.com/what-is-defi/) 413 | 414 | [DeFi - The Decentralized Finance Leaderboard at DeFi Pulse](https://defipulse.com/) 415 | --------------------------------------------------------------------------------