├── .env ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── LICENSE ├── README.md ├── contracts ├── PaymentProcessor.sol ├── chainlink │ └── IAggregatorV3.sol ├── example │ └── TestProcessor.sol ├── interface │ └── IPaymentProcessor.sol └── mock │ ├── Oracle.sol │ └── TestToken.sol ├── coverage.json ├── hardhat.config.js ├── package-lock.json ├── package.json └── test └── PaymentProcessor.test.js /.env: -------------------------------------------------------------------------------- 1 | INFURA_KEY = 12b186d448f342d7a89fd9b75f81eb02 2 | ETHSCAN_KEY = UJEQ352JVK9WTQJ935JXEN1T6W7ABJWAWK 3 | PRIVATE_KEY = 81023c4b2d86ad64b1384f19d1122e8c1493bdd81e09df1c9770c99b9079967e -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | /coverage 4 | 5 | #Hardhat files 6 | cache 7 | artifacts 8 | 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['./chainlink','./mock'] 3 | }; 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": [], 4 | "rules": { 5 | "avoid-suicide": "error", 6 | "avoid-sha3": "warn", 7 | "compiler-version" : ["error","0.8.7"], 8 | "quotes": ["error","double"], 9 | "const-name-snakecase": "warn", 10 | "contract-name-camelcase": "warn", 11 | "event-name-camelcase": "warn", 12 | "func-name-mixedcase": "warn", 13 | "ordering": "warn", 14 | "visibility-modifier-order": "warn", 15 | "function-max-lines": ["warn",50], 16 | "func-visibility": ["warn",{"ignoreConstructors":true}], 17 | "reason-string": ["warn",{"maxLength": 100}], 18 | "not-rely-on-time": "ignore" 19 | } 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Nodeberry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity Payment Processor 2 | 3 | A Library for Processing Stablecoin Payments & ERC20 payments in smart contract development. Built for easier payment processing inside smart contracts. 4 | 5 | Supports 50+ ERC20 tokens & all stablecoins. Integrate to your smart contracts with ease. 6 | 7 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier-solidity/prettier-plugin-solidity) 8 | ![Code-coverage: 98.44%](https://img.shields.io/badge/Code--coverage-98.44%25-green) 9 | 10 | ### Table of contents 11 | 12 | - [Getting Started](#getting-started) 13 | - [Prerequisites](#prerequisites) 14 | - [Installation](#installation) 15 | - [Project structure](#project-structure) 16 | 17 | ### Built with 18 | 19 | - [Hardhat](https://hardhat.org/) - Smart Contract Development Suite 20 | - [Solhint](https://protofire.github.io/solhint/) - Linting Suite 21 | - [Prettier](https://github.com/prettier-solidity/prettier-plugin-solidity) - Automatic Code Formatting 22 | - [Solidity](https://docs.soliditylang.org/en/v0.8.6/) - Smart Contract Programming Language 23 | - [Chainlink](https://chain.link/) - Price Feed (Free) 24 | 25 | --- 26 | 27 | ## Overview 28 | 29 | ### Prerequisites 30 | 31 | The repository is built using hardhat. So it is recommended to install hardhat globally through npm or yarn using the following commands. Also the development of these smart contracts are done in npm version v7.21.1 & NodeJs version v16.1.0 32 | 33 | `sudo npm i -g hardhat` 34 | 35 | ### Installation 36 | 37 | ```console 38 | $ npm i @nodeberry/solidity-payment-processor 39 | ``` 40 | 41 | ### Usage 42 | 43 | Once installed, you can use the contracts in the library by importing them: 44 | 45 | ```solidity 46 | pragma solidity ^0.8.7; 47 | 48 | import "@nodeberry/solidity-payment-processor/contracts/PaymentProcessor.sol"; 49 | 50 | contract TestProcessor is PaymentProcessor { 51 | // Initialize Your Smart Contracts 52 | constructor() PaymentProcessor() {} 53 | 54 | function mockSale(string memory _ticker, uint256 _usd) public virtual { 55 | // Process Payments Equivalent in USD inside your smart contracts 56 | // usd should be represented in 8 decimals - 1 USD = 100000000 57 | payment(_ticker, "", _usd); 58 | } 59 | } 60 | ``` 61 | 62 | _If you're new to smart contract development, head to [Developing Smart Contracts](https://docs.openzeppelin.com/learn/developing-smart-contracts) to learn about creating a new project and compiling your contracts._ 63 | 64 | To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources, nor modify it yourself. The library is designed so that only the contracts and functions you use are deployed, so you don't need to worry about it needlessly increasing gas costs. 65 | 66 | ## Learn More 67 | 68 | ### Project structure 69 | 70 | 1. All contract codes, interfaces and utilites imported in the smart contracts can be found at [/contracts](./contracts) 71 | 2. All chainlink related contracts are found at [/contracts/chainlink](./contracts/chainlink) 72 | 3. All contract interfaces are found at [/contracts/interface](./contracts/interface). 73 | 4. An example implementation is given at [/contracts/example](./contracts/example). 74 | 75 | All configuration is done in hardhat.config.js & linting configurations are made in .solhint.json & .prettierrc 76 | 77 | ### Directory layout 78 | 79 | ├── contracts 80 | ├── chainlink 81 | ├── example 82 | ├── interface 83 | ├── mock 84 | ├── PaymentProcessor.sol 85 | ├── test 86 | ├── .prettierrc 87 | ├── .eslintrc.js 88 | ├── .solcover.js 89 | ├── .solhint.json 90 | ├── package.json 91 | ├── hardhat.config.js 92 | └── README.md 93 | 94 | ### Testing 95 | 96 | For running unit & integration tests: 97 | 98 | ```sh 99 | $ npm run test 100 | ``` 101 | 102 | To run code-coverage: 103 | 104 | ```sh 105 | $ npm run coverage 106 | ``` 107 | 108 | ### Contribute 109 | 110 | Nodeberry Contracts exists thanks to its contributors. There are many ways you can participate and help build high quality software. 111 | 112 | ### License 113 | 114 | The Contracts are released under the MIT License. 115 | -------------------------------------------------------------------------------- /contracts/PaymentProcessor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ISC 2 | pragma solidity ^0.8.7; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 7 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 8 | import "./interface/IPaymentProcessor.sol"; 9 | import "./chainlink/IAggregatorV3.sol"; 10 | 11 | /** 12 | * Implementation of {IPaymentProcessor} 13 | */ 14 | 15 | contract PaymentProcessor is IPaymentProcessor, Ownable { 16 | mapping(bytes => address) private oracleAddress; 17 | mapping(bytes => address) private contractAddress; 18 | mapping(bytes => uint8) private isStableCoin; 19 | 20 | event Payment( 21 | address indexed from, 22 | address indexed merchant, 23 | uint256 amount, 24 | string token, 25 | string notes 26 | ); 27 | 28 | modifier Available(string memory _ticker) { 29 | require( 30 | contractAddress[bytes(_ticker)] != address(0), 31 | "error: token not available" 32 | ); 33 | _; 34 | } 35 | 36 | modifier Stablecoin(string memory _ticker) { 37 | require( 38 | isStableCoin[bytes(_ticker)] == 1, 39 | "error: token not stablecoin" 40 | ); 41 | _; 42 | } 43 | 44 | constructor() Ownable() {} 45 | 46 | function setOracle(address _oracleAddress, string memory _ticker) 47 | public 48 | virtual 49 | override 50 | onlyOwner 51 | returns (bool) 52 | { 53 | require( 54 | _oracleAddress != address(0), 55 | "PoS Error: oracle cannot be a zero address" 56 | ); 57 | bytes memory ticker = bytes(_ticker); 58 | 59 | if (oracleAddress[ticker] == address(0)) { 60 | oracleAddress[ticker] = _oracleAddress; 61 | return true; 62 | } else { 63 | revert("PoS Error: oracle address already found"); 64 | } 65 | } 66 | 67 | function setContract(address _contractAddress, string memory _ticker) 68 | public 69 | virtual 70 | override 71 | onlyOwner 72 | returns (bool) 73 | { 74 | require( 75 | _contractAddress != address(0), 76 | "PoS Error: contract cannot be a zero address" 77 | ); 78 | bytes memory ticker = bytes(_ticker); 79 | 80 | if (contractAddress[ticker] == address(0)) { 81 | contractAddress[ticker] = _contractAddress; 82 | return true; 83 | } else { 84 | revert("PoS Error: contract already initialized."); 85 | } 86 | } 87 | 88 | function replaceOracle(address _newOracle, string memory _ticker) 89 | public 90 | virtual 91 | override 92 | onlyOwner 93 | returns (bool) 94 | { 95 | require( 96 | _newOracle != address(0), 97 | "PoS Error: oracle cannot be a zero address" 98 | ); 99 | bytes memory ticker = bytes(_ticker); 100 | 101 | if (oracleAddress[ticker] != address(0)) { 102 | oracleAddress[ticker] = _newOracle; 103 | return true; 104 | } else { 105 | revert("PoS Error: set oracle to replace."); 106 | } 107 | } 108 | 109 | function replaceContract(address _newAddress, string memory _ticker) 110 | public 111 | virtual 112 | override 113 | onlyOwner 114 | returns (bool) 115 | { 116 | require( 117 | _newAddress != address(0), 118 | "PoS Error: contract cannot be a zero address" 119 | ); 120 | bytes memory ticker = bytes(_ticker); 121 | 122 | if (contractAddress[ticker] != address(0)) { 123 | contractAddress[ticker] = _newAddress; 124 | return true; 125 | } else { 126 | revert("PoS Error: contract not initialized yet."); 127 | } 128 | } 129 | 130 | function markAsStablecoin(string memory _ticker) 131 | public 132 | virtual 133 | override 134 | Available(_ticker) 135 | onlyOwner 136 | returns (bool) 137 | { 138 | isStableCoin[bytes(_ticker)] = 1; 139 | return true; 140 | } 141 | 142 | function payment( 143 | string memory _ticker, 144 | string memory _notes, 145 | uint256 _usd 146 | ) internal virtual returns (bool, uint256) { 147 | if (isStableCoin[bytes(_ticker)] == 1) { 148 | return sPayment(address(this), _ticker, _usd, _notes); 149 | } else { 150 | return tPayment(address(this), _ticker, _usd, _notes); 151 | } 152 | } 153 | 154 | function sPayment( 155 | address _merchant, 156 | string memory _ticker, 157 | uint256 _usd, 158 | string memory _notes 159 | ) 160 | internal 161 | virtual 162 | Available(_ticker) 163 | Stablecoin(_ticker) 164 | returns (bool, uint256) 165 | { 166 | uint256 tokens = sAmount(_ticker, _usd); 167 | address spender = _msgSender(); 168 | address tokenAddress = contractAddress[bytes(_ticker)]; 169 | require( 170 | fetchApproval(_ticker, spender) >= tokens, 171 | "PoS Error: insufficient allowance for spender" 172 | ); 173 | emit Payment(spender, _merchant, tokens, _ticker, _notes); 174 | return ( 175 | IERC20(tokenAddress).transferFrom(spender, _merchant, tokens), 176 | tokens 177 | ); 178 | } 179 | 180 | function sAmount(string memory _ticker, uint256 _usd) 181 | public 182 | view 183 | virtual 184 | returns (uint256) 185 | { 186 | address tokenAddress = contractAddress[bytes(_ticker)]; 187 | uint256 decimals = IERC20Metadata(tokenAddress).decimals(); 188 | if (decimals > 8) { 189 | return _usd * 10**(decimals - 8); 190 | } else { 191 | return _usd / 10**(8 - decimals); 192 | } 193 | } 194 | 195 | function tPayment( 196 | address _merchant, 197 | string memory _ticker, 198 | uint256 _usd, 199 | string memory _notes 200 | ) internal virtual Available(_ticker) returns (bool, uint256) { 201 | uint256 amount = tAmount(_ticker, _usd); 202 | address user = _msgSender(); 203 | 204 | require( 205 | fetchApproval(_ticker, user) >= amount, 206 | "PoS Error: Insufficient Approval" 207 | ); 208 | address tokenAddress = contractAddress[bytes(_ticker)]; 209 | emit Payment(user, _merchant, amount, _ticker, _notes); 210 | return ( 211 | IERC20(tokenAddress).transferFrom(user, _merchant, amount), 212 | amount 213 | ); 214 | } 215 | 216 | function fetchApproval(string memory _ticker, address _holder) 217 | public 218 | view 219 | returns (uint256) 220 | { 221 | address tokenAddress = contractAddress[bytes(_ticker)]; 222 | return IERC20(tokenAddress).allowance(_holder, address(this)); 223 | } 224 | 225 | function tAmount(string memory _ticker, uint256 _usd) 226 | public 227 | view 228 | returns (uint256) 229 | { 230 | uint256 value = _usd * 10**18; 231 | uint256 price = fetchOraclePrice(_ticker); 232 | 233 | address tokenAddress = contractAddress[bytes(_ticker)]; 234 | uint256 decimal = IERC20Metadata(tokenAddress).decimals(); 235 | 236 | require(decimal <= 18, "PoS Error: asset class cannot be supported"); 237 | uint256 decimalCorrection = 18 - decimal; 238 | 239 | uint256 tokensAmount = value / price; 240 | return tokensAmount / 10**decimalCorrection; 241 | } 242 | 243 | function fetchContract(string memory _ticker) 244 | public 245 | view 246 | returns (address) 247 | { 248 | return contractAddress[bytes(_ticker)]; 249 | } 250 | 251 | function fetchOracle(string memory _ticker) public view returns (address) { 252 | return oracleAddress[bytes(_ticker)]; 253 | } 254 | 255 | function fetchOraclePrice(string memory _ticker) 256 | public 257 | view 258 | returns (uint256) 259 | { 260 | address oracle = oracleAddress[bytes(_ticker)]; 261 | (, int256 price, , , ) = IAggregatorV3(oracle).latestRoundData(); 262 | return uint256(price); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /contracts/chainlink/IAggregatorV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ISC 2 | 3 | pragma solidity ^0.8.7; 4 | 5 | interface IAggregatorV3 { 6 | function latestRoundData() 7 | external 8 | view 9 | returns ( 10 | uint80 roundId, 11 | int256 answer, 12 | uint256 startedAt, 13 | uint256 updatedAt, 14 | uint80 answeredInRound 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/example/TestProcessor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ISC 2 | pragma solidity ^0.8.7; 3 | 4 | import "@nodeberry/solidity-payment-processor/contracts/PaymentProcessor.sol"; 5 | 6 | contract TestProcessor is PaymentProcessor { 7 | // Initialize Your Smart Contracts 8 | constructor() PaymentProcessor() {} 9 | 10 | function mockSale(string memory _ticker, uint256 _usd) public virtual { 11 | // Process Payments Equivalent in USD inside your smart contracts 12 | // usd should be represented in 8 decimals - 1 USD = 100000000 13 | payment(_ticker, "", _usd); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interface/IPaymentProcessor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ISC 2 | pragma solidity ^0.8.7; 3 | 4 | /** 5 | * SC for handling payments outside of the marketplace SC. 6 | * 7 | * Provides flexibility for handling new payment methods in future. 8 | * Handles payments now in BNB, ADA, ETH & StableCoins. 9 | * 10 | * All prices are handles as 8-decimal irrespective of oracle source. 11 | */ 12 | 13 | interface IPaymentProcessor { 14 | /** 15 | * @dev sets the address of the oracle for the token ticker for the first time. 16 | * 17 | * Requirements: 18 | * `_oracleAddress` is the chainlink oracle address for price. 19 | * `_ticker` is the TICKER for the asset. Eg., BTC for Bitcoin. 20 | */ 21 | function setOracle(address _oracleAddress, string memory _ticker) 22 | external 23 | returns (bool); 24 | 25 | /** 26 | * @dev sets the address of the contract for token ticker. 27 | * 28 | * Requirements: 29 | * `_ticker` is the TICKER of the asset. 30 | * `_contractAddress` is the address of the token smart contract. 31 | * `_contractAddress` should follow BEP20/ERC20 standards. 32 | * 33 | * @return bool representing the status of the transaction. 34 | */ 35 | function setContract(address _contractAddress, string memory _ticker) 36 | external 37 | returns (bool); 38 | 39 | /** 40 | * @dev replace the address of the oracle for the token ticker. 41 | * 42 | * Requirements: 43 | * `_newOracle` is the chainlink oracle address for price. 44 | * `_ticker` is the TICKER for the asset. Eg., BTC for Bitcoin. 45 | */ 46 | function replaceOracle(address _newOracle, string memory _ticker) 47 | external 48 | returns (bool); 49 | 50 | /** 51 | * @dev replaces the address of an existing contract for token ticker. 52 | * 53 | * Requirements: 54 | * `_ticker` is the TICKER of the asset. 55 | * `_contractAddress` is the address of the token smart contract. 56 | * `_contractAddress` should follow BEP20/ERC20 standards. 57 | * 58 | * @return bool representing the status of the transaction. 59 | */ 60 | function replaceContract(address _newAddress, string memory _ticker) 61 | external 62 | returns (bool); 63 | 64 | /** 65 | * @dev marks a specific asset as stablecoin. 66 | * 67 | * Requirements: 68 | * `_ticker` - TICKER of the token that's contract address is already configured. 69 | * 70 | * @return bool representing the status of the transaction. 71 | */ 72 | function markAsStablecoin(string memory _ticker) external returns (bool); 73 | } 74 | -------------------------------------------------------------------------------- /contracts/mock/Oracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.7; 3 | 4 | import "../chainlink/IAggregatorV3.sol"; 5 | 6 | contract Oracle is IAggregatorV3 { 7 | int256 private price; 8 | 9 | constructor(int256 _price) { 10 | price = _price; 11 | } 12 | 13 | function latestRoundData() 14 | public 15 | view 16 | override 17 | returns ( 18 | uint80 roundId, 19 | int256 answer, 20 | uint256 startedAt, 21 | uint256 updatedAt, 22 | uint80 answeredInRound 23 | ) 24 | { 25 | return (0, price, 0, 0, 0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/mock/TestToken.sol: -------------------------------------------------------------------------------- 1 | // contracts/ERC20token.sol 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.7; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract TestToken is ERC20 { 8 | constructor(uint256 initialSupply) ERC20("TestToken", "TEST") { 9 | _mint(msg.sender, initialSupply); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /coverage.json: -------------------------------------------------------------------------------- 1 | {"contracts/Example/TestProcessor.sol":{"l":{"13":2},"path":"/Users/sujithsomraaj/Desktop/Desktop/research/variable-v1-core/contracts/Example/TestProcessor.sol","s":{"1":2},"b":{},"f":{"1":3,"2":2},"fnMap":{"1":{"name":"constructor","line":8,"loc":{"start":{"line":8,"column":4},"end":{"line":8,"column":38}}},"2":{"name":"mockSale","line":10,"loc":{"start":{"line":10,"column":4},"end":{"line":14,"column":4}}}},"statementMap":{"1":{"start":{"line":13,"column":8},"end":{"line":13,"column":33}}},"branchMap":{}},"contracts/interface/IPaymentProcessor.sol":{"l":{},"path":"/Users/sujithsomraaj/Desktop/Desktop/research/variable-v1-core/contracts/interface/IPaymentProcessor.sol","s":{},"b":{},"f":{},"fnMap":{},"statementMap":{},"branchMap":{}},"contracts/PaymentProcessor.sol":{"l":{"29":3,"33":3,"37":1,"41":1,"53":2,"57":2,"59":2,"60":1,"61":1,"63":1,"74":3,"78":3,"80":3,"81":2,"82":2,"84":1,"95":2,"99":2,"101":2,"102":1,"103":1,"105":1,"116":2,"120":2,"122":2,"123":1,"124":1,"126":1,"138":1,"139":1,"147":2,"148":1,"150":1,"166":1,"167":1,"168":1,"169":1,"173":1,"174":1,"186":2,"187":2,"188":2,"189":2,"191":0,"201":1,"202":1,"204":1,"208":1,"209":1,"210":1,"221":3,"222":3,"230":1,"231":1,"233":1,"234":1,"236":1,"237":1,"239":1,"240":1,"248":2,"252":2,"260":2,"261":2,"262":2},"path":"/Users/sujithsomraaj/Desktop/Desktop/research/variable-v1-core/contracts/PaymentProcessor.sol","s":{"1":3,"2":1,"3":2,"4":2,"5":2,"6":1,"7":1,"8":1,"9":3,"10":3,"11":3,"12":2,"13":2,"14":1,"15":2,"16":2,"17":2,"18":1,"19":1,"20":1,"21":2,"22":2,"23":2,"24":1,"25":1,"26":1,"27":1,"28":1,"29":2,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":2,"39":2,"40":2,"41":2,"42":0,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":3,"50":3,"51":1,"52":1,"53":1,"54":1,"55":1,"56":1,"57":1,"58":1,"59":2,"60":2,"61":2,"62":2,"63":2},"b":{"1":[3,0],"2":[1,0],"3":[2,0],"4":[1,1],"5":[3,0],"6":[2,1],"7":[2,0],"8":[1,1],"9":[2,0],"10":[1,1],"11":[1,1],"12":[1,0],"13":[2,0],"14":[1,0],"15":[1,0]},"f":{"1":3,"2":1,"3":3,"4":2,"5":3,"6":2,"7":2,"8":1,"9":2,"10":1,"11":2,"12":1,"13":3,"14":1,"15":2,"16":2,"17":2},"fnMap":{"1":{"name":"Available","line":28,"loc":{"start":{"line":28,"column":4},"end":{"line":34,"column":4}}},"2":{"name":"Stablecoin","line":36,"loc":{"start":{"line":36,"column":4},"end":{"line":42,"column":4}}},"3":{"name":"constructor","line":44,"loc":{"start":{"line":44,"column":4},"end":{"line":44,"column":29}}},"4":{"name":"setOracle","line":50,"loc":{"start":{"line":46,"column":4},"end":{"line":65,"column":4}}},"5":{"name":"setContract","line":71,"loc":{"start":{"line":67,"column":4},"end":{"line":86,"column":4}}},"6":{"name":"replaceOracle","line":92,"loc":{"start":{"line":88,"column":4},"end":{"line":107,"column":4}}},"7":{"name":"replaceContract","line":113,"loc":{"start":{"line":109,"column":4},"end":{"line":128,"column":4}}},"8":{"name":"markAsStablecoin","line":135,"loc":{"start":{"line":130,"column":4},"end":{"line":140,"column":4}}},"9":{"name":"payment","line":142,"loc":{"start":{"line":142,"column":4},"end":{"line":152,"column":4}}},"10":{"name":"sPayment","line":163,"loc":{"start":{"line":154,"column":4},"end":{"line":178,"column":4}}},"11":{"name":"sAmount","line":180,"loc":{"start":{"line":180,"column":4},"end":{"line":193,"column":4}}},"12":{"name":"tPayment","line":200,"loc":{"start":{"line":195,"column":4},"end":{"line":214,"column":4}}},"13":{"name":"fetchApproval","line":216,"loc":{"start":{"line":216,"column":4},"end":{"line":223,"column":4}}},"14":{"name":"tAmount","line":225,"loc":{"start":{"line":225,"column":4},"end":{"line":241,"column":4}}},"15":{"name":"fetchContract","line":243,"loc":{"start":{"line":243,"column":4},"end":{"line":249,"column":4}}},"16":{"name":"fetchOracle","line":251,"loc":{"start":{"line":251,"column":4},"end":{"line":253,"column":4}}},"17":{"name":"fetchOraclePrice","line":255,"loc":{"start":{"line":255,"column":4},"end":{"line":263,"column":4}}}},"statementMap":{"1":{"start":{"line":29,"column":8},"end":{"line":29,"column":881}},"2":{"start":{"line":37,"column":8},"end":{"line":37,"column":1076}},"3":{"start":{"line":53,"column":8},"end":{"line":53,"column":1408}},"4":{"start":{"line":57,"column":8},"end":{"line":57,"column":44}},"5":{"start":{"line":59,"column":8},"end":{"line":59,"column":1582}},"6":{"start":{"line":60,"column":12},"end":{"line":60,"column":49}},"7":{"start":{"line":61,"column":12},"end":{"line":61,"column":23}},"8":{"start":{"line":63,"column":12},"end":{"line":63,"column":60}},"9":{"start":{"line":74,"column":8},"end":{"line":74,"column":1976}},"10":{"start":{"line":78,"column":8},"end":{"line":78,"column":44}},"11":{"start":{"line":80,"column":8},"end":{"line":80,"column":2154}},"12":{"start":{"line":81,"column":12},"end":{"line":81,"column":53}},"13":{"start":{"line":82,"column":12},"end":{"line":82,"column":23}},"14":{"start":{"line":84,"column":12},"end":{"line":84,"column":61}},"15":{"start":{"line":95,"column":8},"end":{"line":95,"column":2551}},"16":{"start":{"line":99,"column":8},"end":{"line":99,"column":44}},"17":{"start":{"line":101,"column":8},"end":{"line":101,"column":2721}},"18":{"start":{"line":102,"column":12},"end":{"line":102,"column":45}},"19":{"start":{"line":103,"column":12},"end":{"line":103,"column":23}},"20":{"start":{"line":105,"column":12},"end":{"line":105,"column":54}},"21":{"start":{"line":116,"column":8},"end":{"line":116,"column":3104}},"22":{"start":{"line":120,"column":8},"end":{"line":120,"column":44}},"23":{"start":{"line":122,"column":8},"end":{"line":122,"column":3277}},"24":{"start":{"line":123,"column":12},"end":{"line":123,"column":48}},"25":{"start":{"line":124,"column":12},"end":{"line":124,"column":23}},"26":{"start":{"line":126,"column":12},"end":{"line":126,"column":61}},"27":{"start":{"line":138,"column":8},"end":{"line":138,"column":39}},"28":{"start":{"line":139,"column":8},"end":{"line":139,"column":19}},"29":{"start":{"line":147,"column":8},"end":{"line":147,"column":3902}},"30":{"start":{"line":148,"column":12},"end":{"line":148,"column":65}},"31":{"start":{"line":150,"column":12},"end":{"line":150,"column":65}},"32":{"start":{"line":166,"column":8},"end":{"line":166,"column":47}},"33":{"start":{"line":167,"column":8},"end":{"line":167,"column":38}},"34":{"start":{"line":168,"column":8},"end":{"line":168,"column":62}},"35":{"start":{"line":169,"column":8},"end":{"line":169,"column":4536}},"36":{"start":{"line":173,"column":8},"end":{"line":173,"column":65}},"37":{"start":{"line":174,"column":8},"end":{"line":174,"column":4747}},"38":{"start":{"line":186,"column":8},"end":{"line":186,"column":62}},"39":{"start":{"line":187,"column":8},"end":{"line":187,"column":66}},"40":{"start":{"line":188,"column":8},"end":{"line":188,"column":5141}},"41":{"start":{"line":189,"column":12},"end":{"line":189,"column":44}},"42":{"start":{"line":191,"column":12},"end":{"line":191,"column":44}},"43":{"start":{"line":201,"column":8},"end":{"line":201,"column":47}},"44":{"start":{"line":202,"column":8},"end":{"line":202,"column":35}},"45":{"start":{"line":204,"column":8},"end":{"line":204,"column":5582}},"46":{"start":{"line":208,"column":8},"end":{"line":208,"column":62}},"47":{"start":{"line":209,"column":8},"end":{"line":209,"column":62}},"48":{"start":{"line":210,"column":8},"end":{"line":210,"column":5838}},"49":{"start":{"line":221,"column":8},"end":{"line":221,"column":62}},"50":{"start":{"line":222,"column":8},"end":{"line":222,"column":69}},"51":{"start":{"line":230,"column":8},"end":{"line":230,"column":37}},"52":{"start":{"line":231,"column":8},"end":{"line":231,"column":49}},"53":{"start":{"line":233,"column":8},"end":{"line":233,"column":62}},"54":{"start":{"line":234,"column":8},"end":{"line":234,"column":65}},"55":{"start":{"line":236,"column":8},"end":{"line":236,"column":75}},"56":{"start":{"line":237,"column":8},"end":{"line":237,"column":48}},"57":{"start":{"line":239,"column":8},"end":{"line":239,"column":44}},"58":{"start":{"line":240,"column":8},"end":{"line":240,"column":51}},"59":{"start":{"line":248,"column":8},"end":{"line":248,"column":46}},"60":{"start":{"line":252,"column":8},"end":{"line":252,"column":44}},"61":{"start":{"line":260,"column":8},"end":{"line":260,"column":54}},"62":{"start":{"line":261,"column":8},"end":{"line":261,"column":72}},"63":{"start":{"line":262,"column":8},"end":{"line":262,"column":29}}},"branchMap":{"1":{"line":29,"type":"if","locations":[{"start":{"line":29,"column":8},"end":{"line":29,"column":8}},{"start":{"line":29,"column":8},"end":{"line":29,"column":8}}]},"2":{"line":37,"type":"if","locations":[{"start":{"line":37,"column":8},"end":{"line":37,"column":8}},{"start":{"line":37,"column":8},"end":{"line":37,"column":8}}]},"3":{"line":53,"type":"if","locations":[{"start":{"line":53,"column":8},"end":{"line":53,"column":8}},{"start":{"line":53,"column":8},"end":{"line":53,"column":8}}]},"4":{"line":59,"type":"if","locations":[{"start":{"line":59,"column":8},"end":{"line":59,"column":8}},{"start":{"line":59,"column":8},"end":{"line":59,"column":8}}]},"5":{"line":74,"type":"if","locations":[{"start":{"line":74,"column":8},"end":{"line":74,"column":8}},{"start":{"line":74,"column":8},"end":{"line":74,"column":8}}]},"6":{"line":80,"type":"if","locations":[{"start":{"line":80,"column":8},"end":{"line":80,"column":8}},{"start":{"line":80,"column":8},"end":{"line":80,"column":8}}]},"7":{"line":95,"type":"if","locations":[{"start":{"line":95,"column":8},"end":{"line":95,"column":8}},{"start":{"line":95,"column":8},"end":{"line":95,"column":8}}]},"8":{"line":101,"type":"if","locations":[{"start":{"line":101,"column":8},"end":{"line":101,"column":8}},{"start":{"line":101,"column":8},"end":{"line":101,"column":8}}]},"9":{"line":116,"type":"if","locations":[{"start":{"line":116,"column":8},"end":{"line":116,"column":8}},{"start":{"line":116,"column":8},"end":{"line":116,"column":8}}]},"10":{"line":122,"type":"if","locations":[{"start":{"line":122,"column":8},"end":{"line":122,"column":8}},{"start":{"line":122,"column":8},"end":{"line":122,"column":8}}]},"11":{"line":147,"type":"if","locations":[{"start":{"line":147,"column":8},"end":{"line":147,"column":8}},{"start":{"line":147,"column":8},"end":{"line":147,"column":8}}]},"12":{"line":169,"type":"if","locations":[{"start":{"line":169,"column":8},"end":{"line":169,"column":8}},{"start":{"line":169,"column":8},"end":{"line":169,"column":8}}]},"13":{"line":188,"type":"if","locations":[{"start":{"line":188,"column":8},"end":{"line":188,"column":8}},{"start":{"line":188,"column":8},"end":{"line":188,"column":8}}]},"14":{"line":204,"type":"if","locations":[{"start":{"line":204,"column":8},"end":{"line":204,"column":8}},{"start":{"line":204,"column":8},"end":{"line":204,"column":8}}]},"15":{"line":236,"type":"if","locations":[{"start":{"line":236,"column":8},"end":{"line":236,"column":8}},{"start":{"line":236,"column":8},"end":{"line":236,"column":8}}]}}}} -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | require("@nomiclabs/hardhat-ethers"); 5 | require("@nomiclabs/hardhat-truffle5"); 6 | require("@nomiclabs/hardhat-etherscan"); 7 | require("@nomiclabs/hardhat-waffle"); 8 | require("solidity-coverage"); 9 | require('dotenv').config(); 10 | 11 | module.exports = { 12 | solidity: "0.8.7", 13 | networks: { 14 | kovan: { 15 | url: `https://kovan.infura.io/v3/${process.env.INFURA_KEY}`, 16 | accounts: [`0x${process.env.PRIVATE_KEY}`] 17 | } 18 | }, 19 | etherscan: { 20 | apiKey: process.env.ETHSCAN_KEY 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nodeberry/solidity-payment-processor", 3 | "version": "1.0.2", 4 | "description": "Smart Contract Library For Processing ERC20 tokens using a price oracle", 5 | "scripts": { 6 | "compile": "npx hardhat compile", 7 | "coverage": "npx hardhat coverage", 8 | "coverage-badge": "npx hardhat coverage && cat coverage/lcov.info | coveralls", 9 | "prettier": "prettier --write 'contracts/**/*.sol'", 10 | "test": "npx hardhat test" 11 | }, 12 | "devDependencies": { 13 | "@nomiclabs/hardhat-ethers": "^2.0.2", 14 | "@nomiclabs/hardhat-etherscan": "^2.1.6", 15 | "@nomiclabs/hardhat-truffle5": "^2.0.1", 16 | "@nomiclabs/hardhat-waffle": "^2.0.1", 17 | "@openzeppelin/contracts": "^4.3.1", 18 | "chai": "^4.3.4", 19 | "coveralls": "^3.1.1", 20 | "dotenv": "^10.0.0", 21 | "ethereum-waffle": "^3.4.0", 22 | "ethers": "^5.4.6", 23 | "hardhat": "^2.6.4", 24 | "solidity-coverage": "^0.7.17" 25 | }, 26 | "main": ".solcover.js", 27 | "directories": { 28 | "test": "test" 29 | }, 30 | "dependencies": { 31 | "@nodeberry/solidity-payment-processor": "^1.0.2", 32 | "abbrev": "^1.0.9", 33 | "abort-controller": "^3.0.0", 34 | "abstract-leveldown": "^6.3.0", 35 | "accepts": "^1.3.7", 36 | "address": "^1.1.2", 37 | "adm-zip": "^0.4.16", 38 | "aes-js": "^3.0.0", 39 | "agent-base": "^6.0.2", 40 | "ajv": "^6.12.6", 41 | "amdefine": "^1.0.1", 42 | "ansi-colors": "^4.1.1", 43 | "ansi-escapes": "^4.3.2", 44 | "ansi-mark": "^1.0.4", 45 | "ansi-regex": "^3.0.0", 46 | "ansi-styles": "^3.2.1", 47 | "antlr4ts": "^0.5.0-alpha.4", 48 | "anymatch": "^3.1.2", 49 | "argparse": "^1.0.10", 50 | "array-back": "^2.0.0", 51 | "array-flatten": "^1.1.1", 52 | "array-union": "^2.1.0", 53 | "array-uniq": "^1.0.3", 54 | "asn1": "^0.2.4", 55 | "asn1.js": "^5.4.1", 56 | "assert-plus": "^1.0.0", 57 | "assertion-error": "^1.1.0", 58 | "async": "^2.6.3", 59 | "async-eventemitter": "^0.2.4", 60 | "async-limiter": "^1.0.1", 61 | "asynckit": "^0.4.0", 62 | "available-typed-arrays": "^1.0.5", 63 | "aws-sign2": "^0.7.0", 64 | "aws4": "^1.11.0", 65 | "balanced-match": "^1.0.2", 66 | "base-x": "^3.0.8", 67 | "base64-js": "^1.5.1", 68 | "bcrypt-pbkdf": "^1.0.2", 69 | "bech32": "^1.1.4", 70 | "big.js": "^5.2.2", 71 | "bignumber.js": "^7.2.1", 72 | "binary-extensions": "^2.2.0", 73 | "blakejs": "^1.1.1", 74 | "bluebird": "^3.7.2", 75 | "bn.js": "^4.12.0", 76 | "body-parser": "^1.19.0", 77 | "boolbase": "^1.0.0", 78 | "borc": "^2.1.2", 79 | "brace-expansion": "^1.1.11", 80 | "braces": "^3.0.2", 81 | "brorand": "^1.1.0", 82 | "browser-stdout": "^1.3.1", 83 | "browserify-aes": "^1.2.0", 84 | "browserify-cipher": "^1.0.1", 85 | "browserify-des": "^1.0.2", 86 | "browserify-rsa": "^4.1.0", 87 | "browserify-sign": "^4.2.1", 88 | "bs58": "^4.0.1", 89 | "bs58check": "^2.1.2", 90 | "buffer": "^5.7.1", 91 | "buffer-from": "^1.1.2", 92 | "buffer-to-arraybuffer": "^0.0.5", 93 | "buffer-xor": "^1.0.3", 94 | "bufferutil": "^4.0.3", 95 | "bytes": "^3.1.0", 96 | "cacheable-request": "^6.1.0", 97 | "call-bind": "^1.0.2", 98 | "camelcase": "^4.1.0", 99 | "caseless": "^0.12.0", 100 | "cbor": "^5.2.0", 101 | "chalk": "^2.4.2", 102 | "check-error": "^1.0.2", 103 | "cheerio": "^1.0.0-rc.10", 104 | "cheerio-select": "^1.5.0", 105 | "chokidar": "^3.5.2", 106 | "chownr": "^1.1.4", 107 | "ci-info": "^2.0.0", 108 | "cids": "^0.7.5", 109 | "cipher-base": "^1.0.4", 110 | "class-is": "^1.1.0", 111 | "cliui": "^5.0.0", 112 | "clone-response": "^1.0.2", 113 | "code-point-at": "^1.1.0", 114 | "color-convert": "^1.9.3", 115 | "color-name": "^1.1.3", 116 | "combined-stream": "^1.0.8", 117 | "command-exists": "^1.2.9", 118 | "command-line-args": "^4.0.7", 119 | "commander": "^2.20.3", 120 | "concat-map": "^0.0.1", 121 | "content-disposition": "^0.5.3", 122 | "content-hash": "^2.5.2", 123 | "content-type": "^1.0.4", 124 | "cookie": "^0.4.1", 125 | "cookie-signature": "^1.0.6", 126 | "cookiejar": "^2.1.2", 127 | "core-js-pure": "^3.17.3", 128 | "core-util-is": "^1.0.2", 129 | "cors": "^2.8.5", 130 | "crc-32": "^1.2.0", 131 | "create-ecdh": "^4.0.4", 132 | "create-hash": "^1.2.0", 133 | "create-hmac": "^1.1.7", 134 | "cross-spawn": "^6.0.5", 135 | "crypto-browserify": "^3.12.0", 136 | "css-select": "^4.1.3", 137 | "css-what": "^5.0.1", 138 | "d": "^1.0.1", 139 | "dashdash": "^1.14.1", 140 | "death": "^1.1.0", 141 | "debug": "^4.3.2", 142 | "decamelize": "^1.2.0", 143 | "decode-uri-component": "^0.2.0", 144 | "decompress-response": "^3.3.0", 145 | "deep-eql": "^3.0.1", 146 | "deep-is": "^0.1.4", 147 | "defer-to-connect": "^1.1.3", 148 | "deferred-leveldown": "^5.3.0", 149 | "define-properties": "^1.1.3", 150 | "delayed-stream": "^1.0.0", 151 | "delimit-stream": "^0.1.0", 152 | "depd": "^1.1.2", 153 | "des.js": "^1.0.1", 154 | "destroy": "^1.0.4", 155 | "detect-indent": "^5.0.0", 156 | "detect-port": "^1.3.0", 157 | "diff": "^3.5.0", 158 | "diffie-hellman": "^5.0.3", 159 | "dir-glob": "^3.0.1", 160 | "dom-serializer": "^1.3.2", 161 | "dom-walk": "^0.1.2", 162 | "domelementtype": "^2.2.0", 163 | "domhandler": "^4.2.2", 164 | "domutils": "^2.8.0", 165 | "duplexer3": "^0.1.4", 166 | "ecc-jsbn": "^0.1.2", 167 | "ee-first": "^1.1.1", 168 | "elliptic": "^6.5.4", 169 | "emoji-regex": "^7.0.3", 170 | "encodeurl": "^1.0.2", 171 | "encoding-down": "^6.3.0", 172 | "end-of-stream": "^1.4.4", 173 | "enquirer": "^2.3.6", 174 | "entities": "^2.2.0", 175 | "env-paths": "^2.2.1", 176 | "errno": "^0.1.8", 177 | "error-ex": "^1.3.2", 178 | "es-abstract": "^1.18.6", 179 | "es-to-primitive": "^1.2.1", 180 | "es5-ext": "^0.10.53", 181 | "es6-iterator": "^2.0.3", 182 | "es6-symbol": "^3.1.3", 183 | "escape-html": "^1.0.3", 184 | "escape-string-regexp": "^1.0.5", 185 | "escodegen": "^1.8.1", 186 | "esprima": "^4.0.1", 187 | "estraverse": "^1.9.3", 188 | "esutils": "^2.0.3", 189 | "etag": "^1.8.1", 190 | "eth-ens-namehash": "^2.0.8", 191 | "eth-lib": "^0.1.29", 192 | "eth-sig-util": "^2.5.4", 193 | "ethereum-bloom-filters": "^1.0.10", 194 | "ethereum-cryptography": "^0.1.3", 195 | "ethereum-ens": "^0.8.0", 196 | "ethereumjs-abi": "^0.6.8", 197 | "ethereumjs-common": "^1.5.2", 198 | "ethereumjs-tx": "^2.1.2", 199 | "ethereumjs-util": "^7.1.0", 200 | "ethjs-unit": "^0.1.6", 201 | "ethjs-util": "^0.1.6", 202 | "event-target-shim": "^5.0.1", 203 | "eventemitter3": "^4.0.4", 204 | "evp_bytestokey": "^1.0.3", 205 | "exit-on-epipe": "^1.0.1", 206 | "express": "^4.17.1", 207 | "ext": "^1.5.0", 208 | "extend": "^3.0.2", 209 | "extsprintf": "^1.3.0", 210 | "fast-deep-equal": "^3.1.3", 211 | "fast-glob": "^3.2.7", 212 | "fast-json-stable-stringify": "^2.1.0", 213 | "fast-levenshtein": "^2.0.6", 214 | "fastq": "^1.13.0", 215 | "fill-range": "^7.0.1", 216 | "finalhandler": "^1.1.2", 217 | "find-replace": "^1.0.3", 218 | "find-up": "^2.1.0", 219 | "find-yarn-workspace-root": "^2.0.0", 220 | "flat": "^4.1.1", 221 | "follow-redirects": "^1.14.3", 222 | "for-each": "^0.3.3", 223 | "foreach": "^2.0.5", 224 | "forever-agent": "^0.6.1", 225 | "form-data": "^3.0.1", 226 | "forwarded": "^0.2.0", 227 | "fp-ts": "^1.19.3", 228 | "fresh": "^0.5.2", 229 | "fs-extra": "^7.0.1", 230 | "fs-minipass": "^1.2.7", 231 | "fs.realpath": "^1.0.0", 232 | "fsevents": "^2.3.2", 233 | "function-bind": "^1.1.1", 234 | "functional-red-black-tree": "^1.0.1", 235 | "ganache-cli": "^6.12.2", 236 | "ganache-core": "^2.13.2", 237 | "get-caller-file": "^2.0.5", 238 | "get-func-name": "^2.0.0", 239 | "get-intrinsic": "^1.1.1", 240 | "get-stream": "^4.1.0", 241 | "get-symbol-description": "^1.0.0", 242 | "getpass": "^0.1.7", 243 | "ghost-testrpc": "^0.0.2", 244 | "glob": "^7.1.7", 245 | "glob-parent": "^5.1.2", 246 | "global": "^4.4.0", 247 | "global-modules": "^2.0.0", 248 | "global-prefix": "^3.0.0", 249 | "globby": "^10.0.2", 250 | "got": "^9.6.0", 251 | "graceful-fs": "^4.2.8", 252 | "growl": "^1.10.5", 253 | "handlebars": "^4.7.7", 254 | "har-schema": "^2.0.0", 255 | "har-validator": "^5.1.5", 256 | "has": "^1.0.3", 257 | "has-bigints": "^1.0.1", 258 | "has-flag": "^3.0.0", 259 | "has-symbol-support-x": "^1.4.2", 260 | "has-symbols": "^1.0.2", 261 | "has-to-string-tag-x": "^1.4.1", 262 | "has-tostringtag": "^1.0.0", 263 | "hash-base": "^3.1.0", 264 | "hash.js": "^1.1.7", 265 | "he": "^1.2.0", 266 | "highlight.js": "^9.18.5", 267 | "highlightjs-solidity": "^1.2.2", 268 | "hmac-drbg": "^1.0.1", 269 | "hosted-git-info": "^2.8.9", 270 | "htmlparser2": "^6.1.0", 271 | "http-cache-semantics": "^4.1.0", 272 | "http-errors": "^1.7.3", 273 | "http-https": "^1.0.0", 274 | "http-signature": "^1.2.0", 275 | "https-proxy-agent": "^5.0.0", 276 | "iconv-lite": "^0.4.24", 277 | "idna-uts46-hx": "^2.3.1", 278 | "ieee754": "^1.2.1", 279 | "ignore": "^5.1.8", 280 | "immediate": "^3.3.0", 281 | "immutable": "^4.0.0-rc.14", 282 | "inflight": "^1.0.6", 283 | "inherits": "^2.0.4", 284 | "ini": "^1.3.8", 285 | "internal-slot": "^1.0.3", 286 | "interpret": "^1.4.0", 287 | "invert-kv": "^1.0.0", 288 | "io-ts": "^1.10.4", 289 | "ipaddr.js": "^1.9.1", 290 | "is-arguments": "^1.1.1", 291 | "is-arrayish": "^0.2.1", 292 | "is-bigint": "^1.0.4", 293 | "is-binary-path": "^2.1.0", 294 | "is-boolean-object": "^1.1.2", 295 | "is-buffer": "^2.0.5", 296 | "is-callable": "^1.2.4", 297 | "is-ci": "^2.0.0", 298 | "is-date-object": "^1.0.5", 299 | "is-docker": "^2.2.1", 300 | "is-extglob": "^2.1.1", 301 | "is-fullwidth-code-point": "^2.0.0", 302 | "is-function": "^1.0.2", 303 | "is-generator-function": "^1.0.10", 304 | "is-glob": "^4.0.1", 305 | "is-hex-prefixed": "^1.0.0", 306 | "is-negative-zero": "^2.0.1", 307 | "is-number": "^7.0.0", 308 | "is-number-object": "^1.0.6", 309 | "is-object": "^1.0.2", 310 | "is-plain-obj": "^1.1.0", 311 | "is-regex": "^1.1.4", 312 | "is-retry-allowed": "^1.2.0", 313 | "is-stream": "^1.1.0", 314 | "is-string": "^1.0.7", 315 | "is-symbol": "^1.0.4", 316 | "is-typed-array": "^1.1.8", 317 | "is-typedarray": "^1.0.0", 318 | "is-url": "^1.2.4", 319 | "is-utf8": "^0.2.1", 320 | "is-wsl": "^2.2.0", 321 | "isexe": "^2.0.0", 322 | "iso-url": "^0.4.7", 323 | "isstream": "^0.1.2", 324 | "isurl": "^1.0.0", 325 | "js-sha3": "^0.5.7", 326 | "js-yaml": "^3.14.1", 327 | "jsbn": "^0.1.1", 328 | "json-buffer": "^3.0.0", 329 | "json-schema": "^0.2.3", 330 | "json-schema-traverse": "^0.4.1", 331 | "json-stringify-safe": "^5.0.1", 332 | "json-text-sequence": "^0.1.1", 333 | "jsonfile": "^4.0.0", 334 | "jsonschema": "^1.4.0", 335 | "jsprim": "^1.4.1", 336 | "keccak": "^3.0.2", 337 | "keyv": "^3.1.0", 338 | "kind-of": "^6.0.3", 339 | "klaw": "^1.3.1", 340 | "klaw-sync": "^6.0.0", 341 | "lcid": "^1.0.0", 342 | "lcov-parse": "^1.0.0", 343 | "level-codec": "^9.0.2", 344 | "level-concat-iterator": "^2.0.1", 345 | "level-errors": "^2.0.1", 346 | "level-iterator-stream": "^4.0.2", 347 | "level-mem": "^5.0.1", 348 | "level-packager": "^5.1.1", 349 | "level-supports": "^1.0.1", 350 | "level-ws": "^2.0.0", 351 | "levelup": "^4.4.0", 352 | "levn": "^0.3.0", 353 | "load-json-file": "^1.1.0", 354 | "locate-path": "^2.0.0", 355 | "lodash": "^4.17.21", 356 | "lodash.assign": "^4.2.0", 357 | "lodash.clonedeep": "^4.5.0", 358 | "lodash.escaperegexp": "^4.1.2", 359 | "lodash.merge": "^4.6.2", 360 | "lodash.partition": "^4.6.0", 361 | "lodash.sum": "^4.0.2", 362 | "log-driver": "^1.2.7", 363 | "log-symbols": "^3.0.0", 364 | "lowercase-keys": "^1.0.1", 365 | "lru_map": "^0.3.3", 366 | "lru-cache": "^5.1.1", 367 | "ltgt": "^2.2.1", 368 | "mcl-wasm": "^0.7.9", 369 | "md5.js": "^1.3.5", 370 | "media-typer": "^0.3.0", 371 | "memdown": "^5.1.0", 372 | "memorystream": "^0.3.1", 373 | "merge-descriptors": "^1.0.1", 374 | "merge2": "^1.4.1", 375 | "merkle-patricia-tree": "^4.2.1", 376 | "methods": "^1.1.2", 377 | "micromatch": "^4.0.4", 378 | "miller-rabin": "^4.0.1", 379 | "mime": "^1.6.0", 380 | "mime-db": "^1.49.0", 381 | "mime-types": "^2.1.32", 382 | "mimic-response": "^1.0.1", 383 | "min-document": "^2.19.0", 384 | "min-indent": "^1.0.1", 385 | "minimalistic-assert": "^1.0.1", 386 | "minimalistic-crypto-utils": "^1.0.1", 387 | "minimatch": "^3.0.4", 388 | "minimist": "^1.2.5", 389 | "minipass": "^2.9.0", 390 | "minizlib": "^1.3.3", 391 | "mkdirp": "^0.5.5", 392 | "mkdirp-promise": "^5.0.1", 393 | "mnemonist": "^0.38.3", 394 | "mock-fs": "^4.14.0", 395 | "ms": "^2.1.2", 396 | "multibase": "^0.6.1", 397 | "multicodec": "^0.5.7", 398 | "multihashes": "^0.4.21", 399 | "nano-json-stream-parser": "^0.1.2", 400 | "negotiator": "^0.6.2", 401 | "neo-async": "^2.6.2", 402 | "next-tick": "^1.0.0", 403 | "nice-try": "^1.0.5", 404 | "node-addon-api": "^2.0.2", 405 | "node-emoji": "^1.11.0", 406 | "node-environment-flags": "^1.0.6", 407 | "node-fetch": "^2.6.2", 408 | "node-gyp-build": "^4.2.3", 409 | "nofilter": "^1.0.4", 410 | "nopt": "^3.0.6", 411 | "normalize-package-data": "^2.5.0", 412 | "normalize-path": "^3.0.0", 413 | "normalize-url": "^4.5.1", 414 | "nth-check": "^2.0.0", 415 | "number-is-nan": "^1.0.1", 416 | "number-to-bn": "^1.7.0", 417 | "oauth-sign": "^0.9.0", 418 | "object-assign": "^4.1.1", 419 | "object-inspect": "^1.11.0", 420 | "object-keys": "^1.1.1", 421 | "object.assign": "^4.1.0", 422 | "object.getownpropertydescriptors": "^2.1.2", 423 | "obliterator": "^1.6.1", 424 | "oboe": "^2.1.5", 425 | "on-finished": "^2.3.0", 426 | "once": "^1.4.0", 427 | "open": "^7.4.2", 428 | "optionator": "^0.8.3", 429 | "os-locale": "^1.4.0", 430 | "os-tmpdir": "^1.0.2", 431 | "p-cancelable": "^1.1.0", 432 | "p-finally": "^1.0.0", 433 | "p-limit": "^1.3.0", 434 | "p-locate": "^2.0.0", 435 | "p-timeout": "^1.2.1", 436 | "p-try": "^1.0.0", 437 | "pako": "^1.0.11", 438 | "parse-asn1": "^5.1.6", 439 | "parse-headers": "^2.0.4", 440 | "parse-json": "^2.2.0", 441 | "parse5": "^6.0.1", 442 | "parse5-htmlparser2-tree-adapter": "^6.0.1", 443 | "parseurl": "^1.3.3", 444 | "patch-package": "^6.4.7", 445 | "path-browserify": "^1.0.1", 446 | "path-exists": "^3.0.0", 447 | "path-is-absolute": "^1.0.1", 448 | "path-key": "^2.0.1", 449 | "path-parse": "^1.0.7", 450 | "path-to-regexp": "^0.1.7", 451 | "path-type": "^4.0.0", 452 | "pathval": "^1.1.1", 453 | "pbkdf2": "^3.1.2", 454 | "performance-now": "^2.1.0", 455 | "picomatch": "^2.3.0", 456 | "pify": "^4.0.1", 457 | "pinkie": "^2.0.4", 458 | "pinkie-promise": "^2.0.1", 459 | "postinstall-postinstall": "^2.1.0", 460 | "prelude-ls": "^1.1.2", 461 | "prepend-http": "^2.0.0", 462 | "prettier": "^2.4.0", 463 | "printj": "^1.1.2", 464 | "process": "^0.11.10", 465 | "proxy-addr": "^2.0.7", 466 | "prr": "^1.0.1", 467 | "psl": "^1.8.0", 468 | "public-encrypt": "^4.0.3", 469 | "pump": "^3.0.0", 470 | "punycode": "^2.1.0", 471 | "qs": "^6.10.1", 472 | "query-string": "^5.1.1", 473 | "querystring": "^0.2.0", 474 | "queue-microtask": "^1.2.3", 475 | "randombytes": "^2.1.0", 476 | "randomfill": "^1.0.4", 477 | "range-parser": "^1.2.1", 478 | "raw-body": "^2.4.1", 479 | "read-pkg": "^1.1.0", 480 | "read-pkg-up": "^1.0.1", 481 | "readable-stream": "^3.6.0", 482 | "readdirp": "^3.6.0", 483 | "rechoir": "^0.6.2", 484 | "recursive-readdir": "^2.2.2", 485 | "request": "^2.88.2", 486 | "require-directory": "^2.1.1", 487 | "require-from-string": "^2.0.2", 488 | "require-main-filename": "^2.0.0", 489 | "resolve": "^1.17.0", 490 | "responselike": "^1.0.2", 491 | "reusify": "^1.0.4", 492 | "rimraf": "^2.7.1", 493 | "ripemd160": "^2.0.2", 494 | "rlp": "^2.2.6", 495 | "run-parallel": "^1.2.0", 496 | "rustbn.js": "^0.2.0", 497 | "safe-buffer": "^5.2.1", 498 | "safer-buffer": "^2.1.2", 499 | "sc-istanbul": "^0.4.6", 500 | "scrypt-js": "^3.0.1", 501 | "secp256k1": "^4.0.2", 502 | "semaphore-async-await": "^1.5.1", 503 | "semver": "^6.3.0", 504 | "send": "^0.17.1", 505 | "serve-static": "^1.14.1", 506 | "servify": "^0.1.12", 507 | "set-blocking": "^2.0.0", 508 | "setimmediate": "^1.0.5", 509 | "setprototypeof": "^1.1.1", 510 | "sha.js": "^2.4.11", 511 | "shebang-command": "^1.2.0", 512 | "shebang-regex": "^1.0.0", 513 | "shelljs": "^0.8.4", 514 | "side-channel": "^1.0.4", 515 | "simple-concat": "^1.0.1", 516 | "simple-get": "^2.8.1", 517 | "slash": "^3.0.0", 518 | "solc": "^0.6.12", 519 | "source-map": "^0.2.0", 520 | "source-map-support": "^0.5.20", 521 | "spdx-correct": "^3.1.1", 522 | "spdx-exceptions": "^2.3.0", 523 | "spdx-expression-parse": "^3.0.1", 524 | "spdx-license-ids": "^3.0.10", 525 | "sprintf-js": "^1.0.3", 526 | "sshpk": "^1.16.1", 527 | "stacktrace-parser": "^0.1.10", 528 | "statuses": "^1.5.0", 529 | "strict-uri-encode": "^1.1.0", 530 | "string_decoder": "^1.3.0", 531 | "string-width": "^2.1.1", 532 | "string.prototype.trimend": "^1.0.4", 533 | "string.prototype.trimstart": "^1.0.4", 534 | "strip-ansi": "^4.0.0", 535 | "strip-bom": "^2.0.0", 536 | "strip-hex-prefix": "^1.0.0", 537 | "strip-indent": "^2.0.0", 538 | "strip-json-comments": "^2.0.1", 539 | "super-split": "^1.1.0", 540 | "supports-color": "^5.5.0", 541 | "swarm-js": "^0.1.40", 542 | "tar": "^4.4.19", 543 | "test-value": "^2.1.0", 544 | "testrpc": "^0.0.1", 545 | "timed-out": "^4.0.1", 546 | "tmp": "^0.0.33", 547 | "to-readable-stream": "^1.0.0", 548 | "to-regex-range": "^5.0.1", 549 | "toidentifier": "^1.0.0", 550 | "tough-cookie": "^2.5.0", 551 | "true-case-path": "^2.2.1", 552 | "ts-essentials": "^1.0.4", 553 | "ts-generator": "^0.1.1", 554 | "tslib": "^2.3.1", 555 | "tsort": "^0.0.1", 556 | "tunnel-agent": "^0.6.0", 557 | "tweetnacl": "^1.0.3", 558 | "tweetnacl-util": "^0.15.1", 559 | "type": "^1.2.0", 560 | "type-check": "^0.3.2", 561 | "type-detect": "^4.0.8", 562 | "type-fest": "^0.21.3", 563 | "type-is": "^1.6.18", 564 | "typechain": "^3.0.0", 565 | "typedarray-to-buffer": "^3.1.5", 566 | "typescript": "^4.4.3", 567 | "typical": "^2.6.1", 568 | "uglify-js": "^3.14.2", 569 | "ultron": "^1.1.1", 570 | "unbox-primitive": "^1.0.1", 571 | "underscore": "^1.13.1", 572 | "universalify": "^0.1.2", 573 | "unpipe": "^1.0.0", 574 | "uri-js": "^4.4.1", 575 | "url": "^0.11.0", 576 | "url-parse-lax": "^3.0.0", 577 | "url-set-query": "^1.0.0", 578 | "url-to-options": "^1.0.1", 579 | "utf-8-validate": "^5.0.5", 580 | "utf8": "^3.0.0", 581 | "util": "^0.12.4", 582 | "util-deprecate": "^1.0.2", 583 | "util.promisify": "^1.1.1", 584 | "utils-merge": "^1.0.1", 585 | "uuid": "^3.4.0", 586 | "validate-npm-package-license": "^3.0.4", 587 | "varint": "^5.0.2", 588 | "vary": "^1.1.2", 589 | "verror": "^1.10.0", 590 | "web3": "^1.5.2", 591 | "web3-bzz": "^1.5.2", 592 | "web3-core": "^1.5.2", 593 | "web3-core-helpers": "^1.5.2", 594 | "web3-core-method": "^1.5.2", 595 | "web3-core-promievent": "^1.5.2", 596 | "web3-core-requestmanager": "^1.5.2", 597 | "web3-core-subscriptions": "^1.5.2", 598 | "web3-eth": "^1.5.2", 599 | "web3-eth-abi": "^1.5.2", 600 | "web3-eth-accounts": "^1.5.2", 601 | "web3-eth-contract": "^1.5.2", 602 | "web3-eth-ens": "^1.5.2", 603 | "web3-eth-iban": "^1.5.2", 604 | "web3-eth-personal": "^1.5.2", 605 | "web3-net": "^1.5.2", 606 | "web3-providers-http": "^1.5.2", 607 | "web3-providers-ipc": "^1.5.2", 608 | "web3-providers-ws": "^1.5.2", 609 | "web3-shh": "^1.5.2", 610 | "web3-utils": "^1.5.2", 611 | "websocket": "^1.0.34", 612 | "which": "^1.3.1", 613 | "which-boxed-primitive": "^1.0.2", 614 | "which-module": "^2.0.0", 615 | "which-typed-array": "^1.1.7", 616 | "wide-align": "^1.1.3", 617 | "window-size": "^0.2.0", 618 | "word-wrap": "^1.2.3", 619 | "wordwrap": "^1.0.0", 620 | "wrap-ansi": "^5.1.0", 621 | "wrappy": "^1.0.2", 622 | "ws": "^7.4.6", 623 | "xhr": "^2.6.0", 624 | "xhr-request": "^1.1.0", 625 | "xhr-request-promise": "^0.1.3", 626 | "xhr2-cookies": "^1.1.0", 627 | "xmlhttprequest": "^1.8.0", 628 | "xtend": "^4.0.2", 629 | "y18n": "^4.0.3", 630 | "yaeti": "^0.0.6", 631 | "yallist": "^3.1.1", 632 | "yargs": "^13.3.2", 633 | "yargs-parser": "^13.1.2", 634 | "yargs-unparser": "^1.6.0" 635 | }, 636 | "repository": { 637 | "type": "git", 638 | "url": "git+https://github.com/NodeberryInc/nodeberry-contracts.git" 639 | }, 640 | "keywords": [ 641 | "solidity", 642 | "ethereum", 643 | "nodeberry", 644 | "contracts", 645 | "payment", 646 | "processor" 647 | ], 648 | "author": "Nodeberry", 649 | "license": "MIT", 650 | "bugs": { 651 | "url": "https://github.com/NodeberryInc/nodeberry-contracts/issues" 652 | }, 653 | "homepage": "https://github.com/NodeberryInc/nodeberry-contracts#readme" 654 | } 655 | -------------------------------------------------------------------------------- /test/PaymentProcessor.test.js: -------------------------------------------------------------------------------- 1 | const { expect, assert } = require("chai"); 2 | 3 | describe("Payment Processor Test Suite", function() { 4 | let tokenA; 5 | let tokenB; 6 | let oracleA; 7 | let paymentContract; 8 | let deployer; 9 | let initialSupply = ethers.utils.parseEther("1000000"); 10 | let price = ethers.utils.parseUnits("1", 8); // $10 represented in 8 decimals; 11 | 12 | before(async function() { 13 | const PaymentContract = await ethers.getContractFactory("TestProcessor"); 14 | const tokenContract = await ethers.getContractFactory("TestToken"); 15 | const oracleContract = await ethers.getContractFactory("Oracle"); 16 | 17 | tokenA = await tokenContract.deploy(initialSupply); 18 | tokenB = await tokenContract.deploy(initialSupply); 19 | paymentContract = await PaymentContract.deploy(); 20 | oracleA = await oracleContract.deploy(price); 21 | [deployer] = await ethers.getSigners(); 22 | }); 23 | 24 | describe("Validating Deployed Smart Contract", async function() { 25 | it("Should check Token Contract Params", async function() { 26 | expect(await tokenA.decimals()).to.equal(18); 27 | expect(await tokenA.totalSupply()).to.equal(initialSupply); 28 | }); 29 | 30 | it("Should check Oracle Contract Params", async function() { 31 | const data = await oracleA.latestRoundData(); 32 | expect(data[1]).to.equal(price); 33 | }); 34 | 35 | it("Should check Payment Processor Contract Params", async function() { 36 | const owner = await paymentContract.owner(); 37 | expect(owner).to.equal(deployer.address); 38 | }); 39 | }); 40 | 41 | describe("Unit Tests - Configurations & Upgradations", async function() { 42 | it("should throw error when try to replace oracle without setting", async function() { 43 | let err = null; 44 | try { 45 | await paymentContract.replaceOracle(oracleA.address, "TestToken"); 46 | } catch (error) { 47 | err = error; 48 | } 49 | assert.ok(err instanceof Error); 50 | }); 51 | 52 | it("should set oracle Address", async function() { 53 | await paymentContract.setOracle(deployer.address, "TestToken"); 54 | expect(await paymentContract.fetchOracle("TestToken")).to.equal(deployer.address); 55 | }); 56 | 57 | it("should throw error when try to set oracle again", async function() { 58 | let err = null; 59 | try { 60 | await paymentContract.setOracle(oracleA.address, "TestToken"); 61 | } catch (error) { 62 | err = error; 63 | } 64 | assert.ok(err instanceof Error); 65 | }); 66 | 67 | it("should update oracle Address", async function() { 68 | await paymentContract.replaceOracle(oracleA.address, "TestToken"); 69 | expect(await paymentContract.fetchOracle("TestToken")).to.equal(oracleA.address); 70 | }); 71 | 72 | it("should throw error when try to replace contract address without setting", async function() { 73 | let err = null; 74 | try { 75 | await paymentContract.replaceContract(tokenA.address, "TestToken"); 76 | } catch (error) { 77 | err = error; 78 | } 79 | assert.ok(err instanceof Error); 80 | }); 81 | 82 | it("should set token contract Address", async function() { 83 | await paymentContract.setContract(deployer.address, "TestToken"); 84 | await paymentContract.setContract(tokenB.address, "TestStableCoin"); 85 | expect(await paymentContract.fetchContract("TestToken")).to.equal(deployer.address); 86 | }); 87 | 88 | it("should throw error when try to set contract address again", async function() { 89 | let err = null; 90 | try { 91 | await paymentContract.setContract(tokenA.address, "TestToken"); 92 | } catch (error) { 93 | err = error; 94 | } 95 | assert.ok(err instanceof Error); 96 | }); 97 | 98 | it("should update token contract Address", async function() { 99 | await paymentContract.replaceContract(tokenA.address, "TestToken"); 100 | expect(await paymentContract.fetchContract("TestToken")).to.equal(tokenA.address); 101 | }); 102 | 103 | it("should set a token as stablecoin", async function() { 104 | await paymentContract.markAsStablecoin("TestStableCoin"); 105 | }); 106 | }); 107 | 108 | describe("Unit Tests - Fetching", async function() { 109 | it("should return user allowance", async function() { 110 | await tokenA.approve(paymentContract.address, 10); 111 | let result = await paymentContract.fetchApproval("TestToken", deployer.address); 112 | expect(result).to.equal(10); 113 | }); 114 | 115 | it("should return oracle price", async function() { 116 | let result = await paymentContract.fetchOraclePrice("TestToken"); 117 | expect(result).to.equal(price); 118 | }); 119 | }); 120 | 121 | describe("Unit Tests - Stablecoin Payments", async function() { 122 | it("should validate the token decimal calculation", async function() { 123 | let usd = 100000000; 124 | let result = await paymentContract.sAmount("TestStableCoin", usd); 125 | expect(result).to.equal(ethers.utils.parseEther("1")); 126 | }); 127 | 128 | it("should process payments in TestToken as Stablecoin", async function() { 129 | let usd = 100000000; 130 | await tokenB.approve(paymentContract.address, initialSupply); 131 | await paymentContract.mockSale("TestStableCoin", usd); 132 | }); 133 | }); 134 | 135 | describe("Unit Tests - Token Payments", async function() { 136 | it("should process payments in TestToken as Stablecoin", async function() { 137 | let usd = 100000000; 138 | await tokenA.approve(paymentContract.address, initialSupply); 139 | await paymentContract.mockSale("TestToken", usd); 140 | }); 141 | }); 142 | }); 143 | --------------------------------------------------------------------------------