├── .circleci └── config.yml ├── .eslintrc ├── .github └── workflows │ └── cp_test.yml ├── .gitignore ├── .nvmrc ├── .openzeppelin └── project.json ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── CP_Eth.code-workspace ├── LICENSES ├── README.md ├── buidler.config.js ├── contracts ├── ChargedParticles.sol ├── ChargedParticlesEscrowManager.sol ├── ChargedParticlesTokenManager.sol ├── assets │ ├── chai │ │ ├── ChaiEscrow.sol │ │ └── ChaiNucleus.sol │ └── dai │ │ └── Dai.sol ├── interfaces │ ├── IChargedParticlesEscrowManager.sol │ ├── IChargedParticlesTokenManager.sol │ ├── IERC1155.sol │ ├── IERC1155TokenReceiver.sol │ ├── IEscrow.sol │ ├── INucleus.sol │ └── IParticleManager.sol └── lib │ ├── BridgedERC1155.sol │ ├── Common.sol │ ├── ERC1155.sol │ └── EscrowBase.sol ├── deploy ├── ChaiEscrow.js ├── ChaiNucleus.js ├── ChargedParticles.js ├── ChargedParticlesEscrowManager.js ├── ChargedParticlesTokenManager.js └── initialize.js ├── deployments-kovan.json ├── js-utils └── deploy-helpers.js ├── package.json ├── test ├── ChargedParticles.test.js ├── ChargedParticlesEscrowManager.test.js ├── ChargedParticlesTokenManager.test.js └── util │ └── testEnv.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | coveralls: coveralls/coveralls@1.0.6 4 | jobs: 5 | build-and-test: 6 | docker: 7 | - image: 'circleci/node:12.16.3' 8 | steps: 9 | - checkout 10 | - run: 11 | name: Install and Test 12 | command: yarn && yarn coverage 13 | - coveralls/upload 14 | workflows: 15 | build-and-test: 16 | jobs: 17 | - build-and-test -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 11 4 | }, 5 | 6 | "env": { 7 | "es2020": true, 8 | "node": true 9 | } 10 | } -------------------------------------------------------------------------------- /.github/workflows/cp_test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.16.3] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: yarn 28 | - run: yarn test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | tmp/ 4 | npm-debug.log 5 | .DS_Store 6 | .idea 7 | merged/ 8 | .next 9 | .vscode/ 10 | node_modules/ 11 | resources.txt 12 | .env 13 | .versionFilesList.json 14 | build/ 15 | bin/ 16 | notes.txt 17 | cache 18 | 19 | # OpenZeppelin 20 | .openzeppelin/dev-*.json 21 | .openzeppelin/.session 22 | 23 | # Code Coverage 24 | coverage 25 | .coverage 26 | coverage.json 27 | coverageEnv 28 | artifacts 29 | test-results.xml 30 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.16.3 -------------------------------------------------------------------------------- /.openzeppelin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "2.2", 3 | "contracts": { 4 | "ChaiEscrow": "ChaiEscrow", 5 | "ChaiNucleus": "ChaiNucleus", 6 | "ChargedParticles": "ChargedParticles", 7 | "ChargedParticlesEscrowManager": "ChargedParticlesEscrowManager", 8 | "ChargedParticlesTokenManager": "ChargedParticlesTokenManager" 9 | }, 10 | "dependencies": { 11 | "@openzeppelin/contracts-ethereum-package": "^3.0.0" 12 | }, 13 | "name": "charged-particles", 14 | "version": "0.1.1", 15 | "telemetryOptIn": true, 16 | "compiler": { 17 | "manager": "openzeppelin", 18 | "solcVersion": "0.6.10", 19 | "compilerSettings": { 20 | "optimizer": { 21 | "enabled": true, 22 | "runs": "200" 23 | } 24 | }, 25 | "artifactsDir": "build", 26 | "contractsDir": "contracts", 27 | "typechain": { 28 | "enabled": false 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mocha: { reporter: 'mocha-junit-reporter' }, 3 | skipFiles: [ 4 | "assets/dai/Dai.sol", 5 | "test" 6 | ] 7 | }; -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": [], 4 | "rules": { 5 | "func-order": "off", 6 | "mark-callable-contracts": "off", 7 | "no-empty-blocks": "off", 8 | "compiler-version": ["error", "^0.6.0"], 9 | "private-vars-leading-underscore": "off", 10 | "code-complexity": "warn", 11 | "const-name-snakecase": "warn", 12 | "function-max-lines": "warn", 13 | "max-line-length": ["warn", 160], 14 | "avoid-suicide": "error", 15 | "avoid-sha3": "warn" 16 | } 17 | } -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | ChaiNucleus.sol 2 | Dai.sol 3 | -------------------------------------------------------------------------------- /CP_Eth.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "solidity.packageDefaultDependenciesContractsDirectory": "", 9 | "solidity.formatter": "none", 10 | "slither.solcPath": "/Users/robsecord/.nvm/versions/node/v12.16.1/lib/node_modules/solc/solcjs", 11 | "slither.hiddenDetectors": [], 12 | "solidity.compileUsingRemoteVersion": "v0.6.10+commit.9c3226ce", 13 | "secbit.solc": "/Users/robsecord/workspace/Ethereum/ChargedParticles/ChargedParticlesEth/node_modules/solc/solcjs", 14 | "solidity.enableLocalNodeCompiler": false, 15 | "editor.detectIndentation": false 16 | }, 17 | "launch": { 18 | "ethcodeToken": { 19 | "machineID": "e6c27740-8ca8-11ea-8024-ffa47f2b52cb", 20 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYWNoaW5lSUQiOiJlNmMyNzc0MC04Y2E4LTExZWEtODAyNC1mZmE0N2YyYjUyY2IifQ.eHNEN5gsgffhbqY5vAkpzd3ga12bmSgPG84XToaS8N0" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSES: -------------------------------------------------------------------------------- 1 | ChargedParticles -- Interest-bearing NFTs based on the DAI Savings Token 2 | MIT License 3 | Copyright (c) 2019, 2020 Rob Secord 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 | 23 | 24 | 25 | Chai.sol -- a dai savings token 26 | Copyright (C) 2017, 2018, 2019, 2020 dbrock, rain, mrchico, lucasvo, livnev 27 | 28 | This program is free software: you can redistribute it and/or modify 29 | it under the terms of the GNU Affero General Public License as published by 30 | the Free Software Foundation, either version 3 of the License, or 31 | (at your option) any later version. 32 | 33 | This program is distributed in the hope that it will be useful, 34 | but WITHOUT ANY WARRANTY; without even the implied warranty of 35 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 36 | GNU Affero General Public License for more details. 37 | 38 | You should have received a copy of the GNU Affero General Public License 39 | along with this program. If not, see . 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Charged Particles - Solidity Contracts v0.4.2 2 | 3 | [![Discord](https://badgen.net/badge/definft/Charged%20Particles?icon=discord&label=discord)](https://discord.gg/Syh3gjz) 4 | [![Twitter Follow](https://badgen.net/twitter/follow/DeFiNFT?icon=twitter)](https://twitter.com/intent/follow?screen_name=DeFiNFT) 5 | [![built-with openzeppelin](https://img.shields.io/badge/built%20with-OpenZeppelin-3677FF)](https://docs.openzeppelin.com/) 6 | 7 | [![Coverage Status](https://coveralls.io/repos/github/robsecord/ChargedParticlesEth/badge.svg?branch=master)](https://coveralls.io/github/robsecord/ChargedParticlesEth?branch=master&v=123) 8 | ![GitHub last commit](https://img.shields.io/github/last-commit/robsecord/ChargedParticlesEth) 9 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/robsecord/ChargedParticlesEth) 10 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/robsecord/ChargedParticlesEth) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/robsecord/ChargedParticlesEth) 12 | 13 | **Charged Particles** are Non-Fungible Tokens (NFTs) that are minted with an Underlying Asset (ex: **DAI**) and accrue interest via an Interest-bearing token (ex: **CHAI**) giving the Token a "Charge". 14 | 15 | **Coming Soon**: 16 | 17 | - Aave - aTokens 18 | - mStable - mTokens 19 | - Idle Finance - yTokens 20 | 21 | 22 | --- 23 | 24 | #### Production Site (Beta, Ropsten Only) 25 | https://charged-particles.eth.link/ 26 | 27 | #### Staging Site 28 | https://charged-particles.tmnl.co/ 29 | 30 | --- 31 | 32 | #### Value 33 | ```text 34 | Particle Value 35 | = 36 | Intrinsic Value (underlying asset, DAI) 37 | + 38 | Speculative Value (non-fungible rarity) 39 | + 40 | Interest value (accrued in CHAI) 41 | ``` 42 | 43 | #### Value Appreciation 44 | Imagine a Babe Ruth rookie card that was stuffed with $1 and earning interest since 1916! The same might be true 45 | of a Charged Particle NFT in 100 years! 46 | 47 | #### Ownership 48 | Charged Particles are non-custodial NFTs that can be "discharged" at any time by the owner, collecting the interest 49 | from the token. And just like any NFT, they are yours trade, transfer, sell, etc. 50 | 51 | They can also be burned (melted) to reclaim the underlying DAI + interest in full, destroying the token. 52 | Charged Particles, therefore, always have an underlying value in DAI. 53 | 54 | #### Custom Mechanics 55 | Based on the amount of "charge" a token has, Smart Contracts and/or Dapps can decide how to handle the token - custom 56 | mechanics can be designed around the level of "Charge" a token has. 57 | 58 | Imagine an NFT that represents a Sword - the power of that sword could be dependant on the amount of "Charge" the token 59 | has. Or perhaps certain items can only be used once they reach a certain level of charge. 60 | 61 | Other possibilities include battling over the "charge" of a particle - the winner earns the interest from their 62 | competitor's particles. (Still trying to work this part out, ideas are welcome!) 63 | 64 | #### Particle Accelerator 65 | - Fully-decentralized Public Particle Minting Station 66 | - Work-in-progress 67 | - Repo: https://github.com/robsecord/ChargedParticlesWeb 68 | 69 | #### Feedback & Contributions 70 | Feel free to fork and/or use in your own projects! 71 | 72 | And, of course, contributions are always welcome! 73 | 74 | #### Community 75 | Join our community, share ideas and help support the project in anyway you want! 76 | 77 | **Discord**: https://discord.gg/Syh3gjz 78 | 79 | --- 80 | 81 | ### Frameworks/Software used: 82 | - Main Repo: 83 | - OpenZeppelin CLI **v2.8.0** 84 | - OpenZeppelin Upgrades **v2.8.0** 85 | - OpenZeppelin Ethereum Contracts **v3.0.0** 86 | - Solidity **v0.6.10** (solc-js) 87 | - NodeJS **v12.16.3** 88 | - Buidler **v1.3.5** 89 | - EthersJS **v4.0.27** 90 | 91 | ### Prepare environment: 92 | 93 | Create a local .env file with the following (replace ... with your keys): 94 | 95 | ```bash 96 | INFURA_API_KEY="__api_key_only_no_url__" 97 | 98 | KOVAN_PROXY_ADDRESS="__public_address__" 99 | KOVAN_PROXY_MNEMONIC="__12-word_mnemonic__" 100 | 101 | KOVAN_OWNER_ADDRESS="__public_address__" 102 | KOVAN_OWNER_MNEMONIC="__12-word_mnemonic__" 103 | 104 | ROPSTEN_PROXY_ADDRESS="__public_address__" 105 | ROPSTEN_PROXY_MNEMONIC="__12-word_mnemonic__" 106 | 107 | ROPSTEN_OWNER_ADDRESS="__public_address__" 108 | ROPSTEN_OWNER_MNEMONIC="__12-word_mnemonic__" 109 | 110 | MAINNET_PROXY_ADDRESS="__public_address__" 111 | MAINNET_PROXY_MNEMONIC="__12-word_mnemonic__" 112 | 113 | MAINNET_OWNER_ADDRESS="__public_address__" 114 | MAINNET_OWNER_MNEMONIC="__12-word_mnemonic__" 115 | ``` 116 | 117 | ### Deploy: 118 | 119 | 1. yarn 120 | 2. yarn verify 121 | 2. yarn start 122 | 123 | See package.json for more scripts 124 | 125 | --- 126 | 127 | _MIT License_ 128 | 129 | Copyright (c) 2020 Rob Secord 130 | 131 | -------------------------------------------------------------------------------- /buidler.config.js: -------------------------------------------------------------------------------- 1 | const {TASK_COMPILE_GET_COMPILER_INPUT} = require('@nomiclabs/buidler/builtin-tasks/task-names'); 2 | 3 | task(TASK_COMPILE_GET_COMPILER_INPUT).setAction(async (_, __, runSuper) => { 4 | const input = await runSuper(); 5 | input.settings.metadata.useLiteralContent = false; 6 | return input; 7 | }); 8 | 9 | require('dotenv').config(); 10 | 11 | usePlugin('@nomiclabs/buidler-waffle'); 12 | usePlugin('@nomiclabs/buidler-etherscan'); 13 | usePlugin('buidler-gas-reporter'); 14 | usePlugin('solidity-coverage'); 15 | usePlugin('buidler-deploy'); 16 | 17 | const mnemonic = { 18 | proxyAdmin: { 19 | kovan: `${process.env.KOVAN_PROXY_MNEMONIC}`.replace(/_/g, ' '), 20 | ropsten: `${process.env.ROPSTEN_PROXY_MNEMONIC}`.replace(/_/g, ' '), 21 | mainnet: `${process.env.MAINNET_PROXY_MNEMONIC}`.replace(/_/g, ' '), 22 | }, 23 | owner: { 24 | kovan: `${process.env.KOVAN_OWNER_MNEMONIC}`.replace(/_/g, ' '), 25 | ropsten: `${process.env.ROPSTEN_OWNER_MNEMONIC}`.replace(/_/g, ' '), 26 | mainnet: `${process.env.MAINNET_OWNER_MNEMONIC}`.replace(/_/g, ' '), 27 | } 28 | }; 29 | 30 | module.exports = { 31 | solc: { 32 | version: '0.6.10', 33 | optimizer: { 34 | enabled: true, 35 | runs: 200 36 | }, 37 | evmVersion: 'istanbul' 38 | }, 39 | paths: { 40 | artifacts: './build', 41 | deploy: './deploy', 42 | deployments: './deployments' 43 | }, 44 | networks: { 45 | buidlerevm: { 46 | blockGasLimit: 200000000, 47 | allowUnlimitedContractSize: true, 48 | gasPrice: 8e9 49 | }, 50 | coverage: { 51 | url: 'http://127.0.0.1:8555', 52 | blockGasLimit: 200000000, 53 | allowUnlimitedContractSize: true 54 | }, 55 | local: { 56 | url: 'http://127.0.0.1:8545', 57 | blockGasLimit: 200000000 58 | }, 59 | kovan: { 60 | url: `https://kovan.infura.io/v3/${process.env.INFURA_API_KEY}`, 61 | gasPrice: 10e9, 62 | accounts: { 63 | mnemonic: mnemonic.owner.kovan, 64 | initialIndex: 0, 65 | count: 3, 66 | } 67 | }, 68 | ropsten: { 69 | url: `https://ropsten.infura.io/v3/${process.env.INFURA_API_KEY}`, 70 | gasPrice: 10e9, 71 | accounts: { 72 | mnemonic: mnemonic.owner.ropsten, 73 | initialIndex: 0, 74 | count: 3, 75 | } 76 | } 77 | }, 78 | gasReporter: { 79 | currency: 'USD', 80 | gasPrice: 1, 81 | enabled: (process.env.REPORT_GAS) ? true : false 82 | }, 83 | namedAccounts: { 84 | deployer: { 85 | default: 0, 86 | }, 87 | trustedForwarder: { 88 | default: 7, // Account 8 89 | 1: '0x1337c0d31337c0D31337C0d31337c0d31337C0d3', // mainnet 90 | 3: '0x1337c0d31337c0D31337C0d31337c0d31337C0d3', // ropsten 91 | 42: '0x1337c0d31337c0D31337C0d31337c0d31337C0d3', // kovan 92 | }, 93 | dai: { 94 | default: 0, // local; to be deployed by deployer 95 | 1: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // mainnet 96 | 3: '0x31F42841c2db5173425b5223809CF3A38FEde360', // ropsten 97 | 42: '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa', // kovan 98 | } 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /contracts/ChargedParticles.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ChargedParticles.sol -- Interest-bearing NFTs 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | 26 | import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol"; 27 | import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol"; 28 | import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; 29 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 30 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 31 | 32 | import "@opengsn/gsn/contracts/BaseRelayRecipient.sol"; 33 | 34 | import "./interfaces/IChargedParticlesTokenManager.sol"; 35 | import "./interfaces/IChargedParticlesEscrowManager.sol"; 36 | import "./interfaces/IParticleManager.sol"; 37 | 38 | import "./lib/Common.sol"; 39 | 40 | 41 | /** 42 | * @notice Charged Particles Contract - Interest-Bearing NFTs 43 | */ 44 | contract ChargedParticles is Initializable, IParticleManager, BaseRelayRecipient, OwnableUpgradeSafe, Common { 45 | using SafeMath for uint256; 46 | using Address for address payable; 47 | 48 | uint32 constant internal ION_SPECIAL_BIT = 1073741824; // 31st BIT 49 | address constant internal CONTRACT_ID = address(0xC1DA0da0DA0da0DA0da0DA0da0DA0da0DA0da0DA00); 50 | 51 | IChargedParticlesTokenManager public tokenMgr; 52 | IChargedParticlesEscrowManager public escrowMgr; 53 | 54 | // Particles come in many "Types" created by Public Users. 55 | // Each "Type" of Particle has a "Creator", who can set certain parameters 56 | // for the Particle upon Creation. 57 | // Fungible Tokens (ERC20) are called "Plasma" and all tokens are the same in value. 58 | // - These tokens CAN NOT hold a charge, and do not require an underlying asset when minting. 59 | // Non-Fungible Tokens (ERC721) are called "Particles" and all tokens are unique in value. 60 | // - These particles CAN hold a charge, and require a deposit of the underlying asset when minting. 61 | // Particle Creators can also set restrictions on who can "mint" their particles, the max supply and 62 | // how much of the underlying asset is required to mint a particle (1 DAI maybe?). 63 | // These values are all optional, and can be left at 0 (zero) to specify no-limits. 64 | // Non-Fungible Tokens can be either a Series or a Collection. 65 | // A series is a Single, Unique Item with Multiple Copies. NFTs that minted of this type are numbered 66 | // in series, and use the same metadata as the Original Item. 67 | // A collection is a group of Unique Items with only one copy of each. NFTs that are minted of this type 68 | // are Unique and require their own metadata for each. 69 | 70 | // TypeID => Access Type (BITS: 1=Public, 2=Private, 4=Series, 8=Collection) 71 | mapping (uint256 => uint8) internal registeredTypes; 72 | 73 | // TypeID => Type Creator 74 | mapping (uint256 => address) internal typeCreator; 75 | 76 | // TypeID => Max Supply 77 | mapping (uint256 => uint256) internal typeSupply; 78 | 79 | // TypeID => Eth Price for Minting set by Type Creator 80 | mapping (uint256 => uint256) internal mintFee; 81 | 82 | // TypeID => Specific Asset-Pair to be used for this Type 83 | mapping (uint256 => string) internal typeAssetPairId; 84 | 85 | // TypeID => Special bit-markings for this Type 86 | mapping (uint256 => uint32) internal typeSpecialBits; 87 | 88 | // TypeID => Token-Bridge for ERC20/ERC721 89 | mapping (uint256 => address) internal typeTokenBridge; 90 | 91 | // Owner/Creator => ETH Fees earned by Contract/Creators 92 | mapping (address => uint256) internal collectedFees; 93 | 94 | // To Create "Types" (Fungible or Non-Fungible) there is a Fee. 95 | // The Fee can be paid in ETH or in IONs. 96 | // IONs are a custom ERC20 token minted within this contract. 97 | // ETH paid upon minting is stored in contract, withdrawn by contract owner. 98 | // IONs paid upon minting are burned. 99 | // These values are completely optional and can be set to 0 to specify No Fee. 100 | uint256 internal createFeeEth; 101 | uint256 internal createFeeIon; 102 | 103 | // Internal ERC20 Token used for Creating Types; 104 | // needs to be created as a private fungible type within the ERC1155 contract 105 | uint256 internal ionTokenId; 106 | 107 | // Contract Version 108 | bytes16 public version; 109 | 110 | // Contract State 111 | bool public isPaused; 112 | 113 | // 114 | // Modifiers 115 | // 116 | 117 | modifier whenNotPaused() { 118 | require(!isPaused, "CP: PAUSED"); 119 | _; 120 | } 121 | 122 | // 123 | // Events 124 | // 125 | 126 | event ParticleTypeUpdated( 127 | uint256 indexed _particleTypeId, 128 | string indexed _symbol, 129 | bool indexed _isPrivate, 130 | bool _isSeries, 131 | string _assetPairId, 132 | string _uri 133 | ); 134 | 135 | event PlasmaTypeUpdated( 136 | uint256 indexed _plasmaTypeId, 137 | string indexed _symbol, 138 | bool indexed _isPrivate, 139 | uint256 _initialMint, 140 | string _uri 141 | ); 142 | 143 | event ParticleMinted( 144 | address indexed _sender, 145 | address indexed _receiver, 146 | uint256 indexed _tokenId, 147 | string _uri 148 | ); 149 | 150 | event ParticleBurned( 151 | address indexed _from, 152 | uint256 indexed _tokenId 153 | ); 154 | 155 | event PlasmaMinted( 156 | address indexed _sender, 157 | address indexed _receiver, 158 | uint256 indexed _typeId, 159 | uint256 _amount 160 | ); 161 | 162 | event PlasmaBurned( 163 | address indexed _from, 164 | uint256 indexed _typeId, 165 | uint256 _amount 166 | ); 167 | 168 | event CreatorFeesWithdrawn( 169 | address indexed _sender, 170 | address indexed _receiver, 171 | uint256 _amount 172 | ); 173 | 174 | event ContractFeesWithdrawn( 175 | address indexed _sender, 176 | address indexed _receiver, 177 | uint256 _amount 178 | ); 179 | 180 | /***********************************| 181 | | Initialization | 182 | |__________________________________*/ 183 | 184 | function initialize() public initializer { 185 | __Ownable_init(); 186 | version = "v0.4.2"; 187 | } 188 | 189 | /***********************************| 190 | | Public Read | 191 | |__________________________________*/ 192 | 193 | /** 194 | * @notice Gets the URI of a Type or Token 195 | * @param _typeId The ID of the Type or Token 196 | * @return The URI of the Type or Token 197 | */ 198 | function uri(uint256 _typeId) external view returns (string memory) { 199 | return tokenMgr.uri(_typeId); 200 | } 201 | 202 | /** 203 | * @notice Gets the Creator of a Token Type 204 | * @param _typeId The Type ID of the Token 205 | * @return The Creator Address 206 | */ 207 | function getTypeCreator(uint256 _typeId) external view returns (address) { 208 | return typeCreator[_typeId]; 209 | } 210 | 211 | /** 212 | * @notice Gets the Compatibility Bridge for the Token 213 | * @param _typeId The Token ID or the Type ID of the Token 214 | * @return The Token-Bridge Address 215 | */ 216 | function getTypeTokenBridge(uint256 _typeId) external view returns (address) { 217 | _typeId = tokenMgr.getNonFungibleBaseType(_typeId); 218 | return typeTokenBridge[_typeId]; 219 | } 220 | 221 | /** 222 | * @notice Checks if a user is allowed to mint a Token by Type ID 223 | * @param _typeId The Type ID of the Token 224 | * @param _amount The amount of tokens to mint 225 | * @return True if the user can mint the token type 226 | */ 227 | function canMint(uint256 _typeId, uint256 _amount) public view returns (bool) { 228 | // Public 229 | if (registeredTypes[_typeId] & 1 == 1) { 230 | // Has Max 231 | if (typeSupply[_typeId] > 0) { 232 | return tokenMgr.totalMinted(_typeId).add(_amount) <= typeSupply[_typeId]; 233 | } 234 | // No Max 235 | return true; 236 | } 237 | // Private 238 | if (typeCreator[_typeId] != _msgSender()) { 239 | return false; 240 | } 241 | // Has Max 242 | if (typeSupply[_typeId] > 0) { 243 | return tokenMgr.totalMinted(_typeId).add(_amount) <= typeSupply[_typeId]; 244 | } 245 | // No Max 246 | return true; 247 | } 248 | 249 | /** 250 | * @notice Gets the ETH price to create a Token Type 251 | * @param _isNF True if the Type of Token to Create is a Non-Fungible Token 252 | */ 253 | function getCreationPrice(bool _isNF) public view returns (uint256 _eth, uint256 _ion) { 254 | _eth = _isNF ? (createFeeEth.mul(2)) : createFeeEth; 255 | _ion = _isNF ? (createFeeIon.mul(2)) : createFeeIon; 256 | } 257 | 258 | /** 259 | * @notice Gets the Number of this Particle in the Series/Collection 260 | * @param _tokenId The ID of the token 261 | * @return The Series Number of the Particle 262 | */ 263 | function getSeriesNumber(uint256 _tokenId) external view returns (uint256) { 264 | return tokenMgr.getNonFungibleIndex(_tokenId); 265 | } 266 | 267 | /** 268 | * @notice Gets the ETH price to mint a Token of a specific Type 269 | * @param _typeId The Token ID or the Type ID of the Token 270 | * @return The ETH price to mint the Token 271 | */ 272 | function getMintingFee(uint256 _typeId) external view returns (uint256) { 273 | _typeId = tokenMgr.getNonFungibleBaseType(_typeId); 274 | return mintFee[_typeId]; 275 | } 276 | 277 | /** 278 | * @notice Gets the Max-Supply of the Particle Type (0 for infinite) 279 | * @param _typeId The Token ID or the Type ID of the Token 280 | * @return The Maximum Supply of the Token-Type 281 | */ 282 | function getMaxSupply(uint256 _typeId) external view returns (uint256) { 283 | _typeId = tokenMgr.getNonFungibleBaseType(_typeId); 284 | return typeSupply[_typeId]; 285 | } 286 | 287 | /** 288 | * @notice Gets the Number of Minted Particles 289 | * @param _typeId The Token ID or the Type ID of the Token 290 | * @return The Total Minted Supply of the Token-Type 291 | */ 292 | function getTotalMinted(uint256 _typeId) external view returns (uint256) { 293 | _typeId = tokenMgr.getNonFungibleBaseType(_typeId); 294 | return tokenMgr.totalMinted(_typeId); 295 | } 296 | 297 | /***********************************| 298 | | Particle Manager | 299 | |__________________________________*/ 300 | 301 | function contractOwner() external view override returns (address) { 302 | return owner(); 303 | } 304 | 305 | function ownerOf(uint256 _tokenId) external view override returns (address) { 306 | return tokenMgr.ownerOf(_tokenId); 307 | } 308 | 309 | function isApprovedForAll(address _owner, address _operator) external view override returns (bool) { 310 | return tokenMgr.isApprovedForAll(_owner, _operator); 311 | } 312 | 313 | /***********************************| 314 | | Particle Physics | 315 | |__________________________________*/ 316 | 317 | /** 318 | * @notice Gets the Amount of Base DAI held in the Token (amount token was minted with) 319 | * @param _tokenId The ID of the Token 320 | * @return The Base Mass of the Particle 321 | */ 322 | function baseParticleMass(uint256 _tokenId) external view returns (uint256) { 323 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 324 | string memory _assetPairId = typeAssetPairId[_typeId]; 325 | return escrowMgr.baseParticleMass(address(tokenMgr), _tokenId, _assetPairId); 326 | } 327 | 328 | /** 329 | * @notice Gets the amount of Charge the Particle has generated (it's accumulated interest) 330 | * @param _tokenId The ID of the Token 331 | * @return The Current Charge of the Particle 332 | */ 333 | function currentParticleCharge(uint256 _tokenId) external returns (uint256) { 334 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 335 | require(registeredTypes[_typeId] > 0, "CP: INVALID_TYPE"); 336 | require(tokenMgr.isNonFungible(_tokenId), "CP: FUNGIBLE_TYPE"); 337 | 338 | string memory _assetPairId = typeAssetPairId[_typeId]; 339 | return escrowMgr.currentParticleCharge(address(tokenMgr), _tokenId, _assetPairId); 340 | } 341 | 342 | /***********************************| 343 | | Public Create Types | 344 | |__________________________________*/ 345 | 346 | /** 347 | * @notice Creates a new Particle Type (NFT/ERC721) which can later be minted/burned 348 | * NOTE: Requires payment in ETH or IONs 349 | @ @dev see _createParticle() 350 | */ 351 | function createParticle( 352 | string memory _name, 353 | string memory _uri, 354 | string memory _symbol, 355 | uint8 _accessType, 356 | string memory _assetPairId, 357 | uint256 _maxSupply, 358 | uint256 _mintFee, 359 | bool _payWithIons 360 | ) 361 | public 362 | payable 363 | whenNotPaused 364 | returns (uint256 _particleTypeId) 365 | { 366 | (uint256 _ethPrice, uint256 _ionPrice) = getCreationPrice(true); 367 | 368 | if (_payWithIons) { 369 | _collectIons(_msgSender(), _ionPrice); 370 | _ethPrice = 0; 371 | } else { 372 | require(msg.value >= _ethPrice, "CP: INSUFF_FUNDS"); 373 | } 374 | 375 | // Create Particle Type 376 | _particleTypeId = _createParticle( 377 | _name, // Token Name 378 | _uri, // Token Metadata URI 379 | _symbol, // Token Symbol 380 | _accessType, // Token Access Type 381 | _assetPairId, // Asset Pair for Type 382 | _maxSupply, // Max Supply 383 | _mintFee // Price-per-Token in ETH 384 | ); 385 | 386 | // Mark ION-Generated Particles 387 | if (_payWithIons) { 388 | typeSpecialBits[_particleTypeId] = typeSpecialBits[_particleTypeId] | ION_SPECIAL_BIT; 389 | } 390 | 391 | // Collect Fees 392 | // solhint-disable-next-line 393 | else { 394 | collectedFees[CONTRACT_ID] = _ethPrice.add(collectedFees[CONTRACT_ID]); 395 | } 396 | 397 | // Refund over-payment 398 | uint256 _overage = msg.value.sub(_ethPrice); 399 | if (_overage > 0) { 400 | _msgSender().sendValue(_overage); 401 | } 402 | } 403 | 404 | /**` 405 | * @notice Creates a new Plasma Type (FT/ERC20) which can later be minted/burned 406 | * NOTE: Requires payment in ETH or IONs 407 | @ @dev see _createPlasma() 408 | */ 409 | function createPlasma( 410 | string memory _name, 411 | string memory _uri, 412 | string memory _symbol, 413 | bool _isPrivate, 414 | uint256 _maxSupply, 415 | uint256 _mintFee, 416 | uint256 _initialMint, 417 | bool _payWithIons 418 | ) 419 | public 420 | payable 421 | whenNotPaused 422 | returns (uint256 _plasmaTypeId) 423 | { 424 | (uint256 _ethPrice, uint256 _ionPrice) = getCreationPrice(false); 425 | 426 | if (_payWithIons) { 427 | _collectIons(_msgSender(), _ionPrice); 428 | _ethPrice = 0; 429 | } else { 430 | require(msg.value >= _ethPrice, "CP: INSUFF_FUNDS"); 431 | } 432 | 433 | // Create Plasma Type 434 | _plasmaTypeId = _createPlasma( 435 | _name, // Token Name 436 | _uri, // Token Metadata URI 437 | _symbol, // Token Symbol 438 | _isPrivate, // is Private? 439 | _maxSupply, // Max Supply 440 | _mintFee, // Price per Token in ETH 441 | _initialMint // Initial Amount to Mint 442 | ); 443 | 444 | // Mark ION-Generated Particles 445 | if (_payWithIons) { 446 | typeSpecialBits[_plasmaTypeId] = typeSpecialBits[_plasmaTypeId] | ION_SPECIAL_BIT; 447 | } 448 | 449 | // Collect Fees 450 | // solhint-disable-next-line 451 | else { 452 | collectedFees[CONTRACT_ID] = _ethPrice.add(collectedFees[CONTRACT_ID]); 453 | } 454 | 455 | // Refund over-payment 456 | uint256 _overage = msg.value.sub(_ethPrice); 457 | if (_overage > 0) { 458 | _msgSender().sendValue(_overage); 459 | } 460 | } 461 | 462 | /***********************************| 463 | | Public Mint | 464 | |__________________________________*/ 465 | 466 | /** 467 | * @notice Mints a new Particle of the specified Type 468 | * Note: Requires Asset-Token to mint 469 | * @param _to The owner address to assign the new token to 470 | * @param _typeId The Type ID of the new token to mint 471 | * @param _assetAmount The amount of Asset-Tokens to deposit 472 | * @param _uri The Unique URI to the Token Metadata 473 | * @param _data Custom data used for transferring tokens into contracts 474 | * @return The ID of the newly minted token 475 | * 476 | * NOTE: Must approve THIS contract to TRANSFER your Asset-Token on your behalf 477 | */ 478 | function mintParticle( 479 | address _to, 480 | uint256 _typeId, 481 | uint256 _assetAmount, 482 | string memory _uri, 483 | bytes memory _data 484 | ) 485 | public 486 | whenNotPaused 487 | payable 488 | returns (uint256) 489 | { 490 | require(tokenMgr.isNonFungibleBaseType(_typeId), "CP: FUNGIBLE_TYPE"); 491 | require(canMint(_typeId, 1), "CP: CANT_MINT"); 492 | 493 | address _creator = typeCreator[_typeId]; 494 | uint256 _ethPerToken; 495 | 496 | // Check Token Price 497 | if (_msgSender() != _creator) { 498 | _ethPerToken = mintFee[_typeId]; 499 | require(msg.value >= _ethPerToken, "CP: INSUFF_FUNDS"); 500 | } 501 | 502 | // Series-Particles use the Metadata of their Type 503 | if (registeredTypes[_typeId] & 4 == 4) { 504 | _uri = tokenMgr.uri(_typeId); 505 | } 506 | 507 | // Mint Token 508 | uint256 _tokenId = tokenMgr.mint(_to, _typeId, 1, _uri, _data); 509 | 510 | // Energize NFT Particles 511 | energizeParticle(_tokenId, _assetAmount); 512 | 513 | // Log Event 514 | emit ParticleMinted(_msgSender(), _to, _tokenId, _uri); 515 | 516 | // Track Collected Fees 517 | if (_msgSender() != _creator) { 518 | collectedFees[_creator] = _ethPerToken.add(collectedFees[_creator]); 519 | } 520 | 521 | // Refund overpayment 522 | uint256 _overage = msg.value.sub(_ethPerToken); 523 | if (_overage > 0) { 524 | _msgSender().sendValue(_overage); 525 | } 526 | return _tokenId; 527 | } 528 | 529 | /** 530 | * @notice Mints new Plasma of the specified Type 531 | * @param _to The owner address to assign the new tokens to 532 | * @param _typeId The Type ID of the tokens to mint 533 | * @param _amount The amount of tokens to mint 534 | * @param _data Custom data used for transferring tokens into contracts 535 | */ 536 | function mintPlasma( 537 | address _to, 538 | uint256 _typeId, 539 | uint256 _amount, 540 | bytes memory _data 541 | ) 542 | public 543 | whenNotPaused 544 | payable 545 | { 546 | require(tokenMgr.isFungible(_typeId), "CP: NON_FUNGIBLE_TYPE"); 547 | require(canMint(_typeId, _amount), "CP: CANT_MINT"); 548 | 549 | address _creator = (_typeId == ionTokenId) ? CONTRACT_ID : typeCreator[_typeId]; 550 | uint256 _totalEth; 551 | uint256 _ethPerToken; 552 | 553 | // Check Token Price 554 | if (_msgSender() != _creator) { 555 | _ethPerToken = mintFee[_typeId]; 556 | _totalEth = _amount.mul(_ethPerToken); 557 | require(msg.value >= _totalEth, "CP: INSUFF_FUNDS"); 558 | } 559 | 560 | // Mint Token 561 | tokenMgr.mint(_to, _typeId, _amount, "", _data); 562 | emit PlasmaMinted(_msgSender(), _to, _typeId, _amount); 563 | 564 | if (_msgSender() != _creator) { 565 | // Track Collected Fees 566 | collectedFees[_creator] = _totalEth.add(collectedFees[_creator]); 567 | } 568 | 569 | // Refund overpayment 570 | uint256 _overage = msg.value.sub(_totalEth); 571 | if (_overage > 0) { 572 | _msgSender().sendValue(_overage); 573 | } 574 | } 575 | 576 | /***********************************| 577 | | Public Burn | 578 | |__________________________________*/ 579 | 580 | /** 581 | * @notice Destroys a Particle and releases the underlying Asset + Interest (Mass + Charge) 582 | * @param _tokenId The ID of the token to burn 583 | */ 584 | function burnParticle(uint256 _tokenId) external { 585 | address _tokenContract = address(tokenMgr); 586 | address _tokenOwner; 587 | string memory _assetPairId; 588 | 589 | // Verify Token 590 | require(tokenMgr.isNonFungibleBaseType(_tokenId), "CP: FUNGIBLE_TYPE"); 591 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 592 | require(registeredTypes[_typeId] > 0, "CP: INVALID_TYPE"); 593 | 594 | // Prepare Particle Release 595 | _tokenOwner = tokenMgr.ownerOf(_tokenId); 596 | _assetPairId = typeAssetPairId[_typeId]; 597 | escrowMgr.releaseParticle(_tokenOwner, _tokenContract, _tokenId, _assetPairId); 598 | 599 | // Burn Token 600 | tokenMgr.burn(_msgSender(), _tokenId, 1); 601 | 602 | // Release Particle (Payout Asset + Interest) 603 | escrowMgr.finalizeRelease(_tokenOwner, _tokenContract, _tokenId, _assetPairId); 604 | 605 | emit ParticleBurned(_msgSender(), _tokenId); 606 | } 607 | 608 | /** 609 | * @notice Destroys Plasma 610 | * @param _typeId The type of token to burn 611 | * @param _amount The amount of tokens to burn 612 | */ 613 | function burnPlasma(uint256 _typeId, uint256 _amount) external { 614 | // Verify Token 615 | require(tokenMgr.isFungible(_typeId), "CP: NON_FUNGIBLE_TYPE"); 616 | require(registeredTypes[_typeId] > 0, "CP: INVALID_TYPE"); 617 | 618 | // Burn Token 619 | tokenMgr.burn(_msgSender(), _typeId, _amount); 620 | 621 | emit PlasmaBurned(_msgSender(), _typeId, _amount); 622 | } 623 | 624 | /***********************************| 625 | | Energize Particle | 626 | |__________________________________*/ 627 | 628 | /** 629 | * @notice Allows the owner/operator of the Particle to add additional Asset Tokens 630 | * @param _tokenId The ID of the Token 631 | * @param _assetAmount The Amount of Asset Tokens to Energize the Particle with 632 | * @return The amount of Interest-bearing Tokens added to the escrow for the Token 633 | */ 634 | function energizeParticle(uint256 _tokenId, uint256 _assetAmount) 635 | public 636 | whenNotPaused 637 | returns (uint256) 638 | { 639 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 640 | require(tokenMgr.isNonFungibleBaseType(_typeId), "CP: FUNGIBLE_TYPE"); 641 | 642 | // Transfer Asset Token from Caller to Contract 643 | string memory _assetPairId = typeAssetPairId[_typeId]; 644 | _collectAssetToken(_msgSender(), _assetPairId, _assetAmount); 645 | 646 | // Energize Particle; Transfering Asset from Contract to Escrow 647 | _getAssetToken(_assetPairId).approve(address(escrowMgr), _assetAmount); 648 | return escrowMgr.energizeParticle(address(this), _tokenId, _assetPairId, _assetAmount); 649 | } 650 | 651 | /***********************************| 652 | | Discharge Particle | 653 | |__________________________________*/ 654 | 655 | /** 656 | * @notice Allows the owner/operator of the Particle to collect/transfer the interest generated 657 | * from the token without removing the underlying Asset that is held in the token 658 | * @param _receiver The address of the receiver of the discharge 659 | * @param _tokenId The ID of the Token 660 | * @return Two values; 1: Amount of Asset Token Received, 2: Remaining Charge of the Token 661 | */ 662 | function dischargeParticle(address _receiver, uint256 _tokenId) external returns (uint256, uint256) { 663 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 664 | string memory _assetPairId = typeAssetPairId[_typeId]; 665 | return escrowMgr.dischargeParticle(_receiver, address(this), _tokenId, _assetPairId); 666 | } 667 | 668 | /** 669 | * @notice Allows the owner/operator of the Particle to collect/transfer a specific amount of 670 | * the interest generated from the token without removing the underlying Asset that is held in the token 671 | * @param _receiver The address of the receiver of the discharge 672 | * @param _tokenId The ID of the Token 673 | * @param _assetAmount The Amount of Asset Tokens to Discharge from the Particle 674 | * @return Two values; 1: Amount of Asset Token Received, 2: Remaining Charge of the Token 675 | */ 676 | function dischargeParticleAmount(address _receiver, uint256 _tokenId, uint256 _assetAmount) external returns (uint256, uint256) { 677 | uint256 _typeId = tokenMgr.getNonFungibleBaseType(_tokenId); 678 | string memory _assetPairId = typeAssetPairId[_typeId]; 679 | return escrowMgr.dischargeParticleAmount(_receiver, address(this), _tokenId, _assetPairId, _assetAmount); 680 | } 681 | 682 | 683 | /***********************************| 684 | | Type Creator | 685 | |__________________________________*/ 686 | 687 | /** 688 | * @dev Allows a Type Creator to withdraw any fees earned 689 | * @param _receiver The address of the receiver 690 | */ 691 | function withdrawCreatorFees(address payable _receiver) public { 692 | address _creator = _msgSender(); 693 | uint256 _amount = collectedFees[_creator]; 694 | require(_amount > 0, "CP: INSUFF_BALANCE"); 695 | require(_receiver != address(0x0), "CP: INVALID_ADDRESS"); 696 | 697 | // Withdraw Minting Fees (ETH) 698 | collectedFees[_creator] = 0; 699 | _receiver.sendValue(_amount); 700 | emit CreatorFeesWithdrawn(_creator, _receiver, _amount); 701 | } 702 | 703 | /***********************************| 704 | | Only Admin/DAO | 705 | |__________________________________*/ 706 | 707 | /** 708 | * @dev Setup the Creation/Minting Fees 709 | */ 710 | function setupFees(uint256 _createFeeEth, uint256 _createFeeIon) external onlyOwner { 711 | createFeeEth = _createFeeEth; 712 | createFeeIon = _createFeeIon; 713 | } 714 | 715 | /** 716 | * @dev Toggle the "Paused" state of the contract 717 | */ 718 | function setPausedState(bool _paused) external onlyOwner { 719 | isPaused = _paused; 720 | } 721 | 722 | /** 723 | * @dev Register the address of the token manager contract 724 | */ 725 | function registerTokenManager(address _tokenMgr) external onlyOwner { 726 | require(_tokenMgr != address(0x0), "CP: INVALID_ADDRESS"); 727 | tokenMgr = IChargedParticlesTokenManager(_tokenMgr); 728 | } 729 | 730 | /** 731 | * @dev Register the address of the escrow contract 732 | */ 733 | function registerEscrowManager(address _escrowMgr) external onlyOwner { 734 | require(_escrowMgr != address(0x0), "CP: INVALID_ADDRESS"); 735 | escrowMgr = IChargedParticlesEscrowManager(_escrowMgr); 736 | } 737 | 738 | function setTrustedForwarder(address _trustedForwarder) external onlyOwner { 739 | trustedForwarder = _trustedForwarder; 740 | } 741 | 742 | /** 743 | * @dev Setup internal ION Token 744 | */ 745 | function mintIons(string calldata _uri, uint256 _maxSupply, uint256 _mintFee) external onlyOwner returns (uint256) { 746 | require(ionTokenId == 0, "CP: ALREADY_INIT"); 747 | 748 | // Create ION Token Type; 749 | // ERC20, Private, Limited 750 | ionTokenId = _createPlasma( 751 | "Charged Atoms", // Token Name 752 | _uri, // Token Metadata URI 753 | "ION", // Token Symbol 754 | false, // is Private? 755 | _maxSupply, // Max Supply 756 | _mintFee, // Price per Token in ETH 757 | _maxSupply // Amount to mint 758 | ); 759 | 760 | return ionTokenId; 761 | } 762 | 763 | /** 764 | * @dev Allows contract owner to withdraw any ETH fees earned 765 | * Interest-token Fees are collected in Escrow, withdraw from there 766 | */ 767 | function withdrawFees(address payable _receiver) external onlyOwner { 768 | require(_receiver != address(0x0), "CP: INVALID_ADDRESS"); 769 | 770 | uint256 _amount = collectedFees[CONTRACT_ID]; 771 | if (_amount > 0) { 772 | collectedFees[CONTRACT_ID] = 0; 773 | _receiver.sendValue(_amount); 774 | } 775 | emit ContractFeesWithdrawn(_msgSender(), _receiver, _amount); 776 | } 777 | 778 | /***********************************| 779 | | Private Functions | 780 | |__________________________________*/ 781 | 782 | /** 783 | * @notice Creates a new Particle Type (NFT) which can later be minted/burned 784 | * @param _name The Name of the Particle 785 | * @param _uri A unique URI for the Token Type which will serve the JSON metadata 786 | * @param _accessType A bit indicating the Access Type; Private/Public, Series/Collection 787 | * @param _assetPairId The ID of the Asset-Pair that the Particle will use for the Underlying Assets 788 | * @param _maxSupply The Max Supply of Tokens that can be minted 789 | * Provide a value of 0 for no limit 790 | * @param _mintFee The "Mint" Fee that is collected for each Particle and paid to the Particle Type Creator 791 | * @return _particleTypeId The ID of the newly created Particle Type 792 | * Use this ID when Minting Particles of this Type 793 | */ 794 | function _createParticle( 795 | string memory _name, 796 | string memory _uri, 797 | string memory _symbol, 798 | uint8 _accessType, 799 | string memory _assetPairId, 800 | uint256 _maxSupply, 801 | uint256 _mintFee 802 | ) 803 | internal 804 | returns (uint256 _particleTypeId) 805 | { 806 | require(escrowMgr.isAssetPairEnabled(_assetPairId), "CP: INVALID_ASSET_PAIR"); 807 | 808 | // Create Type 809 | _particleTypeId = tokenMgr.createType(_uri, true); // ERC-1155 Non-Fungible 810 | 811 | // Create Token-Bridge 812 | typeTokenBridge[_particleTypeId] = tokenMgr.createErc721Bridge(_particleTypeId, _name, _symbol); 813 | 814 | // Type Access (Public or Private, Series or Collection) 815 | registeredTypes[_particleTypeId] = _accessType; 816 | 817 | // Max Supply of Token; 0 = No Max 818 | typeSupply[_particleTypeId] = _maxSupply; 819 | 820 | // The Eth-per-Token Fee for Minting 821 | mintFee[_particleTypeId] = _mintFee; 822 | 823 | // Creator of Type 824 | typeCreator[_particleTypeId] = _msgSender(); 825 | 826 | // Type Asset-Pair 827 | typeAssetPairId[_particleTypeId] = _assetPairId; 828 | 829 | // Log Event 830 | emit ParticleTypeUpdated( 831 | _particleTypeId, 832 | _symbol, 833 | (_accessType & 2 == 2), // isPrivate 834 | (_accessType & 4 == 4), // isSeries 835 | _assetPairId, 836 | _uri 837 | ); 838 | } 839 | 840 | /** 841 | * @notice Creates a new Plasma Type (FT) which can later be minted/burned 842 | * @param _name The Name of the Particle 843 | * @param _uri A unique URI for the Token Type which will serve the JSON metadata 844 | * @param _isPrivate True if the Type is Private and can only be minted by the creator; otherwise anyone can mint 845 | * @param _maxSupply The Max Supply of Tokens that can be minted 846 | * Provide a value of 0 for no limit 847 | * @param _mintFee The ETH Price of each Token when sold to public 848 | * @param _initialMint The amount of tokens to initially mint 849 | * @return _plasmaTypeId The ID of the newly created Plasma Type 850 | * Use this ID when Minting Plasma of this Type 851 | */ 852 | function _createPlasma( 853 | string memory _name, 854 | string memory _uri, 855 | string memory _symbol, 856 | bool _isPrivate, 857 | uint256 _maxSupply, 858 | uint256 _mintFee, 859 | uint256 _initialMint 860 | ) 861 | internal 862 | returns (uint256 _plasmaTypeId) 863 | { 864 | // Create Type 865 | _plasmaTypeId = tokenMgr.createType(_uri, false); // ERC-1155 Fungible 866 | 867 | // Create Token-Bridge 868 | typeTokenBridge[_plasmaTypeId] = tokenMgr.createErc20Bridge(_plasmaTypeId, _name, _symbol, 18); 869 | 870 | // Type Access (Public or Private minting) 871 | registeredTypes[_plasmaTypeId] = _isPrivate ? 2 : 1; 872 | 873 | // Creator of Type 874 | typeCreator[_plasmaTypeId] = _msgSender(); 875 | 876 | // Max Supply of Token; 0 = No Max 877 | typeSupply[_plasmaTypeId] = _maxSupply; 878 | 879 | // The Eth-per-Token Fee for Minting 880 | mintFee[_plasmaTypeId] = _mintFee; 881 | 882 | // Mint Initial Tokens 883 | if (_initialMint > 0) { 884 | tokenMgr.mint(_msgSender(), _plasmaTypeId, _initialMint, _uri, "ions"); 885 | } 886 | 887 | emit PlasmaTypeUpdated(_plasmaTypeId, _symbol, _isPrivate, _initialMint, _uri); 888 | } 889 | 890 | /** 891 | * @dev Collects the Required IONs from the users wallet during Type Creation and Burns them 892 | * @param _from The owner address to collect the IONs from 893 | * @param _ions The amount of IONs to collect from the user 894 | */ 895 | function _collectIons(address _from, uint256 _ions) internal { 896 | require(tokenMgr.balanceOf(_from, ionTokenId) > 0, "CP: INSUFF_IONS"); 897 | 898 | // Burn IONs from User 899 | tokenMgr.burn(_from, ionTokenId, _ions); 900 | } 901 | 902 | /** 903 | * @dev Collects the Required Asset Token from the users wallet 904 | * @param _from The owner address to collect the Assets from 905 | * @param _assetPairId The ID of the Asset-Pair that the Particle will use for the Underlying Assets 906 | * @param _assetAmount The Amount of Asset Tokens to Collect 907 | */ 908 | function _collectAssetToken(address _from, string memory _assetPairId, uint256 _assetAmount) internal { 909 | IERC20 _assetToken = _getAssetToken(_assetPairId); 910 | uint256 _userAssetBalance = _assetToken.balanceOf(_from); 911 | require(_assetAmount <= _userAssetBalance, "CP: INSUFF_ASSETS"); 912 | // Be sure to Approve this Contract to transfer your Asset Token 913 | require(_assetToken.transferFrom(_from, address(this), _assetAmount), "CP: TRANSFER_FAILED"); 914 | } 915 | 916 | function _getAssetToken(string memory _assetPairId) internal view returns (IERC20) { 917 | address _assetTokenAddress = escrowMgr.getAssetTokenAddress(_assetPairId); 918 | return IERC20(_assetTokenAddress); 919 | } 920 | 921 | function _msgSender() internal override(BaseRelayRecipient, ContextUpgradeSafe) virtual view returns (address payable) { 922 | return BaseRelayRecipient._msgSender(); 923 | } 924 | } 925 | -------------------------------------------------------------------------------- /contracts/ChargedParticlesTokenManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ChargedParticlesTokenManager.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | pragma experimental ABIEncoderV2; 26 | 27 | import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol"; 28 | import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol"; 29 | import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; 30 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 31 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 32 | 33 | import "./lib/BridgedERC1155.sol"; 34 | 35 | 36 | /** 37 | * @notice Charged Particles ERC1155 - Token Manager 38 | */ 39 | contract ChargedParticlesTokenManager is Initializable, OwnableUpgradeSafe, BridgedERC1155 { 40 | using SafeMath for uint256; 41 | using Address for address payable; 42 | 43 | // Integrated Controller Contracts 44 | mapping (address => bool) internal fusedParticles; 45 | // mapping (address => mapping (uint256 => bool)) internal fusedParticleTypes; 46 | mapping (uint256 => address) internal fusedParticleTypes; 47 | 48 | // Contract Version 49 | bytes16 public version; 50 | 51 | // Throws if called by any account other than a Fused-Particle contract. 52 | modifier onlyFusedParticles() { 53 | require(fusedParticles[msg.sender], "CPTM: ONLY_FUSED"); 54 | _; 55 | } 56 | 57 | /***********************************| 58 | | Initialization | 59 | |__________________________________*/ 60 | 61 | function initialize() public override initializer { 62 | __Ownable_init(); 63 | BridgedERC1155.initialize(); 64 | version = "v0.4.2"; 65 | } 66 | 67 | 68 | /***********************************| 69 | | Public Read | 70 | |__________________________________*/ 71 | 72 | function isNonFungible(uint256 _id) external override pure returns(bool) { 73 | return _id & TYPE_NF_BIT == TYPE_NF_BIT; 74 | } 75 | function isFungible(uint256 _id) external override pure returns(bool) { 76 | return _id & TYPE_NF_BIT == 0; 77 | } 78 | function getNonFungibleIndex(uint256 _id) external override pure returns(uint256) { 79 | return _id & NF_INDEX_MASK; 80 | } 81 | function getNonFungibleBaseType(uint256 _id) external override pure returns(uint256) { 82 | return _id & TYPE_MASK; 83 | } 84 | function isNonFungibleBaseType(uint256 _id) external override pure returns(bool) { 85 | return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK == 0); 86 | } 87 | function isNonFungibleItem(uint256 _id) external override pure returns(bool) { 88 | return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK != 0); 89 | } 90 | 91 | /** 92 | * @notice Gets the Creator of a Token Type 93 | * @param _typeId The Type ID of the Token 94 | * @return The Creator Address 95 | */ 96 | function getTypeCreator(uint256 _typeId) external view returns (address) { 97 | return fusedParticleTypes[_typeId]; 98 | } 99 | 100 | 101 | /***********************************| 102 | | Only Charged Particles | 103 | |__________________________________*/ 104 | 105 | /** 106 | * @dev Creates a new Particle Type, either FT or NFT 107 | */ 108 | function createType( 109 | string calldata _uri, 110 | bool isNF 111 | ) 112 | external 113 | override 114 | onlyFusedParticles 115 | returns (uint256) 116 | { 117 | uint256 _typeId = _createType(_uri, isNF); 118 | fusedParticleTypes[_typeId] = msg.sender; 119 | return _typeId; 120 | } 121 | 122 | /** 123 | * @dev Mints a new Particle, either FT or NFT 124 | */ 125 | function mint( 126 | address _to, 127 | uint256 _typeId, 128 | uint256 _amount, 129 | string calldata _uri, 130 | bytes calldata _data 131 | ) 132 | external 133 | override 134 | onlyFusedParticles 135 | returns (uint256) 136 | { 137 | require(fusedParticleTypes[_typeId] == msg.sender, "CPTM: ONLY_FUSED"); 138 | return _mint(_to, _typeId, _amount, _uri, _data); 139 | } 140 | 141 | /** 142 | * @dev Mints a Batch of new Particles, either FT or NFT 143 | */ 144 | // function mintBatch( 145 | // address _to, 146 | // uint256[] calldata _types, 147 | // uint256[] calldata _amounts, 148 | // string[] calldata _uris, 149 | // bytes calldata _data 150 | // ) 151 | // external 152 | // override 153 | // onlyFusedParticles 154 | // returns (uint256[] memory) 155 | // { 156 | // for (uint256 i = 0; i < _types.length; i++) { 157 | // require(fusedParticleTypes[_types[i]] == msg.sender, "CPTM: ONLY_FUSED"); 158 | // } 159 | // return _mintBatch(_to, _types, _amounts, _uris, _data); 160 | // } 161 | 162 | /** 163 | * @dev Burns an existing Particle, either FT or NFT 164 | */ 165 | function burn( 166 | address _from, 167 | uint256 _tokenId, 168 | uint256 _amount 169 | ) 170 | external 171 | override 172 | onlyFusedParticles 173 | { 174 | uint256 _typeId = _tokenId; 175 | if (_tokenId & TYPE_NF_BIT == TYPE_NF_BIT) { 176 | _typeId = _tokenId & TYPE_MASK; 177 | } 178 | require(fusedParticleTypes[_typeId] == msg.sender, "CPTM: ONLY_FUSED"); 179 | _burn(_from, _tokenId, _amount); 180 | } 181 | 182 | /** 183 | * @dev Burns a Batch of existing Particles, either FT or NFT 184 | */ 185 | // function burnBatch( 186 | // address _from, 187 | // uint256[] calldata _tokenIds, 188 | // uint256[] calldata _amounts 189 | // ) 190 | // external 191 | // override 192 | // onlyFusedParticles 193 | // { 194 | // for (uint256 i = 0; i < _tokenIds.length; i++) { 195 | // uint256 _typeId = _tokenIds[i]; 196 | // if (_typeId & TYPE_NF_BIT == TYPE_NF_BIT) { 197 | // _typeId = _typeId & TYPE_MASK; 198 | // } 199 | // require(fusedParticleTypes[_typeId] == msg.sender, "CPTM: ONLY_FUSED"); 200 | // } 201 | // _burnBatch(_from, _tokenIds, _amounts); 202 | // } 203 | 204 | /** 205 | * @dev Creates an ERC20 Token Bridge Contract to interface with the ERC1155 Contract 206 | */ 207 | function createErc20Bridge( 208 | uint256 _typeId, 209 | string calldata _name, 210 | string calldata _symbol, 211 | uint8 _decimals 212 | ) 213 | external 214 | override 215 | onlyFusedParticles 216 | returns (address) 217 | { 218 | require(fusedParticleTypes[_typeId] == msg.sender, "CPTM: ONLY_FUSED"); 219 | return _createErc20Bridge(_typeId, _name, _symbol, _decimals); 220 | } 221 | 222 | /** 223 | * @dev Creates an ERC721 Token Bridge Contract to interface with the ERC1155 Contract 224 | */ 225 | function createErc721Bridge( 226 | uint256 _typeId, 227 | string calldata _name, 228 | string calldata _symbol 229 | ) 230 | external 231 | override 232 | onlyFusedParticles 233 | returns (address) 234 | { 235 | require(fusedParticleTypes[_typeId] == msg.sender, "CPTM: ONLY_FUSED"); 236 | return _createErc721Bridge(_typeId, _name, _symbol); 237 | } 238 | 239 | 240 | /***********************************| 241 | | Only Admin/DAO | 242 | |__________________________________*/ 243 | 244 | /** 245 | * @dev Adds an Integration Controller Contract as a Fused Particle to allow Creating/Minting 246 | */ 247 | function registerContractType(address _particleAddress, bool _fusedState) external onlyOwner { 248 | fusedParticles[_particleAddress] = _fusedState; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /contracts/assets/chai/ChaiEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ChargedParticlesEscrowManager.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | 26 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 27 | import "../../lib/EscrowBase.sol"; 28 | 29 | /** 30 | * @notice Chai-Escrow for Charged Particles 31 | */ 32 | contract ChaiEscrow is EscrowBase { 33 | using SafeMath for uint256; 34 | 35 | /** 36 | * @notice Gets the Amount of Asset Tokens that have been Deposited into the Particle 37 | * representing the Mass of the Particle. 38 | * @param _tokenUuid The ID of the Token within the External Contract 39 | * @return The Amount of underlying Assets held within the Token 40 | */ 41 | function baseParticleMass(uint256 _tokenUuid) external override view returns (uint256) { 42 | return _baseParticleMass(_tokenUuid); 43 | } 44 | 45 | /** 46 | * @notice Gets the amount of Interest that the Particle has generated representing 47 | * the Charge of the Particle 48 | * @param _tokenUuid The ID of the Token within the External Contract 49 | * @return The amount of interest the Token has generated (in Asset Token) 50 | */ 51 | function currentParticleCharge(uint256 _tokenUuid) external override returns (uint256) { 52 | return _currentParticleCharge(_tokenUuid); 53 | } 54 | 55 | /***********************************| 56 | | Energize Particles | 57 | |__________________________________*/ 58 | 59 | /** 60 | * @notice Fund Particle with Asset Token 61 | * Must be called by the Owner providing the Asset 62 | * Owner must Approve THIS contract as Operator of Asset 63 | * 64 | * NOTE: DO NOT Energize an ERC20 Token, as anyone who holds any amount 65 | * of the same ERC20 token could discharge or release the funds. 66 | * All holders of the ERC20 token would essentially be owners of the Charged Particle. 67 | * 68 | * @param _tokenUuid The ID of the Token to Energize 69 | * @param _assetAmount The Amount of Asset Token to Energize the Token with 70 | * @return The amount of Interest-bearing Tokens added to the escrow for the Token 71 | */ 72 | function energizeParticle( 73 | address _contractAddress, 74 | uint256 _tokenUuid, 75 | uint256 _assetAmount 76 | ) 77 | external 78 | override 79 | onlyEscrow 80 | whenNotPaused 81 | returns (uint256) 82 | { 83 | uint256 _existingInterest = interestTokenBalance[_tokenUuid]; 84 | uint256 _existingBalance = assetTokenBalance[_tokenUuid]; 85 | uint256 _newBalance = _assetAmount.add(_existingBalance); 86 | 87 | // Validate Minimum-Required Balance 88 | require(_newBalance >= MIN_DEPOSIT_FEE, "CHE: INSUFF_DEPOSIT"); 89 | 90 | // Collect Asset Token (reverts on fail) 91 | // Has to be msg.sender, otherwise anyone could energize anyone else's particles, 92 | // with the victim's assets, provided the victim has approved this contract in the past. 93 | // If contracts wish to energize a particle, they must first collect the asset 94 | // from the user, and approve this contract to transfer from the source contract 95 | _collectAssetToken(msg.sender, _assetAmount); 96 | 97 | // Tokenize Interest 98 | uint256 _interestAmount = _tokenizeInterest(_contractAddress, _assetAmount); 99 | 100 | // Track Asset Token Balance (Mass of each Particle) 101 | assetTokenBalance[_tokenUuid] = _assetAmount.add(_existingBalance); 102 | 103 | // Track Interest-bearing Token Balance (Charge of each Particle) 104 | interestTokenBalance[_tokenUuid] = _interestAmount.add(_existingInterest); 105 | 106 | // Return amount of Interest-bearing Token energized 107 | return _interestAmount; 108 | } 109 | 110 | /***********************************| 111 | | Discharge Particles | 112 | |__________________________________*/ 113 | 114 | /** 115 | * @notice Allows the owner or operator of the Token to collect or transfer the interest generated 116 | * from the token without removing the underlying Asset that is held within the token. 117 | * @param _receiver The Address to Receive the Discharged Asset Tokens 118 | * @param _tokenUuid The ID of the Token to Discharge 119 | * @return Two values; 1: Amount of Asset Token Received, 2: Remaining Charge of the Token 120 | */ 121 | function dischargeParticle( 122 | address _receiver, 123 | uint256 _tokenUuid 124 | ) 125 | external 126 | override 127 | onlyEscrow 128 | whenNotPaused 129 | returns (uint256, uint256) 130 | { 131 | uint256 _currentCharge = _currentParticleCharge(_tokenUuid); 132 | return _discharge(_receiver, _tokenUuid, _currentCharge); 133 | } 134 | 135 | /** 136 | * @notice Allows the owner or operator of the Token to collect or transfer a specific amount the interest 137 | * generated from the token without removing the underlying Asset that is held within the token. 138 | * @param _receiver The Address to Receive the Discharged Asset Tokens 139 | * @param _tokenUuid The ID of the Token to Discharge 140 | * @param _assetAmount The specific amount of Asset Token to Discharge from the Token 141 | * @return Two values; 1: Amount of Asset Token Received, 2: Remaining Charge of the Token 142 | */ 143 | function dischargeParticleAmount( 144 | address _receiver, 145 | uint256 _tokenUuid, 146 | uint256 _assetAmount 147 | ) 148 | external 149 | override 150 | onlyEscrow 151 | whenNotPaused 152 | returns (uint256, uint256) 153 | { 154 | return _discharge(_receiver, _tokenUuid, _assetAmount); 155 | } 156 | 157 | /** 158 | * @notice Releases the Full amount of Asset + Interest held within the Particle by Asset-Pair 159 | * Tokens that require Burn before Release MUST call "finalizeRelease" after Burning the Token. 160 | * In such cases, the Order of Operations should be: 161 | * 1. call "releaseParticle" 162 | * 2. Burn Token 163 | * 3. call "finalizeRelease" 164 | * This should be done in a single, atomic transaction 165 | * 166 | * @param _receiver The Address to Receive the Released Asset Tokens 167 | * @param _tokenUuid The ID of the Token to Release 168 | * @return The Total Amount of Asset Token Released including all converted Interest 169 | */ 170 | function releaseParticle( 171 | address _receiver, 172 | uint256 _tokenUuid 173 | ) 174 | external 175 | override 176 | onlyEscrow 177 | returns (uint256) 178 | { 179 | require(_baseParticleMass(_tokenUuid) > 0, "CHE: INSUFF_MASS"); 180 | 181 | // Release Particle to Receiver 182 | return _payoutFull(_tokenUuid, _receiver); 183 | } 184 | 185 | /***********************************| 186 | | Collect Fees | 187 | |__________________________________*/ 188 | 189 | /** 190 | * @notice Allows External Contracts to withdraw any Fees earned 191 | * @param _contractAddress The Address to the External Contract to withdraw Collected Fees for 192 | * @param _receiver The Address of the Receiver of the Collected Fees 193 | */ 194 | function withdrawFees(address _contractAddress, address _receiver) external override onlyEscrow returns (uint256) { 195 | uint256 _interestAmount = collectedFees[_contractAddress]; 196 | if (_interestAmount > 0) { 197 | collectedFees[_contractAddress] = 0; 198 | _withdrawFees(_receiver, _interestAmount); 199 | } 200 | return _interestAmount; 201 | } 202 | 203 | /***********************************| 204 | | Internal Functions | 205 | |__________________________________*/ 206 | 207 | /** 208 | * @notice Gets the Amount of Asset Tokens that have been Deposited into the Particle 209 | * representing the Mass of the Particle. 210 | * @param _tokenUuid The ID of the Token within the External Contract 211 | * @return The Amount of underlying Assets held within the Token 212 | */ 213 | function _baseParticleMass(uint256 _tokenUuid) internal view returns (uint256) { 214 | return assetTokenBalance[_tokenUuid]; 215 | } 216 | 217 | /** 218 | * @notice Gets the amount of Interest that the Particle has generated representing 219 | * the Charge of the Particle 220 | * @param _tokenUuid The ID of the Token within the External Contract 221 | * @return The amount of interest the Token has generated (in Asset Token) 222 | */ 223 | function _currentParticleCharge(uint256 _tokenUuid) internal returns (uint256) { 224 | uint256 _rawBalance = interestTokenBalance[_tokenUuid]; 225 | uint256 _currentCharge = interestToken.toAsset(_rawBalance); 226 | uint256 _originalCharge = assetTokenBalance[_tokenUuid]; 227 | if (_originalCharge >= _currentCharge) { return 0; } 228 | return _currentCharge.sub(_originalCharge); 229 | } 230 | 231 | /** 232 | * @dev Collects the Required Asset Token from the users wallet 233 | */ 234 | function _collectAssetToken(address _from, uint256 _assetAmount) internal { 235 | uint256 _userAssetBalance = assetToken.balanceOf(_from); 236 | require(_assetAmount <= _userAssetBalance, "CHE: INSUFF_ASSETS"); 237 | // Be sure to Approve this Contract to transfer your Asset Token 238 | require(assetToken.transferFrom(_from, address(this), _assetAmount), "CHE: TRANSFER_FAILED"); 239 | } 240 | 241 | /** 242 | * @dev Converts an Asset Token to an Interest Token 243 | */ 244 | function _tokenizeInterest( 245 | address _contractAddress, 246 | uint256 _assetAmount 247 | ) 248 | internal 249 | returns (uint256) 250 | { 251 | address _self = address(this); 252 | uint256 _preBalance = interestToken.interestBalance(_self); 253 | interestToken.depositAsset(_self, _assetAmount); 254 | uint256 _postBalance = interestToken.interestBalance(_self); 255 | return _getMassByDeposit(_contractAddress, _postBalance.sub(_preBalance)); 256 | } 257 | 258 | /** 259 | * @dev Discharges the Interest from a Token 260 | */ 261 | function _discharge( 262 | address _receiver, 263 | uint256 _tokenUuid, 264 | uint256 _assetAmount 265 | ) 266 | internal 267 | returns (uint256, uint256) 268 | { 269 | // Validate Discharge Amount 270 | uint256 _currentCharge = _currentParticleCharge(_tokenUuid); 271 | require(_currentCharge > 0, "CHE: INSUFF_CHARGE"); 272 | require(_currentCharge <= _assetAmount, "CHE: INSUFF_BALANCE"); 273 | 274 | // Precalculate Amount to Discharge to Receiver 275 | (uint256 _interestAmount, uint256 _receivedAmount) = _siphonAsset(_assetAmount); 276 | 277 | // Track Interest-bearing Token Balance (Mass of each Particle) 278 | uint256 _interestBalance = interestTokenBalance[_tokenUuid].sub(_interestAmount); 279 | interestTokenBalance[_tokenUuid] = _interestBalance; 280 | 281 | // Transfer Assets to Receiver 282 | _payoutAssets(_receiver, _receivedAmount); 283 | 284 | // AmountReceived, Remaining charge 285 | return (_receivedAmount, _currentCharge.sub(_receivedAmount)); 286 | } 287 | 288 | /** 289 | * @dev Collects a Specified Asset Amount of the Asset Token from the Interest Token stored for the Particle 290 | */ 291 | function _siphonAsset(uint256 _assetAmount) internal returns (uint256, uint256) { 292 | address _self = address(this); 293 | 294 | // Collect Interest 295 | // contract receives Asset Token, 296 | // function call returns amount of Interest-token exchanged 297 | uint256 _preAssetAmount = assetToken.balanceOf(_self); 298 | uint256 _interestAmount = interestToken.withdrawAsset(_self, _assetAmount); 299 | uint256 _postAssetAmount = assetToken.balanceOf(_self); 300 | uint256 _receivedAmount = _postAssetAmount.sub(_preAssetAmount); 301 | 302 | // Transfer Interest 303 | return (_interestAmount, _receivedAmount); 304 | } 305 | 306 | /** 307 | * @dev Collects a Specified Interest Amount of the Asset Token from the Interest Token stored for the Particle 308 | */ 309 | function _siphonInterest(uint256 _interestAmount) internal returns (uint256, uint256) { 310 | address _self = address(this); 311 | 312 | // Collect Interest 313 | // contract receives Asset Token, 314 | uint256 _preAssetAmount = assetToken.balanceOf(_self); 315 | interestToken.withdrawInterest(_self, _interestAmount); 316 | uint256 _postAssetAmount = assetToken.balanceOf(_self); 317 | uint256 _receivedAmount = _postAssetAmount.sub(_preAssetAmount); 318 | 319 | // Transfer Interest 320 | return (_interestAmount, _receivedAmount); 321 | } 322 | 323 | /** 324 | * @dev Pays out a specified amount of the Asset Token 325 | */ 326 | function _payoutAssets(address _receiver, uint256 _assetAmount) internal { 327 | address _self = address(this); 328 | require(assetToken.transferFrom(_self, _receiver, _assetAmount), "CHE: TRANSFER_FAILED"); 329 | } 330 | 331 | /** 332 | * @dev Pays out the full amount of the Asset Token + Interest Token 333 | */ 334 | function _payoutFull(uint256 _tokenUuid, address _receiver) internal returns (uint256) { 335 | // Get Interest-bearing Token Balance & Reset 336 | uint256 _interestAmount = interestTokenBalance[_tokenUuid]; 337 | interestTokenBalance[_tokenUuid] = 0; 338 | 339 | // Determine Amount of Assets to Transfer to Receiver 340 | (, uint256 _receivedAmount) = _siphonInterest(_interestAmount); 341 | 342 | // Transfer Assets to Receiver 343 | _payoutAssets(_receiver, _receivedAmount); 344 | 345 | return _receivedAmount; 346 | } 347 | 348 | /** 349 | * @dev Withdraws Fees in the form of Asset Tokens 350 | */ 351 | function _withdrawFees(address _receiver, uint256 _interestAmount) internal { 352 | // Determine Amount of Assets to Transfer to Receiver 353 | (, uint256 _receivedAmount) = _siphonInterest(_interestAmount); 354 | 355 | // Transfer Assets to Receiver 356 | _payoutAssets(_receiver, _receivedAmount); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /contracts/assets/chai/ChaiNucleus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ChaiNucleus.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | 25 | // [original] chai.sol -- a dai savings token 26 | // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico, lucasvo, livnev 27 | 28 | // This program is free software: you can redistribute it and/or modify 29 | // it under the terms of the GNU Affero General Public License as published by 30 | // the Free Software Foundation, either version 3 of the License, or 31 | // (at your option) any later version. 32 | // 33 | // This program is distributed in the hope that it will be useful, 34 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 35 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 36 | // GNU Affero General Public License for more details. 37 | // 38 | // You should have received a copy of the GNU Affero General Public License 39 | // along with this program. If not, see . 40 | 41 | 42 | // Modified for use in Charged Particles by @robsecord ; 43 | // added functions to read interest by specific amount rather than entire user balance 44 | // removed permit since this Chai should only be controlled 45 | // via the ChargedParticles contract 46 | 47 | 48 | // Resources: 49 | // ~~~~~~~~~~ 50 | // https://github.com/dapphub/chai/blob/master/src/chai.sol 51 | // https://github.com/makerdao/developerguides/blob/master/dai/dsr-integration-guide/dsr-integration-guide-01.md#smart-contract-addresses-and-abis 52 | // 53 | // Contract Addresses/ABI: 54 | // https://changelog.makerdao.com/releases/mainnet/1.0.0/index.html 55 | // https://changelog.makerdao.com/releases/kovan/0.2.17/index.html 56 | 57 | 58 | pragma solidity 0.6.10; 59 | 60 | import "../../interfaces/INucleus.sol"; 61 | 62 | interface VatLike { 63 | function hope(address) external; 64 | } 65 | 66 | interface PotLike { 67 | function chi() external returns (uint256); 68 | function rho() external returns (uint256); 69 | function drip() external returns (uint256); 70 | function join(uint256) external; 71 | function exit(uint256) external; 72 | } 73 | 74 | interface JoinLike { 75 | function join(address, uint) external; 76 | function exit(address, uint) external; 77 | } 78 | 79 | interface GemLike { 80 | function transferFrom(address, address, uint) external returns (bool); 81 | function approve(address, uint) external returns (bool); 82 | } 83 | 84 | contract ChaiNucleus is INucleus { 85 | // --- Data --- 86 | VatLike public vat; 87 | PotLike public pot; 88 | JoinLike public daiJoin; 89 | GemLike public daiToken; 90 | 91 | // --- ERC20 Data --- 92 | string public constant name = "ParticleChai"; 93 | string public constant symbol = "PCHAI"; 94 | string public constant version = "1"; 95 | uint8 public constant decimals = 18; 96 | uint256 public totalSupply; 97 | 98 | mapping (address => uint) private balanceOf; 99 | 100 | event Approval(address indexed src, address indexed guy, uint wad); 101 | event Transfer(address indexed src, address indexed dst, uint wad); 102 | 103 | // --- Math --- 104 | uint constant RAY = 10 ** 27; 105 | function add(uint x, uint y) internal pure returns (uint z) { 106 | require((z = x + y) >= x); 107 | } 108 | function sub(uint x, uint y) internal pure returns (uint z) { 109 | require((z = x - y) <= x); 110 | } 111 | function mul(uint x, uint y) internal pure returns (uint z) { 112 | require(y == 0 || (z = x * y) / y == x); 113 | } 114 | function rmul(uint x, uint y) internal pure returns (uint z) { 115 | // always rounds down 116 | z = mul(x, y) / RAY; 117 | } 118 | function rdiv(uint x, uint y) internal pure returns (uint z) { 119 | // always rounds down 120 | z = mul(x, RAY) / y; 121 | } 122 | function rdivup(uint x, uint y) internal pure returns (uint z) { 123 | // always rounds up 124 | z = add(mul(x, RAY), sub(y, 1)) / y; 125 | } 126 | 127 | function initialize() public { 128 | vat.hope(address(daiJoin)); 129 | vat.hope(address(pot)); 130 | 131 | daiToken.approve(address(daiJoin), uint(-1)); 132 | } 133 | 134 | function initRopsten() public { 135 | vat = VatLike(0xFfCFcAA53b61cF5F332b4FBe14033c1Ff5A391eb); // MCD_VAT 136 | pot = PotLike(0x9588a660241aeA569B3965e2f00631f2C5eDaE33); // MCD_POT 137 | daiJoin = JoinLike(0xA0b569e9E0816A20Ab548D692340cC28aC7Be986); // MCD_JOIN_DAI 138 | daiToken = GemLike(0x31F42841c2db5173425b5223809CF3A38FEde360); // MCD_DAI 139 | 140 | initialize(); 141 | } 142 | 143 | function initKovan() public { 144 | vat = VatLike(0xbA987bDB501d131f766fEe8180Da5d81b34b69d9); // MCD_VAT 145 | pot = PotLike(0xEA190DBDC7adF265260ec4dA6e9675Fd4f5A78bb); // MCD_POT 146 | daiJoin = JoinLike(0x5AA71a3ae1C0bd6ac27A1f28e1415fFFB6F15B8c); // MCD_JOIN_DAI 147 | daiToken = GemLike(0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa); // MCD_DAI 148 | 149 | initialize(); 150 | } 151 | 152 | function initMainnet() public { 153 | vat = VatLike(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B); // MCD_VAT 154 | pot = PotLike(0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7); // MCD_POT 155 | daiJoin = JoinLike(0x9759A6Ac90977b93B58547b4A71c78317f391A28); // MCD_JOIN_DAI 156 | daiToken = GemLike(0x6B175474E89094C44Da98b954EedeAC495271d0F); // MCD_DAI 157 | 158 | initialize(); 159 | } 160 | 161 | /** 162 | * @dev Balance in Interest-bearing Token 163 | */ 164 | // function balanceOf(address _account) external returns (uint); 165 | function interestBalance(address _account) external override returns (uint) { 166 | return balanceOf[_account]; 167 | } 168 | 169 | /** 170 | * @dev Balance in Asset Token 171 | */ 172 | // function dai(address usr) external returns (uint wad); 173 | function assetBalance(address _account) external override returns (uint) { 174 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 175 | return rmul(chi, balanceOf[_account]); 176 | } 177 | 178 | /** 179 | * @dev Get amount of Asset Token equivalent to Interest Token 180 | */ 181 | // function dai(uint chai) external returns (uint wad); // Added 182 | function toAsset(uint _interestAmount) external override returns (uint) { 183 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 184 | return rmul(chi, _interestAmount); 185 | } 186 | 187 | /** 188 | * @dev Get amount of Interest Token equivalent to Asset Token 189 | */ 190 | // function chai(uint _dai) external returns (uint pie); // Added 191 | function toInterest(uint _assetAmount) external override returns (uint) { 192 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 193 | // rounding up ensures usr gets at least _assetAmount dai 194 | return rdivup(_assetAmount, chi); 195 | } 196 | 197 | /** 198 | * @dev Deposit Asset Token and receive Interest-bearing Token 199 | */ 200 | // function join(address dst, uint wad) external; 201 | function depositAsset(address _account, uint _assetAmount) external override { 202 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 203 | uint pie = rdiv(_assetAmount, chi); 204 | balanceOf[_account] = add(balanceOf[_account], pie); 205 | totalSupply = add(totalSupply, pie); 206 | 207 | daiToken.transferFrom(msg.sender, address(this), _assetAmount); 208 | daiJoin.join(address(this), _assetAmount); 209 | pot.join(pie); 210 | 211 | emit Transfer(address(0), _account, pie); 212 | } 213 | 214 | /** 215 | * @dev Withdraw amount specified in Interest-bearing Token 216 | */ 217 | // function exit(address src, uint wad) public; 218 | function withdrawInterest(address _account, uint _interestAmount) external override { 219 | _withdrawInterest(_account, _interestAmount); 220 | } 221 | 222 | /** 223 | * @dev Withdraw amount specified in Asset Token 224 | */ 225 | // function draw(address src, uint wad) external returns (uint _chai); 226 | function withdrawAsset(address _account, uint _assetAmount) external override returns (uint) { 227 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 228 | // rounding up ensures usr gets at least _assetAmount dai 229 | uint _chai = rdivup(_assetAmount, chi); 230 | _withdrawInterest(_account, _chai); 231 | return _chai; 232 | } 233 | 234 | /** 235 | * @dev Withdraw amount specified in Interest-bearing Token 236 | */ 237 | // function exit(address src, uint wad) public; 238 | function _withdrawInterest(address _account, uint _interestAmount) internal { 239 | require(_account == msg.sender, "pchai/insufficient-allowance"); 240 | require(balanceOf[_account] >= _interestAmount, "pchai/insufficient-balance"); 241 | 242 | balanceOf[_account] = sub(balanceOf[_account], _interestAmount); 243 | totalSupply = sub(totalSupply, _interestAmount); 244 | 245 | uint chi = (now > pot.rho()) ? pot.drip() : pot.chi(); 246 | pot.exit(_interestAmount); 247 | daiJoin.exit(msg.sender, rmul(chi, _interestAmount)); 248 | emit Transfer(_account, address(0), _interestAmount); 249 | } 250 | 251 | } 252 | -------------------------------------------------------------------------------- /contracts/assets/dai/Dai.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.10; 4 | 5 | contract Dai { 6 | // --- ERC20 Data --- 7 | string public constant name = "Dai Testcoin"; 8 | string public constant symbol = "DAI"; 9 | string public constant version = "1"; 10 | uint8 public constant decimals = 18; 11 | uint256 public totalSupply; 12 | 13 | mapping (address => uint) public balanceOf; 14 | mapping (address => mapping (address => uint)) public allowance; 15 | mapping (address => uint) public nonces; 16 | 17 | event Approval(address indexed src, address indexed guy, uint wad); 18 | event Transfer(address indexed src, address indexed dst, uint wad); 19 | 20 | // --- Math --- 21 | function add(uint x, uint y) internal pure returns (uint z) { 22 | require((z = x + y) >= x); 23 | } 24 | function sub(uint x, uint y) internal pure returns (uint z) { 25 | require((z = x - y) <= x); 26 | } 27 | 28 | constructor() public { 29 | } 30 | 31 | // --- Token --- 32 | function transfer(address dst, uint wad) external returns (bool) { 33 | return transferFrom(msg.sender, dst, wad); 34 | } 35 | function transferFrom(address src, address dst, uint wad) 36 | public returns (bool) 37 | { 38 | require(balanceOf[src] >= wad, "Dai/insufficient-balance"); 39 | if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { 40 | require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance"); 41 | allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); 42 | } 43 | balanceOf[src] = sub(balanceOf[src], wad); 44 | balanceOf[dst] = add(balanceOf[dst], wad); 45 | emit Transfer(src, dst, wad); 46 | return true; 47 | } 48 | function mint(address usr, uint wad) external { 49 | balanceOf[usr] = add(balanceOf[usr], wad); 50 | totalSupply = add(totalSupply, wad); 51 | emit Transfer(address(0), usr, wad); 52 | } 53 | function burn(address usr, uint wad) external { 54 | require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); 55 | if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) { 56 | require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance"); 57 | allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); 58 | } 59 | balanceOf[usr] = sub(balanceOf[usr], wad); 60 | totalSupply = sub(totalSupply, wad); 61 | emit Transfer(usr, address(0), wad); 62 | } 63 | function approve(address usr, uint wad) external returns (bool) { 64 | allowance[msg.sender][usr] = wad; 65 | emit Approval(msg.sender, usr, wad); 66 | return true; 67 | } 68 | 69 | // --- Alias --- 70 | function push(address usr, uint wad) external { 71 | transferFrom(msg.sender, usr, wad); 72 | } 73 | function pull(address usr, uint wad) external { 74 | transferFrom(usr, msg.sender, wad); 75 | } 76 | function move(address src, address dst, uint wad) external { 77 | transferFrom(src, dst, wad); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/interfaces/IChargedParticlesEscrowManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // IChargedParticlesEscrow.sol -- Interest-bearing NFTs 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | 26 | 27 | /** 28 | * @notice Interface for the Charged Particles Escrow 29 | */ 30 | interface IChargedParticlesEscrowManager { 31 | 32 | function isAssetPairEnabled(string calldata _assetPairId) external view returns (bool); 33 | function getAssetPairsCount() external view returns (uint); 34 | function getAssetPairByIndex(uint _index) external view returns (string calldata); 35 | function getAssetTokenEscrow(string calldata _assetPairId) external view returns (address); 36 | function getAssetTokenAddress(string calldata _assetPairId) external view returns (address); 37 | function getInterestTokenAddress(string calldata _assetPairId) external view returns (address); 38 | 39 | function getUUID(address _contractAddress, uint256 _id) external pure returns (uint256); 40 | function getAssetMinDeposit(address _contractAddress) external view returns (uint256); 41 | function getAssetMaxDeposit(address _contractAddress) external view returns (uint256); 42 | function getFeesForDeposit(address _contractAddress, uint256 _interestTokenAmount) external view returns (uint256, uint256); 43 | function getFeeForDeposit(address _contractAddress, uint256 _interestTokenAmount) external view returns (uint256); 44 | 45 | function setDischargeApproval(address _contractAddress, uint256 _tokenId, address _operator) external; 46 | function isApprovedForDischarge(address _contractAddress, uint256 _tokenId, address _operator) external view returns (bool); 47 | 48 | function baseParticleMass(address _contractAddress, uint256 _tokenId, string calldata _assetPairId) external view returns (uint256); 49 | function currentParticleCharge(address _contractAddress, uint256 _tokenId, string calldata _assetPairId) external returns (uint256); 50 | 51 | /***********************************| 52 | | Register Contract Settings | 53 | |(For External Contract Integration)| 54 | |__________________________________*/ 55 | 56 | function isContractOwner(address _account, address _contract) external view returns (bool); 57 | function registerContractType(address _contractAddress) external; 58 | function registerContractSettingReleaseBurn(address _contractAddress, bool _releaseRequiresBurn) external; 59 | function registerContractSettingAssetPair(address _contractAddress, string calldata _assetPairId) external; 60 | function registerContractSettingDepositFee(address _contractAddress, uint256 _depositFee) external; 61 | function registerContractSettingMinDeposit(address _contractAddress, uint256 _minDeposit) external; 62 | function registerContractSettingMaxDeposit(address _contractAddress, uint256 _maxDeposit) external; 63 | 64 | function withdrawContractFees(address _contractAddress, address _receiver, string calldata _assetPairId) external; 65 | 66 | /***********************************| 67 | | Particle Charge | 68 | |__________________________________*/ 69 | 70 | function energizeParticle( 71 | address _contractAddress, 72 | uint256 _tokenId, 73 | string calldata _assetPairId, 74 | uint256 _assetAmount 75 | ) external returns (uint256); 76 | 77 | function dischargeParticle( 78 | address _receiver, 79 | address _contractAddress, 80 | uint256 _tokenId, 81 | string calldata _assetPairId 82 | ) external returns (uint256, uint256); 83 | 84 | function dischargeParticleAmount( 85 | address _receiver, 86 | address _contractAddress, 87 | uint256 _tokenId, 88 | string calldata _assetPairId, 89 | uint256 _assetAmount 90 | ) external returns (uint256, uint256); 91 | 92 | function releaseParticle( 93 | address _receiver, 94 | address _contractAddress, 95 | uint256 _tokenId, 96 | string calldata _assetPairId 97 | ) external returns (uint256); 98 | 99 | function finalizeRelease( 100 | address _receiver, 101 | address _contractAddress, 102 | uint256 _tokenId, 103 | string calldata _assetPairId 104 | ) external returns (uint256); 105 | } 106 | -------------------------------------------------------------------------------- /contracts/interfaces/IChargedParticlesTokenManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // IChargedParticlesTokenManager.sol -- Token Manager 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | pragma experimental ABIEncoderV2; 26 | 27 | /** 28 | * @notice Interface for Charged Particles ERC1155 - Token Manager 29 | */ 30 | interface IChargedParticlesTokenManager { 31 | function isNonFungible(uint256 _id) external pure returns(bool); 32 | function isFungible(uint256 _id) external pure returns(bool); 33 | function getNonFungibleIndex(uint256 _id) external pure returns(uint256); 34 | function getNonFungibleBaseType(uint256 _id) external pure returns(uint256); 35 | function isNonFungibleBaseType(uint256 _id) external pure returns(bool); 36 | function isNonFungibleItem(uint256 _id) external pure returns(bool); 37 | 38 | function createType(string calldata _uri, bool isNF) external returns (uint256); 39 | 40 | function mint( 41 | address _to, 42 | uint256 _typeId, 43 | uint256 _amount, 44 | string calldata _uri, 45 | bytes calldata _data 46 | ) external returns (uint256); 47 | 48 | // function mintBatch( 49 | // address _to, 50 | // uint256[] calldata _types, 51 | // uint256[] calldata _amounts, 52 | // string[] calldata _uris, 53 | // bytes calldata _data 54 | // ) external returns (uint256[] memory); 55 | 56 | function burn( 57 | address _from, 58 | uint256 _tokenId, 59 | uint256 _amount 60 | ) external; 61 | 62 | // function burnBatch( 63 | // address _from, 64 | // uint256[] calldata _tokenIds, 65 | // uint256[] calldata _amounts 66 | // ) external; 67 | 68 | function createErc20Bridge(uint256 _typeId, string calldata _name, string calldata _symbol, uint8 _decimals) external returns (address); 69 | function createErc721Bridge(uint256 _typeId, string calldata _name, string calldata _symbol) external returns (address); 70 | 71 | function uri(uint256 _id) external view returns (string memory); 72 | function totalSupply(uint256 _typeId) external view returns (uint256); 73 | function totalMinted(uint256 _typeId) external view returns (uint256); 74 | function ownerOf(uint256 _tokenId) external view returns (address); 75 | function balanceOf(address _tokenOwner, uint256 _typeId) external view returns (uint256); 76 | // function balanceOfBatch(address[] calldata _owners, uint256[] calldata _typeIds) external view returns (uint256[] memory); 77 | function isApprovedForAll(address _tokenOwner, address _operator) external view returns (bool); 78 | } 79 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.10; 4 | 5 | interface IERC1155 { 6 | 7 | /****************************************| 8 | | Events | 9 | |_______________________________________*/ 10 | 11 | /** 12 | * @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, 13 | * including zero amount transfers as well as minting or burning 14 | * Operator MUST be msg.sender 15 | * When minting/creating tokens, the `_from` field MUST be set to `0x0` 16 | * When burning/destroying tokens, the `_to` field MUST be set to `0x0` 17 | * The total amount transferred from address 0x0 minus the total amount transferred to 0x0 may be used by 18 | * clients and exchanges to be added to the "circulating supply" for a given token ID 19 | * To broadcast the existence of a token ID with no initial balance, the contract SHOULD emit the 20 | * TransferSingle event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_amount` of 0 21 | */ 22 | event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _amount); 23 | 24 | /** 25 | * @dev Either TransferSingle or TransferBatch MUST emit when tokens are transferred, 26 | * including zero amount transfers as well as minting or burning 27 | * Operator MUST be msg.sender 28 | * When minting/creating tokens, the `_from` field MUST be set to `0x0` 29 | * When burning/destroying tokens, the `_to` field MUST be set to `0x0` 30 | * The total amount transferred from address 0x0 minus the total amount transferred to 0x0 may be used by 31 | * clients and exchanges to be added to the "circulating supply" for a given token ID 32 | * To broadcast the existence of multiple token IDs with no initial balance, this SHOULD emit the 33 | * TransferBatch event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_amount` of 0 34 | */ 35 | event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts); 36 | 37 | /** 38 | * @dev MUST emit when an approval is updated 39 | */ 40 | event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 41 | 42 | /** 43 | * @dev MUST emit when the URI is updated for a token ID 44 | * URIs are defined in RFC 3986 45 | * The URI MUST point a JSON file that conforms to the "ERC-1155 Metadata JSON Schema" 46 | */ 47 | event URI(string _amount, uint256 indexed _id); 48 | 49 | 50 | /****************************************| 51 | | Functions | 52 | |_______________________________________*/ 53 | 54 | /** 55 | * @notice Transfers amount of an _id from the _from address to the _to address specified 56 | * @dev MUST emit TransferSingle event on success 57 | * Caller must be approved to manage the _from account's tokens (see isApprovedForAll) 58 | * MUST throw if `_to` is the zero address 59 | * MUST throw if balance of sender for token `_id` is lower than the `_amount` sent 60 | * MUST throw on any other error 61 | * When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). 62 | * If so, it MUST call `onERC1155Received` on `_to` and revert if the return amount is not 63 | * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` 64 | * @param _from Source address 65 | * @param _to Target address 66 | * @param _id ID of the token type 67 | * @param _amount Transfered amount 68 | * @param _data Additional data with no specified format, sent in call to `_to` 69 | */ 70 | function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes calldata _data) external; 71 | 72 | /** 73 | * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call) 74 | * @dev MUST emit TransferBatch event on success 75 | * Caller must be approved to manage the _from account's tokens (see isApprovedForAll) 76 | * MUST throw if `_to` is the zero address 77 | * MUST throw if length of `_ids` is not the same as length of `_amounts` 78 | * MUST throw if any of the balance of sender for token `_ids` is lower than the respective `_amounts` sent 79 | * MUST throw on any other error 80 | * When transfer is complete, this function MUST check if `_to` is a smart contract (code size > 0). 81 | * If so, it MUST call `onERC1155BatchReceived` on `_to` and revert if the return amount is not 82 | * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` 83 | * Transfers and events MUST occur in the array order they were submitted (_ids[0] before _ids[1], etc) 84 | * @param _from Source addresses 85 | * @param _to Target addresses 86 | * @param _ids IDs of each token type 87 | * @param _amounts Transfer amounts per token type 88 | * @param _data Additional data with no specified format, sent in call to `_to` 89 | */ 90 | function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _amounts, bytes calldata _data) external; 91 | 92 | /** 93 | * @notice Get the balance of an account's Tokens 94 | * @param _owner The address of the token holder 95 | * @param _id ID of the Token 96 | * @return The _owner's balance of the Token type requested 97 | */ 98 | function balanceOf(address _owner, uint256 _id) external view returns (uint256); 99 | 100 | /** 101 | * @notice Get the balance of multiple account/token pairs 102 | * @param _owners The addresses of the token holders 103 | * @param _ids ID of the Tokens 104 | * @return The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair) 105 | */ 106 | // function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory); 107 | 108 | /** 109 | * @notice Enable or disable approval for a third party ("operator") to manage all of caller's tokens 110 | * @dev MUST emit the ApprovalForAll event on success 111 | * @param _operator Address to add to the set of authorized operators 112 | * @param _approved True if the operator is approved, false to revoke approval 113 | */ 114 | function setApprovalForAll(address _operator, bool _approved) external; 115 | 116 | /** 117 | * @notice Queries the approval status of an operator for a given owner 118 | * @param _owner The owner of the Tokens 119 | * @param _operator Address of authorized operator 120 | * @return isOperator True if the operator is approved, false if not 121 | */ 122 | function isApprovedForAll(address _owner, address _operator) external view returns (bool isOperator); 123 | } 124 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC1155TokenReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.6.10; 4 | 5 | /** 6 | * @dev ERC-1155 interface for accepting safe transfers. 7 | */ 8 | interface IERC1155TokenReceiver { 9 | 10 | /** 11 | * @notice Handle the receipt of a single ERC1155 token type 12 | * @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, 13 | * at the end of a `safeTransferFrom` after the balance has been updated 14 | * This function MAY throw to revert and reject the transfer 15 | * Return of other amount than the magic value MUST result in the transaction being reverted 16 | * Note: The token contract address is always the message sender 17 | * @param _operator The address which called the `safeTransferFrom` function 18 | * @param _from The address which previously owned the token 19 | * @param _id The id of the token being transferred 20 | * @param _amount The amount of tokens being transferred 21 | * @param _data Additional data with no specified format 22 | * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` 23 | */ 24 | function onERC1155Received( 25 | address _operator, 26 | address _from, 27 | uint256 _id, 28 | uint256 _amount, 29 | bytes calldata _data 30 | ) external returns(bytes4); 31 | 32 | /** 33 | * @notice Handle the receipt of multiple ERC1155 token types 34 | * @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, 35 | * at the end of a `safeBatchTransferFrom` after the balances have been updated 36 | * This function MAY throw to revert and reject the transfer 37 | * Return of other amount than the magic value WILL result in the transaction being reverted 38 | * Note: The token contract address is always the message sender 39 | * @param _operator The address which called the `safeBatchTransferFrom` function 40 | * @param _from The address which previously owned the token 41 | * @param _ids An array containing ids of each token being transferred 42 | * @param _amounts An array containing amounts of each token being transferred 43 | * @param _data Additional data with no specified format 44 | * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` 45 | */ 46 | function onERC1155BatchReceived( 47 | address _operator, 48 | address _from, 49 | uint256[] calldata _ids, 50 | uint256[] calldata _amounts, 51 | bytes calldata _data 52 | ) external returns(bytes4); 53 | 54 | /** 55 | * @notice Indicates whether a contract implements the `ERC1155TokenReceiver` functions and so can accept ERC1155 token types. 56 | * @param interfaceID The ERC-165 interface ID that is queried for support.s 57 | * @dev This function MUST return true if it implements the ERC1155TokenReceiver interface and ERC-165 interface. 58 | * This function MUST NOT consume more than 5,000 gas. 59 | * @return Whether ERC-165 or ERC1155TokenReceiver interfaces are supported. 60 | */ 61 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 62 | } 63 | -------------------------------------------------------------------------------- /contracts/interfaces/IEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // INucleus.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity ^0.6.10; 25 | 26 | /** 27 | * @title Particle Escrow interface 28 | * @dev The base escrow for underlying assets attached to Charged Particles 29 | */ 30 | interface IEscrow { 31 | // 32 | // Must override: 33 | // 34 | function isPaused() external view returns (bool); 35 | function baseParticleMass(uint256 _tokenUuid) external view returns (uint256); 36 | function currentParticleCharge(uint256 _tokenUuid) external returns (uint256); 37 | function energizeParticle(address _contractAddress, uint256 _tokenUuid, uint256 _assetAmount) external returns (uint256); 38 | 39 | function dischargeParticle(address _receiver, uint256 _tokenUuid) external returns (uint256, uint256); 40 | function dischargeParticleAmount(address _receiver, uint256 _tokenUuid, uint256 _assetAmount) external returns (uint256, uint256); 41 | 42 | function releaseParticle(address _receiver, uint256 _tokenUuid) external returns (uint256); 43 | 44 | function withdrawFees(address _contractAddress, address _receiver) external returns (uint256); 45 | 46 | // 47 | // Inherited from EscrowBase: 48 | // 49 | function getAssetTokenAddress() external view returns (address); 50 | function getInterestTokenAddress() external view returns (address); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/interfaces/INucleus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // INucleus.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity ^0.6.10; 25 | 26 | /** 27 | * @title Particle Nucleus interface 28 | * @dev The base layer for underlying assets attached to Charged Particles 29 | */ 30 | interface INucleus { 31 | // Balance in Asset Token 32 | function assetBalance(address _account) external returns (uint); 33 | 34 | // Balance in Interest-bearing Token 35 | function interestBalance(address _account) external returns (uint); 36 | 37 | // Get amount of Asset Token equivalent to Interest Token 38 | function toAsset(uint _interestAmount) external returns (uint); 39 | 40 | // Get amount of Interest Token equivalent to Asset Token 41 | function toInterest(uint _assetAmount) external returns (uint); 42 | 43 | // Deposit Asset Token and receive Interest-bearing Token 44 | function depositAsset(address _account, uint _assetAmount) external; 45 | 46 | // Withdraw amount specified in Interest-bearing Token 47 | function withdrawInterest(address _account, uint _interestAmount) external; 48 | 49 | // Withdraw amount specified in Asset Token 50 | function withdrawAsset(address _account, uint _assetAmount) external returns (uint); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/interfaces/IParticleManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // IParticleManager.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity >=0.6.0; 25 | 26 | interface IParticleManager { 27 | function contractOwner() external view returns (address); 28 | function ownerOf(uint256 _tokenId) external view returns (address); 29 | function isApprovedForAll(address _owner, address _operator) external view returns (bool); 30 | } 31 | -------------------------------------------------------------------------------- /contracts/lib/Common.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // Common.sol -- Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | 26 | 27 | /** 28 | * @notice Common Vars for Charged Particles 29 | */ 30 | contract Common { 31 | 32 | uint256 constant internal DEPOSIT_FEE_MODIFIER = 1e4; // 10000 (100%) 33 | uint256 constant internal MAX_CUSTOM_DEPOSIT_FEE = 5e3; // 5000 (50%) 34 | uint256 constant internal MIN_DEPOSIT_FEE = 1e6; // 1000000 (0.000000000001 ETH or 1000000 WEI) 35 | 36 | bytes32 constant public ROLE_DAO_GOV = keccak256("ROLE_DAO_GOV"); 37 | bytes32 constant public ROLE_MAINTAINER = keccak256("ROLE_MAINTAINER"); 38 | 39 | // Fungibility-Type Flags 40 | uint256 constant internal TYPE_MASK = uint256(uint128(~0)) << 128; 41 | uint256 constant internal NF_INDEX_MASK = uint128(~0); 42 | uint256 constant internal TYPE_NF_BIT = 1 << 255; 43 | 44 | // Interface Signatures 45 | bytes4 constant internal INTERFACE_SIGNATURE_ERC165 = 0x01ffc9a7; 46 | bytes4 constant internal INTERFACE_SIGNATURE_ERC721 = 0x80ac58cd; 47 | bytes4 constant internal INTERFACE_SIGNATURE_ERC1155 = 0xd9b67a26; 48 | bytes4 constant internal ERC1155_RECEIVED_VALUE = 0xf23a6e61; 49 | bytes4 constant internal ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /contracts/lib/ERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // ERC1155.sol - Charged Particles 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | pragma experimental ABIEncoderV2; 26 | 27 | import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol"; 28 | import "@openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol"; 29 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 30 | import "@openzeppelin/contracts-ethereum-package/contracts/introspection/IERC165.sol"; 31 | 32 | import "../interfaces/IChargedParticlesTokenManager.sol"; 33 | import "../interfaces/IERC1155TokenReceiver.sol"; 34 | 35 | import "./Common.sol"; 36 | 37 | 38 | /** 39 | * @notice Implementation of ERC1155 Multi-Token Standard contract 40 | */ 41 | abstract contract ERC1155 is Initializable, Common, IChargedParticlesTokenManager, IERC165 { 42 | using Address for address; 43 | using SafeMath for uint256; 44 | 45 | // Type Nonce for each Unique Type 46 | uint256 internal nonce; 47 | 48 | // 49 | // Generic (ERC20 & ERC721) 50 | // 51 | // Account => Operator => Allowed? 52 | mapping (address => mapping (address => bool)) internal operators; // Operator Approval for All Tokens by Type 53 | // TypeID => Total Minted Supply 54 | mapping (uint256 => uint256) internal mintedByType; 55 | 56 | // 57 | // ERC20 Specific 58 | // 59 | // Account => TypeID => ERC20 Balance 60 | mapping (address => mapping (uint256 => uint256)) internal balances; 61 | // TypeID => Total Circulating Supply (reduced on burn) 62 | mapping (uint256 => uint256) internal supplyByType; 63 | 64 | // 65 | // ERC721 Specific 66 | // 67 | // TokenID => Operator 68 | mapping (uint256 => address) internal tokenApprovals; // Operator Approval per Token 69 | // TokenID => Owner 70 | mapping (uint256 => address) internal nfOwners; 71 | // TokenID => URI for the Token Metadata 72 | mapping (uint256 => string) internal tokenUri; 73 | // 74 | // Enumerable NFTs 75 | mapping (uint256 => mapping (uint256 => uint256)) internal ownedTokensByTypeIndex; 76 | mapping (uint256 => mapping (uint256 => uint256)) internal allTokensByTypeIndex; 77 | mapping (uint256 => mapping (address => uint256[])) internal ownedTokensByType; 78 | mapping (uint256 => uint256[]) internal allTokensByType; // Circulating Supply 79 | 80 | // 81 | // Events 82 | // 83 | 84 | event TransferSingle( 85 | address indexed _operator, 86 | address indexed _from, 87 | address indexed _to, 88 | uint256 _id, 89 | uint256 _amount 90 | ); 91 | 92 | event TransferBatch( 93 | address indexed _operator, 94 | address indexed _from, 95 | address indexed _to, 96 | uint256[] _ids, 97 | uint256[] _amounts 98 | ); 99 | 100 | event Approval( 101 | address indexed _owner, 102 | address indexed _operator, 103 | uint256 indexed _tokenId 104 | ); 105 | 106 | event ApprovalForAll( 107 | address indexed _owner, 108 | address indexed _operator, 109 | bool _approved 110 | ); 111 | 112 | event URI( 113 | uint256 indexed _id, // ID = Type or Token ID 114 | string _uri 115 | ); 116 | 117 | /***********************************| 118 | | Initialization | 119 | |__________________________________*/ 120 | 121 | function initialize() public virtual initializer { 122 | } 123 | 124 | /***********************************| 125 | | Public Read | 126 | |__________________________________*/ 127 | 128 | /** 129 | * @notice Gets the URI of the Token Metadata 130 | * @param _id The Type ID of the Token to get the URI for 131 | * @return The URI of the Token Metadata 132 | */ 133 | function uri(uint256 _id) external override view returns (string memory) { 134 | return tokenUri[_id]; 135 | } 136 | 137 | /** 138 | * @notice Checks if a specific token interface is supported 139 | * @param _interfaceID The ID of the Interface to check 140 | * @return True if the interface ID is supported 141 | */ 142 | function supportsInterface(bytes4 _interfaceID) external override view returns (bool) { 143 | if (_interfaceID == INTERFACE_SIGNATURE_ERC165 || 144 | _interfaceID == INTERFACE_SIGNATURE_ERC1155) { 145 | return true; 146 | } 147 | return false; 148 | } 149 | 150 | /** 151 | * @notice Gets the Total Circulating Supply of a Token-Type 152 | * @param _typeId The Type ID of the Token 153 | * @return The Total Circulating Supply of the Token-Type 154 | */ 155 | function totalSupply(uint256 _typeId) external override view returns (uint256) { 156 | return _totalSupply(_typeId); 157 | } 158 | 159 | /** 160 | * @notice Gets the Total Minted Supply of a Token-Type 161 | * @param _typeId The Type ID of the Token 162 | * @return The Total Minted Supply of the Token-Type 163 | */ 164 | function totalMinted(uint256 _typeId) external override view returns (uint256) { 165 | return _totalMinted(_typeId); 166 | } 167 | 168 | /** 169 | * @notice Gets the Owner of a Non-fungible Token (ERC-721 only) 170 | * @param _tokenId The ID of the Token 171 | * @return The Address of the Owner of the Token 172 | */ 173 | function ownerOf(uint256 _tokenId) external override view returns (address) { 174 | return _ownerOf(_tokenId); 175 | } 176 | 177 | /** 178 | * @notice Get the balance of an account's Tokens 179 | * @param _tokenOwner The address of the token holder 180 | * @param _typeId The Type ID of the Token 181 | * @return The Owner's Balance of the Token-Type 182 | */ 183 | function balanceOf(address _tokenOwner, uint256 _typeId) external override view returns (uint256) { 184 | return _balanceOf(_tokenOwner, _typeId); 185 | } 186 | 187 | /** 188 | * @notice Get the balance of multiple account/token pairs 189 | * @param _owners The addresses of the token holders 190 | * @param _typeIds The Type IDs of the Tokens 191 | * @return The Owner's Balance of the Token-Types 192 | */ 193 | // function balanceOfBatch(address[] calldata _owners, uint256[] calldata _typeIds) external override view returns (uint256[] memory) { 194 | // return _balanceOfBatch(_owners, _typeIds); 195 | // } 196 | 197 | /** 198 | * @notice Gets a specific Token by Index of a Users Enumerable Non-fungible Tokens (ERC-721 only) 199 | * @param _typeId The Type ID of the Token 200 | * @param _owner The address of the Token Holder 201 | * @param _index The Index of the Token 202 | * @return The ID of the Token by Owner, Type & Index 203 | */ 204 | function tokenOfOwnerByIndex(uint256 _typeId, address _owner, uint256 _index) public view returns (uint256) { 205 | require(_index < _balanceOf(_owner, _typeId), "E1155: INVALID_INDEX"); 206 | return ownedTokensByType[_typeId][_owner][_index]; 207 | } 208 | 209 | /** 210 | * @notice Gets a specific Token by Index in All Enumerable Non-fungible Tokens (ERC-721 only) 211 | * @param _typeId The Type ID of the Token 212 | * @param _index The Index of the Token 213 | * @return The ID of the Token by Type & Index 214 | */ 215 | function tokenByIndex(uint256 _typeId, uint256 _index) public view returns (uint256) { 216 | require(_index < _totalSupply(_typeId), "E1155: INVALID_INDEX"); 217 | return allTokensByType[_typeId][_index]; 218 | } 219 | 220 | /** 221 | * @notice Sets an Operator as Approved to Manage a specific Token 222 | * @param _operator Address to add to the set of authorized operators 223 | * @param _tokenId The ID of the Token 224 | */ 225 | function approve(address _operator, uint256 _tokenId) public { 226 | address _owner = _ownerOf(_tokenId); 227 | require(_operator != _owner, "E1155: INVALID_OPERATOR"); 228 | require(msg.sender == _owner || isApprovedForAll(_owner, msg.sender), "E1155: NOT_OPERATOR"); 229 | 230 | tokenApprovals[_tokenId] = _operator; 231 | emit Approval(_owner, _operator, _tokenId); 232 | } 233 | 234 | /** 235 | * @notice Gets the Approved Operator of a specific Token 236 | * @param _tokenId The ID of the Token 237 | * @return The address of the approved operator 238 | */ 239 | function getApproved(uint256 _tokenId) public view returns (address) { 240 | address owner = _ownerOf(_tokenId); 241 | require(owner != address(0x0), "E1155: INVALID_TOKEN"); 242 | return tokenApprovals[_tokenId]; 243 | } 244 | 245 | /** 246 | * @notice Enable or disable approval for a third party ("operator") to manage all of caller's tokens 247 | * @param _operator Address to add to the set of authorized operators 248 | * @param _approved True if the operator is approved, false to revoke approval 249 | */ 250 | function setApprovalForAll(address _operator, bool _approved) public { 251 | operators[msg.sender][_operator] = _approved; 252 | emit ApprovalForAll(msg.sender, _operator, _approved); 253 | } 254 | 255 | /** 256 | * @notice Queries the approval status of an operator for a given owner 257 | * @param _tokenOwner The owner of the Tokens 258 | * @param _operator Address of authorized operator 259 | * @return True if the operator is approved, false if not 260 | */ 261 | function isApprovedForAll(address _tokenOwner, address _operator) public override view returns (bool) { 262 | return operators[_tokenOwner][_operator]; 263 | } 264 | 265 | /** 266 | * @notice Transfers amount of an _id from the _from address to the _to address specified 267 | * @param _from The Address of the Token Holder 268 | * @param _to The Address of the Token Receiver 269 | * @param _id ID of the Token 270 | * @param _amount The Amount to transfer 271 | */ 272 | function transferFrom(address _from, address _to, uint256 _id, uint256 _amount) public { 273 | require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), "E1155: NOT_OPERATOR"); 274 | require(_to != address(0x0), "E1155: INVALID_ADDRESS"); 275 | 276 | _safeTransferFrom(_from, _to, _id, _amount); 277 | } 278 | 279 | /** 280 | * @notice Safe-transfers amount of an _id from the _from address to the _to address specified 281 | * @param _from The Address of the Token Holder 282 | * @param _to The Address of the Token Receiver 283 | * @param _id ID of the Token 284 | * @param _amount The Amount to transfer 285 | * @param _data Additional data with no specified format, sent in call to `_to` 286 | */ 287 | function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data) public { 288 | require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), "E1155: NOT_OPERATOR"); 289 | require(_to != address(0x0), "E1155: INVALID_ADDRESS"); 290 | 291 | _safeTransferFrom(_from, _to, _id, _amount); 292 | _callonERC1155Received(_from, _to, _id, _amount, _data); 293 | } 294 | 295 | /** 296 | * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call) 297 | * @param _from The Address of the Token Holder 298 | * @param _to The Address of the Token Receiver 299 | * @param _ids IDs of each Token 300 | * @param _amounts The Amount to transfer per Token 301 | * @param _data Additional data with no specified format, sent in call to `_to` 302 | */ 303 | // function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) public { 304 | // require((msg.sender == _from) || isApprovedForAll(_from, msg.sender), "E1155: NOT_OPERATOR"); 305 | // require(_to != address(0x0),"E1155: INVALID_ADDRESS"); 306 | 307 | // _safeBatchTransferFrom(_from, _to, _ids, _amounts); 308 | // _callonERC1155BatchReceived(_from, _to, _ids, _amounts, _data); 309 | // } 310 | 311 | /***********************************| 312 | | Private Functions | 313 | |__________________________________*/ 314 | 315 | /** 316 | * @dev Gets the Total Circulating Supply of a Token-Type 317 | * @param _typeId The Type ID of the Token 318 | * @return The Total Circulating Supply of the Token-Type 319 | */ 320 | function _totalSupply(uint256 _typeId) internal view returns (uint256) { 321 | if (_typeId & TYPE_NF_BIT == TYPE_NF_BIT) { 322 | return allTokensByType[_typeId].length; 323 | } 324 | return supplyByType[_typeId]; 325 | } 326 | 327 | /** 328 | * @dev Gets the Total Minted Supply of a Token-Type 329 | * @param _typeId The Type ID of the Token 330 | * @return The Total Minted Supply of the Token-Type 331 | */ 332 | function _totalMinted(uint256 _typeId) internal view returns (uint256) { 333 | return mintedByType[_typeId]; 334 | } 335 | 336 | /** 337 | * @dev Gets the Owner of a Non-fungible Token (ERC-721 only) 338 | * @param _tokenId The ID of the Token 339 | * @return The Address of the Owner of the Token 340 | */ 341 | function _ownerOf(uint256 _tokenId) internal view returns (address) { 342 | require(_tokenId & TYPE_NF_BIT == TYPE_NF_BIT, "E1155: INVALID_TYPE"); 343 | return nfOwners[_tokenId]; 344 | } 345 | 346 | /** 347 | * @dev Get the balance of an account's Tokens 348 | * @param _tokenOwner The address of the token holder 349 | * @param _typeId The Type ID of the Token 350 | * @return The Owner's Balance of the Token-Type 351 | */ 352 | function _balanceOf(address _tokenOwner, uint256 _typeId) internal view returns (uint256) { 353 | // Non-fungible 354 | if (_typeId & TYPE_NF_BIT == TYPE_NF_BIT) { 355 | _typeId = _typeId & TYPE_MASK; 356 | return ownedTokensByType[_typeId][_tokenOwner].length; 357 | } 358 | // Fungible 359 | return balances[_tokenOwner][_typeId]; 360 | } 361 | 362 | /** 363 | * @dev Get the balance of multiple account/token pairs 364 | * @param _owners The addresses of the token holders 365 | * @param _typeIds The Type IDs of the Tokens 366 | * @return The Owner's Balance of the Token-Types 367 | */ 368 | function _balanceOfBatch(address[] memory _owners, uint256[] memory _typeIds) internal view returns (uint256[] memory) { 369 | require(_owners.length == _typeIds.length, "E1155: ARRAY_LEN_MISMATCH"); 370 | 371 | uint256[] memory _balances = new uint256[](_owners.length); 372 | for (uint256 i = 0; i < _owners.length; ++i) { 373 | uint256 id = _typeIds[i]; 374 | address owner = _owners[i]; 375 | 376 | // Non-fungible 377 | if (id & TYPE_NF_BIT == TYPE_NF_BIT) { 378 | id = id & TYPE_MASK; 379 | _balances[i] = ownedTokensByType[id][owner].length; 380 | } 381 | // Fungible 382 | else { 383 | _balances[i] = balances[owner][id]; 384 | } 385 | } 386 | 387 | return _balances; 388 | } 389 | 390 | /** 391 | * @dev Transfers amount amount of an _id from the _from address to the _to address specified 392 | * @param _from The Address of the Token Holder 393 | * @param _to The Address of the Token Receiver 394 | * @param _id ID of the Token 395 | * @param _amount The Amount to transfer 396 | */ 397 | function _safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount) internal { 398 | // Non-Fungible 399 | if (_id & TYPE_NF_BIT == TYPE_NF_BIT) { 400 | uint256 _typeId = _id & TYPE_MASK; 401 | 402 | require(nfOwners[_id] == _from, "E1155: INVALID_OWNER"); 403 | nfOwners[_id] = _to; 404 | _amount = 1; 405 | 406 | _removeTokenFromOwnerEnumeration(_typeId, _from, _id); 407 | _addTokenToOwnerEnumeration(_typeId, _to, _id); 408 | } 409 | // Fungible 410 | else { 411 | // require(_amount <= balances[_from][_id]); // SafeMath will throw if balance is negative 412 | balances[_from][_id] = balances[_from][_id].sub(_amount); // Subtract amount 413 | balances[_to][_id] = balances[_to][_id].add(_amount); // Add amount 414 | } 415 | 416 | // Emit event 417 | emit TransferSingle(msg.sender, _from, _to, _id, _amount); 418 | } 419 | 420 | /** 421 | * @dev Send multiple types of Tokens from the _from address to the _to address (with safety call) 422 | * @param _from The Address of the Token Holder 423 | * @param _to The Address of the Token Receiver 424 | * @param _ids IDs of each Token 425 | * @param _amounts The Amount to transfer per Token 426 | */ 427 | function _safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts) internal { 428 | require(_ids.length == _amounts.length, "E1155: ARRAY_LEN_MISMATCH"); 429 | 430 | uint256 id; 431 | uint256 amount; 432 | uint256 typeId; 433 | uint256 nTransfer = _ids.length; 434 | for (uint256 i = 0; i < nTransfer; ++i) { 435 | id = _ids[i]; 436 | amount = _amounts[i]; 437 | 438 | if (id & TYPE_NF_BIT == TYPE_NF_BIT) { // Non-Fungible 439 | typeId = id & TYPE_MASK; 440 | require(nfOwners[id] == _from, "E1155: INVALID_OWNER"); 441 | nfOwners[id] = _to; 442 | 443 | _removeTokenFromOwnerEnumeration(typeId, _from, id); 444 | _addTokenToOwnerEnumeration(typeId, _to, id); 445 | } else { 446 | // require(amount <= balances[_from][id]); // SafeMath will throw if balance is negative 447 | balances[_from][id] = balances[_from][id].sub(amount); 448 | balances[_to][id] = balances[_to][id].add(amount); 449 | } 450 | } 451 | 452 | emit TransferBatch(msg.sender, _from, _to, _ids, _amounts); 453 | } 454 | 455 | /** 456 | * @dev Creates a new Type, either FT or NFT 457 | * @param _uri The Metadata URI of the Token (ERC721 only) 458 | * @param _isNF True for NFT Types; False otherwise 459 | */ 460 | function _createType(string memory _uri, bool _isNF) internal returns (uint256 _type) { 461 | require(bytes(_uri).length > 0, "E1155: INVALID_URI"); 462 | 463 | _type = (++nonce << 128); 464 | if (_isNF) { 465 | _type = _type | TYPE_NF_BIT; 466 | } 467 | tokenUri[_type] = _uri; 468 | 469 | // emit a Transfer event with Create semantic to help with discovery. 470 | emit TransferSingle(msg.sender, address(0x0), address(0x0), _type, 0); 471 | emit URI(_type, _uri); 472 | } 473 | 474 | /** 475 | * @dev Mints a new Token, either FT or NFT 476 | * @param _to The Address of the Token Receiver 477 | * @param _type The Type ID of the Token 478 | * @param _amount The amount of the Token to Mint 479 | * @param _uri The Metadata URI of the Token (ERC721 only) 480 | * @param _data Additional data with no specified format, sent in call to `_to` 481 | * @return The Token ID of the newly minted Token 482 | */ 483 | function _mint(address _to, uint256 _type, uint256 _amount, string memory _uri, bytes memory _data) internal returns (uint256) { 484 | uint256 _tokenId; 485 | 486 | // Non-fungible 487 | if (_type & TYPE_NF_BIT == TYPE_NF_BIT) { 488 | uint256 index = mintedByType[_type].add(1); 489 | mintedByType[_type] = index; 490 | 491 | _tokenId = _type | index; 492 | nfOwners[_tokenId] = _to; 493 | tokenUri[_tokenId] = _uri; 494 | _amount = 1; 495 | 496 | _addTokenToOwnerEnumeration(_type, _to, _tokenId); 497 | _addTokenToAllTokensEnumeration(_type, _tokenId); 498 | } 499 | 500 | // Fungible 501 | else { 502 | _tokenId = _type; 503 | supplyByType[_type] = supplyByType[_type].add(_amount); 504 | mintedByType[_type] = mintedByType[_type].add(_amount); 505 | balances[_to][_type] = balances[_to][_type].add(_amount); 506 | } 507 | 508 | emit TransferSingle(msg.sender, address(0x0), _to, _tokenId, _amount); 509 | _callonERC1155Received(address(0x0), _to, _tokenId, _amount, _data); 510 | 511 | return _tokenId; 512 | } 513 | 514 | /** 515 | * @dev Mints a Batch of new Tokens, either FT or NFT 516 | * @param _to The Address of the Token Receiver 517 | * @param _types The Type IDs of the Tokens 518 | * @param _amounts The amounts of the Tokens to Mint 519 | * @param _uris The Metadata URI of the Tokens (ERC721 only) 520 | * @param _data Additional data with no specified format, sent in call to `_to` 521 | * @return The Token IDs of the newly minted Tokens 522 | */ 523 | // function _mintBatch( 524 | // address _to, 525 | // uint256[] memory _types, 526 | // uint256[] memory _amounts, 527 | // string[] memory _uris, 528 | // bytes memory _data 529 | // ) 530 | // internal 531 | // returns (uint256[] memory) 532 | // { 533 | // require(_types.length == _amounts.length, "E1155: ARRAY_LEN_MISMATCH"); 534 | // uint256 _type; 535 | // uint256 _index; 536 | // uint256 _tokenId; 537 | // uint256 _count = _types.length; 538 | 539 | // uint256[] memory _tokenIds = new uint256[](_count); 540 | 541 | // for (uint256 i = 0; i < _count; i++) { 542 | // _type = _types[i]; 543 | 544 | // // Non-fungible 545 | // if (_type & TYPE_NF_BIT == TYPE_NF_BIT) { 546 | // _index = mintedByType[_type].add(1); 547 | // mintedByType[_type] = _index; 548 | 549 | // _tokenId = _type | _index; 550 | // nfOwners[_tokenId] = _to; 551 | // _tokenIds[i] = _tokenId; 552 | // tokenUri[_tokenId] = _uris[i]; 553 | // _amounts[i] = 1; 554 | 555 | // _addTokenToOwnerEnumeration(_type, _to, _tokenId); 556 | // _addTokenToAllTokensEnumeration(_type, _tokenId); 557 | // } 558 | 559 | // // Fungible 560 | // else { 561 | // _tokenIds[i] = _type; 562 | // supplyByType[_type] = supplyByType[_type].add(_amounts[i]); 563 | // mintedByType[_type] = mintedByType[_type].add(_amounts[i]); 564 | // balances[_to][_type] = balances[_to][_type].add(_amounts[i]); 565 | // } 566 | // } 567 | 568 | // emit TransferBatch(msg.sender, address(0x0), _to, _tokenIds, _amounts); 569 | // _callonERC1155BatchReceived(address(0x0), _to, _tokenIds, _amounts, _data); 570 | 571 | // return _tokenIds; 572 | // } 573 | 574 | /** 575 | * @dev Burns an existing Token, either FT or NFT 576 | * @param _from The Address of the Token Holder 577 | * @param _tokenId The ID of the Token 578 | * @param _amount The Amount to burn 579 | */ 580 | function _burn(address _from, uint256 _tokenId, uint256 _amount) internal { 581 | uint256 _typeId = _tokenId; 582 | 583 | // Non-fungible 584 | if (_tokenId & TYPE_NF_BIT == TYPE_NF_BIT) { 585 | address _tokenOwner = _ownerOf(_tokenId); 586 | require(_tokenOwner == _from || isApprovedForAll(_tokenOwner, _from), "E1155: NOT_OPERATOR"); 587 | nfOwners[_tokenId] = address(0x0); 588 | tokenUri[_tokenId] = ""; 589 | _typeId = _tokenId & TYPE_MASK; 590 | _amount = 1; 591 | 592 | _removeTokenFromOwnerEnumeration(_typeId, _tokenOwner, _tokenId); 593 | _removeTokenFromAllTokensEnumeration(_typeId, _tokenId); 594 | } 595 | 596 | // Fungible 597 | else { 598 | require(_balanceOf(_from, _tokenId) >= _amount, "E1155: INSUFF_BALANCE"); 599 | supplyByType[_typeId] = supplyByType[_typeId].sub(_amount); 600 | balances[_from][_typeId] = balances[_from][_typeId].sub(_amount); 601 | } 602 | 603 | emit TransferSingle(msg.sender, _from, address(0x0), _tokenId, _amount); 604 | } 605 | 606 | /** 607 | * @dev Burns a Batch of existing Tokens, either FT or NFT 608 | * @param _from The Address of the Token Holder 609 | * @param _tokenIds The IDs of the Tokens 610 | * @param _amounts The Amounts to Burn of each Token 611 | */ 612 | // function _burnBatch(address _from, uint256[] memory _tokenIds, uint256[] memory _amounts) internal { 613 | // require(_tokenIds.length == _amounts.length, "E1155: ARRAY_LEN_MISMATCH"); 614 | 615 | // uint256 _tokenId; 616 | // uint256 _typeId; 617 | // address _tokenOwner; 618 | // uint256 _count = _tokenIds.length; 619 | // for (uint256 i = 0; i < _count; i++) { 620 | // _tokenId = _tokenIds[i]; 621 | // _typeId = _tokenId; 622 | 623 | // // Non-fungible 624 | // if (_tokenId & TYPE_NF_BIT == TYPE_NF_BIT) { 625 | // _tokenOwner = _ownerOf(_tokenId); 626 | // require(_tokenOwner == _from || isApprovedForAll(_tokenOwner, _from), "E1155: NOT_OPERATOR"); 627 | // nfOwners[_tokenId] = address(0x0); 628 | // tokenUri[_tokenId] = ""; 629 | // _typeId = _tokenId & TYPE_MASK; 630 | // _amounts[i] = 1; 631 | 632 | // _removeTokenFromOwnerEnumeration(_typeId, _tokenOwner, _tokenId); 633 | // _removeTokenFromAllTokensEnumeration(_typeId, _tokenId); 634 | // } 635 | 636 | // // Fungible 637 | // else { 638 | // require(_balanceOf(_from, _tokenId) >= _amounts[i], "E1155: INSUFF_BALANCE"); 639 | // supplyByType[_typeId] = supplyByType[_typeId].sub(_amounts[i]); 640 | // balances[_from][_tokenId] = balances[_from][_tokenId].sub(_amounts[i]); 641 | // } 642 | // } 643 | 644 | // emit TransferBatch(msg.sender, _from, address(0x0), _tokenIds, _amounts); 645 | // } 646 | 647 | /** 648 | * @dev Adds NFT Tokens to a Users Enumerable List 649 | * @param _typeId The Type ID of the Token 650 | * @param _to The Address of the Token Receiver 651 | * @param _tokenId The ID of the Token 652 | */ 653 | function _addTokenToOwnerEnumeration(uint256 _typeId, address _to, uint256 _tokenId) internal { 654 | ownedTokensByTypeIndex[_typeId][_tokenId] = ownedTokensByType[_typeId][_to].length; 655 | ownedTokensByType[_typeId][_to].push(_tokenId); 656 | } 657 | 658 | /** 659 | * @dev Adds NFT Tokens to the All-Tokens Enumerable List 660 | * @param _typeId The Type ID of the Token 661 | * @param _tokenId The ID of the Token 662 | */ 663 | function _addTokenToAllTokensEnumeration(uint256 _typeId, uint256 _tokenId) internal { 664 | allTokensByTypeIndex[_typeId][_tokenId] = allTokensByType[_typeId].length; 665 | allTokensByType[_typeId].push(_tokenId); 666 | } 667 | 668 | /** 669 | * @dev Removes NFT Tokens from a Users Enumerable List 670 | * @param _typeId The Type ID of the Token 671 | * @param _from The Address of the Token Holder 672 | * @param _tokenId The ID of the T oken 673 | */ 674 | function _removeTokenFromOwnerEnumeration(uint256 _typeId, address _from, uint256 _tokenId) internal { 675 | uint256 _lastTokenIndex = ownedTokensByType[_typeId][_from].length.sub(1); 676 | uint256 _tokenIndex = ownedTokensByTypeIndex[_typeId][_tokenId]; 677 | 678 | if (_tokenIndex != _lastTokenIndex) { 679 | uint256 _lastTokenId = ownedTokensByType[_typeId][_from][_lastTokenIndex]; 680 | 681 | ownedTokensByType[_typeId][_from][_tokenIndex] = _lastTokenId; 682 | ownedTokensByTypeIndex[_typeId][_lastTokenId] = _tokenIndex; 683 | } 684 | ownedTokensByType[_typeId][_from].pop(); 685 | ownedTokensByTypeIndex[_typeId][_tokenId] = 0; 686 | } 687 | 688 | /** 689 | * @dev Removes NFT Tokens from the All-Tokens Enumerable List 690 | * @param _typeId The Type ID of the Token 691 | * @param _tokenId The ID of the Token 692 | */ 693 | function _removeTokenFromAllTokensEnumeration(uint256 _typeId, uint256 _tokenId) internal { 694 | uint256 _lastTokenIndex = allTokensByType[_typeId].length.sub(1); 695 | uint256 _tokenIndex = allTokensByTypeIndex[_typeId][_tokenId]; 696 | uint256 _lastTokenId = allTokensByType[_typeId][_lastTokenIndex]; 697 | 698 | allTokensByType[_typeId][_tokenIndex] = _lastTokenId; 699 | allTokensByTypeIndex[_typeId][_lastTokenId] = _tokenIndex; 700 | 701 | allTokensByType[_typeId].pop(); 702 | allTokensByTypeIndex[_typeId][_tokenId] = 0; 703 | } 704 | 705 | /** 706 | * @dev Check if the Receiver is a Contract and ensure compatibility to the ERC1155 spec 707 | */ 708 | function _callonERC1155Received(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data) internal { 709 | // Check if recipient is a contract 710 | if (_to.isContract()) { 711 | bytes4 retval = IERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _amount, _data); 712 | require(retval == ERC1155_RECEIVED_VALUE, "E1155: INVALID_RECEIVER"); 713 | } 714 | } 715 | 716 | /** 717 | * @dev Check if the Receiver is a Contract and ensure compatibility to the ERC1155 spec 718 | */ 719 | // function _callonERC1155BatchReceived(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data) internal { 720 | // // Pass data if recipient is a contract 721 | // if (_to.isContract()) { 722 | // bytes4 retval = IERC1155TokenReceiver(_to).onERC1155BatchReceived(msg.sender, _from, _ids, _amounts, _data); 723 | // require(retval == ERC1155_BATCH_RECEIVED_VALUE, "E1155: INVALID_RECEIVER"); 724 | // } 725 | // } 726 | } 727 | -------------------------------------------------------------------------------- /contracts/lib/EscrowBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // EscrowBase.sol 4 | // Copyright (c) 2019, 2020 Rob Secord 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | pragma solidity 0.6.10; 25 | 26 | import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol"; 27 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 28 | import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol"; 29 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 30 | 31 | import "../interfaces/IEscrow.sol"; 32 | import "../interfaces/INucleus.sol"; 33 | import "../interfaces/IChargedParticlesEscrowManager.sol"; 34 | 35 | import "./Common.sol"; 36 | 37 | /** 38 | * @notice Escrow-Base Contract 39 | */ 40 | abstract contract EscrowBase is Initializable, OwnableUpgradeSafe, IEscrow, Common { 41 | using SafeMath for uint256; 42 | 43 | IChargedParticlesEscrowManager internal escrowMgr; 44 | 45 | // Contract Interface to Asset Token 46 | IERC20 internal assetToken; 47 | 48 | // Contract Interface to Interest-bearing Token 49 | INucleus internal interestToken; 50 | 51 | // These values are used to track the amount of Interest-bearing Tokens each Particle holds. 52 | // The Interest-bearing Token is always redeemable for 53 | // more and more of the Asset Token over time, thus the interest. 54 | // 55 | // TokenUUID => Balance 56 | mapping (uint256 => uint256) internal interestTokenBalance; // Current Balance in Interest Token 57 | mapping (uint256 => uint256) internal assetTokenBalance; // Original Amount Deposited in Asset Token 58 | 59 | // Contract => Deposit Fees earned for External Contracts 60 | // (collected in Interest-bearing Token, paid out in Asset Token) 61 | mapping (address => uint256) internal collectedFees; 62 | 63 | bool public paused; 64 | 65 | // Throws if called by any account other than the Charged Particles Escrow Controller. 66 | modifier onlyEscrow() { 67 | require(msg.sender == address(escrowMgr), "CPEB: INVALID_ESCROW"); 68 | _; 69 | } 70 | 71 | // Throws if called by any account other than the Charged Particles Escrow Controller. 72 | modifier whenNotPaused() { 73 | require(paused != true, "CPEB: PAUSED"); 74 | _; 75 | } 76 | 77 | /***********************************| 78 | | Initialization | 79 | |__________________________________*/ 80 | 81 | function initialize() public initializer { 82 | __Ownable_init(); 83 | paused = true; 84 | } 85 | 86 | /***********************************| 87 | | Public | 88 | |__________________________________*/ 89 | 90 | function isPaused() external override view returns (bool) { 91 | return paused; 92 | } 93 | 94 | function getAssetTokenAddress() external override view returns (address) { 95 | return address(assetToken); 96 | } 97 | 98 | function getInterestTokenAddress() external override view returns (address) { 99 | return address(interestToken); 100 | } 101 | 102 | 103 | /***********************************| 104 | | Only Admin/DAO | 105 | |__________________________________*/ 106 | 107 | /** 108 | * @dev Connects to the Charged Particles Escrow-Controller 109 | */ 110 | function setPausedState(bool _paused) external onlyOwner { 111 | paused = _paused; 112 | } 113 | 114 | /** 115 | * @dev Connects to the Charged Particles Escrow-Controller 116 | */ 117 | function setEscrowManager(address _escrowMgr) external onlyOwner { 118 | escrowMgr = IChargedParticlesEscrowManager(_escrowMgr); 119 | } 120 | 121 | /** 122 | * @dev Register Contracts for Asset/Interest Pairs 123 | */ 124 | function registerAssetPair(address _assetTokenAddress, address _interestTokenAddress) external onlyOwner { 125 | require(_assetTokenAddress != address(0x0), "CPEB: INVALID_ASSET_TOKEN"); 126 | require(_interestTokenAddress != address(0x0), "CPEB: INVALID_INTEREST_TOKEN"); 127 | 128 | // Register Addresses 129 | assetToken = IERC20(_assetTokenAddress); 130 | interestToken = INucleus(_interestTokenAddress); 131 | 132 | // Allow this contract to Tokenize Interest of Asset 133 | assetToken.approve(_interestTokenAddress, uint(-1)); 134 | } 135 | 136 | /***********************************| 137 | | Private Functions | 138 | |__________________________________*/ 139 | 140 | /** 141 | * @dev Calculates the amount of Interest-bearing Tokens are held within a Particle after Fees 142 | * @return The actual amount of Interest-bearing Tokens used to fund the Particle minus fees 143 | */ 144 | function _getMassByDeposit( 145 | address _contractAddress, 146 | uint256 _interestTokenAmount 147 | ) 148 | internal 149 | returns (uint256) 150 | { 151 | // Internal Fees 152 | address _escrow = address(escrowMgr); 153 | (uint256 _depositFee, uint256 _customFee) = escrowMgr.getFeesForDeposit(_contractAddress, _interestTokenAmount); 154 | collectedFees[_escrow] = _depositFee.add(collectedFees[_escrow]); 155 | 156 | // Custom Fees for External Contract 157 | if (_customFee > 0) { 158 | collectedFees[_contractAddress] = _customFee.add(collectedFees[_contractAddress]); 159 | } 160 | 161 | // Total Deposit 162 | return _interestTokenAmount.sub(_depositFee).sub(_customFee); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /deploy/ChaiEscrow.js: -------------------------------------------------------------------------------- 1 | 2 | const { contractDeployer } = require('../js-utils/deploy-helpers') 3 | const contractName = 'ChaiEscrow'; 4 | 5 | module.exports = contractDeployer(contractName); 6 | module.exports.tags = [contractName]; 7 | -------------------------------------------------------------------------------- /deploy/ChaiNucleus.js: -------------------------------------------------------------------------------- 1 | 2 | const { contractDeployer } = require('../js-utils/deploy-helpers') 3 | const contractName = 'ChaiNucleus'; 4 | 5 | module.exports = contractDeployer(contractName); 6 | module.exports.tags = [contractName]; 7 | -------------------------------------------------------------------------------- /deploy/ChargedParticles.js: -------------------------------------------------------------------------------- 1 | 2 | const { contractDeployer } = require('../js-utils/deploy-helpers') 3 | const contractName = 'ChargedParticles'; 4 | 5 | module.exports = contractDeployer(contractName); 6 | module.exports.tags = [contractName]; 7 | -------------------------------------------------------------------------------- /deploy/ChargedParticlesEscrowManager.js: -------------------------------------------------------------------------------- 1 | 2 | const { contractDeployer } = require('../js-utils/deploy-helpers') 3 | const contractName = 'ChargedParticlesEscrowManager'; 4 | 5 | module.exports = contractDeployer(contractName); 6 | module.exports.tags = [contractName]; 7 | -------------------------------------------------------------------------------- /deploy/ChargedParticlesTokenManager.js: -------------------------------------------------------------------------------- 1 | 2 | const { contractDeployer } = require('../js-utils/deploy-helpers') 3 | const contractName = 'ChargedParticlesTokenManager'; 4 | 5 | module.exports = contractDeployer(contractName); 6 | module.exports.tags = [contractName]; 7 | -------------------------------------------------------------------------------- /deploy/initialize.js: -------------------------------------------------------------------------------- 1 | // using plugin: buidler-deploy 2 | // reference: https://buidler.dev/plugins/buidler-deploy.html 3 | 4 | const { 5 | contractManager, 6 | chainName, 7 | presets, 8 | toWei, 9 | } = require('../js-utils/deploy-helpers') 10 | 11 | module.exports = async (bre) => { 12 | const { ethers, getNamedAccounts, deployments } = bre 13 | const { log } = deployments 14 | const network = await ethers.provider.getNetwork() 15 | const _getDeployedContract = contractManager(bre) 16 | 17 | // Named accounts, defined in buidler.config.js: 18 | const { deployer, trustedForwarder, dai } = await getNamedAccounts() 19 | 20 | log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 21 | log("Charged Particles - Contract Initialization"); 22 | log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); 23 | 24 | // const signers = await ethers.getSigners() 25 | // log({signers}) 26 | 27 | log(" Using Network: ", chainName(network.chainId)) 28 | log(" Using Accounts:") 29 | log(" - Deployer: ", deployer) 30 | log(" ") 31 | 32 | const ChaiEscrow = await _getDeployedContract('ChaiEscrow') 33 | const ChaiNucleus = await _getDeployedContract('ChaiNucleus') 34 | const ChargedParticles = await _getDeployedContract('ChargedParticles') 35 | const ChargedParticlesEscrowManager = await _getDeployedContract('ChargedParticlesEscrowManager') 36 | const ChargedParticlesTokenManager = await _getDeployedContract('ChargedParticlesTokenManager') 37 | 38 | let Dai = {address: dai} 39 | if (dai === deployer) { 40 | Dai = await _getDeployedContract('Dai') 41 | } else { 42 | log("\n Using Dai at: ", dai) 43 | } 44 | 45 | log("\n Initializing ChaiNucleus...") 46 | if (network.chainId === 1) { 47 | await ChaiNucleus.initMainnet() 48 | } else if (network.chainId === 3) { 49 | await ChaiNucleus.initRopsten() 50 | } else if (network.chainId === 42) { 51 | await ChaiNucleus.initKovan() 52 | } 53 | 54 | log("\n Initializing ChaiEscrow...") 55 | await ChaiEscrow.initialize() 56 | log(" Initializing ChargedParticlesEscrowManager...") 57 | await ChargedParticlesEscrowManager.initialize() 58 | log(" Initializing ChargedParticlesTokenManager...") 59 | await ChargedParticlesTokenManager.initialize() 60 | log(" Initializing ChargedParticles...") 61 | await ChargedParticles.initialize() 62 | 63 | log("\n Preparing ChaiEscrow...") 64 | await ChaiEscrow.setEscrowManager(ChargedParticlesEscrowManager.address) 65 | await ChaiEscrow.registerAssetPair(Dai.address, ChaiNucleus.address) 66 | await ChaiEscrow.setPausedState(false) 67 | 68 | log(" Preparing ChargedParticlesEscrowManager...") 69 | await ChargedParticlesEscrowManager.setDepositFee(presets.EscrowManager.fees.deposit) 70 | await ChargedParticlesEscrowManager.registerAssetPair("chai", ChaiEscrow.address) 71 | 72 | log(" Preparing ChargedParticlesTokenManager...") 73 | await ChargedParticlesTokenManager.registerContractType(ChargedParticles.address, true) 74 | 75 | log(" Preparing ChargedParticles...") 76 | await ChargedParticles.setTrustedForwarder(trustedForwarder) 77 | await ChargedParticles.registerTokenManager(ChargedParticlesTokenManager.address) 78 | await ChargedParticles.registerEscrowManager(ChargedParticlesEscrowManager.address) 79 | await ChargedParticles.setupFees(presets.ChargedParticles.fees.eth, presets.ChargedParticles.fees.ion) 80 | 81 | log(" Register ChargedParticles with Escrow...") 82 | await ChargedParticlesEscrowManager.registerContractType(ChargedParticles.address) 83 | 84 | log("\n Enabling Contracts...") 85 | await ChargedParticles.setPausedState(false) 86 | 87 | // log("\n Minting ION Tokens...") 88 | // const ionToken = presets.ChargedParticles.ionToken 89 | // await ChargedParticles.mintIons(ionToken.URI, ionToken.maxSupply, ionToken.mintFee) 90 | 91 | // Display Contract Addresses 92 | log("\n Contract Deployments Complete!\n\n Contracts:") 93 | log(" - ChaiEscrow: ", ChaiEscrow.address) 94 | log(" - ChaiNucleus: ", ChaiNucleus.address) 95 | log(" - ChargedParticles: ", ChargedParticles.address) 96 | log(" - ChargedParticlesEscrowManager: ", ChargedParticlesEscrowManager.address) 97 | log(" - ChargedParticlesTokenManager: ", ChargedParticlesTokenManager.address) 98 | 99 | log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 100 | } 101 | module.exports.runAtTheEnd = true; 102 | -------------------------------------------------------------------------------- /js-utils/deploy-helpers.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require('ethers') 2 | 3 | const toWei = ethers.utils.parseEther 4 | const toEth = ethers.utils.formatEther 5 | const toStr = (val) => ethers.utils.toUtf8String(val).replace(/\0/g, '') 6 | 7 | const txOverrides = (options = {}) => ({gas: 15000000, ...options}) 8 | 9 | const chainName = (chainId) => { 10 | switch (chainId) { 11 | case 1: return 'Mainnet' 12 | case 3: return 'Ropsten' 13 | case 42: return 'Kovan' 14 | case 31337: return 'BuidlerEVM' 15 | default: return 'Unknown' 16 | } 17 | } 18 | 19 | const presets = { 20 | ChargedParticles: { 21 | fees: { 22 | eth: toWei('0.001'), 23 | ion: toWei('1'), 24 | }, 25 | ionToken: { 26 | URI: 'https://ipfs.io/ipfs/QmbNDYSzPUuEKa8ppv1W11fVJVZdGBUku2ZDKBqmUmyQdT', 27 | maxSupply: toWei('2000000'), 28 | mintFee: toWei('0.0001'), 29 | name: 'Charged Atoms', 30 | symbol: 'ION' 31 | } 32 | }, 33 | EscrowManager: { 34 | fees: { 35 | deposit: 50, // 0.5% 36 | } 37 | } 38 | } 39 | 40 | const _getDeployedContract = async (bre, deployer, contractName, contractArgs = []) => { 41 | const {deployments} = bre 42 | const {deployIfDifferent, log} = deployments; 43 | const overrides = txOverrides({from: deployer}) 44 | 45 | let contract = await deployments.getOrNull(contractName) 46 | if (!contract) { 47 | log(` Deploying ${contractName}...`) 48 | const deployResult = await deployIfDifferent(['data'], contractName, overrides, contractName, ...contractArgs) 49 | contract = await deployments.get(contractName) 50 | if (deployResult.newlyDeployed) { 51 | log(` - deployed at ${contract.address} for ${deployResult.receipt.gasUsed} WEI`) 52 | } 53 | } 54 | return contract 55 | } 56 | 57 | // Used in deployment initialization scripts and unit-tests 58 | const contractManager = (bre) => async (contractName, contractArgs = []) => { 59 | const [ deployer ] = await bre.ethers.getSigners() 60 | 61 | // Return an Ethers Contract instance with the "deployer" as Signer 62 | const contract = await _getDeployedContract(bre, deployer._address, contractName, contractArgs) 63 | return new bre.ethers.Contract(contract.address, contract.abi, deployer) 64 | } 65 | 66 | // Used in deployment scripts run by buidler-deploy 67 | const contractDeployer = (contractName, contractArgs = []) => async (bre) => { 68 | const {getNamedAccounts} = bre 69 | const namedAccounts = await getNamedAccounts() 70 | return await _getDeployedContract(bre, namedAccounts.deployer, contractName, contractArgs) 71 | } 72 | 73 | 74 | module.exports = { 75 | txOverrides, 76 | chainName, 77 | contractDeployer, 78 | contractManager, 79 | presets, 80 | toWei, 81 | toEth, 82 | toStr, 83 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charged-particles", 3 | "version": "0.4.2", 4 | "keywords": [ 5 | "ethereum", 6 | "nft", 7 | "non-fungible", 8 | "defi" 9 | ], 10 | "engines": { 11 | "node": ">=12.16.3", 12 | "npm": ">=6.14.4" 13 | }, 14 | "scripts": { 15 | "reinstall": "rm -rf node_modules && rm -f yarn.lock && yarn clean && yarn clean-test && yarn", 16 | "verify": "yarn hint && yarn test", 17 | "clean": "rm -rf build cache", 18 | "clean-test": "rm -rf deployments/buidlerevm_31337 test-results.xml", 19 | "compile": "buidler --show-stack-traces --max-memory 8192 compile", 20 | "test": "yarn clean-test && buidler test", 21 | "hint": "solhint \"contracts/**/*.sol\"", 22 | "coverage": "yarn clean && yarn clean-test && buidler compile && buidler coverage --network coverage --temp build; rm -rf cache", 23 | "gas": "REPORT_GAS=true buidler test --network local", 24 | "start": "buidler node --port 8545", 25 | "deploy-local": "buidler deploy --network local", 26 | "deploy-kovan": "buidler deploy --network kovan --export ./deployments-kovan.json", 27 | "deploy-ropsten": "buidler deploy --network ropsten --export ./deployments-ropsten.json", 28 | "gen-docs": "solidity-docgen -i contracts -o docs" 29 | }, 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@nomiclabs/buidler": "^1.3.8", 33 | "@nomiclabs/buidler-ethers": "^2.0.0", 34 | "@nomiclabs/buidler-etherscan": "^1.3.3", 35 | "@nomiclabs/buidler-waffle": "^2.0.0", 36 | "@opengsn/gsn": "^0.9.0", 37 | "@openzeppelin/cli": "^2.8.0", 38 | "@openzeppelin/contracts-ethereum-package": "^3.0.0", 39 | "@openzeppelin/upgrades": "^2.8.0", 40 | "@resolver-engine/core": "^0.3.3", 41 | "@truffle/hdwallet-provider": "^1.0.34", 42 | "buidler-deploy": "^0.4.5", 43 | "buidler-gas-reporter": "^0.1.3", 44 | "chai": "^4.2.0", 45 | "chalk": "^4.0.0", 46 | "debug": "^4.1.1", 47 | "dotenv": "^8.0.0", 48 | "eslint": "^7.0.0", 49 | "eslint-plugin-jest": "^23.10.0", 50 | "ethereum-waffle": "^3.0.0", 51 | "ethers": "^5.0.3", 52 | "ganache-cli": "^6.9.0", 53 | "husky": "^4.2.5", 54 | "lodash": "^4.17.15", 55 | "mocha-junit-reporter": "^2.0.0", 56 | "openzeppelin-solidity": "^2.4.0", 57 | "oz-console": "^2.0.1", 58 | "solc": "0.6.4", 59 | "solhint": "^3.0.0", 60 | "solidity-coverage": "^0.7.4", 61 | "solidity-docgen": "^0.5.3" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/ChargedParticles.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | buidler, 3 | deployments, 4 | deployMockContract, 5 | ethers, 6 | expect, 7 | EMPTY_STR, 8 | ZERO_ADDRESS, 9 | } = require('./util/testEnv'); 10 | 11 | const { 12 | contractManager, 13 | txOverrides, 14 | toWei, 15 | toEth, 16 | toStr, 17 | presets, 18 | } = require('../js-utils/deploy-helpers'); 19 | 20 | const Dai = require('../build/Dai.json'); 21 | const ChaiNucleus = require('../build/ChaiNucleus.json'); 22 | const ChaiEscrow = require('../build/ChaiEscrow.json'); 23 | const ChargedParticlesTokenManager = require('../build/ChargedParticlesTokenManager.json'); 24 | const ChargedParticlesEscrowManager = require('../build/ChargedParticlesEscrowManager.json'); 25 | 26 | const debug = require('debug')('ChargedParticles.test'); 27 | 28 | const particle = { 29 | name: "test-particle", 30 | uri: "https://example.com", 31 | symbol: "TEST", 32 | accessType: 1, 33 | assetPair: "chai", 34 | maxSupply: toWei('20000'), 35 | mintFee: toWei('0.0001'), 36 | payWithIons: true 37 | }; 38 | 39 | describe('ChargedParticles Contract', function () { 40 | let deployer, primaryWallet, secondaryWallet; 41 | let chargedParticles, chargedParticlesPrimary, chargedParticlesSecondary; 42 | let chargedParticlesTokenManager; 43 | let chargedParticlesEscrowManager; 44 | let particleCreationParams; 45 | let chaiEscrow; 46 | let dai; 47 | 48 | let ionTokenId = 123; 49 | let particleTypeId = 456; 50 | let particleTokenId = 789; 51 | 52 | let ion = presets.ChargedParticles.ionToken; 53 | 54 | const _getDeployedContract = contractManager(buidler); 55 | 56 | describe("before minting ions", () => { 57 | 58 | beforeEach(async () => { 59 | [deployer, primaryWallet, secondaryWallet] = await buidler.ethers.getSigners(); 60 | await deployments.fixture(); 61 | 62 | // Mock Contracts 63 | dai = await deployMockContract(deployer, Dai.abi, txOverrides()); 64 | chaiNucleus = await deployMockContract(deployer, ChaiNucleus.abi, txOverrides()); 65 | chaiEscrow = await deployMockContract(deployer, ChaiEscrow.abi, txOverrides()); 66 | chargedParticlesTokenManager = await deployMockContract(deployer, ChargedParticlesTokenManager.abi, txOverrides()); 67 | chargedParticlesEscrowManager = await deployMockContract(deployer, ChargedParticlesEscrowManager.abi, txOverrides()); 68 | 69 | // Contract under test 70 | chargedParticles = await _getDeployedContract('ChargedParticles'); 71 | 72 | // Prep contract under test 73 | await chargedParticles.registerTokenManager(chargedParticlesTokenManager.address); 74 | await chargedParticles.registerEscrowManager(chargedParticlesEscrowManager.address); 75 | 76 | // Pre-mocks 77 | await chargedParticlesEscrowManager.mock.getAssetTokenAddress.withArgs('chai').returns(dai.address) 78 | 79 | // Primary & Secondary Signers on Contract 80 | chargedParticlesPrimary = chargedParticles.connect(primaryWallet); 81 | chargedParticlesSecondary = chargedParticles.connect(secondaryWallet); 82 | }); 83 | 84 | it('should maintain correct versioning', async () => { 85 | expect(toStr(await chargedParticles.version())).to.equal('v0.4.2'); 86 | }); 87 | 88 | it('should only allow modifications from Role: Admin/DAO', async () => { 89 | const ethFee = toWei('1'); 90 | const ionFee = toWei('2'); 91 | 92 | // Test Non-Admin 93 | await expect(chargedParticlesSecondary.setupFees(ethFee, ionFee)) 94 | .to.be.revertedWith('Ownable: caller is not the owner'); 95 | 96 | // Test Admin 97 | await chargedParticles.setupFees(ethFee, ionFee); 98 | 99 | const { 0: createFeeEth, 1: createFeeIon } = await chargedParticles.getCreationPrice(false); 100 | debug({createFeeEth, createFeeIon}); 101 | expect(createFeeEth).to.equal(ethFee); 102 | expect(createFeeIon).to.equal(ionFee); 103 | 104 | const { 0: createFeeEthForNFT, 1: createFeeIonForNFT } = await chargedParticles.getCreationPrice(true); 105 | debug({createFeeEthForNFT, createFeeIonForNFT}); 106 | expect(createFeeEthForNFT).to.equal(ethFee.mul(2)); 107 | expect(createFeeIonForNFT).to.equal(ionFee.mul(2)); 108 | }); 109 | 110 | it('should only allow modifications from Role: Maintainer', async () => { 111 | let isPaused = await chargedParticlesSecondary.isPaused(); 112 | expect(isPaused).to.equal(false); 113 | 114 | // Test Non-Admin 115 | await expect(chargedParticlesSecondary.setPausedState(false)) 116 | .to.be.revertedWith('Ownable: caller is not the owner'); 117 | 118 | // Test Admin - Toggle True 119 | await chargedParticles.setPausedState(true); 120 | 121 | isPaused = await chargedParticlesSecondary.isPaused(); 122 | expect(isPaused).to.equal(true); 123 | 124 | // Test Admin - Toggle False 125 | await chargedParticles.setPausedState(false); 126 | 127 | isPaused = await chargedParticles.isPaused(); 128 | expect(isPaused).to.equal(false); 129 | }); 130 | 131 | it('should allow the Admin/DAO to assign a Valid Token Manager', async () => { 132 | await expect(chargedParticlesSecondary.registerTokenManager(chargedParticlesTokenManager.address)) 133 | .to.be.revertedWith('Ownable: caller is not the owner'); 134 | 135 | await expect(chargedParticles.registerTokenManager(ZERO_ADDRESS)) 136 | .to.be.revertedWith('CP: INVALID_ADDRESS'); 137 | 138 | await chargedParticles.registerTokenManager(chargedParticlesTokenManager.address); 139 | expect(await chargedParticles.tokenMgr()).to.equal(chargedParticlesTokenManager.address); 140 | }); 141 | 142 | it('should only allow Admin/DAO to Mint IONs one time', async () => { 143 | 144 | // Test Non-Admin/DAO 145 | await expect(chargedParticlesSecondary.mintIons(ion.URI, ion.maxSupply, ion.mintFee)) 146 | .to.be.revertedWith('Ownable: caller is not the owner'); 147 | 148 | // Mocks 149 | await chargedParticlesTokenManager.mock.createType.withArgs(ion.URI, false).returns(ionTokenId); 150 | await chargedParticlesTokenManager.mock.createErc20Bridge.withArgs(ionTokenId, ion.name, ion.symbol, 18).returns(ZERO_ADDRESS); 151 | await chargedParticlesTokenManager.mock.mint 152 | // .withArgs(deployer._address, ionTokenId, ion.maxSupply, ion.URI, ethers.utils.formatBytes32String('ions')) 153 | .returns(ionTokenId); 154 | 155 | // Test Mint 156 | chargedParticles.on('PlasmaTypeUpdated', (_plasmaTypeId, _symbol, _isPrivate, _initialMint, _uri, event) => { 157 | expect(_symbol).to.equal(web3.utils.keccak256('ION')); 158 | expect(_isPrivate).to.equal(false); 159 | expect(_initialMint).to.equal(ion.maxSupply); 160 | expect(_uri).to.equal(ion.uri); 161 | }); 162 | 163 | await chargedParticles.mintIons(ion.URI, ion.maxSupply, ion.mintFee); 164 | 165 | await expect(chargedParticles.mintIons(ion.URI, ion.maxSupply, ion.mintFee)) 166 | .to.be.revertedWith("CP: ALREADY_INIT"); 167 | }); 168 | 169 | describe("after minting ions", () => { 170 | let assetAmount = toWei('100'); 171 | 172 | beforeEach(async () => { 173 | particleCreationParams = [ 174 | particle.name, 175 | particle.uri, 176 | particle.symbol, 177 | particle.accessType, 178 | particle.assetPair, 179 | particle.maxSupply, 180 | particle.mintFee, 181 | particle.payWithIons 182 | ]; 183 | 184 | // Mocks 185 | await chargedParticlesTokenManager.mock.createType.withArgs(ion.URI, false).returns(ionTokenId); 186 | await chargedParticlesTokenManager.mock.createErc20Bridge.withArgs(ionTokenId, ion.name, ion.symbol, 18).returns(ZERO_ADDRESS); 187 | await chargedParticlesTokenManager.mock.burn.returns(); 188 | await chargedParticlesTokenManager.mock.mint 189 | // .withArgs(deployer._address, ionTokenId, ion.maxSupply, ion.URI, ethers.utils.formatBytes32String('ions')) 190 | .returns(ionTokenId); 191 | 192 | // Test 193 | await chargedParticles.mintIons(ion.URI, ion.maxSupply, ion.mintFee); 194 | }); 195 | 196 | it('should not be able to create a particle with an insufficient ION balance', async () => { 197 | // Mocks 198 | await chargedParticlesEscrowManager.mock.isAssetPairEnabled.withArgs('chai').returns(true); 199 | await chargedParticlesTokenManager.mock.balanceOf.withArgs(secondaryWallet._address, ionTokenId).returns(0); 200 | 201 | // Test 202 | await expect(chargedParticlesSecondary.createParticle(...particleCreationParams)) 203 | .to.be.revertedWith('CP: INSUFF_IONS'); 204 | }); 205 | 206 | describe.only("after creating particle", () => { 207 | 208 | beforeEach(async () => { 209 | particleCreationParams = [ 210 | particle.name, 211 | particle.uri, 212 | particle.symbol, 213 | particle.accessType, 214 | particle.assetPair, 215 | particle.maxSupply, 216 | particle.mintFee, 217 | false 218 | ]; 219 | 220 | // Mocks 221 | await chargedParticlesEscrowManager.mock.isAssetPairEnabled.withArgs('chai').returns(true); 222 | 223 | await chargedParticlesTokenManager.mock.totalMinted.withArgs(particleTypeId).returns(0); 224 | await chargedParticlesTokenManager.mock.isNonFungibleBaseType.withArgs(particleTypeId).returns(true); 225 | await chargedParticlesTokenManager.mock.createType.withArgs(particle.uri, true).returns(particleTypeId); 226 | await chargedParticlesTokenManager.mock.createErc721Bridge.withArgs(particleTypeId, particle.name, particle.symbol).returns(ZERO_ADDRESS); 227 | 228 | // Prep 229 | await chargedParticlesPrimary.createParticle(...particleCreationParams, { value: presets.ChargedParticles.fees.eth.mul(2) }); 230 | }); 231 | 232 | it('cannot mint particles with insufficient funds', async () => { 233 | // Mocks 234 | await chargedParticlesTokenManager.mock.getNonFungibleBaseType.withArgs(particleTokenId).returns(particleTypeId); 235 | await chargedParticlesTokenManager.mock.mint 236 | .withArgs(secondaryWallet._address, particleTypeId, 1, particle.uri, EMPTY_STR) 237 | .returns(particleTokenId); 238 | 239 | // Test 240 | await expect(chargedParticlesSecondary.mintParticle( 241 | secondaryWallet._address, 242 | particleTypeId, 243 | assetAmount, 244 | particle.uri, 245 | EMPTY_STR 246 | )).to.be.revertedWith('CP: INSUFF_FUNDS'); 247 | }); 248 | 249 | it('cannot mint particles with insufficient assets', async () => { 250 | // Mocks 251 | await dai.mock.balanceOf.withArgs(secondaryWallet._address).returns(0); 252 | await chargedParticlesTokenManager.mock.totalMinted.withArgs(particleTypeId).returns(0); 253 | await chargedParticlesTokenManager.mock.isNonFungibleBaseType.withArgs(particleTypeId).returns(true); 254 | await chargedParticlesTokenManager.mock.uri.withArgs(particleTypeId).returns(particle.uri); 255 | await chargedParticlesTokenManager.mock.getNonFungibleBaseType.withArgs(particleTokenId).returns(particleTypeId); 256 | await chargedParticlesTokenManager.mock.mint 257 | .withArgs(secondaryWallet._address, particleTypeId, 1, particle.uri, EMPTY_STR) 258 | .returns(particleTokenId); 259 | 260 | // Test 261 | await expect(chargedParticlesSecondary.mintParticle( 262 | secondaryWallet._address, 263 | particleTypeId, 264 | assetAmount, 265 | particle.uri, 266 | EMPTY_STR, 267 | { value: particle.mintFee } 268 | )).to.be.revertedWith('CP: INSUFF_ASSETS'); 269 | }); 270 | 271 | it('can mint particles with sufficient funds & assets', async () => { 272 | // Mocks 273 | await dai.mock.balanceOf.withArgs(secondaryWallet._address).returns(toWei('1000')); 274 | await dai.mock.approve.withArgs(chargedParticlesEscrowManager.address, assetAmount).returns(true); 275 | await dai.mock.transferFrom 276 | .withArgs(secondaryWallet._address, chargedParticles.address, assetAmount) 277 | .returns(true); 278 | 279 | await chargedParticlesTokenManager.mock.totalMinted.withArgs(particleTypeId).returns(0); 280 | await chargedParticlesTokenManager.mock.isNonFungibleBaseType.withArgs(particleTypeId).returns(true); 281 | await chargedParticlesTokenManager.mock.uri.withArgs(particleTypeId).returns(particle.uri); 282 | await chargedParticlesTokenManager.mock.getNonFungibleBaseType.withArgs(particleTokenId).returns(particleTypeId); 283 | await chargedParticlesTokenManager.mock.mint 284 | .withArgs(secondaryWallet._address, particleTypeId, 1, particle.uri, EMPTY_STR) 285 | .returns(particleTokenId); 286 | 287 | await chargedParticlesEscrowManager.mock.energizeParticle 288 | .withArgs(chargedParticles.address, particleTokenId, 'chai', assetAmount) 289 | .returns(assetAmount); 290 | 291 | // Test 292 | await expect(chargedParticlesSecondary.mintParticle( 293 | secondaryWallet._address, 294 | particleTypeId, 295 | assetAmount, 296 | particle.uri, 297 | EMPTY_STR, 298 | { value: particle.mintFee } 299 | )) 300 | .to.emit(chargedParticlesSecondary, 'ParticleMinted') 301 | .withArgs(secondaryWallet._address, secondaryWallet._address, particleTokenId, particle.uri); 302 | }); 303 | 304 | // it('withdrawFees', async () => { 305 | // const balanceBefore1 = await web3.eth.getBalance(ionHodler); 306 | // const receipt1 = await contractInstance.methods.withdrawFees(ionHodler).send({ from: owner, gas: 5e6 }); 307 | // const balanceAfter1 = await web3.eth.getBalance(ionHodler); 308 | 309 | // expectEvent(receipt1, 'ContractFeesWithdrawn', { 310 | // _sender: owner, 311 | // _receiver: ionHodler, 312 | // _amount: '0' 313 | // }); 314 | // expect((balanceAfter1 - balanceBefore1).toString()).toBe('0'); 315 | 316 | // await contractInstance.methods.mintPlasma(ionHodler, ionTokenId, 3, []).send({ from: nonOwner, gas: 5e6, value: web3.utils.toWei('5', 'ether') }); 317 | 318 | // const balanceBefore2 = await web3.eth.getBalance(ionHodler); 319 | // const receipt2 = await contractInstance.methods.withdrawFees(ionHodler).send({ from: owner, gas: 5e6 }); 320 | // const balanceAfter2 = await web3.eth.getBalance(ionHodler); 321 | 322 | // expectEvent(receipt2, 'ContractFeesWithdrawn', { 323 | // _sender: owner, 324 | // _receiver: ionHodler, 325 | // _amount: web3.utils.toWei('3', 'ether').toString() 326 | // }); 327 | // expect(web3.utils.fromWei((balanceAfter2 - balanceBefore2).toString(), 'ether')).toBe('3'); 328 | 329 | // const balanceBefore3 = await web3.eth.getBalance(ionHodler); 330 | // const receipt3 = await contractInstance.methods.withdrawFees(ionHodler).send({ from: owner, gas: 5e6 }); 331 | // const balanceAfter3 = await web3.eth.getBalance(ionHodler); 332 | 333 | // expectEvent(receipt3, 'ContractFeesWithdrawn', { 334 | // _sender: owner, 335 | // _receiver: ionHodler, 336 | // _amount: '0' 337 | // }); 338 | // expect((balanceAfter3 - balanceBefore3).toString()).toBe('0'); 339 | // }); 340 | }); 341 | 342 | }); 343 | 344 | }); 345 | 346 | }); 347 | 348 | // describe('ChargedParticles', () => { 349 | // let [owner, nonOwner, ionHodler] = accounts; 350 | // let chargedParticles; 351 | // let tokenManagerInstance; 352 | // let helper; 353 | 354 | // beforeEach(async () => { 355 | // helper = await TestHelper(); 356 | // }); 357 | 358 | // it('initializer', async () => { 359 | // debug('initializer'); 360 | // // chargedParticles = await helper.createProxy(ChargedParticles, { initMethod: 'initialize' }); 361 | // chargedParticles = await ChargedParticles.new(); 362 | // debug({chargedParticles}); 363 | // const version = await chargedParticles.version({ from: owner }); 364 | // debug({version}); 365 | // expect(web3.utils.hexToAscii(version)).toMatch("v0.4.2"); 366 | // }); 367 | 368 | 369 | // describe('with Token manager', () => { 370 | // beforeEach(async () => { 371 | // chargedParticles = await helper.createProxy(ChargedParticles, { initMethod: 'initialize', initArgs: [owner] }); 372 | // tokenManagerInstance = await helper.createProxy(ChargedParticlesTokenManager, { initMethod: 'initialize', initArgs: [owner] }); 373 | // }); 374 | 375 | // it('registerTokenManager', async () => { 376 | // await expectRevert( 377 | // chargedParticles.registerTokenManager(tokenManagerInstance.address).send({ from: nonOwner }), 378 | // "Ownable: caller is not the owner" 379 | // ); 380 | 381 | // await expectRevert( 382 | // chargedParticles.registerTokenManager(constants.ZERO_ADDRESS).send({ from: owner }), 383 | // "E412" 384 | // ); 385 | // }); 386 | 387 | // it('mintIons', async () => { 388 | // const mintFee = web3.utils.toWei('1', 'ether') 389 | 390 | // await chargedParticles.registerTokenManager(tokenManagerInstance.address).send({ from: owner }); 391 | // await tokenManagerInstance.methods.setFusedParticleState(chargedParticles.address, true).send({ from: owner }); 392 | // await expectRevert( 393 | // chargedParticles.mintIons('https://www.example.com', 1337, 42, mintFee).send({ from: nonOwner }), 394 | // "Ownable: caller is not the owner" 395 | // ); 396 | // await expectRevert( 397 | // chargedParticles.mintIons('', 1337, 42, mintFee).send({ from: owner }), 398 | // "E107" 399 | // ); 400 | 401 | // const receipt = await chargedParticles.mintIons('https://www.example.com', 1337, 42, mintFee).send({ from: owner, gas: 5e6 }); 402 | // expectEvent(receipt, 'PlasmaTypeUpdated', { 403 | // _symbol: web3.utils.keccak256('ION'), 404 | // _isPrivate: false, 405 | // _initialMint: '42', 406 | // _uri: 'https://www.example.com' 407 | // }); 408 | 409 | // await expectRevert( 410 | // chargedParticles.mintIons('https://www.example.com', 1337, 42, mintFee).send({ from: owner }), 411 | // "E416" 412 | // ); 413 | // }); 414 | 415 | // describe('Public Read of Ion token', () => { 416 | // let ionTokenId; 417 | 418 | // beforeEach(async () => { 419 | // await chargedParticles.registerTokenManager(tokenManagerInstance.address).send({ from: owner }); 420 | // await tokenManagerInstance.methods.setFusedParticleState(chargedParticles.address, true).send({ from: owner }); 421 | // const receipt = await chargedParticles.mintIons( 422 | // 'https://www.example.com', 423 | // 1337, 424 | // 42, 425 | // web3.utils.toWei('1', 'ether') 426 | // ).send({ from: owner, gas: 5e6 }); 427 | // ionTokenId = receipt.events['PlasmaTypeUpdated'].returnValues['_plasmaTypeId']; 428 | // }); 429 | 430 | // it('uri', async () => { 431 | // const uri = await chargedParticles.uri(ionTokenId).call({ from: nonOwner }); 432 | // expect(uri).to.equal('https://www.example.com'); 433 | // }); 434 | 435 | // it('getTypeCreator', async () => { 436 | // const typeCreator = await chargedParticles.getTypeCreator(ionTokenId).call({ from: nonOwner }); 437 | // expect(typeCreator).to.equal(owner); 438 | // }); 439 | 440 | // it('getTypeTokenBridge', async () => { 441 | // const bridgeAddress = await chargedParticles.getTypeTokenBridge(ionTokenId).call({ from: nonOwner }); 442 | // expect(bridgeAddress).not.to.equal(owner); 443 | // expect(bridgeAddress).not.to.equal(nonOwner); 444 | // expect(bridgeAddress).not.to.equal(ionHodler); 445 | // expect(bridgeAddress).not.to.equal(chargedParticles.address); 446 | // expect(bridgeAddress).not.to.equal(tokenManagerInstance.address); 447 | // }); 448 | 449 | // it('canMint', async () => { 450 | // let canMint = await chargedParticles.canMint(ionTokenId, 1).call({ from: nonOwner }); 451 | // expect(canMint).to.equal(true); 452 | // canMint = await chargedParticles.canMint(ionTokenId, 2000).call({ from: nonOwner }); 453 | // expect(canMint).to.equal(false); 454 | // }); 455 | 456 | // it('getSeriesNumber', async () => { 457 | // const seriesNumber = await chargedParticles.getSeriesNumber(ionTokenId).call({ from: nonOwner }); 458 | // expect(seriesNumber).to.equal('0'); 459 | // }); 460 | 461 | // it('getMintingFee', async () => { 462 | // const mintFee = await chargedParticles.getMintingFee(ionTokenId).call({ from: nonOwner }); 463 | // expect(web3.utils.fromWei(mintFee, 'ether')).to.equal('1'); 464 | // }); 465 | 466 | // it('getMaxSupply', async () => { 467 | // const maxSupply = await chargedParticles.getMaxSupply(ionTokenId).call({ from: nonOwner }); 468 | // expect(maxSupply).to.equal('1337'); 469 | // }); 470 | 471 | // it('getTotalMinted', async () => { 472 | // const totalMinted = await chargedParticles.getTotalMinted(ionTokenId).call({ from: nonOwner }); 473 | // expect(totalMinted).to.equal('42'); 474 | // }); 475 | // }); 476 | 477 | // describe('Operations on Ion token', () => { 478 | // let ionTokenId; 479 | 480 | // beforeEach(async () => { 481 | // await chargedParticles.registerTokenManager(tokenManagerInstance.address).send({ from: owner }); 482 | // await tokenManagerInstance.methods.setFusedParticleState(chargedParticles.address, true).send({ from: owner }); 483 | // const receipt = await chargedParticles.mintIons( 484 | // 'https://www.example.com', 485 | // 1337, 486 | // 42, 487 | // web3.utils.toWei('1', 'ether') 488 | // ).send({ from: owner, gas: 5e6 }); 489 | // ionTokenId = receipt.events['PlasmaTypeUpdated'].returnValues['_plasmaTypeId']; 490 | // }); 491 | 492 | // it('mintParticle', async () => { 493 | // await chargedParticles.setPausedState(true).send({ from: owner }); 494 | // await expectRevert( 495 | // chargedParticles.mintParticle(ionHodler, ionTokenId, 10, '', []).send({ from: owner }), 496 | // "E417" 497 | // ); 498 | // await chargedParticles.setPausedState(false).send({ from: owner }); 499 | 500 | // await expectRevert( 501 | // chargedParticles.mintParticle(ionHodler, ionTokenId, 10, '', []).send({ from: owner }), 502 | // "E104" 503 | // ); 504 | // }); 505 | 506 | // it('burnParticle', async () => { 507 | // expectRevert( 508 | // chargedParticles.burnParticle(ionTokenId).send({ from: owner }), 509 | // "E104" 510 | // ); 511 | // }); 512 | 513 | // it('energizeParticle', async () => { 514 | // expectRevert( 515 | // chargedParticles.energizeParticle(ionTokenId, 10).send({ from: owner }), 516 | // "E104" 517 | // ); 518 | // }); 519 | 520 | // it('mintPlasma from creator', async () => { 521 | // await chargedParticles.setPausedState(true).send({ from: owner }); 522 | // await expectRevert( 523 | // chargedParticles.mintPlasma(ionHodler, ionTokenId, 10, []).send({ from: owner }), 524 | // "E417" 525 | // ); 526 | // await chargedParticles.setPausedState(false).send({ from: owner }); 527 | 528 | // // Mint amount exceends limit 529 | // await expectRevert( 530 | // chargedParticles.mintPlasma(ionHodler, ionTokenId, 9999, []).send({ from: owner }), 531 | // "E407" 532 | // ); 533 | 534 | // const receipt = await chargedParticles.mintPlasma(ionHodler, ionTokenId, 3, []).send({ from: owner, gas: 5e6 }); 535 | // expectEvent(receipt, 'PlasmaMinted', { 536 | // _sender: owner, 537 | // _receiver: ionHodler, 538 | // _typeId: ionTokenId, 539 | // _amount: '3' 540 | // }); 541 | // expect(await tokenManagerInstance.methods.balanceOf(ionHodler, ionTokenId).call()).to.equal('3'); 542 | // }); 543 | 544 | // it('mintPlasma from non-creator', async () => { 545 | // // Mint amount exceends limit 546 | // await expectRevert( 547 | // chargedParticles.mintPlasma(ionHodler, ionTokenId, 9999, []).send({ from: nonOwner }), 548 | // "E407" 549 | // ); 550 | 551 | // // Not including the minting fee with the transaction 552 | // await expectRevert( 553 | // chargedParticles.mintPlasma(ionHodler, ionTokenId, 10, []).send({ from: nonOwner, gas: 5e6 }), 554 | // "E404" 555 | // ); 556 | 557 | // const receipt = await chargedParticles 558 | // .mintPlasma(ionHodler, ionTokenId, 3, []) 559 | // .send({ from: nonOwner, gas: 5e6, value: web3.utils.toWei('5', 'ether') }); 560 | 561 | // expectEvent(receipt, 'PlasmaMinted', { 562 | // _sender: nonOwner, 563 | // _receiver: ionHodler, 564 | // _typeId: ionTokenId, 565 | // _amount: '3' 566 | // }); 567 | 568 | // const balance = await tokenManagerInstance.methods.balanceOf(ionHodler, ionTokenId).call({ from: ionHodler }); 569 | // expect(balance).to.equal('3'); 570 | // }); 571 | 572 | // it('withdrawFees', async () => { 573 | // await chargedParticles.mintPlasma(ionHodler, ionTokenId, 3, []).send({ from: nonOwner, gas: 5e6, value: web3.utils.toWei('5', 'ether') }); 574 | 575 | // expectRevert( 576 | // chargedParticles.withdrawFees(ionHodler).send({ from: nonOwner, gas: 5e6 }), 577 | // "Ownable: caller is not the owner" 578 | // ); 579 | 580 | // expectRevert( 581 | // chargedParticles.withdrawFees('0x0000000000000000000000000000000000000000').send({ from: owner, gas: 5e6 }), 582 | // "E412" 583 | // ); 584 | 585 | // const balanceBefore = await web3.eth.getBalance(ionHodler); 586 | // const receipt = await chargedParticles.withdrawFees(ionHodler).send({ from: owner, gas: 5e6 }); 587 | // const balanceAfter = await web3.eth.getBalance(ionHodler); 588 | 589 | // expectEvent(receipt, 'ContractFeesWithdrawn', { 590 | // _sender: owner, 591 | // _receiver: ionHodler, 592 | // _amount: web3.utils.toWei('3', 'ether').toString() 593 | // }); 594 | // expect(web3.utils.fromWei((balanceAfter - balanceBefore).toString(), 'ether')).to.equal('3'); 595 | // }); 596 | 597 | // it('burnPlasma', async () => { 598 | // expectRevert( 599 | // chargedParticles.burnPlasma(ionTokenId, 10).send({ from: nonOwner }), 600 | // "E106" 601 | // ); 602 | 603 | // const receipt = await chargedParticles.burnPlasma(ionTokenId, 10).send({ from: owner }); 604 | 605 | // expectEvent(receipt, 'PlasmaBurned', { 606 | // _from: owner, 607 | // _typeId: ionTokenId, 608 | // _amount: '10' 609 | // }); 610 | 611 | // const balance = await tokenManagerInstance.methods.balanceOf(owner, ionTokenId).call({ from: owner }); 612 | // expect(balance).to.equal('32'); 613 | // }); 614 | // }); 615 | // }); 616 | // }); 617 | -------------------------------------------------------------------------------- /test/ChargedParticlesEscrowManager.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | buidler, 3 | deployments, 4 | ethers, 5 | expect, 6 | EMPTY_STR, 7 | ZERO_ADDRESS, 8 | } = require('./util/testEnv'); 9 | 10 | const { 11 | contractManager, 12 | toWei, 13 | toEth, 14 | toStr, 15 | } = require('../js-utils/deploy-helpers'); 16 | 17 | const debug = require('debug')('ChargedParticlesEscrowManager.test'); 18 | 19 | describe('ChargedParticlesEscrowManager Contract', function () { 20 | let deployer; 21 | let primaryWallet; 22 | let secondaryWallet; 23 | 24 | let chargedParticlesEscrowManager; 25 | 26 | const _getDeployedContract = contractManager(buidler); 27 | 28 | beforeEach(async () => { 29 | [deployer, primaryWallet, secondaryWallet] = await buidler.ethers.getSigners(); 30 | 31 | await deployments.fixture(); 32 | chargedParticlesEscrowManager = await _getDeployedContract('ChargedParticlesEscrowManager'); 33 | }); 34 | 35 | it('maintains correct versioning', async () => { 36 | expect(toStr(await chargedParticlesEscrowManager.version())).to.equal('v0.4.2'); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/ChargedParticlesTokenManager.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | buidler, 3 | deployments, 4 | ethers, 5 | expect, 6 | EMPTY_STR, 7 | ZERO_ADDRESS, 8 | } = require('./util/testEnv'); 9 | 10 | const { 11 | contractManager, 12 | toWei, 13 | toEth, 14 | toStr, 15 | } = require('../js-utils/deploy-helpers'); 16 | 17 | const debug = require('debug')('ChargedParticlesTokenManager.test'); 18 | 19 | describe('ChargedParticlesTokenManager Contract', function () { 20 | let deployer; 21 | let primaryWallet; 22 | let secondaryWallet; 23 | 24 | let chargedParticlesTokenManager; 25 | 26 | const _getDeployedContract = contractManager(buidler); 27 | 28 | beforeEach(async () => { 29 | [deployer, primaryWallet, secondaryWallet] = await buidler.ethers.getSigners(); 30 | 31 | await deployments.fixture(); 32 | chargedParticlesTokenManager = await _getDeployedContract('ChargedParticlesTokenManager'); 33 | }); 34 | 35 | it('maintains correct versioning', async () => { 36 | expect(toStr(await chargedParticlesTokenManager.version())).to.equal('v0.4.2'); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/util/testEnv.js: -------------------------------------------------------------------------------- 1 | const buidler = require('@nomiclabs/buidler'); 2 | const { deployments } = buidler; 3 | 4 | const { deployContract, deployMockContract } = require('ethereum-waffle'); 5 | const { ethers } = require('ethers'); 6 | const { expect } = require('chai'); 7 | 8 | // buidler.ethers.errors.setLogLevel('error'); 9 | 10 | module.exports = { 11 | buidler, 12 | deployments, 13 | ethers, 14 | expect, 15 | 16 | deployContract, 17 | deployMockContract, 18 | 19 | EMPTY_STR: ethers.utils.formatBytes32String(""), 20 | ZERO_ADDRESS: ethers.constants.AddressZero, 21 | }; 22 | --------------------------------------------------------------------------------