├── .env.template ├── .gitignore ├── README.md ├── cache ├── .hardhat_contract_sizer_output.json ├── hardhat-network-fork │ └── recommendations-already-shown.json └── solidity-files-cache.json ├── config ├── index.ts └── tokens.ts ├── contracts ├── FixedSupplyToken.sol ├── _0xBitcoin2Token.sol ├── _0xBitcoinToken.sol ├── _0xBitcoinTokenTest.sol ├── _0xTestToken.sol ├── tests │ ├── Testable.sol │ └── xBitsToken_Test.sol └── xBitsToken.sol ├── deploy └── deploy.ts ├── deployments ├── hardhat │ ├── .chainId │ ├── solcInputs │ │ ├── 04f8ca623280474d11edb7e48a2c883a.json │ │ ├── 07e78a4b657d072cf65511750feb3d9a.json │ │ ├── 1805f786114c1cd5e37ff9c472a2efef.json │ │ ├── 26aa8ef30c9fafdd532f21aa0b26eef3.json │ │ ├── 32998620ab2b0a7c680b3f92fd46bada.json │ │ ├── 3688909cf7a3167c6e449d134c4c6935.json │ │ ├── 3dcce4d8b3537e8ae3deb0f9db17a1b0.json │ │ ├── 5613346afd896da29bc5b5df3f2a3892.json │ │ ├── 5aad27f9ff2d1103a567fe3c25b5f8fb.json │ │ ├── 5b02b6ec46345fb40f7b5599caa8f0f3.json │ │ ├── 5dbe16f16a441a40fd7ac8c6698d6ea1.json │ │ ├── 66ddabc15a75d0134b85194064d82718.json │ │ ├── 762679b3f7194f7bdf22cee2d03286ec.json │ │ ├── 7715d79399aba34d75e5f59d5eb32e25.json │ │ ├── 789b7b6f7edf01924c5e22e48a9de09c.json │ │ ├── 981c4cb54c55f921ad179d08bcb96c3e.json │ │ ├── 9836f820fe784e3094ba3f37dc640ed8.json │ │ ├── abaad5073595a7832ec6110c30a9f194.json │ │ ├── bd0840a00cd4a6f81e6a90386c72c7f8.json │ │ ├── be5a1f57b6fe3b20ffbb532bd08deafb.json │ │ ├── d56f325354ddd5bba76a0c76cc98e3c4.json │ │ ├── dbd5e2d95fce7b36a2c6e0b1c518aef3.json │ │ ├── e52c581ccf5c595194baafe1c087c8c6.json │ │ ├── ea5841bf41c59f931622a197eeef4414.json │ │ └── f05e28e6fff2cff7584b22ad6584ada3.json │ └── xBitsToken_Test.json └── rinkeby │ ├── .chainId │ ├── .latestDeploymentBlock │ ├── FixedSupplyToken.json │ └── solcInputs │ └── f1bd4eb63df8f5fb61900f6a1d878bed.json ├── hardhat.config.ts ├── helpers ├── chai-helpers.ts ├── consts.ts ├── deploy-helpers.ts ├── formatMsg.ts ├── get-funds.ts ├── hre-extensions.ts ├── oz-contract-helpers.ts ├── rrequire.ts ├── tasks │ └── test.ts └── types.d.ts ├── package.json ├── scripts ├── deploy.js ├── publish.js └── watch.js ├── tasks.ts ├── test-old ├── solidity-helper.js ├── test-main.js └── web3helpers.js ├── test ├── lib │ └── EIP2616SDK.ts ├── test-utils.ts └── token.spec.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock /.env.template: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # Template file for LOCAL environment variables. 3 | ############################################################ 4 | 5 | # Mnemonic used to deploy the smart contracts. 6 | # The default value is pre-configured to use it with Ganache locally. 7 | # Note: Keep safe your mnemonic. 8 | MNEMONIC_KEY="blossom spatial metal assault riot bullet truck update forward brave slide way" 9 | 10 | 11 | DEFAULT_NETWORK=hardhat 12 | 13 | MAINNET_RPC_URL= 14 | KOVAN_RPC_URL= 15 | GOERLI_RPC_URL= 16 | RINKEBY_RPC_URL= 17 | ROPSTEN_RPC_URL= 18 | POLYGON_RPC_URL= 19 | MUMBAI_RPC_URL= 20 | 21 | 22 | # Infura key used to deploy smart contracts 23 | INFURA_KEY=add-your-infura-key-here 24 | 25 | # Etherscan API key. This is used to verify smart contract in the Ethereum networks. 26 | ETHERSCAN_API_KEY=add-your-etherscan-api-key-here 27 | 28 | # CoinMarketCap API key used for gas reporting 29 | CMC_KEY=add-your-coinmarketcap-key-here 30 | 31 | # Gas price (in Gwei) used to deploy smart contract. 32 | # Note: Check current gas price in the Ethereum network before deploying. 33 | GAS_PRICE_GWEI_KEY=20 34 | 35 | # Disables logging via `hre.log` 36 | DISABLE_LOGS='false' 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /build/contracts 4 | 5 | /artifacts 6 | .env 7 | 8 | generated/* 9 | 10 | *.secret -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0xBitcoin 3 | [Deployed on Ethereum](https://etherscan.io/address/0xb6ed7644c69416d67b522e20bc294a9a9b405b31) 4 | 5 | ![0xbitcoin_small](https://user-images.githubusercontent.com/36060731/35717032-b47d34d0-07aa-11e8-9d1a-48dafbbb2ca0.png) 6 | 7 | 8 | #### An ERC20 token that is mined using PoW through a SmartContract 9 | 10 | * No pre-mine 11 | * No ICO 12 | * 21,000,000 tokens total 13 | * Difficulty target auto-adjusts with PoW hashrate 14 | * Rewards decrease as more tokens are minted 15 | * Compatible with all services that support ERC20 tokens 16 | 17 | 18 | 19 | #### Commands 20 | 21 | > yarn 22 | > yarn test 23 | 24 | #### How does it work? 25 | 26 | Typically, ERC20 tokens will grant all tokens to the owner or will have an ICO and demand that large amounts of Ether be sent to the owner. Instead of granting tokens to the 'contract owner', all 0xbitcoin tokens are locked within the smart contract initially. These tokens are dispensed, 50 at a time, by calling the function 'mint' and using Proof of Work, just like mining bitcoin. Here is what that looks like: 27 | 28 | 29 | function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) { 30 | 31 | 32 | uint reward_amount = getMiningReward(); 33 | 34 | 35 | bytes32 digest = keccak256(challengeNumber, msg.sender, nonce ); 36 | 37 | 38 | if (digest != challenge_digest) revert(); 39 | 40 | //the digest must be smaller than the target 41 | if(uint256(digest) > miningTarget) revert(); 42 | 43 | 44 | uint hashFound = rewardHashesFound[digest]; 45 | rewardHashesFound[digest] = epochCount; 46 | if(hashFound != 0) revert(); //prevent the same answer from awarding twice 47 | 48 | balances[msg.sender] = balances[msg.sender].add(reward_amount); 49 | 50 | tokensMinted = tokensMinted.add(reward_amount); 51 | 52 | //set readonly diagnostics data 53 | lastRewardTo = msg.sender; 54 | lastRewardAmount = reward_amount; 55 | lastRewardEthBlockNumber = block.number; 56 | 57 | //start a new round of mining with a new 'challengeNumber' 58 | _startNewMiningEpoch(); 59 | 60 | Mint(msg.sender, reward_amount, epochCount, challengeNumber ); 61 | 62 | return true; 63 | 64 | } 65 | 66 | 67 | As you can see, a special number called a 'nonce' has to be passed into this function in order for tokens to be dispensed. This number has to fit a special 'puzzle' similar to a sudoku puzzle, and this is called Proof of Work. To find this special number, it is necessary to run a mining program. A cpu miner exists for mining 0xbitcoin tokens and it can be downloaded here: 68 | 69 | https://github.com/0xbitcoin/0xbitcoin-miner 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /cache/.hardhat_contract_sizer_output.json: -------------------------------------------------------------------------------- 1 | [{"fullName":"@openzeppelin/contracts/security/ReentrancyGuard.sol:ReentrancyGuard","displayName":"ReentrancyGuard","size":0,"previousSize":null},{"fullName":"@openzeppelin/contracts/access/Ownable.sol:Ownable","displayName":"Ownable","size":0,"previousSize":null},{"fullName":"@openzeppelin/contracts/utils/Context.sol:Context","displayName":"Context","size":0,"previousSize":null},{"fullName":"@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20","displayName":"IERC20","size":0,"previousSize":null},{"fullName":"contracts/FixedSupplyToken.sol:FixedSupplyToken","displayName":"FixedSupplyToken","size":1679,"previousSize":1679},{"fullName":"contracts/PayspecV2.sol:Payspec","displayName":"Payspec","size":6469,"previousSize":null}] -------------------------------------------------------------------------------- /cache/hardhat-network-fork/recommendations-already-shown.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /cache/solidity-files-cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-cache-2", 3 | "files": { 4 | "/home/andy/dev/payspec-contract/node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol": { 5 | "lastModificationDate": 1651428791203, 6 | "contentHash": "ad7c2d0af148c8f9f097d65deeb4da6b", 7 | "sourceName": "@openzeppelin/contracts/token/ERC20/IERC20.sol", 8 | "solcConfig": { 9 | "version": "0.8.4", 10 | "settings": { 11 | "optimizer": { 12 | "enabled": true, 13 | "runs": 200 14 | }, 15 | "outputSelection": { 16 | "*": { 17 | "*": [ 18 | "abi", 19 | "evm.bytecode", 20 | "evm.deployedBytecode", 21 | "evm.methodIdentifiers", 22 | "metadata", 23 | "devdoc", 24 | "userdoc", 25 | "storageLayout", 26 | "evm.gasEstimates" 27 | ], 28 | "": [ 29 | "ast" 30 | ] 31 | } 32 | }, 33 | "metadata": { 34 | "useLiteralContent": true 35 | } 36 | } 37 | }, 38 | "imports": [], 39 | "versionPragmas": [ 40 | "^0.8.0" 41 | ], 42 | "artifacts": [ 43 | "IERC20" 44 | ] 45 | }, 46 | "/home/andy/dev/payspec-contract/node_modules/@openzeppelin/contracts/utils/Context.sol": { 47 | "lastModificationDate": 1651428789439, 48 | "contentHash": "5f2c5c4b6af2dd4551027144797bc8be", 49 | "sourceName": "@openzeppelin/contracts/utils/Context.sol", 50 | "solcConfig": { 51 | "version": "0.8.4", 52 | "settings": { 53 | "optimizer": { 54 | "enabled": true, 55 | "runs": 200 56 | }, 57 | "outputSelection": { 58 | "*": { 59 | "*": [ 60 | "abi", 61 | "evm.bytecode", 62 | "evm.deployedBytecode", 63 | "evm.methodIdentifiers", 64 | "metadata", 65 | "devdoc", 66 | "userdoc", 67 | "storageLayout", 68 | "evm.gasEstimates" 69 | ], 70 | "": [ 71 | "ast" 72 | ] 73 | } 74 | }, 75 | "metadata": { 76 | "useLiteralContent": true 77 | } 78 | } 79 | }, 80 | "imports": [], 81 | "versionPragmas": [ 82 | "^0.8.0" 83 | ], 84 | "artifacts": [ 85 | "Context" 86 | ] 87 | }, 88 | "/home/andy/dev/payspec-contract/contracts/FixedSupplyToken.sol": { 89 | "lastModificationDate": 1651428469436, 90 | "contentHash": "aa77919a295713e22c8d3e1da8b27356", 91 | "sourceName": "contracts/FixedSupplyToken.sol", 92 | "solcConfig": { 93 | "version": "0.8.4", 94 | "settings": { 95 | "optimizer": { 96 | "enabled": true, 97 | "runs": 200 98 | }, 99 | "outputSelection": { 100 | "*": { 101 | "*": [ 102 | "abi", 103 | "evm.bytecode", 104 | "evm.deployedBytecode", 105 | "evm.methodIdentifiers", 106 | "metadata", 107 | "devdoc", 108 | "userdoc", 109 | "storageLayout", 110 | "evm.gasEstimates" 111 | ], 112 | "": [ 113 | "ast" 114 | ] 115 | } 116 | }, 117 | "metadata": { 118 | "useLiteralContent": true 119 | } 120 | } 121 | }, 122 | "imports": [ 123 | "@openzeppelin/contracts/token/ERC20/IERC20.sol" 124 | ], 125 | "versionPragmas": [ 126 | "^0.8.0" 127 | ], 128 | "artifacts": [ 129 | "FixedSupplyToken" 130 | ] 131 | }, 132 | "/home/andy/dev/payspec-contract/contracts/PayspecV2.sol": { 133 | "lastModificationDate": 1651428469436, 134 | "contentHash": "98ae309aeed6d5aa0d6c95b21d3c8dbc", 135 | "sourceName": "contracts/PayspecV2.sol", 136 | "solcConfig": { 137 | "version": "0.8.4", 138 | "settings": { 139 | "optimizer": { 140 | "enabled": true, 141 | "runs": 200 142 | }, 143 | "outputSelection": { 144 | "*": { 145 | "*": [ 146 | "abi", 147 | "evm.bytecode", 148 | "evm.deployedBytecode", 149 | "evm.methodIdentifiers", 150 | "metadata", 151 | "devdoc", 152 | "userdoc", 153 | "storageLayout", 154 | "evm.gasEstimates" 155 | ], 156 | "": [ 157 | "ast" 158 | ] 159 | } 160 | }, 161 | "metadata": { 162 | "useLiteralContent": true 163 | } 164 | } 165 | }, 166 | "imports": [ 167 | "@openzeppelin/contracts/access/Ownable.sol", 168 | "@openzeppelin/contracts/token/ERC20/IERC20.sol", 169 | "@openzeppelin/contracts/security/ReentrancyGuard.sol" 170 | ], 171 | "versionPragmas": [ 172 | "^0.8.0" 173 | ], 174 | "artifacts": [ 175 | "Payspec" 176 | ] 177 | }, 178 | "/home/andy/dev/payspec-contract/node_modules/@openzeppelin/contracts/access/Ownable.sol": { 179 | "lastModificationDate": 1651428789419, 180 | "contentHash": "8398972af73b4e9e5ff3b31cad86234f", 181 | "sourceName": "@openzeppelin/contracts/access/Ownable.sol", 182 | "solcConfig": { 183 | "version": "0.8.4", 184 | "settings": { 185 | "optimizer": { 186 | "enabled": true, 187 | "runs": 200 188 | }, 189 | "outputSelection": { 190 | "*": { 191 | "*": [ 192 | "abi", 193 | "evm.bytecode", 194 | "evm.deployedBytecode", 195 | "evm.methodIdentifiers", 196 | "metadata", 197 | "devdoc", 198 | "userdoc", 199 | "storageLayout", 200 | "evm.gasEstimates" 201 | ], 202 | "": [ 203 | "ast" 204 | ] 205 | } 206 | }, 207 | "metadata": { 208 | "useLiteralContent": true 209 | } 210 | } 211 | }, 212 | "imports": [ 213 | "../utils/Context.sol" 214 | ], 215 | "versionPragmas": [ 216 | "^0.8.0" 217 | ], 218 | "artifacts": [ 219 | "Ownable" 220 | ] 221 | }, 222 | "/home/andy/dev/payspec-contract/node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol": { 223 | "lastModificationDate": 1651428789439, 224 | "contentHash": "92f9448b23a90ea3bb932ee55cc3ccce", 225 | "sourceName": "@openzeppelin/contracts/security/ReentrancyGuard.sol", 226 | "solcConfig": { 227 | "version": "0.8.4", 228 | "settings": { 229 | "optimizer": { 230 | "enabled": true, 231 | "runs": 200 232 | }, 233 | "outputSelection": { 234 | "*": { 235 | "*": [ 236 | "abi", 237 | "evm.bytecode", 238 | "evm.deployedBytecode", 239 | "evm.methodIdentifiers", 240 | "metadata", 241 | "devdoc", 242 | "userdoc", 243 | "storageLayout", 244 | "evm.gasEstimates" 245 | ], 246 | "": [ 247 | "ast" 248 | ] 249 | } 250 | }, 251 | "metadata": { 252 | "useLiteralContent": true 253 | } 254 | } 255 | }, 256 | "imports": [], 257 | "versionPragmas": [ 258 | "^0.8.0" 259 | ], 260 | "artifacts": [ 261 | "ReentrancyGuard" 262 | ] 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /config/index.ts: -------------------------------------------------------------------------------- 1 | import { Network } from 'hardhat/types' 2 | import { AllNetworkTokens, Tokens } from '../helpers/types' 3 | 4 | import { tokens } from './tokens' 5 | 6 | /** 7 | * Checks if the network is Ethereum mainnet or one of its testnets 8 | * @param network {Network} Hardhat Network object 9 | * @param strict {boolean} Weather the check for mainnet should be restricted to exact match 10 | * @return boolean Boolean if the current network is Ethereum 11 | */ 12 | export const isEthereumNetwork = (network: Network, strict = false): boolean => 13 | strict 14 | ? getNetworkName(network) === 'mainnet' 15 | : ['mainnet', 'kovan', 'rinkeby', 'ropsten'].some( 16 | (n) => n === getNetworkName(network) 17 | ) 18 | 19 | /** 20 | * Gets the current network name. If there is a `FORKING_NETWORK` environment variable set that is returned instead. 21 | * @param network {Network} Hardhat network object 22 | * @return string The current network name 23 | */ 24 | export const getNetworkName = (network: Network): string => 25 | process.env.FORKING_NETWORK ?? network.name 26 | 27 | /** 28 | * Gets the object of tokens specified by the config file including an `all` field which list every token by its symbol. 29 | * @param network {Network} Hardhat Network object 30 | * @return AllNetworkTokens Object of all tokens for the specified network 31 | */ 32 | export const getTokens = (network: Network): AllNetworkTokens => { 33 | const networkTokens = tokens[getNetworkName(network)] 34 | const all = Object.keys(networkTokens).reduce( 35 | (map, type) => ({ 36 | ...map, 37 | ...networkTokens[type], 38 | }), 39 | {} 40 | ) 41 | return { 42 | ...networkTokens, 43 | all, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/tokens.ts: -------------------------------------------------------------------------------- 1 | import { NetworkTokens } from '../helpers/types' 2 | 3 | const polygonTokens: NetworkTokens = { 4 | aave: { 5 | ADAI: '0x27F8D03b3a2196956ED754baDc28D73be8830A6e', // amDAI 6 | AUSDC: '0x1a13F4Ca1d028320A707D99520AbFefca3998b7F', // amUSDC 7 | AUSDT: '0x60D55F02A771d515e077c9C2403a1ef324885CeC', // amUSDT 8 | AWETH: '0x28424507fefb6f7f8E9D3860F56504E4e5f5f390', // amWETH 9 | AWBTC: '0x5c2ed810328349100A66B82b78a1791B101C9D61', // amWBTC 10 | AWMATIC: '0x8dF3aad3a84da6b69A4DA8aeC3eA40d9091B2Ac4', // amWMATIC 11 | }, 12 | poolTogether: { 13 | PTDAI: '0x3e35681E6439961EC7F2b1ABaB6b967D6a645270', 14 | PTDAIS: '0xB102A0Ba3707A94a64CE63c7BeA8039680e1ad5C', 15 | PTUSDT: '0x9ecB26631098973834925eb453De1908Ea4bdD4e', 16 | PTUSDC: '0x6a304dFdb9f808741244b6bfEe65ca7B3b3A6076', 17 | }, 18 | erc20: { 19 | // Aave 20 | AAVE: '0xD6DF932A45C0f255f85145f286eA0b292B21C90B', 21 | // Pool Together 22 | // ERC20 23 | WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', 24 | WMATIC: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', 25 | DAI: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', 26 | USDC: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', 27 | USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', 28 | LINK: '0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39', 29 | WBTC: '0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6', 30 | }, 31 | } 32 | 33 | export const tokens: Record = { 34 | kovan: { 35 | compound: { 36 | CDAI: '0xf0d0eb522cfa50b716b3b1604c4f0fa6f04376ad', 37 | CUSDC: '0x4a92e71227d294f041bd82dd8f78591b75140d63', 38 | CETH: '0x41b5844f4680a8c38fbb695b7f9cfd1f64474a72', 39 | CUSDT: '0x3f0a0ea2f86bae6362cf9799b523ba06647da018', 40 | }, 41 | erc20: { 42 | // Compound 43 | COMP: '0x61460874a7196d6a22d1ee4922473664b3e95270', 44 | // Aave 45 | // Pool Together 46 | // Yearn 47 | // ERC20 48 | WETH: '0xd0a1e359811322d97991e03f863a0c30c2cf029c', 49 | DAI: '0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa', 50 | USDC: '0xb7a4f3e9097c08da09517b5ab877f7a917224ede', 51 | USDT: '0x07de306ff27a2b630b1141956844eb1552b956b5', 52 | LINK: '0xa36085F69e2889c224210F603D836748e7dC0088', 53 | }, 54 | }, 55 | rinkeby: { 56 | compound: { 57 | CDAI: '0x6D7F0754FFeb405d23C51CE938289d4835bE3b14', 58 | CUSDC: '0x5B281A6DdA0B271e91ae35DE655Ad301C976edb1', 59 | CETH: '0xd6801a1dffcd0a410336ef88def4320d6df1883e', 60 | CUSDT: '0x2fb298bdbef468638ad6653ff8376575ea41e768', 61 | }, 62 | erc20: { 63 | // Compound 64 | // Aave 65 | // Pool Together 66 | // Yearn 67 | // ERC20 68 | WETH: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 69 | DAI: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', 70 | USDC: '0x4DBCdF9B62e891a7cec5A2568C3F4FAF9E8Abe2b', 71 | USDT: '0x3B00Ef435fA4FcFF5C209a37d1f3dcff37c705aD', 72 | LINK: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709', 73 | }, 74 | }, 75 | ropsten: { 76 | compound: { 77 | CDAI: '0xdb5Ed4605C11822811a39F94314fDb8F0fb59A2C', 78 | CUSDC: '0x8aF93cae804cC220D1A608d4FA54D1b6ca5EB361', 79 | CETH: '0xbe839b6d93e3ea47effcca1f27841c917a8794f3', 80 | CUSDT: '0xf6958cf3127e62d3eb26c79f4f45d3f3b2ccded4', 81 | }, 82 | erc20: { 83 | // Compound 84 | COMP: '0xf76d4a441e4ba86a923ce32b89aff89dbccaa075', 85 | // Aave 86 | // Pool Together 87 | // Yearn 88 | // ERC20 89 | WETH: '0xc778417e063141139fce010982780140aa0cd5ab', 90 | DAI: '0xc2118d4d90b274016cB7a54c03EF52E6c537D957', 91 | USDC: '0x0D9C8723B343A8368BebE0B5E89273fF8D712e3C', 92 | USDT: '0x516de3a7A567d81737e3a46ec4FF9cFD1fcb0136', 93 | LINK: '0x20fE562d797A42Dcb3399062AE9546cd06f63280', 94 | }, 95 | }, 96 | polygon: polygonTokens, 97 | polygon_mumbai: { 98 | aave: { 99 | ADAI: '0x639cB7b21ee2161DF9c882483C9D55c90c20Ca3e', // amDAI 100 | AUSDC: '0x2271e3Fef9e15046d09E1d78a8FF038c691E9Cf9', // amUSDC 101 | AUSDT: '0xF8744C0bD8C7adeA522d6DDE2298b17284A79D1b', // amUSDT 102 | AWETH: '0x7aE20397Ca327721F013BB9e140C707F82871b56', // amWETH 103 | AWBTC: '0xc9276ECa6798A14f64eC33a526b547DAd50bDa2F', // amWBTC 104 | AWMATIC: '0xF45444171435d0aCB08a8af493837eF18e86EE27', // amWMATIC 105 | }, 106 | erc20: { 107 | // Aave 108 | AAVE: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', 109 | // Pool Together 110 | // ERC20 111 | WETH: '0x3C68CE8504087f89c640D02d133646d98e64ddd9', 112 | WMATIC: '0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889', 113 | DAI: '0x001B3B4d0F3714Ca98ba10F6042DaEbF0B1B7b6F', 114 | USDC: '0x2058A9D7613eEE744279e3856Ef0eAda5FCbaA7e', 115 | USDT: '0xBD21A10F619BE90d6066c941b04e340841F1F989', 116 | LINK: '0x326C977E6efc84E512bB9C30f76E30c160eD06FB', 117 | WBTC: '0x0d787a4a1548f673ed375445535a6c7A1EE56180', 118 | }, 119 | }, 120 | mainnet: { 121 | compound: { 122 | CDAI: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', 123 | CUSDC: '0x39aa39c021dfbae8fac545936693ac917d5e7563', 124 | CETH: '0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5', 125 | CUSDT: '0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9', 126 | CWBTC: '0xccf4429db6322d5c611ee964527d42e5d685dd6a', // cWBTC2 127 | CLINK: '0xface851a4921ce59e912d19329929ce6da6eb0c7', 128 | }, 129 | aave: { 130 | ADAI: '0x028171bCA77440897B824Ca71D1c56caC55b68A3', // aDAI 131 | AUSDC: '0xBcca60bB61934080951369a648Fb03DF4F96263C', // aUSDC 132 | AUSDT: '0x3Ed3B47Dd13EC9a98b44e6204A523E766B225811', // aUSDT 133 | AWETH: '0x030bA81f1c18d280636F32af80b9AAd02Cf0854e', // aWETH 134 | AWBTC: '0x9ff58f4fFB29fA2266Ab25e75e2A8b3503311656', // aWBTC 135 | ALINK: '0x5c2ed810328349100A66B82b78a1791B101C9D61', // aLINK 136 | }, 137 | poolTogether: { 138 | PTDAI: '0x334cbb5858417aee161b53ee0d5349ccf54514cf', 139 | PTDAIS: '0x0A2E7f69fe9588fa7fBa5F5864236883Cd4AaC6d', 140 | PTUSDC: '0xD81b1A8B1AD00Baa2D6609E0BAE28A38713872f7', 141 | }, 142 | yearn: { 143 | YDAI: '0x19D3364A399d251E894aC732651be8B0E4e85001', // v2 - DAI yVault 144 | YUSDT: '0x7Da96a3891Add058AdA2E826306D812C638D87a7', 145 | }, 146 | erc20: { 147 | // Compound 148 | COMP: '0xc00e94cb662c3520282e6f5717214004a7f26888', 149 | // Aave 150 | AAVE: '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', 151 | // Pool Together 152 | // Yearn 153 | // ERC20 154 | WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 155 | DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F', 156 | USDC: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 157 | USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', 158 | LINK: '0x514910771AF9Ca656af840dff83E8264EcF986CA', 159 | WBTC: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', 160 | SNX: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', 161 | MKR: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', 162 | YFI: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', 163 | }, 164 | }, 165 | } 166 | -------------------------------------------------------------------------------- /contracts/FixedSupplyToken.sol: -------------------------------------------------------------------------------- 1 | 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | // ---------------------------------------------------------------------------------------------- 6 | // Sample fixed supply token contract 7 | // Enjoy. (c) BokkyPooBah 2017. The MIT Licence. 8 | // ---------------------------------------------------------------------------------------------- 9 | 10 | // ERC Token Standard #20 Interface 11 | // https://github.com/ethereum/EIPs/issues/20 12 | 13 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 14 | 15 | contract FixedSupplyToken is IERC20 { 16 | string public constant symbol = "FIXED"; 17 | string public constant name = "Example Fixed Supply Token"; 18 | uint8 public constant decimals = 8; 19 | uint256 _totalSupply = 21000000 * 10**uint(decimals); 20 | 21 | 22 | 23 | // Owner of this contract 24 | address public owner; 25 | 26 | // Balances for each account 27 | mapping(address => uint256) public balances; 28 | 29 | // Owner of account approves the transfer of an amount to another account 30 | mapping(address => mapping (address => uint256)) public allowed; 31 | 32 | // Functions with this modifier can only be executed by the owner 33 | modifier onlyOwner() { 34 | if (msg.sender != owner) { 35 | revert(); 36 | } 37 | _; 38 | } 39 | 40 | // Constructor 41 | constructor() public { 42 | owner = msg.sender; 43 | balances[owner] = _totalSupply; 44 | } 45 | 46 | 47 | function mint(address to, uint256 amount) public { 48 | balances[to] += amount; 49 | } 50 | 51 | function totalSupply() public view override returns (uint256 totalSupply) { 52 | totalSupply = _totalSupply; 53 | } 54 | 55 | // What is the balance of a particular account? 56 | function balanceOf(address _owner) public view override returns (uint256 balance) { 57 | return balances[_owner]; 58 | } 59 | 60 | // Transfer the balance from owner's account to another account 61 | function transfer(address _to, uint256 _amount) public override returns (bool success) { 62 | if (balances[msg.sender] >= _amount 63 | && _amount > 0 64 | && balances[_to] + _amount > balances[_to]) { 65 | balances[msg.sender] -= _amount; 66 | balances[_to] += _amount; 67 | emit Transfer(msg.sender, _to, _amount); 68 | return true; 69 | } else { 70 | return false; 71 | } 72 | } 73 | 74 | // Send _value amount of tokens from address _from to address _to 75 | // The transferFrom method is used for a withdraw workflow, allowing contracts to send 76 | // tokens on your behalf, for example to "deposit" to a contract address and/or to charge 77 | // fees in sub-currencies; the command should fail unless the _from account has 78 | // deliberately authorized the sender of the message via some mechanism; we propose 79 | // these standardized APIs for approval: 80 | function transferFrom( 81 | address _from, 82 | address _to, 83 | uint256 _amount 84 | ) public override returns (bool success) { 85 | if (balances[_from] >= _amount 86 | && allowed[_from][msg.sender] >= _amount 87 | && _amount > 0 88 | && balances[_to] + _amount > balances[_to]) { 89 | balances[_from] -= _amount; 90 | allowed[_from][msg.sender] -= _amount; 91 | balances[_to] += _amount; 92 | emit Transfer(_from, _to, _amount); 93 | return true; 94 | } else { 95 | return false; 96 | } 97 | } 98 | 99 | // Allow _spender to withdraw from your account, multiple times, up to the _value amount. 100 | // If this function is called again it overwrites the current allowance with _value. 101 | function approve(address _spender, uint256 _amount) public override returns (bool success) { 102 | allowed[msg.sender][_spender] = _amount; 103 | emit Approval(msg.sender, _spender, _amount); 104 | return true; 105 | } 106 | 107 | function allowance(address _owner, address _spender) public override view returns (uint256 remaining) { 108 | return allowed[_owner][_spender]; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /contracts/_0xBitcoin2Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.6; 2 | 3 | 4 | // ---------------------------------------------------------------------------- 5 | 6 | // Mineable ERC20 Token using Proof Of Work 7 | 8 | // 9 | 10 | // Symbol : 0xBTC2 11 | 12 | // Name : 0xBitcoin2 13 | 14 | // Total supply: 21,000,000.00 15 | 16 | // Decimals : 8 17 | 18 | // Version : 2 19 | 20 | // 21 | 22 | 23 | // ---------------------------------------------------------------------------- 24 | 25 | 26 | 27 | 28 | 29 | 30 | // ---------------------------------------------------------------------------- 31 | 32 | // ERC Token Standard #20 Interface 33 | 34 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 35 | 36 | // ---------------------------------------------------------------------------- 37 | 38 | abstract contract ERC20Interface { 39 | 40 | function totalSupply() external virtual view returns (uint); 41 | 42 | function balanceOf(address tokenOwner) external virtual view returns (uint balance); 43 | 44 | function allowance(address tokenOwner, address spender) external virtual view returns (uint remaining); 45 | 46 | function transfer(address to, uint tokens) external virtual returns (bool success); 47 | 48 | function approve(address spender, uint tokens) external virtual returns (bool success); 49 | 50 | function transferFrom(address from, address to, uint tokens) external virtual returns (bool success); 51 | 52 | function _approve(address owner, address spender, uint tokens) internal virtual returns (bool success); 53 | 54 | function _transfer(address from, address to, uint tokens) internal virtual returns (bool success); 55 | 56 | event Transfer(address indexed from, address indexed to, uint tokens); 57 | 58 | event Approval(address indexed tokenOwner, address indexed spender, uint tokens); 59 | 60 | } 61 | 62 | contract ERC20Standard is ERC20Interface { 63 | 64 | string public symbol; 65 | string public name; 66 | 67 | uint8 public decimals; 68 | 69 | mapping(address => uint) balances; 70 | mapping(address => mapping(address => uint)) allowed; 71 | 72 | uint public override totalSupply; 73 | 74 | constructor(string memory _symbol, string memory _name, uint8 _decimals){ 75 | symbol = _symbol; 76 | name = _name; 77 | decimals = _decimals; 78 | } 79 | 80 | function _transfer(address from, address to, uint tokens) internal override returns (bool success) { 81 | 82 | balances[from] = balances[from] - (tokens); 83 | 84 | balances[to] = balances[to] + (tokens); 85 | 86 | emit Transfer(from, to, tokens); 87 | 88 | return true; 89 | } 90 | 91 | 92 | 93 | // ------------------------------------------------------------------------ 94 | 95 | // Get the token balance for account `tokenOwner` 96 | 97 | // ------------------------------------------------------------------------ 98 | 99 | function balanceOf(address tokenOwner) public override view returns (uint balance) { 100 | 101 | return balances[tokenOwner]; 102 | 103 | } 104 | 105 | 106 | 107 | // ------------------------------------------------------------------------ 108 | 109 | // Transfer the balance from token owner's account to `to` account 110 | 111 | // - Owner's account must have sufficient balance to transfer 112 | 113 | // - 0 value transfers are allowed 114 | 115 | // ------------------------------------------------------------------------ 116 | 117 | function transfer(address to, uint tokens) public override returns (bool success) { 118 | 119 | return _transfer(msg.sender, to, tokens); 120 | 121 | } 122 | 123 | 124 | 125 | 126 | // ------------------------------------------------------------------------ 127 | 128 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 129 | 130 | // from the token owner's account 131 | 132 | // 133 | 134 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 135 | 136 | // recommends that there are no checks for the approval double-spend attack 137 | 138 | // as this should be implemented in user interfaces 139 | 140 | // ------------------------------------------------------------------------ 141 | 142 | function approve(address spender, uint tokens) public override returns (bool success) { 143 | 144 | return _approve(msg.sender, spender,tokens); 145 | 146 | } 147 | 148 | function _approve(address owner, address spender, uint tokens) internal override returns (bool success) { 149 | 150 | allowed[owner][spender] = tokens; 151 | 152 | emit Approval(owner, spender, tokens); 153 | 154 | return true; 155 | 156 | } 157 | 158 | 159 | // ------------------------------------------------------------------------ 160 | 161 | // Transfer `tokens` from the `from` account to the `to` account 162 | 163 | // 164 | 165 | // The calling account must already have sufficient tokens approve(...)-d 166 | 167 | // for spending from the `from` account and 168 | 169 | // - From account must have sufficient balance to transfer 170 | 171 | // - Spender must have sufficient allowance to transfer 172 | 173 | // - 0 value transfers are allowed 174 | 175 | // ------------------------------------------------------------------------ 176 | 177 | function transferFrom(address from, address to, uint tokens) public override returns (bool success) { 178 | 179 | allowed[from][msg.sender] = allowed[from][msg.sender] - (tokens); 180 | 181 | return _transfer(from,to,tokens); 182 | 183 | } 184 | 185 | 186 | // ------------------------------------------------------------------------ 187 | 188 | // Returns the amount of tokens approved by the owner that can be 189 | 190 | // transferred to the spender's account 191 | 192 | // ------------------------------------------------------------------------ 193 | 194 | function allowance(address tokenOwner, address spender) public override view returns (uint remaining) { 195 | 196 | return allowed[tokenOwner][spender]; 197 | 198 | } 199 | 200 | 201 | } 202 | 203 | 204 | 205 | 206 | abstract contract EIP918Interface { 207 | 208 | function challengeNumber() virtual external returns (bytes32); 209 | function tokensMinted() virtual external returns (uint256); 210 | function miningTarget() virtual external returns (uint256); 211 | function maxSupplyForEra() virtual external returns (uint256); 212 | function latestDifficultyPeriodStarted() virtual external returns (uint256); 213 | function rewardEra() virtual external returns (uint256); 214 | function epochCount() virtual external returns (uint256); 215 | function getMiningReward() virtual external returns (uint256); 216 | 217 | } 218 | 219 | 220 | library ECRecover { 221 | /** 222 | * @notice Recover signer's address from a signed message 223 | * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol 224 | * Modifications: Accept v, r, and s as separate arguments 225 | * @param digest Keccak-256 hash digest of the signed message 226 | * @param v v of the signature 227 | * @param r r of the signature 228 | * @param s s of the signature 229 | * @return Signer address 230 | */ 231 | function recover( 232 | bytes32 digest, 233 | uint8 v, 234 | bytes32 r, 235 | bytes32 s 236 | ) internal pure returns (address) { 237 | // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature 238 | // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines 239 | // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most 240 | // signatures from current libraries generate a unique signature with an s-value in the lower half order. 241 | // 242 | // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value 243 | // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or 244 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 245 | // these malleable signatures as well. 246 | if ( 247 | uint256(s) > 248 | 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 249 | ) { 250 | revert("ECRecover: invalid signature 's' value"); 251 | } 252 | 253 | if (v != 27 && v != 28) { 254 | revert("ECRecover: invalid signature 'v' value"); 255 | } 256 | 257 | // If the signature is valid (and not malleable), return the signer address 258 | address signer = ecrecover(digest, v, r, s); 259 | require(signer != address(0), "ECRecover: invalid signature"); 260 | 261 | return signer; 262 | } 263 | } 264 | 265 | 266 | 267 | contract EIP712Domain { 268 | /** 269 | * @dev EIP712 Domain Separator 270 | */ 271 | bytes32 public DOMAIN_SEPARATOR; 272 | } 273 | 274 | 275 | 276 | /** 277 | * @title EIP712 278 | * @notice A library that provides EIP712 helper functions 279 | */ 280 | library EIP712 { 281 | /** 282 | * @notice Make EIP712 domain separator 283 | * @param name Contract name 284 | * @param version Contract version 285 | * @return Domain separator 286 | */ 287 | function makeDomainSeparator(string memory name, string memory version) 288 | internal 289 | view 290 | returns (bytes32) 291 | { 292 | uint256 chainId; 293 | assembly { 294 | chainId := chainid() 295 | } 296 | return 297 | keccak256( 298 | abi.encode( 299 | // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") 300 | 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, 301 | keccak256(bytes(name)), 302 | keccak256(bytes(version)), 303 | chainId, 304 | address(this) 305 | ) 306 | ); 307 | } 308 | 309 | /** 310 | * @notice Recover signer's address from a EIP712 signature 311 | * @param domainSeparator Domain separator 312 | * @param v v of the signature 313 | * @param r r of the signature 314 | * @param s s of the signature 315 | * @param typeHashAndData Type hash concatenated with data 316 | * @return Signer's address 317 | */ 318 | function recover( 319 | bytes32 domainSeparator, 320 | uint8 v, 321 | bytes32 r, 322 | bytes32 s, 323 | bytes memory typeHashAndData 324 | ) internal pure returns (address) { 325 | bytes32 digest = keccak256( 326 | abi.encodePacked( 327 | "\x19\x01", 328 | domainSeparator, 329 | keccak256(typeHashAndData) 330 | ) 331 | ); 332 | return ECRecover.recover(digest, v, r, s); 333 | } 334 | 335 | } 336 | 337 | 338 | /** 339 | * @title EIP-2612 340 | * @notice Provide internal implementation for gas-abstracted approvals 341 | */ 342 | abstract contract EIP2612 is EIP712Domain,ERC20Interface { 343 | // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") 344 | bytes32 345 | public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; 346 | 347 | mapping(address => uint256) private _permitNonces; 348 | 349 | /** 350 | * @notice Nonces for permit 351 | * @param owner Token owner's address (Authorizer) 352 | * @return Next nonce 353 | */ 354 | function nonces(address owner) external view returns (uint256) { 355 | return _permitNonces[owner]; 356 | } 357 | 358 | /** 359 | * @notice Verify a signed approval permit and execute if valid 360 | * @param owner Token owner's address (Authorizer) 361 | * @param spender Spender's address 362 | * @param value Amount of allowance 363 | * @param deadline The time at which this expires (unix time) 364 | * @param v v of the signature 365 | * @param r r of the signature 366 | * @param s s of the signature 367 | */ 368 | function _permit( 369 | address owner, 370 | address spender, 371 | uint256 value, 372 | uint256 deadline, 373 | uint8 v, 374 | bytes32 r, 375 | bytes32 s 376 | ) internal { 377 | require(deadline >= block.timestamp, "Permit is expired"); 378 | 379 | bytes memory data = abi.encode( 380 | PERMIT_TYPEHASH, 381 | owner, 382 | spender, 383 | value, 384 | _permitNonces[owner]++, 385 | deadline 386 | ); 387 | require( 388 | EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner, 389 | "EIP2612: invalid signature" 390 | ); 391 | 392 | _approve(owner, spender, value); 393 | } 394 | 395 | 396 | } 397 | 398 | 399 | 400 | 401 | 402 | library ExtendedMath { 403 | 404 | 405 | //return the smaller of the two inputs (a or b) 406 | function limitLessThan(uint a, uint b) internal pure returns (uint c) { 407 | 408 | if(a > b) return b; 409 | 410 | return a; 411 | 412 | } 413 | } 414 | 415 | 416 | 417 | contract _0xBitcoinToken2 is ERC20Standard("0xBTC2","0xBitcoin2",8), EIP2612 { 418 | 419 | using ExtendedMath for uint; 420 | 421 | string public constant version = "2"; 422 | 423 | uint public latestDifficultyPeriodStarted; 424 | 425 | uint public epochCount; 426 | 427 | uint public _BLOCKS_PER_READJUSTMENT = 1024; 428 | uint public _MINIMUM_TARGET = 2**16; 429 | uint public _MAXIMUM_TARGET = 2**234; 430 | 431 | 432 | uint public miningTarget; 433 | bytes32 public challengeNumber; 434 | 435 | uint public rewardEra; 436 | uint public maxSupplyForEra; 437 | 438 | uint public currentMiningReward; 439 | address public lastRewardTo; 440 | uint public lastRewardAmount; 441 | uint public lastRewardEthBlockNumber; 442 | 443 | uint public tokensMinted; 444 | uint public maximumSupply; 445 | 446 | address public originalTokenContract; 447 | 448 | 449 | 450 | event Mint(address from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); 451 | 452 | 453 | constructor( address _originalTokenContract ) { 454 | 455 | originalTokenContract = _originalTokenContract; 456 | 457 | DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(name, version); 458 | 459 | maximumSupply = 21000000 * 10**uint(decimals); 460 | 461 | // The deployer starts with nothing! All 0xBTC must be mined with Proof of Work. 462 | 463 | initialize(); 464 | 465 | } 466 | 467 | //set values to continue state forwards where it left off 468 | function initialize() internal virtual { 469 | 470 | epochCount = EIP918Interface( originalTokenContract ).epochCount(); 471 | 472 | tokensMinted = EIP918Interface( originalTokenContract ).tokensMinted(); 473 | 474 | rewardEra = EIP918Interface(originalTokenContract).rewardEra(); 475 | maxSupplyForEra = EIP918Interface(originalTokenContract).maxSupplyForEra(); 476 | 477 | miningTarget = EIP918Interface(originalTokenContract).miningTarget(); 478 | 479 | latestDifficultyPeriodStarted = EIP918Interface(originalTokenContract).latestDifficultyPeriodStarted(); 480 | challengeNumber = EIP918Interface(originalTokenContract).challengeNumber(); 481 | 482 | currentMiningReward = EIP918Interface(originalTokenContract).getMiningReward(); 483 | 484 | } 485 | 486 | 487 | function mint(uint256 nonce, bytes32) public returns (bool success) { 488 | 489 | return mintTo(nonce,msg.sender); 490 | 491 | } 492 | 493 | function mintTo(uint256 nonce, address minter) public returns (bool success) { 494 | 495 | //the PoW must contain work that includes a recent ethereum block hash (challenge number) and the msg.sender's address to prevent MITM attacks 496 | bytes32 digest = keccak256(abi.encodePacked(keccak256(abi.encodePacked(challengeNumber, minter, nonce )))); 497 | 498 | //the digest must be smaller than the target 499 | if(uint256(digest) > miningTarget) revert(); 500 | 501 | //only allow one reward for each block 502 | require(lastRewardEthBlockNumber != block.number); 503 | 504 | balances[minter] = balances[minter] + (currentMiningReward); 505 | emit Transfer(address(this), minter, currentMiningReward); 506 | 507 | tokensMinted = tokensMinted + currentMiningReward; 508 | totalSupply = totalSupply + currentMiningReward; 509 | 510 | //Cannot mint more tokens than there are 511 | require(tokensMinted <= maxSupplyForEra); 512 | 513 | //set readonly diagnostics data 514 | lastRewardTo = minter; 515 | lastRewardAmount = currentMiningReward; 516 | lastRewardEthBlockNumber = block.number; 517 | 518 | _startNewMiningEpoch(); 519 | 520 | emit Mint(minter, currentMiningReward, epochCount, challengeNumber ); 521 | 522 | return true; 523 | 524 | } 525 | 526 | 527 | 528 | function _startNewMiningEpoch() internal { 529 | 530 | //if max supply for the era will be exceeded next reward round then enter the new era before that happens 531 | 532 | //32 is the final reward era, almost all tokens minted 533 | //once the final era is reached, more tokens will not be given out because the assert function 534 | if(tokensMinted + (currentMiningReward) > maxSupplyForEra && rewardEra < 31) 535 | { 536 | rewardEra = rewardEra + 1; 537 | currentMiningReward = (50 * 10**uint(decimals) ) / ( 2**rewardEra ) ; 538 | } 539 | 540 | //set the next minted supply at which the era will change 541 | //total supply is 2100000000000000 because of 8 decimal places 542 | maxSupplyForEra = maximumSupply - (maximumSupply / ( 2**(rewardEra + 1))); 543 | 544 | epochCount = epochCount + 1; 545 | 546 | //every so often, readjust difficulty. Dont readjust when deploying 547 | if(epochCount % _BLOCKS_PER_READJUSTMENT == 0) 548 | { 549 | uint ethBlocksSinceLastDifficultyPeriod = block.number - latestDifficultyPeriodStarted; 550 | 551 | _reAdjustDifficulty(ethBlocksSinceLastDifficultyPeriod); 552 | } 553 | 554 | 555 | //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks 556 | challengeNumber = blockhash(block.number - 1); 557 | 558 | } 559 | 560 | 561 | 562 | function _reAdjustDifficulty(uint ethBlocksSinceLastDifficultyPeriod) internal { 563 | 564 | 565 | uint targetEthBlocksPerDiffPeriod = _BLOCKS_PER_READJUSTMENT * 60; 566 | 567 | //if there were less eth blocks passed in time than expected 568 | if( ethBlocksSinceLastDifficultyPeriod < targetEthBlocksPerDiffPeriod ) 569 | { 570 | uint excess_block_pct = (targetEthBlocksPerDiffPeriod * (100)) / ( ethBlocksSinceLastDifficultyPeriod ); 571 | 572 | uint excess_block_pct_extra = (excess_block_pct - 100).limitLessThan(1000); 573 | // If there were 5% more blocks mined than expected then this is 5. If there were 100% more blocks mined than expected then this is 100. 574 | 575 | //make it harder 576 | miningTarget = miningTarget - ((miningTarget / 2000) * excess_block_pct_extra); //by up to 50 % 577 | }else{ 578 | uint shortage_block_pct = (ethBlocksSinceLastDifficultyPeriod * (100)) / ( targetEthBlocksPerDiffPeriod ); 579 | 580 | uint shortage_block_pct_extra = (shortage_block_pct - 100).limitLessThan(1000); //always between 0 and 1000 581 | 582 | //make it easier 583 | miningTarget = miningTarget + ((miningTarget / 2000) * shortage_block_pct_extra); //by up to 50 % 584 | } 585 | 586 | 587 | latestDifficultyPeriodStarted = block.number; 588 | 589 | if(miningTarget < _MINIMUM_TARGET) //most difficult 590 | { 591 | miningTarget = _MINIMUM_TARGET; 592 | } 593 | 594 | if(miningTarget > _MAXIMUM_TARGET) //most easy 595 | { 596 | miningTarget = _MAXIMUM_TARGET; 597 | } 598 | } 599 | 600 | 601 | 602 | function getChallengeNumber() public view returns (bytes32) { 603 | return challengeNumber; 604 | } 605 | 606 | //the number of zeroes the digest of the PoW solution requires. Auto adjusts 607 | function getMiningDifficulty() public view returns (uint) { 608 | return _MAXIMUM_TARGET / (miningTarget); 609 | } 610 | 611 | function getMiningTarget() public view returns (uint) { 612 | return miningTarget; 613 | } 614 | 615 | 616 | 617 | 618 | /** 619 | * @notice Update allowance with a signed permit 620 | * @param owner Token owner's address (Authorizer) 621 | * @param spender Spender's address 622 | * @param value Amount of allowance 623 | * @param deadline Expiration time, seconds since the epoch 624 | * @param v v of the signature 625 | * @param r r of the signature 626 | * @param s s of the signature 627 | */ 628 | function permit( 629 | address owner, 630 | address spender, 631 | uint256 value, 632 | uint256 deadline, 633 | uint8 v, 634 | bytes32 r, 635 | bytes32 s 636 | ) external { 637 | _permit(owner, spender, value, deadline, v, r, s); 638 | } 639 | 640 | 641 | // ------------------------------------------------------------------------ 642 | 643 | // Don't accept ETH 644 | 645 | // ------------------------------------------------------------------------ 646 | 647 | receive() external payable virtual { 648 | 649 | revert(); 650 | 651 | } 652 | 653 | 654 | } 655 | -------------------------------------------------------------------------------- /contracts/_0xBitcoinToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | // ---------------------------------------------------------------------------- 5 | 6 | // '0xBitcoin Token' contract 7 | 8 | // Mineable ERC20 Token using Proof Of Work 9 | 10 | // 11 | 12 | // Symbol : 0xBTC 13 | 14 | // Name : 0xBitcoin Token 15 | 16 | // Total supply: 21,000,000.00 17 | 18 | // Decimals : 8 19 | 20 | // 21 | 22 | 23 | // ---------------------------------------------------------------------------- 24 | 25 | 26 | 27 | // ---------------------------------------------------------------------------- 28 | 29 | // Safe maths 30 | 31 | // ---------------------------------------------------------------------------- 32 | 33 | library SafeMath { 34 | 35 | function add(uint a, uint b) internal pure returns (uint c) { 36 | 37 | c = a + b; 38 | 39 | require(c >= a); 40 | 41 | } 42 | 43 | function sub(uint a, uint b) internal pure returns (uint c) { 44 | 45 | require(b <= a); 46 | 47 | c = a - b; 48 | 49 | } 50 | 51 | function mul(uint a, uint b) internal pure returns (uint c) { 52 | 53 | c = a * b; 54 | 55 | require(a == 0 || c / a == b); 56 | 57 | } 58 | 59 | function div(uint a, uint b) internal pure returns (uint c) { 60 | 61 | require(b > 0); 62 | 63 | c = a / b; 64 | 65 | } 66 | 67 | } 68 | 69 | 70 | 71 | library ExtendedMath { 72 | 73 | 74 | //return the smaller of the two inputs (a or b) 75 | function limitLessThan(uint a, uint b) internal pure returns (uint c) { 76 | 77 | if(a > b) return b; 78 | 79 | return a; 80 | 81 | } 82 | } 83 | 84 | // ---------------------------------------------------------------------------- 85 | 86 | // ERC Token Standard #20 Interface 87 | 88 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 89 | 90 | // ---------------------------------------------------------------------------- 91 | 92 | contract ERC20Interface { 93 | 94 | function totalSupply() public constant returns (uint); 95 | 96 | function balanceOf(address tokenOwner) public constant returns (uint balance); 97 | 98 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining); 99 | 100 | function transfer(address to, uint tokens) public returns (bool success); 101 | 102 | function approve(address spender, uint tokens) public returns (bool success); 103 | 104 | function transferFrom(address from, address to, uint tokens) public returns (bool success); 105 | 106 | 107 | event Transfer(address indexed from, address indexed to, uint tokens); 108 | 109 | event Approval(address indexed tokenOwner, address indexed spender, uint tokens); 110 | 111 | } 112 | 113 | 114 | 115 | // ---------------------------------------------------------------------------- 116 | 117 | // Contract function to receive approval and execute function in one call 118 | 119 | // 120 | 121 | // Borrowed from MiniMeToken 122 | 123 | // ---------------------------------------------------------------------------- 124 | 125 | contract ApproveAndCallFallBack { 126 | 127 | function receiveApproval(address from, uint256 tokens, address token, bytes data) public; 128 | 129 | } 130 | 131 | 132 | 133 | // ---------------------------------------------------------------------------- 134 | 135 | // Owned contract 136 | 137 | // ---------------------------------------------------------------------------- 138 | 139 | contract Owned { 140 | 141 | address public owner; 142 | 143 | address public newOwner; 144 | 145 | 146 | event OwnershipTransferred(address indexed _from, address indexed _to); 147 | 148 | 149 | function Owned() public { 150 | 151 | owner = msg.sender; 152 | 153 | } 154 | 155 | 156 | modifier onlyOwner { 157 | 158 | require(msg.sender == owner); 159 | 160 | _; 161 | 162 | } 163 | 164 | 165 | function transferOwnership(address _newOwner) public onlyOwner { 166 | 167 | newOwner = _newOwner; 168 | 169 | } 170 | 171 | function acceptOwnership() public { 172 | 173 | require(msg.sender == newOwner); 174 | 175 | OwnershipTransferred(owner, newOwner); 176 | 177 | owner = newOwner; 178 | 179 | newOwner = address(0); 180 | 181 | } 182 | 183 | } 184 | 185 | 186 | 187 | // ---------------------------------------------------------------------------- 188 | 189 | // ERC20 Token, with the addition of symbol, name and decimals and an 190 | 191 | // initial fixed supply 192 | 193 | // ---------------------------------------------------------------------------- 194 | 195 | contract _0xBitcoinToken is ERC20Interface, Owned { 196 | 197 | using SafeMath for uint; 198 | using ExtendedMath for uint; 199 | 200 | 201 | string public symbol; 202 | 203 | string public name; 204 | 205 | uint8 public decimals; 206 | 207 | uint public _totalSupply; 208 | 209 | 210 | 211 | uint public latestDifficultyPeriodStarted; 212 | 213 | 214 | 215 | uint public epochCount;//number of 'blocks' mined 216 | 217 | 218 | uint public _BLOCKS_PER_READJUSTMENT = 1024; 219 | 220 | 221 | //a little number 222 | uint public _MINIMUM_TARGET = 2**16; 223 | 224 | 225 | //a big number is easier ; just find a solution that is smaller 226 | //uint public _MAXIMUM_TARGET = 2**224; bitcoin uses 224 227 | uint public _MAXIMUM_TARGET = 2**234; 228 | 229 | 230 | uint public miningTarget; 231 | 232 | bytes32 public challengeNumber; //generate a new one when a new reward is minted 233 | 234 | 235 | 236 | uint public rewardEra; 237 | uint public maxSupplyForEra; 238 | 239 | 240 | address public lastRewardTo; 241 | uint public lastRewardAmount; 242 | uint public lastRewardEthBlockNumber; 243 | 244 | bool locked = false; 245 | 246 | mapping(bytes32 => bytes32) solutionForChallenge; 247 | 248 | uint public tokensMinted; 249 | 250 | mapping(address => uint) balances; 251 | 252 | 253 | mapping(address => mapping(address => uint)) allowed; 254 | 255 | 256 | event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); 257 | 258 | // ------------------------------------------------------------------------ 259 | 260 | // Constructor 261 | 262 | // ------------------------------------------------------------------------ 263 | 264 | function _0xBitcoinToken() public onlyOwner{ 265 | 266 | 267 | 268 | symbol = "0xBTC"; 269 | 270 | name = "0xBitcoin Token"; 271 | 272 | decimals = 8; 273 | 274 | _totalSupply = 21000000 * 10**uint(decimals); 275 | 276 | if(locked) revert(); 277 | locked = true; 278 | 279 | tokensMinted = 0; 280 | 281 | rewardEra = 0; 282 | maxSupplyForEra = _totalSupply.div(2); 283 | 284 | miningTarget = _MAXIMUM_TARGET; 285 | 286 | latestDifficultyPeriodStarted = block.number; 287 | 288 | _startNewMiningEpoch(); 289 | 290 | 291 | //The owner gets nothing! You must mine this ERC20 token 292 | //balances[owner] = _totalSupply; 293 | //Transfer(address(0), owner, _totalSupply); 294 | 295 | } 296 | 297 | 298 | 299 | 300 | function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) { 301 | 302 | 303 | //the PoW must contain work that includes a recent ethereum block hash (challenge number) and the msg.sender's address to prevent MITM attacks 304 | bytes32 digest = keccak256(challengeNumber, msg.sender, nonce ); 305 | 306 | //the challenge digest must match the expected 307 | if (digest != challenge_digest) revert(); 308 | 309 | //the digest must be smaller than the target 310 | if(uint256(digest) > miningTarget) revert(); 311 | 312 | 313 | //only allow one reward for each challenge 314 | bytes32 solution = solutionForChallenge[challengeNumber]; 315 | solutionForChallenge[challengeNumber] = digest; 316 | if(solution != 0x0) revert(); //prevent the same answer from awarding twice 317 | 318 | 319 | uint reward_amount = getMiningReward(); 320 | 321 | balances[msg.sender] = balances[msg.sender].add(reward_amount); 322 | 323 | tokensMinted = tokensMinted.add(reward_amount); 324 | 325 | 326 | //Cannot mint more tokens than there are 327 | assert(tokensMinted <= maxSupplyForEra); 328 | 329 | //set readonly diagnostics data 330 | lastRewardTo = msg.sender; 331 | lastRewardAmount = reward_amount; 332 | lastRewardEthBlockNumber = block.number; 333 | 334 | 335 | _startNewMiningEpoch(); 336 | 337 | Mint(msg.sender, reward_amount, epochCount, challengeNumber ); 338 | 339 | return true; 340 | 341 | } 342 | 343 | 344 | //a new 'block' to be mined 345 | function _startNewMiningEpoch() internal { 346 | 347 | //if max supply for the era will be exceeded next reward round then enter the new era before that happens 348 | 349 | //40 is the final reward era, almost all tokens minted 350 | //once the final era is reached, more tokens will not be given out because the assert function 351 | if( tokensMinted.add(getMiningReward()) > maxSupplyForEra && rewardEra < 39) 352 | { 353 | rewardEra = rewardEra + 1; 354 | } 355 | 356 | //set the next minted supply at which the era will change 357 | // total supply is 2100000000000000 because of 8 decimal places 358 | maxSupplyForEra = _totalSupply - _totalSupply.div( 2**(rewardEra + 1)); 359 | 360 | epochCount = epochCount.add(1); 361 | 362 | //every so often, readjust difficulty. Dont readjust when deploying 363 | if(epochCount % _BLOCKS_PER_READJUSTMENT == 0) 364 | { 365 | _reAdjustDifficulty(); 366 | } 367 | 368 | 369 | //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks 370 | //do this last since this is a protection mechanism in the mint() function 371 | challengeNumber = block.blockhash(block.number - 1); 372 | 373 | } 374 | 375 | 376 | 377 | 378 | //https://en.bitcoin.it/wiki/Difficulty#What_is_the_formula_for_difficulty.3F 379 | //as of 2017 the bitcoin difficulty was up to 17 zeroes, it was only 8 in the early days 380 | 381 | //readjust the target by 5 percent 382 | function _reAdjustDifficulty() internal { 383 | 384 | 385 | uint ethBlocksSinceLastDifficultyPeriod = block.number - latestDifficultyPeriodStarted; 386 | //assume 360 ethereum blocks per hour 387 | 388 | //we want miners to spend 10 minutes to mine each 'block', about 60 ethereum blocks = one 0xbitcoin epoch 389 | uint epochsMined = _BLOCKS_PER_READJUSTMENT; //256 390 | 391 | uint targetEthBlocksPerDiffPeriod = epochsMined * 60; //should be 60 times slower than ethereum 392 | 393 | //if there were less eth blocks passed in time than expected 394 | if( ethBlocksSinceLastDifficultyPeriod < targetEthBlocksPerDiffPeriod ) 395 | { 396 | uint excess_block_pct = (targetEthBlocksPerDiffPeriod.mul(100)).div( ethBlocksSinceLastDifficultyPeriod ); 397 | 398 | uint excess_block_pct_extra = excess_block_pct.sub(100).limitLessThan(1000); 399 | // If there were 5% more blocks mined than expected then this is 5. If there were 100% more blocks mined than expected then this is 100. 400 | 401 | //make it harder 402 | miningTarget = miningTarget.sub(miningTarget.div(2000).mul(excess_block_pct_extra)); //by up to 50 % 403 | }else{ 404 | uint shortage_block_pct = (ethBlocksSinceLastDifficultyPeriod.mul(100)).div( targetEthBlocksPerDiffPeriod ); 405 | 406 | uint shortage_block_pct_extra = shortage_block_pct.sub(100).limitLessThan(1000); //always between 0 and 1000 407 | 408 | //make it easier 409 | miningTarget = miningTarget.add(miningTarget.div(2000).mul(shortage_block_pct_extra)); //by up to 50 % 410 | } 411 | 412 | 413 | 414 | latestDifficultyPeriodStarted = block.number; 415 | 416 | if(miningTarget < _MINIMUM_TARGET) //very difficult 417 | { 418 | miningTarget = _MINIMUM_TARGET; 419 | } 420 | 421 | if(miningTarget > _MAXIMUM_TARGET) //very easy 422 | { 423 | miningTarget = _MAXIMUM_TARGET; 424 | } 425 | } 426 | 427 | 428 | //this is a recent ethereum block hash, used to prevent pre-mining future blocks 429 | function getChallengeNumber() public constant returns (bytes32) { 430 | return challengeNumber; 431 | } 432 | 433 | //the number of zeroes the digest of the PoW solution requires. Auto adjusts 434 | function getMiningDifficulty() public constant returns (uint) { 435 | return _MAXIMUM_TARGET.div(miningTarget); 436 | } 437 | 438 | function getMiningTarget() public constant returns (uint) { 439 | return miningTarget; 440 | } 441 | 442 | 443 | 444 | //21m coins total 445 | //reward begins at 50 and is cut in half every reward era (as tokens are mined) 446 | function getMiningReward() public constant returns (uint) { 447 | //once we get half way thru the coins, only get 25 per block 448 | 449 | //every reward era, the reward amount halves. 450 | 451 | return (50 * 10**uint(decimals) ).div( 2**rewardEra ) ; 452 | 453 | } 454 | 455 | //help debug mining software 456 | function getMintDigest(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number) public view returns (bytes32 digesttest) { 457 | 458 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 459 | 460 | return digest; 461 | 462 | } 463 | 464 | //help debug mining software 465 | function checkMintSolution(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number, uint testTarget) public view returns (bool success) { 466 | 467 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 468 | 469 | if(uint256(digest) > testTarget) revert(); 470 | 471 | return (digest == challenge_digest); 472 | 473 | } 474 | 475 | 476 | 477 | // ------------------------------------------------------------------------ 478 | 479 | // Total supply 480 | 481 | // ------------------------------------------------------------------------ 482 | 483 | function totalSupply() public constant returns (uint) { 484 | 485 | return _totalSupply - balances[address(0)]; 486 | 487 | } 488 | 489 | 490 | 491 | // ------------------------------------------------------------------------ 492 | 493 | // Get the token balance for account `tokenOwner` 494 | 495 | // ------------------------------------------------------------------------ 496 | 497 | function balanceOf(address tokenOwner) public constant returns (uint balance) { 498 | 499 | return balances[tokenOwner]; 500 | 501 | } 502 | 503 | 504 | 505 | // ------------------------------------------------------------------------ 506 | 507 | // Transfer the balance from token owner's account to `to` account 508 | 509 | // - Owner's account must have sufficient balance to transfer 510 | 511 | // - 0 value transfers are allowed 512 | 513 | // ------------------------------------------------------------------------ 514 | 515 | function transfer(address to, uint tokens) public returns (bool success) { 516 | 517 | balances[msg.sender] = balances[msg.sender].sub(tokens); 518 | 519 | balances[to] = balances[to].add(tokens); 520 | 521 | Transfer(msg.sender, to, tokens); 522 | 523 | return true; 524 | 525 | } 526 | 527 | 528 | 529 | // ------------------------------------------------------------------------ 530 | 531 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 532 | 533 | // from the token owner's account 534 | 535 | // 536 | 537 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 538 | 539 | // recommends that there are no checks for the approval double-spend attack 540 | 541 | // as this should be implemented in user interfaces 542 | 543 | // ------------------------------------------------------------------------ 544 | 545 | function approve(address spender, uint tokens) public returns (bool success) { 546 | 547 | allowed[msg.sender][spender] = tokens; 548 | 549 | Approval(msg.sender, spender, tokens); 550 | 551 | return true; 552 | 553 | } 554 | 555 | 556 | 557 | // ------------------------------------------------------------------------ 558 | 559 | // Transfer `tokens` from the `from` account to the `to` account 560 | 561 | // 562 | 563 | // The calling account must already have sufficient tokens approve(...)-d 564 | 565 | // for spending from the `from` account and 566 | 567 | // - From account must have sufficient balance to transfer 568 | 569 | // - Spender must have sufficient allowance to transfer 570 | 571 | // - 0 value transfers are allowed 572 | 573 | // ------------------------------------------------------------------------ 574 | 575 | function transferFrom(address from, address to, uint tokens) public returns (bool success) { 576 | 577 | balances[from] = balances[from].sub(tokens); 578 | 579 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); 580 | 581 | balances[to] = balances[to].add(tokens); 582 | 583 | Transfer(from, to, tokens); 584 | 585 | return true; 586 | 587 | } 588 | 589 | 590 | 591 | // ------------------------------------------------------------------------ 592 | 593 | // Returns the amount of tokens approved by the owner that can be 594 | 595 | // transferred to the spender's account 596 | 597 | // ------------------------------------------------------------------------ 598 | 599 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining) { 600 | 601 | return allowed[tokenOwner][spender]; 602 | 603 | } 604 | 605 | 606 | 607 | // ------------------------------------------------------------------------ 608 | 609 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 610 | 611 | // from the token owner's account. The `spender` contract function 612 | 613 | // `receiveApproval(...)` is then executed 614 | 615 | // ------------------------------------------------------------------------ 616 | 617 | function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { 618 | 619 | allowed[msg.sender][spender] = tokens; 620 | 621 | Approval(msg.sender, spender, tokens); 622 | 623 | ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); 624 | 625 | return true; 626 | 627 | } 628 | 629 | 630 | 631 | // ------------------------------------------------------------------------ 632 | 633 | // Don't accept ETH 634 | 635 | // ------------------------------------------------------------------------ 636 | 637 | function () public payable { 638 | 639 | revert(); 640 | 641 | } 642 | 643 | 644 | 645 | // ------------------------------------------------------------------------ 646 | 647 | // Owner can transfer out any accidentally sent ERC20 tokens 648 | 649 | // ------------------------------------------------------------------------ 650 | 651 | function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) { 652 | 653 | return ERC20Interface(tokenAddress).transfer(owner, tokens); 654 | 655 | } 656 | 657 | } 658 | -------------------------------------------------------------------------------- /contracts/_0xBitcoinTokenTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | // ---------------------------------------------------------------------------- 5 | 6 | // '0xBitcoin Token' contract 7 | 8 | // Mineable ERC20 Token using Proof Of Work 9 | 10 | // 11 | 12 | // Symbol : 0xBTC 13 | 14 | // Name : 0xBitcoin Token 15 | 16 | // Total supply: 21,000,000.00 17 | 18 | // Decimals : 8 19 | 20 | // 21 | 22 | 23 | // ---------------------------------------------------------------------------- 24 | 25 | 26 | 27 | // ---------------------------------------------------------------------------- 28 | 29 | // Safe maths 30 | 31 | // ---------------------------------------------------------------------------- 32 | 33 | library SafeMath { 34 | 35 | function add(uint a, uint b) internal pure returns (uint c) { 36 | 37 | c = a + b; 38 | 39 | require(c >= a); 40 | 41 | } 42 | 43 | function sub(uint a, uint b) internal pure returns (uint c) { 44 | 45 | require(b <= a); 46 | 47 | c = a - b; 48 | 49 | } 50 | 51 | function mul(uint a, uint b) internal pure returns (uint c) { 52 | 53 | c = a * b; 54 | 55 | require(a == 0 || c / a == b); 56 | 57 | } 58 | 59 | function div(uint a, uint b) internal pure returns (uint c) { 60 | 61 | require(b > 0); 62 | 63 | c = a / b; 64 | 65 | } 66 | 67 | } 68 | 69 | 70 | 71 | library ExtendedMath { 72 | 73 | 74 | //return the smaller of the two inputs (a or b) 75 | function limitLessThan(uint a, uint b) internal pure returns (uint c) { 76 | 77 | if(a > b) return b; 78 | 79 | return a; 80 | 81 | } 82 | } 83 | 84 | // ---------------------------------------------------------------------------- 85 | 86 | // ERC Token Standard #20 Interface 87 | 88 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 89 | 90 | // ---------------------------------------------------------------------------- 91 | 92 | contract ERC20Interface { 93 | 94 | function totalSupply() public constant returns (uint); 95 | 96 | function balanceOf(address tokenOwner) public constant returns (uint balance); 97 | 98 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining); 99 | 100 | function transfer(address to, uint tokens) public returns (bool success); 101 | 102 | function approve(address spender, uint tokens) public returns (bool success); 103 | 104 | function transferFrom(address from, address to, uint tokens) public returns (bool success); 105 | 106 | 107 | event Transfer(address indexed from, address indexed to, uint tokens); 108 | 109 | event Approval(address indexed tokenOwner, address indexed spender, uint tokens); 110 | 111 | } 112 | 113 | 114 | 115 | // ---------------------------------------------------------------------------- 116 | 117 | // Contract function to receive approval and execute function in one call 118 | 119 | // 120 | 121 | // Borrowed from MiniMeToken 122 | 123 | // ---------------------------------------------------------------------------- 124 | 125 | contract ApproveAndCallFallBack { 126 | 127 | function receiveApproval(address from, uint256 tokens, address token, bytes data) public; 128 | 129 | } 130 | 131 | 132 | 133 | // ---------------------------------------------------------------------------- 134 | 135 | // Owned contract 136 | 137 | // ---------------------------------------------------------------------------- 138 | 139 | contract Owned { 140 | 141 | address public owner; 142 | 143 | address public newOwner; 144 | 145 | 146 | event OwnershipTransferred(address indexed _from, address indexed _to); 147 | 148 | 149 | function Owned() public { 150 | 151 | owner = msg.sender; 152 | 153 | } 154 | 155 | 156 | modifier onlyOwner { 157 | 158 | require(msg.sender == owner); 159 | 160 | _; 161 | 162 | } 163 | 164 | 165 | function transferOwnership(address _newOwner) public onlyOwner { 166 | 167 | newOwner = _newOwner; 168 | 169 | } 170 | 171 | function acceptOwnership() public { 172 | 173 | require(msg.sender == newOwner); 174 | 175 | OwnershipTransferred(owner, newOwner); 176 | 177 | owner = newOwner; 178 | 179 | newOwner = address(0); 180 | 181 | } 182 | 183 | } 184 | 185 | 186 | 187 | // ---------------------------------------------------------------------------- 188 | 189 | // ERC20 Token, with the addition of symbol, name and decimals and an 190 | 191 | // initial fixed supply 192 | 193 | // ---------------------------------------------------------------------------- 194 | 195 | contract _0xBitcoinTokenTest is ERC20Interface, Owned { 196 | 197 | using SafeMath for uint; 198 | using ExtendedMath for uint; 199 | 200 | 201 | string public symbol; 202 | 203 | string public name; 204 | 205 | uint8 public decimals; 206 | 207 | uint public _totalSupply; 208 | 209 | 210 | 211 | uint public latestDifficultyPeriodStarted; 212 | 213 | 214 | 215 | uint public epochCount;//number of 'blocks' mined 216 | 217 | 218 | uint public _BLOCKS_PER_READJUSTMENT = 1024; 219 | 220 | 221 | //a little number 222 | uint public _MINIMUM_TARGET = 2**16; 223 | 224 | 225 | //a big number is easier ; just find a solution that is smaller 226 | //uint public _MAXIMUM_TARGET = 2**224; bitcoin uses 224 227 | uint public _MAXIMUM_TARGET = 2**234; 228 | 229 | 230 | uint public miningTarget; 231 | 232 | bytes32 public challengeNumber; //generate a new one when a new reward is minted 233 | 234 | 235 | 236 | uint public rewardEra; 237 | uint public maxSupplyForEra; 238 | 239 | 240 | address public lastRewardTo; 241 | uint public lastRewardAmount; 242 | uint public lastRewardEthBlockNumber; 243 | 244 | bool locked = false; 245 | 246 | mapping(bytes32 => bytes32) solutionForChallenge; 247 | 248 | uint public tokensMinted; 249 | 250 | mapping(address => uint) balances; 251 | 252 | 253 | mapping(address => mapping(address => uint)) allowed; 254 | 255 | 256 | event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); 257 | 258 | // ------------------------------------------------------------------------ 259 | 260 | // Constructor 261 | 262 | // ------------------------------------------------------------------------ 263 | 264 | function _0xBitcoinTokenTest() public onlyOwner{ 265 | 266 | 267 | 268 | symbol = "0xBTC"; 269 | 270 | name = "0xBitcoin Token"; 271 | 272 | decimals = 8; 273 | 274 | _totalSupply = 21000000 * 10**uint(decimals); 275 | 276 | if(locked) revert(); 277 | locked = true; 278 | 279 | tokensMinted = 30000; 280 | 281 | rewardEra = 0; 282 | maxSupplyForEra = _totalSupply.div(2); 283 | 284 | miningTarget = _MAXIMUM_TARGET; 285 | 286 | latestDifficultyPeriodStarted = 1001; 287 | 288 | _startNewMiningEpoch(); 289 | 290 | } 291 | 292 | function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) { 293 | mintTest(); 294 | } 295 | 296 | 297 | function mintTest() public returns (bool success) { 298 | 299 | 300 | //the PoW must contain work that includes a recent ethereum block hash (challenge number) and the msg.sender's address to prevent MITM attacks 301 | bytes32 digest = keccak256(challengeNumber, msg.sender, 0 ); 302 | 303 | //the challenge digest must match the expected 304 | //if (digest != challenge_digest) revert(); 305 | 306 | //the digest must be smaller than the target 307 | //if(uint256(digest) > miningTarget) revert(); 308 | 309 | 310 | //only allow one reward for each challenge 311 | bytes32 solution = solutionForChallenge[challengeNumber]; 312 | solutionForChallenge[challengeNumber] = digest; 313 | //if(solution != 0x0) revert(); //prevent the same answer from awarding twice 314 | 315 | 316 | uint reward_amount = getMiningReward(); 317 | 318 | balances[msg.sender] = balances[msg.sender].add(reward_amount); 319 | 320 | tokensMinted = tokensMinted.add(reward_amount); 321 | 322 | 323 | //Cannot mint more tokens than there are 324 | //assert(tokensMinted <= maxSupplyForEra); 325 | 326 | //set readonly diagnostics data 327 | lastRewardTo = msg.sender; 328 | lastRewardAmount = reward_amount; 329 | lastRewardEthBlockNumber = block.number; 330 | 331 | 332 | _startNewMiningEpoch(); 333 | 334 | Mint(msg.sender, reward_amount, epochCount, challengeNumber ); 335 | 336 | return true; 337 | 338 | } 339 | 340 | 341 | //a new 'block' to be mined 342 | function _startNewMiningEpoch() internal { 343 | 344 | //if max supply for the era will be exceeded next reward round then enter the new era before that happens 345 | 346 | //40 is the final reward era, almost all tokens minted 347 | //once the final era is reached, more tokens will not be given out because the assert function 348 | if( tokensMinted.add(getMiningReward()) > maxSupplyForEra && rewardEra < 39) 349 | { 350 | rewardEra = rewardEra + 1; 351 | } 352 | 353 | //set the next minted supply at which the era will change 354 | // total supply is 2100000000000000 because of 8 decimal places 355 | maxSupplyForEra = _totalSupply - _totalSupply.div( 2**(rewardEra + 1)); 356 | 357 | epochCount = epochCount.add(1); 358 | 359 | //every so often, readjust difficulty. Dont readjust when deploying 360 | if(epochCount % _BLOCKS_PER_READJUSTMENT == 0) 361 | { 362 | _reAdjustDifficulty(); 363 | } 364 | 365 | 366 | //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks 367 | //do this last since this is a protection mechanism in the mint() function 368 | challengeNumber = block.blockhash(block.number - 1); 369 | 370 | 371 | 372 | 373 | 374 | 375 | } 376 | 377 | 378 | 379 | 380 | //https://en.bitcoin.it/wiki/Difficulty#What_is_the_formula_for_difficulty.3F 381 | //as of 2017 the bitcoin difficulty was up to 17 zeroes, it was only 8 in the early days 382 | 383 | //readjust the target by 5 percent 384 | function _reAdjustDifficulty() internal { 385 | 386 | 387 | uint ethBlocksSinceLastDifficultyPeriod = block.number - latestDifficultyPeriodStarted; 388 | //assume 360 ethereum blocks per hour 389 | 390 | //we want miners to spend 10 minutes to mine each 'block', about 60 ethereum blocks = one 0xbitcoin epoch 391 | uint epochsMined = _BLOCKS_PER_READJUSTMENT; //256 392 | 393 | uint targetEthBlocksPerDiffPeriod = epochsMined * 60; //should be 60 times slower than ethereum 394 | 395 | //if there were less eth blocks passed in time than expected 396 | if( ethBlocksSinceLastDifficultyPeriod < targetEthBlocksPerDiffPeriod ) 397 | { 398 | uint excess_block_pct = (targetEthBlocksPerDiffPeriod.mul(100)).div( ethBlocksSinceLastDifficultyPeriod ); 399 | 400 | uint excess_block_pct_extra = excess_block_pct.sub(100).limitLessThan(1000); 401 | // If there were 5% more blocks mined than expected then this is 5. If there were 100% more blocks mined than expected then this is 100. 402 | 403 | //make it harder 404 | miningTarget = miningTarget.sub(miningTarget.div(2000).mul(excess_block_pct_extra)); //by up to 50 % 405 | }else{ 406 | uint shortage_block_pct = (ethBlocksSinceLastDifficultyPeriod.mul(100)).div( targetEthBlocksPerDiffPeriod ); 407 | 408 | uint shortage_block_pct_extra = shortage_block_pct.sub(100).limitLessThan(1000); //always between 0 and 1000 409 | 410 | //make it easier 411 | miningTarget = miningTarget.add(miningTarget.div(2000).mul(shortage_block_pct_extra)); //by up to 50 % 412 | } 413 | 414 | 415 | 416 | latestDifficultyPeriodStarted = block.number; 417 | 418 | if(miningTarget < _MINIMUM_TARGET) //very difficult 419 | { 420 | miningTarget = _MINIMUM_TARGET; 421 | } 422 | 423 | if(miningTarget > _MAXIMUM_TARGET) //very easy 424 | { 425 | miningTarget = _MAXIMUM_TARGET; 426 | } 427 | } 428 | 429 | 430 | //this is a recent ethereum block hash, used to prevent pre-mining future blocks 431 | function getChallengeNumber() public constant returns (bytes32) { 432 | return challengeNumber; 433 | } 434 | 435 | //the number of zeroes the digest of the PoW solution requires. Auto adjusts 436 | function getMiningDifficulty() public constant returns (uint) { 437 | return _MAXIMUM_TARGET.div(miningTarget); 438 | } 439 | 440 | function getMiningTarget() public constant returns (uint) { 441 | return miningTarget; 442 | } 443 | 444 | 445 | 446 | //21m coins total 447 | //reward begins at 50 and is cut in half every reward era (as tokens are mined) 448 | function getMiningReward() public constant returns (uint) { 449 | //once we get half way thru the coins, only get 25 per block 450 | 451 | //every reward era, the reward amount halves. 452 | 453 | return (50 * 10**uint(decimals) ).div( 2**rewardEra ) ; 454 | 455 | } 456 | 457 | //help debug mining software 458 | function getMintDigest(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number) public view returns (bytes32 digesttest) { 459 | 460 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 461 | 462 | return digest; 463 | 464 | } 465 | 466 | //help debug mining software 467 | function checkMintSolution(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number, uint testTarget) public view returns (bool success) { 468 | 469 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 470 | 471 | if(uint256(digest) > testTarget) revert(); 472 | 473 | return (digest == challenge_digest); 474 | 475 | } 476 | 477 | 478 | 479 | // ------------------------------------------------------------------------ 480 | 481 | // Total supply 482 | 483 | // ------------------------------------------------------------------------ 484 | 485 | function totalSupply() public constant returns (uint) { 486 | 487 | return _totalSupply - balances[address(0)]; 488 | 489 | } 490 | 491 | 492 | 493 | // ------------------------------------------------------------------------ 494 | 495 | // Get the token balance for account `tokenOwner` 496 | 497 | // ------------------------------------------------------------------------ 498 | 499 | function balanceOf(address tokenOwner) public constant returns (uint balance) { 500 | 501 | return balances[tokenOwner]; 502 | 503 | } 504 | 505 | 506 | 507 | // ------------------------------------------------------------------------ 508 | 509 | // Transfer the balance from token owner's account to `to` account 510 | 511 | // - Owner's account must have sufficient balance to transfer 512 | 513 | // - 0 value transfers are allowed 514 | 515 | // ------------------------------------------------------------------------ 516 | 517 | function transfer(address to, uint tokens) public returns (bool success) { 518 | 519 | balances[msg.sender] = balances[msg.sender].sub(tokens); 520 | 521 | balances[to] = balances[to].add(tokens); 522 | 523 | Transfer(msg.sender, to, tokens); 524 | 525 | return true; 526 | 527 | } 528 | 529 | 530 | 531 | // ------------------------------------------------------------------------ 532 | 533 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 534 | 535 | // from the token owner's account 536 | 537 | // 538 | 539 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 540 | 541 | // recommends that there are no checks for the approval double-spend attack 542 | 543 | // as this should be implemented in user interfaces 544 | 545 | // ------------------------------------------------------------------------ 546 | 547 | function approve(address spender, uint tokens) public returns (bool success) { 548 | 549 | allowed[msg.sender][spender] = tokens; 550 | 551 | Approval(msg.sender, spender, tokens); 552 | 553 | return true; 554 | 555 | } 556 | 557 | 558 | 559 | // ------------------------------------------------------------------------ 560 | 561 | // Transfer `tokens` from the `from` account to the `to` account 562 | 563 | // 564 | 565 | // The calling account must already have sufficient tokens approve(...)-d 566 | 567 | // for spending from the `from` account and 568 | 569 | // - From account must have sufficient balance to transfer 570 | 571 | // - Spender must have sufficient allowance to transfer 572 | 573 | // - 0 value transfers are allowed 574 | 575 | // ------------------------------------------------------------------------ 576 | 577 | function transferFrom(address from, address to, uint tokens) public returns (bool success) { 578 | 579 | balances[from] = balances[from].sub(tokens); 580 | 581 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); 582 | 583 | balances[to] = balances[to].add(tokens); 584 | 585 | Transfer(from, to, tokens); 586 | 587 | return true; 588 | 589 | } 590 | 591 | 592 | 593 | // ------------------------------------------------------------------------ 594 | 595 | // Returns the amount of tokens approved by the owner that can be 596 | 597 | // transferred to the spender's account 598 | 599 | // ------------------------------------------------------------------------ 600 | 601 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining) { 602 | 603 | return allowed[tokenOwner][spender]; 604 | 605 | } 606 | 607 | 608 | 609 | // ------------------------------------------------------------------------ 610 | 611 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 612 | 613 | // from the token owner's account. The `spender` contract function 614 | 615 | // `receiveApproval(...)` is then executed 616 | 617 | // ------------------------------------------------------------------------ 618 | 619 | function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { 620 | 621 | allowed[msg.sender][spender] = tokens; 622 | 623 | Approval(msg.sender, spender, tokens); 624 | 625 | ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); 626 | 627 | return true; 628 | 629 | } 630 | 631 | 632 | 633 | // ------------------------------------------------------------------------ 634 | 635 | // Don't accept ETH 636 | 637 | // ------------------------------------------------------------------------ 638 | 639 | function () public payable { 640 | 641 | revert(); 642 | 643 | } 644 | 645 | 646 | 647 | // ------------------------------------------------------------------------ 648 | 649 | // Owner can transfer out any accidentally sent ERC20 tokens 650 | 651 | // ------------------------------------------------------------------------ 652 | 653 | function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) { 654 | 655 | return ERC20Interface(tokenAddress).transfer(owner, tokens); 656 | 657 | } 658 | 659 | } 660 | -------------------------------------------------------------------------------- /contracts/_0xTestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | /* 4 | A clone of the 0xBitcoin contract with slight changes to make it unit-test-able 5 | 6 | */ 7 | 8 | 9 | // ---------------------------------------------------------------------------- 10 | 11 | // '0xBitcoin Token' contract 12 | 13 | // Mineable ERC20 Token using Proof Of Work 14 | 15 | // 16 | 17 | // Symbol : 0xBTC 18 | 19 | // Name : 0xBitcoin Token 20 | 21 | // Total supply: 21,000,000.00 22 | 23 | // Decimals : 8 24 | 25 | // 26 | 27 | 28 | // ---------------------------------------------------------------------------- 29 | 30 | 31 | 32 | // ---------------------------------------------------------------------------- 33 | 34 | // Safe maths 35 | 36 | // ---------------------------------------------------------------------------- 37 | 38 | library SafeMath { 39 | 40 | function add(uint a, uint b) internal pure returns (uint c) { 41 | 42 | c = a + b; 43 | 44 | require(c >= a); 45 | 46 | } 47 | 48 | function sub(uint a, uint b) internal pure returns (uint c) { 49 | 50 | require(b <= a); 51 | 52 | c = a - b; 53 | 54 | } 55 | 56 | function mul(uint a, uint b) internal pure returns (uint c) { 57 | 58 | c = a * b; 59 | 60 | require(a == 0 || c / a == b); 61 | 62 | } 63 | 64 | function div(uint a, uint b) internal pure returns (uint c) { 65 | 66 | require(b > 0); 67 | 68 | c = a / b; 69 | 70 | } 71 | 72 | } 73 | 74 | 75 | 76 | library ExtendedMath { 77 | 78 | 79 | //return the smaller of the two inputs (a or b) 80 | function limitLessThan(uint a, uint b) internal pure returns (uint c) { 81 | 82 | if(a > b) return b; 83 | 84 | return a; 85 | 86 | } 87 | } 88 | 89 | // ---------------------------------------------------------------------------- 90 | 91 | // ERC Token Standard #20 Interface 92 | 93 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 94 | 95 | // ---------------------------------------------------------------------------- 96 | 97 | contract ERC20Interface { 98 | 99 | function totalSupply() public constant returns (uint); 100 | 101 | function balanceOf(address tokenOwner) public constant returns (uint balance); 102 | 103 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining); 104 | 105 | function transfer(address to, uint tokens) public returns (bool success); 106 | 107 | function approve(address spender, uint tokens) public returns (bool success); 108 | 109 | function transferFrom(address from, address to, uint tokens) public returns (bool success); 110 | 111 | 112 | event Transfer(address indexed from, address indexed to, uint tokens); 113 | 114 | event Approval(address indexed tokenOwner, address indexed spender, uint tokens); 115 | 116 | } 117 | 118 | 119 | 120 | // ---------------------------------------------------------------------------- 121 | 122 | // Contract function to receive approval and execute function in one call 123 | 124 | // 125 | 126 | // Borrowed from MiniMeToken 127 | 128 | // ---------------------------------------------------------------------------- 129 | 130 | contract ApproveAndCallFallBack { 131 | 132 | function receiveApproval(address from, uint256 tokens, address token, bytes data) public; 133 | 134 | } 135 | 136 | 137 | 138 | // ---------------------------------------------------------------------------- 139 | 140 | // Owned contract 141 | 142 | // ---------------------------------------------------------------------------- 143 | 144 | contract Owned { 145 | 146 | address public owner; 147 | 148 | address public newOwner; 149 | 150 | 151 | event OwnershipTransferred(address indexed _from, address indexed _to); 152 | 153 | 154 | function Owned() public { 155 | 156 | owner = msg.sender; 157 | 158 | } 159 | 160 | 161 | modifier onlyOwner { 162 | 163 | require(msg.sender == owner); 164 | 165 | _; 166 | 167 | } 168 | 169 | 170 | function transferOwnership(address _newOwner) public onlyOwner { 171 | 172 | newOwner = _newOwner; 173 | 174 | } 175 | 176 | function acceptOwnership() public { 177 | 178 | require(msg.sender == newOwner); 179 | 180 | OwnershipTransferred(owner, newOwner); 181 | 182 | owner = newOwner; 183 | 184 | newOwner = address(0); 185 | 186 | } 187 | 188 | } 189 | 190 | 191 | 192 | // ---------------------------------------------------------------------------- 193 | 194 | // ERC20 Token, with the addition of symbol, name and decimals and an 195 | 196 | // initial fixed supply 197 | 198 | // ---------------------------------------------------------------------------- 199 | 200 | contract _0xBitcoinToken is ERC20Interface, Owned { 201 | 202 | using SafeMath for uint; 203 | using ExtendedMath for uint; 204 | 205 | 206 | string public symbol; 207 | 208 | string public name; 209 | 210 | uint8 public decimals; 211 | 212 | uint public _totalSupply; 213 | 214 | 215 | 216 | uint public latestDifficultyPeriodStarted; 217 | 218 | 219 | 220 | uint public epochCount;//number of 'blocks' mined 221 | 222 | 223 | uint public _BLOCKS_PER_READJUSTMENT = 1024; 224 | 225 | 226 | //a little number 227 | uint public _MINIMUM_TARGET = 2**16; 228 | 229 | 230 | //a big number is easier ; just find a solution that is smaller 231 | //uint public _MAXIMUM_TARGET = 2**224; bitcoin uses 224 232 | uint public _MAXIMUM_TARGET = 2**234; 233 | 234 | 235 | uint public miningTarget; 236 | 237 | bytes32 public challengeNumber; //generate a new one when a new reward is minted 238 | 239 | 240 | 241 | uint public rewardEra; 242 | uint public maxSupplyForEra; 243 | 244 | 245 | address public lastRewardTo; 246 | uint public lastRewardAmount; 247 | uint public lastRewardEthBlockNumber; 248 | 249 | bool locked = false; 250 | 251 | mapping(bytes32 => bytes32) solutionForChallenge; 252 | 253 | uint public tokensMinted; 254 | 255 | mapping(address => uint) balances; 256 | 257 | 258 | mapping(address => mapping(address => uint)) allowed; 259 | 260 | 261 | event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); 262 | 263 | // ------------------------------------------------------------------------ 264 | 265 | // Constructor 266 | 267 | // ------------------------------------------------------------------------ 268 | 269 | function _0xBitcoinToken() public onlyOwner{ 270 | 271 | 272 | 273 | symbol = "0xBTC"; 274 | 275 | name = "0xBitcoin Token"; 276 | 277 | decimals = 8; 278 | 279 | _totalSupply = 21000000 * 10**uint(decimals); 280 | 281 | if(locked) revert(); 282 | locked = true; 283 | 284 | tokensMinted = 0; 285 | 286 | rewardEra = 0; 287 | maxSupplyForEra = _totalSupply.div(2); 288 | 289 | miningTarget = _MAXIMUM_TARGET; 290 | 291 | latestDifficultyPeriodStarted = block.number; 292 | 293 | _startNewMiningEpoch(); 294 | 295 | 296 | //The owner gets nothing! You must mine this ERC20 token 297 | //balances[owner] = _totalSupply; 298 | //Transfer(address(0), owner, _totalSupply); 299 | 300 | } 301 | 302 | 303 | 304 | 305 | function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) { 306 | 307 | 308 | //the PoW must contain work that includes a recent ethereum block hash (challenge number) and the msg.sender's address to prevent MITM attacks 309 | bytes32 digest = keccak256(challengeNumber, msg.sender, nonce ); 310 | 311 | //the challenge digest must match the expected 312 | if (digest != challenge_digest) revert(); 313 | 314 | //the digest must be smaller than the target 315 | // if(uint256(digest) > miningTarget) revert(); //removed for unit testing 316 | 317 | 318 | //only allow one reward for each challenge 319 | bytes32 solution = solutionForChallenge[challengeNumber]; 320 | solutionForChallenge[challengeNumber] = digest; 321 | if(solution != 0x0) revert(); //prevent the same answer from awarding twice 322 | 323 | 324 | uint reward_amount = getMiningReward(); 325 | 326 | balances[msg.sender] = balances[msg.sender].add(reward_amount); 327 | 328 | tokensMinted = tokensMinted.add(reward_amount); 329 | 330 | 331 | //Cannot mint more tokens than there are 332 | assert(tokensMinted <= maxSupplyForEra); 333 | 334 | //set readonly diagnostics data 335 | lastRewardTo = msg.sender; 336 | lastRewardAmount = reward_amount; 337 | lastRewardEthBlockNumber = block.number; 338 | 339 | 340 | _startNewMiningEpoch(); 341 | 342 | Mint(msg.sender, reward_amount, epochCount, challengeNumber ); 343 | 344 | return true; 345 | 346 | } 347 | 348 | 349 | //a new 'block' to be mined 350 | function _startNewMiningEpoch() public { 351 | 352 | //if max supply for the era will be exceeded next reward round then enter the new era before that happens 353 | 354 | //40 is the final reward era, almost all tokens minted 355 | //once the final era is reached, more tokens will not be given out because the assert function 356 | if( tokensMinted.add(getMiningReward()) > maxSupplyForEra && rewardEra < 39) 357 | { 358 | rewardEra = rewardEra + 1; 359 | } 360 | 361 | //set the next minted supply at which the era will change 362 | // total supply is 2100000000000000 because of 8 decimal places 363 | maxSupplyForEra = _totalSupply - _totalSupply.div( 2**(rewardEra + 1)); 364 | 365 | epochCount = epochCount.add(1); 366 | 367 | //every so often, readjust difficulty. Dont readjust when deploying 368 | if(epochCount % _BLOCKS_PER_READJUSTMENT == 0) 369 | { 370 | _reAdjustDifficulty(); 371 | } 372 | 373 | 374 | //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks 375 | //do this last since this is a protection mechanism in the mint() function 376 | challengeNumber = block.blockhash(block.number - 1); 377 | 378 | 379 | 380 | 381 | 382 | 383 | } 384 | 385 | 386 | 387 | 388 | //https://en.bitcoin.it/wiki/Difficulty#What_is_the_formula_for_difficulty.3F 389 | //as of 2017 the bitcoin difficulty was up to 17 zeroes, it was only 8 in the early days 390 | 391 | //readjust the target by 5 percent 392 | function _reAdjustDifficulty() public { 393 | 394 | 395 | uint ethBlocksSinceLastDifficultyPeriod = block.number - latestDifficultyPeriodStarted; 396 | //assume 360 ethereum blocks per hour 397 | 398 | //we want miners to spend 10 minutes to mine each 'block', about 60 ethereum blocks = one 0xbitcoin epoch 399 | uint epochsMined = _BLOCKS_PER_READJUSTMENT; //256 400 | 401 | uint targetEthBlocksPerDiffPeriod = epochsMined * 60; //should be 60 times slower than ethereum 402 | 403 | //if there were less eth blocks passed in time than expected 404 | if( ethBlocksSinceLastDifficultyPeriod < targetEthBlocksPerDiffPeriod ) 405 | { 406 | uint excess_block_pct = (targetEthBlocksPerDiffPeriod.mul(100)).div( ethBlocksSinceLastDifficultyPeriod ); 407 | 408 | uint excess_block_pct_extra = excess_block_pct.sub(100).limitLessThan(1000); 409 | // If there were 5% more blocks mined than expected then this is 5. If there were 100% more blocks mined than expected then this is 100. 410 | 411 | //make it harder 412 | miningTarget = miningTarget.sub(miningTarget.div(2000).mul(excess_block_pct_extra)); //by up to 50 % 413 | }else{ 414 | uint shortage_block_pct = (ethBlocksSinceLastDifficultyPeriod.mul(100)).div( targetEthBlocksPerDiffPeriod ); 415 | 416 | uint shortage_block_pct_extra = shortage_block_pct.sub(100).limitLessThan(1000); //always between 0 and 1000 417 | 418 | //make it easier 419 | miningTarget = miningTarget.add(miningTarget.div(2000).mul(shortage_block_pct_extra)); //by up to 50 % 420 | } 421 | 422 | 423 | 424 | latestDifficultyPeriodStarted = block.number; 425 | 426 | if(miningTarget < _MINIMUM_TARGET) //very difficult 427 | { 428 | miningTarget = _MINIMUM_TARGET; 429 | } 430 | 431 | if(miningTarget > _MAXIMUM_TARGET) //very easy 432 | { 433 | miningTarget = _MAXIMUM_TARGET; 434 | } 435 | } 436 | 437 | 438 | // THESE METHODS ARE FOR UNIT TESTING ONLY 439 | function testSetTokensMinted(uint amount) public { 440 | tokensMinted = amount; 441 | } 442 | 443 | 444 | 445 | 446 | 447 | 448 | //--------------- 449 | 450 | 451 | //this is a recent ethereum block hash, used to prevent pre-mining future blocks 452 | function getChallengeNumber() public constant returns (bytes32) { 453 | return challengeNumber; 454 | } 455 | 456 | //the number of zeroes the digest of the PoW solution requires. Auto adjusts 457 | function getMiningDifficulty() public constant returns (uint) { 458 | return _MAXIMUM_TARGET.div(miningTarget); 459 | } 460 | 461 | function getMiningTarget() public constant returns (uint) { 462 | return miningTarget; 463 | } 464 | 465 | 466 | 467 | //21m coins total 468 | //reward begins at 50 and is cut in half every reward era (as tokens are mined) 469 | function getMiningReward() public constant returns (uint) { 470 | //once we get half way thru the coins, only get 25 per block 471 | 472 | //every reward era, the reward amount halves. 473 | 474 | return (50 * 10**uint(decimals) ).div( 2**rewardEra ) ; 475 | 476 | } 477 | 478 | //help debug mining software 479 | function getMintDigest(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number) public view returns (bytes32 digesttest) { 480 | 481 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 482 | 483 | return digest; 484 | 485 | } 486 | 487 | //help debug mining software 488 | function checkMintSolution(uint256 nonce, bytes32 challenge_digest, bytes32 challenge_number, uint testTarget) public view returns (bool success) { 489 | 490 | bytes32 digest = keccak256(challenge_number,msg.sender,nonce); 491 | 492 | if(uint256(digest) > testTarget) revert(); 493 | 494 | return (digest == challenge_digest); 495 | 496 | } 497 | 498 | 499 | 500 | // ------------------------------------------------------------------------ 501 | 502 | // Total supply 503 | 504 | // ------------------------------------------------------------------------ 505 | 506 | function totalSupply() public constant returns (uint) { 507 | 508 | return _totalSupply - balances[address(0)]; 509 | 510 | } 511 | 512 | 513 | 514 | // ------------------------------------------------------------------------ 515 | 516 | // Get the token balance for account `tokenOwner` 517 | 518 | // ------------------------------------------------------------------------ 519 | 520 | function balanceOf(address tokenOwner) public constant returns (uint balance) { 521 | 522 | return balances[tokenOwner]; 523 | 524 | } 525 | 526 | 527 | 528 | // ------------------------------------------------------------------------ 529 | 530 | // Transfer the balance from token owner's account to `to` account 531 | 532 | // - Owner's account must have sufficient balance to transfer 533 | 534 | // - 0 value transfers are allowed 535 | 536 | // ------------------------------------------------------------------------ 537 | 538 | function transfer(address to, uint tokens) public returns (bool success) { 539 | 540 | balances[msg.sender] = balances[msg.sender].sub(tokens); 541 | 542 | balances[to] = balances[to].add(tokens); 543 | 544 | Transfer(msg.sender, to, tokens); 545 | 546 | return true; 547 | 548 | } 549 | 550 | 551 | 552 | // ------------------------------------------------------------------------ 553 | 554 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 555 | 556 | // from the token owner's account 557 | 558 | // 559 | 560 | // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md 561 | 562 | // recommends that there are no checks for the approval double-spend attack 563 | 564 | // as this should be implemented in user interfaces 565 | 566 | // ------------------------------------------------------------------------ 567 | 568 | function approve(address spender, uint tokens) public returns (bool success) { 569 | 570 | allowed[msg.sender][spender] = tokens; 571 | 572 | Approval(msg.sender, spender, tokens); 573 | 574 | return true; 575 | 576 | } 577 | 578 | 579 | 580 | // ------------------------------------------------------------------------ 581 | 582 | // Transfer `tokens` from the `from` account to the `to` account 583 | 584 | // 585 | 586 | // The calling account must already have sufficient tokens approve(...)-d 587 | 588 | // for spending from the `from` account and 589 | 590 | // - From account must have sufficient balance to transfer 591 | 592 | // - Spender must have sufficient allowance to transfer 593 | 594 | // - 0 value transfers are allowed 595 | 596 | // ------------------------------------------------------------------------ 597 | 598 | function transferFrom(address from, address to, uint tokens) public returns (bool success) { 599 | 600 | balances[from] = balances[from].sub(tokens); 601 | 602 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); 603 | 604 | balances[to] = balances[to].add(tokens); 605 | 606 | Transfer(from, to, tokens); 607 | 608 | return true; 609 | 610 | } 611 | 612 | 613 | 614 | // ------------------------------------------------------------------------ 615 | 616 | // Returns the amount of tokens approved by the owner that can be 617 | 618 | // transferred to the spender's account 619 | 620 | // ------------------------------------------------------------------------ 621 | 622 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining) { 623 | 624 | return allowed[tokenOwner][spender]; 625 | 626 | } 627 | 628 | 629 | 630 | // ------------------------------------------------------------------------ 631 | 632 | // Token owner can approve for `spender` to transferFrom(...) `tokens` 633 | 634 | // from the token owner's account. The `spender` contract function 635 | 636 | // `receiveApproval(...)` is then executed 637 | 638 | // ------------------------------------------------------------------------ 639 | 640 | function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { 641 | 642 | allowed[msg.sender][spender] = tokens; 643 | 644 | Approval(msg.sender, spender, tokens); 645 | 646 | ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); 647 | 648 | return true; 649 | 650 | } 651 | 652 | 653 | 654 | // ------------------------------------------------------------------------ 655 | 656 | // Don't accept ETH 657 | 658 | // ------------------------------------------------------------------------ 659 | 660 | function () public payable { 661 | 662 | revert(); 663 | 664 | } 665 | 666 | 667 | 668 | // ------------------------------------------------------------------------ 669 | 670 | // Owner can transfer out any accidentally sent ERC20 tokens 671 | 672 | // ------------------------------------------------------------------------ 673 | 674 | function transferAnyERC20Token(address tokenAddress, uint tokens) public onlyOwner returns (bool success) { 675 | 676 | return ERC20Interface(tokenAddress).transfer(owner, tokens); 677 | 678 | } 679 | 680 | } 681 | -------------------------------------------------------------------------------- /contracts/tests/Testable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.0 <0.9.0; 2 | 3 | // SPDX-License-Identifier: MIT 4 | 5 | abstract contract Testable { 6 | receive() external payable virtual {} 7 | } 8 | -------------------------------------------------------------------------------- /contracts/tests/xBitsToken_Test.sol: -------------------------------------------------------------------------------- 1 | // Adds Test library to the context 2 | 3 | import "@mangrovedao/hardhat-test-solidity/test.sol"; 4 | 5 | import "./Testable.sol"; 6 | import "../xBitsToken.sol"; 7 | 8 | pragma solidity ^0.8.6; 9 | 10 | // `_Test` suffix means it is a test contract 11 | contract xBitsToken_Test is Testable,xBitsToken(address(0)) { 12 | 13 | 14 | receive() external payable override(Testable, xBitsToken) {} 15 | 16 | function initialize() internal override{ 17 | 18 | epochCount = 100; 19 | 20 | miningTarget = _MAXIMUM_TARGET; 21 | 22 | Test.eq(totalSupply, 21000000 * 10**uint(decimals),"Incorrect total supply"); 23 | 24 | maxSupplyForEra = totalSupply - (totalSupply / ( 2**(rewardEra + 1))); 25 | 26 | Test.eq(maxSupplyForEra, 10500000 * 10**uint(decimals),"Incorrect total supply"); 27 | 28 | } 29 | 30 | // `_test` suffix means it is a test function 31 | function miningEpoch_test() public { 32 | 33 | super._startNewMiningEpoch(); 34 | 35 | Test.eq(epochCount,101,"invalid epoch count"); 36 | } 37 | 38 | function adjustDifficuly_test() public { 39 | 40 | Test.eq(miningTarget,_MAXIMUM_TARGET,"invalid mining target"); 41 | 42 | super._reAdjustDifficulty(600); 43 | 44 | Test.eq(epochCount,100,"invalid epoch count"); 45 | //Test.eq(miningTarget,_MAXIMUM_TARGET,"invalid mining target"); 46 | 47 | miningTarget = _MAXIMUM_TARGET; 48 | super._reAdjustDifficulty(1); 49 | 50 | 51 | miningTarget = _MAXIMUM_TARGET; 52 | super._reAdjustDifficulty(1024); 53 | 54 | miningTarget = _MAXIMUM_TARGET; 55 | super._reAdjustDifficulty(1024*60); 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | import { DeployFunction } from 'hardhat-deploy/types' 2 | 3 | import { deploy } from '../helpers/deploy-helpers' 4 | import { BigNumberish, BigNumber as BN } from 'ethers' 5 | import { HardhatRuntimeEnvironment } from 'hardhat/types' 6 | import { getTokens, getNetworkName} from '../config' 7 | import { ethers } from 'hardhat' 8 | 9 | const deployOptions: DeployFunction = async (hre) => { 10 | const { run, network } = hre 11 | 12 | // Make sure contracts are compiled 13 | await run('compile') 14 | 15 | console.log('') 16 | console.log('********** Deploying **********', { indent: 1 }) 17 | console.log('') 18 | 19 | 20 | 21 | const originalToken = await deploy({ 22 | contract: '_0xBitcoinTokenTest', 23 | args: [ ], 24 | skipIfAlreadyDeployed: false, 25 | hre, 26 | }) 27 | 28 | const upgradeToken = await deploy({ 29 | contract: 'xBitsToken', 30 | args: [ originalToken.address ], 31 | skipIfAlreadyDeployed: false, 32 | hre, 33 | }) 34 | 35 | 36 | 37 | } 38 | 39 | deployOptions.tags = ['primary'] 40 | deployOptions.dependencies = [] 41 | 42 | export default deployOptions 43 | -------------------------------------------------------------------------------- /deployments/hardhat/.chainId: -------------------------------------------------------------------------------- 1 | 31337 -------------------------------------------------------------------------------- /deployments/hardhat/solcInputs/bd0840a00cd4a6f81e6a90386c72c7f8.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/tests/xBitsToken_Test.sol": { 5 | "content": "// Adds Test library to the context\nimport \"@mangrovedao/hardhat-test-solidity/test.sol\";\npragma solidity ^0.8.6;\n\n// `_Test` suffix means it is a test contract\ncontract xBitsToken_Test {\n\n receive() external payable {} // necessary to receive eth from test runner\n\n // `_test` suffix means it is a test function\n function addition_test() public {\n prepare();\n // Logging will be interpreted by hardhat-test-solidity\n Test.eq(4,2+2,\"oh no\");\n }\n\n // Will not be interpreted as a test function\n function prepare() public {}\n}" 6 | }, 7 | "@mangrovedao/hardhat-test-solidity/test.sol": { 8 | "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.6.0;\n\n// Should be kept in sync with ./lib.js\n\nlibrary Test {\n /* \n * Expect events from contracts\n */\n event ExpectFrom(address from);\n event StopExpecting();\n\n // Usage: from a test contract `t`, call `expectFrom(a)`. \n // Any subsequent non-special event emitted by `t` will mean \n // \"I expect `a` to emit the exact same event\". \n // The order of expectations must be respected.\n function expectFrom(address from) internal {\n emit ExpectFrom(from);\n }\n\n // After using `expectFrom` and emitting some events you expect\n // to see emitted elsewhere, you can use `stopExpecting` to emit \n // further, normal events from your test.\n function stopExpecting() internal {\n emit StopExpecting();\n }\n\n\n /* \n * Boolean test\n */\n event TestTrue(bool success, string message);\n\n // Succeed iff success is true\n function check(bool success, string memory message) internal {\n emit TestTrue(success, message);\n }\n\n\n /* \n * Always fail, always succeed\n */\n function fail(string memory message) internal {\n emit TestTrue(false, message);\n }\n\n function succeed() internal {\n emit TestTrue(true, \"Success\");\n }\n\n /* \n * Equality testing\n * ! overloaded as `eq` for everything except for bytes use `eq0`.\n */\n\n // Bytes\n event TestEqBytes(bool success, bytes actual, bytes expected, string message);\n\n function eq0(\n bytes memory actual,\n bytes memory expected,\n string memory message\n ) internal returns (bool) {\n bool success = keccak256((actual)) == keccak256((expected));\n emit TestEqBytes(success, actual, expected, message);\n return success;\n }\n\n // Byte32\n event TestEqBytes32(\n bool success,\n bytes32 actual,\n bytes32 expected,\n string message\n );\n\n function eq(\n bytes32 actual,\n bytes32 expected,\n string memory message\n ) internal returns (bool) {\n bool success = (actual == expected);\n emit TestEqBytes32(success, actual, expected, message);\n return success;\n }\n\n // Bool\n event TestEqBool(bool success, bool actual, bool expected, string message);\n function eq(\n bool actual,\n bool expected,\n string memory message\n ) internal returns (bool) {\n bool success = (actual == expected);\n emit TestEqBool(success, actual, expected, message);\n return success;\n }\n\n // uints\n event TestEqUint(bool success, uint actual, uint expected, string message);\n\n function eq(\n uint actual,\n uint expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual == expected;\n emit TestEqUint(success, actual, expected, message);\n return success;\n }\n\n // strings\n event TestEqString(\n bool success,\n string actual,\n string expected,\n string message\n );\n\n function eq(\n string memory actual,\n string memory expected,\n string memory message\n ) internal returns (bool) {\n bool success = keccak256(bytes((actual))) == keccak256(bytes((expected)));\n emit TestEqString(success, actual, expected, message);\n return success;\n }\n\n // addresses\n event TestEqAddress(\n bool success,\n address actual,\n address expected,\n string message\n );\n\n\n function eq(\n address actual,\n address expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual == expected;\n emit TestEqAddress(success, actual, expected, message);\n return success;\n }\n\n /* \n * Inequality testing\n */\n event TestLess(bool success, uint actual, uint expected, string message);\n function less(\n uint actual,\n uint expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual < expected;\n emit TestLess(success, actual, expected, message);\n return success;\n }\n\n event TestLessEq(bool success, uint actual, uint expected, string message);\n function lessEq(\n uint actual,\n uint expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual <= expected;\n emit TestLessEq(success, actual, expected, message);\n return success;\n }\n\n event TestMore(bool success, uint actual, uint expected, string message);\n function more(\n uint actual,\n uint expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual > expected;\n emit TestMore(success, actual, expected, message);\n return success;\n }\n\n event TestMoreEq(bool success, uint actual, uint expected, string message);\n function moreEq(\n uint actual,\n uint expected,\n string memory message\n ) internal returns (bool) {\n bool success = actual >= expected;\n emit TestMoreEq(success, actual, expected, message);\n return success;\n }\n}\n\n// /* Either cast your arguments to address when you call balanceOf logging functions\n// or add `is address` to your ERC20s\n// or use the overloads with `address` types */\ninterface ERC20BalanceOf {\n function balanceOf(address account) view external returns (uint);\n}\n\n\nlibrary Display {\n /* ****************************************************************\n * Register/read address->name mappings to make logs easier to read.\n *****************************************************************/\n /* \n * Names are stored in the contract using the library.\n */\n\n // Disgusting hack so a library can manipulate storage refs.\n bytes32 constant NAMES_POS = keccak256(\"Display.NAMES_POS\");\n // Store mapping in library caller's storage.\n // That's quite fragile.\n struct Registers {\n mapping(address => string) map;\n }\n\n // Also send mapping to javascript test interpreter. The interpreter COULD\n // just make an EVM call to map every name but that would probably be very\n // slow. So we cache locally.\n event Register(address addr, string name);\n\n function registers() internal view returns (Registers storage) {\n this; // silence warning about pure mutability\n Registers storage regs;\n bytes32 _slot = NAMES_POS;\n assembly {\n regs.slot := _slot\n }\n return regs;\n }\n\n /*\n * Give a name to an address for logging purposes\n * @example\n * ```solidity\n * address addr = address(new Contract());\n * register(addr,\"My Contract instance\");\n * ```\n */\n\n function register(address addr, string memory name) internal {\n registers().map[addr] = name;\n emit Register(addr, name);\n }\n\n /*\n * Read the name of a registered address. Default: \"\". \n */\n function nameOf(address addr) internal view returns (string memory) {\n string memory s = registers().map[addr];\n if (keccak256(bytes(s)) != keccak256(bytes(\"\"))) {\n return s;\n } else {\n return \"\";\n }\n }\n\n /* 1 arg logging (string/uint) */\n\n event LogString(string a);\n\n function log(string memory a) internal {\n emit LogString(a);\n }\n\n event LogUint(uint a);\n\n function log(uint a) internal {\n emit LogUint(a);\n }\n\n /* 2 arg logging (string/uint) */\n\n event LogStringString(string a, string b);\n\n function log(string memory a, string memory b) internal {\n emit LogStringString(a, b);\n }\n\n event LogStringUint(string a, uint b);\n\n function log(string memory a, uint b) internal {\n emit LogStringUint(a, b);\n }\n\n event LogUintUint(uint a, uint b);\n\n function log(uint a, uint b) internal {\n emit LogUintUint(a, b);\n }\n\n event LogUintString(uint a, string b);\n\n function log(uint a, string memory b) internal {\n emit LogUintString(a, b);\n }\n\n /* 3 arg logging (string/uint) */\n\n event LogStringStringString(string a, string b, string c);\n\n function log(\n string memory a,\n string memory b,\n string memory c\n ) internal {\n emit LogStringStringString(a, b, c);\n }\n\n event LogStringStringUint(string a, string b, uint c);\n\n function log(\n string memory a,\n string memory b,\n uint c\n ) internal {\n emit LogStringStringUint(a, b, c);\n }\n\n event LogStringUintUint(string a, uint b, uint c);\n\n function log(\n string memory a,\n uint b,\n uint c\n ) internal {\n emit LogStringUintUint(a, b, c);\n }\n\n event LogStringUintString(string a, uint b, string c);\n\n function log(\n string memory a,\n uint b,\n string memory c\n ) internal {\n emit LogStringUintString(a, b, c);\n }\n\n event LogUintUintUint(uint a, uint b, uint c);\n\n function log(\n uint a,\n uint b,\n uint c\n ) internal {\n emit LogUintUintUint(a, b, c);\n }\n\n event LogUintStringUint(uint a, string b, uint c);\n\n function log(\n uint a,\n string memory b,\n uint c\n ) internal {\n emit LogUintStringUint(a, b, c);\n }\n\n event LogUintStringString(uint a, string b, string c);\n\n function log(\n uint a,\n string memory b,\n string memory c\n ) internal {\n emit LogUintStringString(a, b, c);\n }\n\n /* End of register/read section */\n event ERC20Balances(address[] tokens, address[] accounts, uint[] balances);\n\n function logBalances(\n address[1] memory _tokens, \n address _a0\n ) internal {\n address[] memory tokens = new address[](1);\n tokens[0] = _tokens[0];\n address[] memory accounts = new address[](1);\n accounts[0] = _a0;\n logBalances(tokens, accounts);\n }\n\n function logBalances(\n address[1] memory _tokens,\n address _a0,\n address _a1\n ) internal {\n address[] memory tokens = new address[](1);\n tokens[0] = _tokens[0];\n address[] memory accounts = new address[](2);\n accounts[0] = _a0;\n accounts[1] = _a1;\n logBalances(tokens, accounts);\n }\n\n function logBalances(\n address[1] memory _tokens,\n address _a0,\n address _a1,\n address _a2\n ) internal {\n address[] memory tokens = new address[](1);\n tokens[0] = _tokens[0];\n address[] memory accounts = new address[](3);\n accounts[0] = _a0;\n accounts[1] = _a1;\n accounts[2] = _a2;\n logBalances(tokens, accounts);\n }\n\n function logBalances(\n address[2] memory _tokens,\n address _a0\n ) internal {\n address[] memory tokens = new address[](2);\n tokens[0] = _tokens[0];\n tokens[1] = _tokens[1];\n address[] memory accounts = new address[](1);\n accounts[0] = _a0;\n logBalances(tokens, accounts);\n }\n\n function logBalances(\n address[2] memory _tokens,\n address _a0,\n address _a1\n ) internal {\n address[] memory tokens = new address[](2);\n tokens[0] = _tokens[0];\n tokens[1] = _tokens[1];\n address[] memory accounts = new address[](2);\n accounts[0] = _a0;\n accounts[1] = _a1;\n logBalances(tokens, accounts);\n }\n\n function logBalances(\n address[2] memory _tokens,\n address _a0,\n address _a1,\n address _a2\n ) internal {\n address[] memory tokens = new address[](2);\n tokens[0] = _tokens[0];\n tokens[1] = _tokens[1];\n address[] memory accounts = new address[](3);\n accounts[0] = _a0;\n accounts[1] = _a1;\n accounts[2] = _a2;\n logBalances(tokens, accounts);\n }\n\n /* takes [t1,...,tM], [a1,...,aN]\n logs also [...b(t1,aj) ... b(tM,aj) ...] */\n\n function logBalances(address[] memory tokens, address[] memory accounts)\n internal\n {\n uint[] memory balances = new uint[](tokens.length * accounts.length);\n for (uint i = 0; i < tokens.length; i++) {\n for (uint j = 0; j < accounts.length; j++) {\n uint bal = ERC20BalanceOf(tokens[i]).balanceOf(accounts[j]);\n balances[i * accounts.length + j] = bal;\n //console.log(tokens[i].symbol(),nameOf(accounts[j]),bal);\n }\n }\n emit ERC20Balances(tokens, accounts, balances);\n }\n\n}" 9 | } 10 | }, 11 | "settings": { 12 | "optimizer": { 13 | "enabled": false, 14 | "runs": 200 15 | }, 16 | "outputSelection": { 17 | "*": { 18 | "*": [ 19 | "abi", 20 | "evm.bytecode", 21 | "evm.deployedBytecode", 22 | "evm.methodIdentifiers", 23 | "metadata", 24 | "devdoc", 25 | "userdoc", 26 | "storageLayout", 27 | "evm.gasEstimates" 28 | ], 29 | "": [ 30 | "ast" 31 | ] 32 | } 33 | }, 34 | "metadata": { 35 | "useLiteralContent": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /deployments/rinkeby/.chainId: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /deployments/rinkeby/.latestDeploymentBlock: -------------------------------------------------------------------------------- 1 | 2 | 10602037 3 | -------------------------------------------------------------------------------- /deployments/rinkeby/solcInputs/f1bd4eb63df8f5fb61900f6a1d878bed.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/FixedSupplyToken.sol": { 5 | "content": " \n\npragma solidity ^0.8.0;\n\n// ----------------------------------------------------------------------------------------------\n// Sample fixed supply token contract\n// Enjoy. (c) BokkyPooBah 2017. The MIT Licence.\n// ----------------------------------------------------------------------------------------------\n\n// ERC Token Standard #20 Interface\n// https://github.com/ethereum/EIPs/issues/20\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n \ncontract FixedSupplyToken is IERC20 {\n string public constant symbol = \"FIXED\";\n string public constant name = \"Example Fixed Supply Token\";\n uint8 public constant decimals = 8;\n uint256 _totalSupply = 21000000 * 10**uint(decimals);\n\n\n\n // Owner of this contract\n address public owner;\n\n // Balances for each account\n mapping(address => uint256) public balances;\n\n // Owner of account approves the transfer of an amount to another account\n mapping(address => mapping (address => uint256)) public allowed;\n\n // Functions with this modifier can only be executed by the owner\n modifier onlyOwner() {\n if (msg.sender != owner) {\n revert();\n }\n _;\n }\n\n // Constructor\n constructor() public {\n owner = msg.sender;\n balances[owner] = _totalSupply;\n }\n\n\n function mint(address to, uint256 amount) public {\n balances[to] += amount;\n }\n\n function totalSupply() public view override returns (uint256 totalSupply) {\n totalSupply = _totalSupply;\n }\n\n // What is the balance of a particular account?\n function balanceOf(address _owner) public view override returns (uint256 balance) {\n return balances[_owner];\n }\n\n // Transfer the balance from owner's account to another account\n function transfer(address _to, uint256 _amount) public override returns (bool success) {\n if (balances[msg.sender] >= _amount\n && _amount > 0\n && balances[_to] + _amount > balances[_to]) {\n balances[msg.sender] -= _amount;\n balances[_to] += _amount;\n emit Transfer(msg.sender, _to, _amount);\n return true;\n } else {\n return false;\n }\n }\n\n // Send _value amount of tokens from address _from to address _to\n // The transferFrom method is used for a withdraw workflow, allowing contracts to send\n // tokens on your behalf, for example to \"deposit\" to a contract address and/or to charge\n // fees in sub-currencies; the command should fail unless the _from account has\n // deliberately authorized the sender of the message via some mechanism; we propose\n // these standardized APIs for approval:\n function transferFrom(\n address _from,\n address _to,\n uint256 _amount\n ) public override returns (bool success) {\n if (balances[_from] >= _amount\n && allowed[_from][msg.sender] >= _amount\n && _amount > 0\n && balances[_to] + _amount > balances[_to]) {\n balances[_from] -= _amount;\n allowed[_from][msg.sender] -= _amount;\n balances[_to] += _amount;\n emit Transfer(_from, _to, _amount);\n return true;\n } else {\n return false;\n }\n }\n\n // Allow _spender to withdraw from your account, multiple times, up to the _value amount.\n // If this function is called again it overwrites the current allowance with _value.\n function approve(address _spender, uint256 _amount) public override returns (bool success) {\n allowed[msg.sender][_spender] = _amount;\n emit Approval(msg.sender, _spender, _amount);\n return true;\n }\n\n function allowance(address _owner, address _spender) public override view returns (uint256 remaining) {\n return allowed[_owner][_spender];\n }\n}\n" 6 | }, 7 | "@openzeppelin/contracts/token/ERC20/IERC20.sol": { 8 | "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n" 9 | }, 10 | "contracts/Payspec.sol": { 11 | "content": "pragma solidity ^0.8.0;\n\n/*\nPAYSPEC: Atomic and deterministic invoicing system\n\nGenerate offchain invoices based on sell-order data and allow users to fulfill those order invoices onchain.\n\n*/\n \n\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\n\n\n\n \ncontract Payspec is Ownable, ReentrancyGuard {\n\n uint256 public immutable contractVersion = 100;\n address immutable ETHER_ADDRESS = address(0x0000000000000000000000000000000000000010);\n \n mapping(bytes32 => Invoice) public invoices; \n\n bool lockedByOwner = false; \n\n event CreatedInvoice(bytes32 uuid); \n event PaidInvoice(bytes32 uuid, address from);\n\n\n struct Invoice {\n bytes32 uuid;\n string description;\n uint256 nonce;\n bool created;\n\n\n address token;\n uint256 amountDue;\n address payTo;\n\n address[] feeAddresses;\n uint[] feePercents;\n\n address paidBy;\n uint256 amountPaid;\n uint256 ethBlockPaidAt;\n\n\n uint256 ethBlockExpiresAt;\n\n }\n\n\n\n constructor( ) public {\n\n } \n \n\n function lockContract() public onlyOwner {\n lockedByOwner = true;\n }\n\n\n \n\n\n function createAndPayInvoice( string memory description, uint256 nonce, address token, uint256 amountDue, address payTo, address[] memory feeAddresses, uint[] memory feePercents, uint256 ethBlockExpiresAt, bytes32 expecteduuid ) \n public \n payable \n nonReentrant\n returns (bool) {\n \n if(token == ETHER_ADDRESS){\n require(msg.value == amountDue, \"Transaction sent incorrect ETH amount.\");\n }else{\n require(msg.value == 0, \"Transaction sent ETH for an ERC20 invoice.\");\n }\n \n bytes32 newuuid = _createInvoice(description,nonce,token,amountDue,payTo,feeAddresses, feePercents,ethBlockExpiresAt,expecteduuid);\n require(newuuid == expecteduuid);\n return _payInvoice(newuuid);\n }\n\n function _createInvoice( string memory description, uint256 nonce, address token, uint256 amountDue, address payTo, address[] memory feeAddresses, uint[] memory feePercents, uint256 ethBlockExpiresAt, bytes32 expecteduuid ) \n private \n returns (bytes32 uuid) { \n\n\n bytes32 newuuid = getInvoiceUUID(description, nonce, token, amountDue, payTo, feeAddresses, feePercents, ethBlockExpiresAt ) ;\n\n require(!lockedByOwner);\n require( newuuid == expecteduuid );\n require( invoices[newuuid].uuid == 0 ); //make sure you do not overwrite invoices\n require(feeAddresses.length == feePercents.length);\n\n require(ethBlockExpiresAt == 0 || block.number < ethBlockExpiresAt);\n\n invoices[newuuid] = Invoice({\n uuid:newuuid,\n description:description,\n nonce: nonce,\n token: token,\n amountDue: amountDue,\n payTo: payTo,\n paidBy: address(0),\n feeAddresses: feeAddresses,\n feePercents: feePercents,\n amountPaid: 0,\n ethBlockPaidAt: 0,\n ethBlockExpiresAt: ethBlockExpiresAt,\n created:true\n });\n\n\n emit CreatedInvoice(newuuid);\n\n return newuuid;\n }\n\n function _payInvoice( bytes32 invoiceUUID ) private returns (bool) {\n\n address from = msg.sender;\n\n require(!lockedByOwner);\n require( invoices[invoiceUUID].uuid == invoiceUUID ); //make sure invoice exists\n require( invoiceWasPaid(invoiceUUID) == false ); \n\n uint totalAmountDueInFees = 0; // invoices[invoiceUUID].amountDue.mul( fee_pct ).div(100);\n\n\n\n for(uint i=0;i= invoices[invoiceUUID].amountDue;\n }\n\n function invoiceWasCreated( bytes32 invoiceUUID ) public view returns (bool){\n\n return invoices[invoiceUUID].created ;\n }\n\n\n\n function getInvoiceDescription( bytes32 invoiceUUID ) public view returns (string memory){\n\n return invoices[invoiceUUID].description;\n }\n\n function getInvoiceTokenCurrency( bytes32 invoiceUUID ) public view returns (address){\n\n return invoices[invoiceUUID].token;\n }\n\n\n function getInvoiceAmountPaid( bytes32 invoiceUUID ) public view returns (uint){\n\n return invoices[invoiceUUID].amountPaid;\n }\n\n function getInvoicePayer( bytes32 invoiceUUID ) public view returns (address){\n\n return invoices[invoiceUUID].paidBy;\n }\n\n function getInvoiceEthBlockPaidAt( bytes32 invoiceUUID ) public view returns (uint){\n\n return invoices[invoiceUUID].ethBlockPaidAt;\n }\n\n \n\n\n}\n" 12 | }, 13 | "@openzeppelin/contracts/access/Ownable.sol": { 14 | "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" 15 | }, 16 | "@openzeppelin/contracts/security/ReentrancyGuard.sol": { 17 | "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n // On the first call to nonReentrant, _notEntered will be true\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n\n _;\n\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n}\n" 18 | }, 19 | "@openzeppelin/contracts/utils/Context.sol": { 20 | "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n" 21 | } 22 | }, 23 | "settings": { 24 | "optimizer": { 25 | "enabled": true, 26 | "runs": 200 27 | }, 28 | "outputSelection": { 29 | "*": { 30 | "*": [ 31 | "abi", 32 | "evm.bytecode", 33 | "evm.deployedBytecode", 34 | "evm.methodIdentifiers", 35 | "metadata", 36 | "devdoc", 37 | "userdoc", 38 | "storageLayout", 39 | "evm.gasEstimates" 40 | ], 41 | "": [ 42 | "ast" 43 | ] 44 | } 45 | }, 46 | "metadata": { 47 | "useLiteralContent": true 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | // This adds support for typescript paths mappings 2 | import 'tsconfig-paths/register' 3 | import '@nomiclabs/hardhat-waffle' 4 | import '@tenderly/hardhat-tenderly' 5 | import 'hardhat-contract-sizer' 6 | import 'hardhat-deploy' 7 | import 'hardhat-gas-reporter' 8 | import '@nomiclabs/hardhat-ethers' 9 | import '@typechain/hardhat' 10 | 11 | 12 | import fs from 'fs' 13 | import path from 'path' 14 | 15 | import { 16 | TransactionReceipt, 17 | TransactionRequest, 18 | } from '@ethersproject/providers' 19 | //import { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/src/types' 20 | import chalk from 'chalk' 21 | import { config } from 'dotenv' 22 | import { Signer, utils } from 'ethers' 23 | //import { HardhatUserConfig, task } from '@tenderly/hardhat-tenderly' 24 | import { 25 | HardhatNetworkHDAccountsUserConfig, 26 | NetworkUserConfig, 27 | } from 'hardhat/types' 28 | import rrequire from './helpers/rrequire' 29 | import semver from 'semver' 30 | import { task ,HardhatUserConfig} from 'hardhat/config' 31 | import { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types' 32 | 33 | 34 | 35 | 36 | 37 | require('@mangrovedao/hardhat-test-solidity'); 38 | 39 | 40 | const NODE_VERSION = 'v16.20.1' 41 | if (!semver.satisfies(process.version, NODE_VERSION)) 42 | throw new Error( 43 | `Incorrect NodeJS version being used (${process.version}). Expected: ${NODE_VERSION}` 44 | ) 45 | 46 | config() 47 | const { isAddress, getAddress, formatUnits, parseUnits, parseEther } = utils 48 | 49 | const { 50 | COMPILING, 51 | CMC_KEY, 52 | DEFAULT_NETWORK, 53 | FORKING_NETWORK, 54 | SAVE_GAS_REPORT, 55 | SKIP_SIZER, 56 | TESTING, 57 | } = process.env 58 | 59 | const isCompiling = COMPILING === 'true' 60 | const skipContractSizer = SKIP_SIZER === 'true' && !isCompiling 61 | if (!isCompiling) { 62 | rrequire(path.resolve(__dirname, 'helpers', 'tasks')) 63 | require('./helpers/hre-extensions') 64 | } 65 | 66 | const isTesting = TESTING === '1' 67 | if (isTesting) { 68 | require('./helpers/chai-helpers') 69 | } 70 | 71 | // 72 | // Select the network you want to deploy to here: 73 | // 74 | const defaultNetwork = DEFAULT_NETWORK ?? 'hardhat' 75 | 76 | const pathToMnemonic = path.resolve(__dirname, 'mnemonic.secret') 77 | 78 | export const getMnemonic = (): string => { 79 | try { 80 | return fs.readFileSync(pathToMnemonic).toString().trim() 81 | } catch (e) { 82 | // @ts-ignore 83 | if (defaultNetwork !== 'localhost') { 84 | console.log( 85 | '☢️ WARNING: No mnemonic file created for a deploy account. Try `yarn run generate` and then `yarn run account`.' 86 | ) 87 | } 88 | } 89 | return '' 90 | } 91 | 92 | const accounts: HardhatNetworkHDAccountsUserConfig = { 93 | mnemonic: getMnemonic(), 94 | count: 15, 95 | accountsBalance: parseEther('100000000').toString(), 96 | } 97 | 98 | const networkUrls: { [network: string]: string } = { 99 | mainnet: process.env.MAINNET_RPC_URL ?? '', 100 | kovan: process.env.KOVAN_RPC_URL ?? '', 101 | rinkeby: process.env.RINKEBY_RPC_URL ?? 'https://eth-rinkeby.alchemyapi.io/v2/k78WV2Yf8yVKW42DzDXz4EKfHgRyR1kK', 102 | ropsten: process.env.ROPSTEN_RPC_URL ?? '', 103 | polygon: process.env.POLYGON_RPC_URL ?? '', 104 | mumbai: process.env.MUMBAI_RPC_URL ?? '', 105 | goerli: process.env.GOERLI_RPC_URL ?? '', 106 | xdai: process.env.XDAI_RPC_URL ?? '', 107 | rinkebyArbitrum: process.env.RINKEBY_ARBITRUM_RPC_URL ?? '', 108 | optimism: process.env.OPTIMISM_RPC_URL ?? '', 109 | kovanOptimism: process.env.KOVAN_OPTIMISM_RPC_URL ?? '', 110 | fujiAvalanche: process.env.FUJI_AVALANCHE_RPC_URL ?? '', 111 | mainnetAvalanche: process.env.MAINNET_AVALANCHE_RPC_URL ?? '', 112 | testnetHarmony: process.env.TESTNET_HARMONY_RPC_URL ?? '', 113 | mainnetHarmony: process.env.MAINNET_HARMONY_RPC_URL ?? '', 114 | } 115 | 116 | const getLatestDeploymentBlock = (networkName: string): number | undefined => { 117 | try { 118 | return parseInt( 119 | fs 120 | .readFileSync( 121 | path.resolve( 122 | __dirname, 123 | 'deployments', 124 | networkName, 125 | '.latestDeploymentBlock' 126 | ) 127 | ) 128 | .toString() 129 | ) 130 | } catch { 131 | // Network deployment does not exist 132 | } 133 | } 134 | 135 | const networkConfig = (config: NetworkUserConfig): NetworkUserConfig => ({ 136 | live: true, 137 | ...config, 138 | accounts, 139 | gas: 'auto', 140 | saveDeployments: true 141 | }) 142 | 143 | /* 144 | 📡 This is where you configure your deploy configuration for 🏗 scaffold-eth 145 | 146 | check out `packages/scripts/deploy.js` to customize your deployment 147 | 148 | out of the box it will auto deploy anything in the `contracts` folder and named *.sol 149 | plus it will use *.args for constructor args 150 | */ 151 | 152 | const mainnetGwei = 21 153 | 154 | // eslint-disable-next-line @typescript-eslint/consistent-type-assertions 155 | export default { 156 | defaultNetwork, 157 | 158 | etherscan: { 159 | apiKey: '{see `updateEtherscanConfig` function in utils/hre-extensions.ts}', 160 | }, 161 | 162 | tenderly: { 163 | username: 'soltel', 164 | project: '{see `updateTenderlyConfig` function in utils/hre-extensions.ts}', 165 | }, 166 | 167 | paths: { 168 | cache: './generated/cache', 169 | artifacts: './generated/artifacts', 170 | }, 171 | 172 | typechain: { 173 | outDir: './generated/typechain', 174 | }, 175 | 176 | external: { 177 | contracts: [ 178 | { 179 | artifacts: 'node_modules/hardhat-deploy/extendedArtifacts', 180 | }, 181 | ], 182 | }, 183 | 184 | solidity: { 185 | compilers: [ 186 | { 187 | version: '0.8.6', 188 | settings: { 189 | optimizer: { 190 | enabled: !isTesting, 191 | runs: 200, 192 | }, 193 | }, 194 | }, 195 | { 196 | version: '0.4.18', 197 | settings: { 198 | optimizer: { 199 | enabled: !isTesting, 200 | runs: 200, 201 | }, 202 | }, 203 | }, 204 | { 205 | version: '0.4.24', 206 | settings: { 207 | optimizer: { 208 | enabled: !isTesting, 209 | runs: 200, 210 | }, 211 | }, 212 | }, 213 | ], 214 | }, 215 | 216 | ovm: { 217 | solcVersion: '0.8.4', 218 | }, 219 | 220 | contractSizer: { 221 | runOnCompile: skipContractSizer, 222 | alphaSort: false, 223 | disambiguatePaths: false, 224 | }, 225 | 226 | /** 227 | * gas reporter configuration that let's you know 228 | * an estimate of gas for contract deployments and function calls 229 | * More here: https://hardhat.org/plugins/hardhat-gas-reporter.html 230 | */ 231 | gasReporter: { 232 | enabled: true, 233 | currency: 'USD', 234 | coinmarketcap: CMC_KEY, 235 | outputFile: SAVE_GAS_REPORT ? 'gas-reporter.txt' : undefined, 236 | noColors: !!SAVE_GAS_REPORT, 237 | showMethodSig: false, 238 | showTimeSpent: true, 239 | }, 240 | 241 | namedAccounts: { 242 | deployer: { 243 | default: 0, // here this will by default take the first account as deployer 244 | }, 245 | funder: 1, 246 | miner: 2 247 | 248 | }, 249 | 250 | // if you want to deploy to a testnet, mainnet, or xdai, you will need to configure: 251 | // 1. An Infura key (or similar) 252 | // 2. A private key for the deployer 253 | // DON'T PUSH THESE HERE!!! 254 | // An `example.env` has been provided in the Hardhat root. Copy it and rename it `.env` 255 | // Follow the directions, and uncomment the network you wish to deploy to. 256 | 257 | networks: { 258 | hardhat: networkConfig({ 259 | chainId: 31337, 260 | live: false, 261 | allowUnlimitedContractSize: true, 262 | saveDeployments: !isTesting, 263 | forking:undefined 264 | /* 265 | 266 | FORKING_NETWORK == null 267 | ? undefined 268 | : { 269 | enabled: true, 270 | url: networkUrls[FORKING_NETWORK], 271 | blockNumber: getLatestDeploymentBlock(FORKING_NETWORK), 272 | }, 273 | 274 | */ 275 | }), 276 | localhost: networkConfig({ 277 | url: 'http://localhost:8545', 278 | live: false, 279 | }), 280 | mainnet: networkConfig({ 281 | url: networkUrls.mainnet, 282 | chainId: 1, 283 | gasPrice: mainnetGwei * 1000000000, 284 | }), 285 | kovan: networkConfig({ 286 | url: networkUrls.kovan, 287 | chainId: 42, 288 | }), 289 | rinkeby: networkConfig({ 290 | url: networkUrls.rinkeby, 291 | chainId: 4, 292 | }), 293 | ropsten: networkConfig({ 294 | url: networkUrls.ropsten, 295 | chainId: 3, 296 | }), 297 | goerli: networkConfig({ 298 | url: networkUrls.goerli, 299 | // chainId: , 300 | }), 301 | xdai: networkConfig({ 302 | url: networkUrls.xdai, 303 | // chainId: , 304 | gasPrice: 1000000000, 305 | }), 306 | polygon: networkConfig({ 307 | url: networkUrls.polygon, 308 | chainId: 137, 309 | // gasPrice: 1000000000, 310 | }), 311 | mumbai: networkConfig({ 312 | url: networkUrls.mumbai, 313 | gasPrice: 2100000000, // @lazycoder - deserves another Sherlock badge 314 | chainId: 80001, 315 | }), 316 | rinkebyArbitrum: networkConfig({ 317 | url: networkUrls.rinkebyArbitrum, 318 | gasPrice: 0, 319 | companionNetworks: { 320 | l1: 'rinkeby', 321 | }, 322 | }), 323 | localArbitrum: networkConfig({ 324 | url: 'http://localhost:8547', 325 | gasPrice: 0, 326 | companionNetworks: { 327 | l1: 'localArbitrumL1', 328 | }, 329 | live: false, 330 | }), 331 | localArbitrumL1: networkConfig({ 332 | url: 'http://localhost:7545', 333 | gasPrice: 0, 334 | companionNetworks: { 335 | l2: 'localArbitrum', 336 | }, 337 | live: false, 338 | }), 339 | optimism: networkConfig({ 340 | url: networkUrls.optimism, 341 | companionNetworks: { 342 | l1: 'mainnet', 343 | }, 344 | }), 345 | kovanOptimism: networkConfig({ 346 | url: networkUrls.kovanOptimism, 347 | companionNetworks: { 348 | l1: 'kovan', 349 | }, 350 | }), 351 | localOptimism: networkConfig({ 352 | url: 'http://localhost:8545', 353 | companionNetworks: { 354 | l1: 'localOptimismL1', 355 | }, 356 | live: false, 357 | }), 358 | localOptimismL1: networkConfig({ 359 | url: 'http://localhost:9545', 360 | gasPrice: 0, 361 | companionNetworks: { 362 | l2: 'localOptimism', 363 | }, 364 | live: false, 365 | }), 366 | localAvalanche: networkConfig({ 367 | url: 'http://localhost:9650/ext/bc/C/rpc', 368 | gasPrice: 225000000000, 369 | chainId: 43112, 370 | live: false, 371 | }), 372 | fujiAvalanche: networkConfig({ 373 | url: networkUrls.fujiAvalanche, 374 | gasPrice: 225000000000, 375 | chainId: 43113, 376 | }), 377 | mainnetAvalanche: networkConfig({ 378 | url: networkUrls.mainnetAvalanche, 379 | gasPrice: 225000000000, 380 | chainId: 43114, 381 | }), 382 | testnetHarmony: networkConfig({ 383 | url: networkUrls.testnetHarmony, 384 | gasPrice: 1000000000, 385 | chainId: 1666700000, 386 | }), 387 | mainnetHarmony: networkConfig({ 388 | url: networkUrls.mainnetHarmony, 389 | gasPrice: 1000000000, 390 | chainId: 1666600000, 391 | }), 392 | }, 393 | 394 | mocha: { 395 | timeout: 60000, 396 | }, 397 | } 398 | 399 | const DEBUG = false 400 | 401 | const debug = (text: string): void => { 402 | if (DEBUG) { 403 | console.log(text) 404 | } 405 | } 406 | 407 | task('wallet', 'Create a wallet (pk) link', async (_, { ethers }) => { 408 | const randomWallet = ethers.Wallet.createRandom() 409 | const privateKey = randomWallet._signingKey().privateKey 410 | console.log(`🔐 WALLET Generated as ${randomWallet.address}`) 411 | console.log(`🔗 http://localhost:3000/pk#${privateKey}`) 412 | }) 413 | 414 | task('fundedwallet', 'Create a wallet (pk) link and fund it with deployer?') 415 | .addOptionalParam( 416 | 'amount', 417 | 'Amount of ETH to send to wallet after generating' 418 | ) 419 | .addOptionalParam('url', 'URL to add pk to') 420 | .setAction(async (taskArgs, { ethers }) => { 421 | const randomWallet = ethers.Wallet.createRandom() 422 | console.log(`🔐 WALLET Generated as ${randomWallet.address}`) 423 | const url: string = taskArgs.url ? taskArgs.url : 'http://localhost:3000' 424 | 425 | const amount: string = taskArgs.amount ? taskArgs.amount : '0.01' 426 | const tx = { 427 | to: randomWallet.address, 428 | value: ethers.utils.parseEther(amount), 429 | } 430 | 431 | // SEND USING LOCAL DEPLOYER MNEMONIC IF THERE IS ONE 432 | // IF NOT SEND USING LOCAL HARDHAT NODE: 433 | const localDeployerMnemonic = getMnemonic() 434 | if (localDeployerMnemonic) { 435 | let deployerWallet = ethers.Wallet.fromMnemonic(localDeployerMnemonic) 436 | deployerWallet = deployerWallet.connect(ethers.provider) 437 | console.log( 438 | `💵 Sending ${amount} ETH to ${randomWallet.address} using deployer account` 439 | ) 440 | const sendResult = await deployerWallet.sendTransaction(tx) 441 | 442 | console.log() 443 | console.log(`${url}/pk#${randomWallet.privateKey}`) 444 | console.log() 445 | 446 | return sendResult 447 | } else { 448 | console.log( 449 | `💵 Sending ${amount} ETH to ${randomWallet.address} using local node` 450 | ) 451 | console.log() 452 | console.log(`${url}/pk#${randomWallet.privateKey}`) 453 | console.log() 454 | 455 | return await send(ethers.provider.getSigner(), tx) 456 | } 457 | }) 458 | 459 | task( 460 | 'generate', 461 | 'Create a mnemonic for builder deploys', 462 | async (_, { ethers }) => { 463 | const wallet = ethers.Wallet.createRandom() 464 | if (DEBUG) { 465 | console.log('mnemonic', wallet.mnemonic.phrase) 466 | console.log('fullPath', wallet.mnemonic.path) 467 | console.log('privateKey', wallet.privateKey) 468 | } 469 | 470 | console.log( 471 | `🔐 Account Generated as ${wallet.address} and set as mnemonic in packages/hardhat` 472 | ) 473 | console.log( 474 | "💬 Use 'yarn run account' to get more information about the deployment account." 475 | ) 476 | 477 | fs.writeFileSync(`./${wallet.address}.secret`, wallet.mnemonic.phrase) 478 | fs.writeFileSync('./mnemonic.secret', wallet.mnemonic.phrase) 479 | } 480 | ) 481 | 482 | task( 483 | 'mineContractAddress', 484 | 'Looks for a deployer account that will give leading zeros' 485 | ) 486 | .addOptionalParam('searchFor', 'String to search for') 487 | .addOptionalParam('startsWith', 'String to search for') 488 | .setAction(async (taskArgs, { ethers }) => { 489 | if (!taskArgs.searchFor && !taskArgs.startsWith) { 490 | console.error(chalk.red('No arguments set.')) 491 | return 492 | } 493 | 494 | let wallet: ReturnType 495 | let contractAddress = '' 496 | let attempt = 0 497 | let shouldRetry = true 498 | while (shouldRetry) { 499 | if (attempt > 0) { 500 | process.stdout.clearLine(0) 501 | process.stdout.cursorTo(0) 502 | } 503 | attempt++ 504 | process.stdout.write(`Mining attempt ${attempt}`) 505 | 506 | wallet = ethers.Wallet.createRandom() 507 | contractAddress = ethers.utils.getContractAddress({ 508 | from: wallet.address, 509 | nonce: 0, 510 | }) 511 | 512 | if (taskArgs.searchFor) { 513 | shouldRetry = contractAddress.indexOf(taskArgs.searchFor) != 0 514 | } else if (taskArgs.startsWith) { 515 | shouldRetry = 516 | !contractAddress 517 | .substr(2) 518 | .startsWith(taskArgs.startsWith.toLowerCase()) && 519 | !contractAddress 520 | .substr(2) 521 | .startsWith(taskArgs.startsWith.toUpperCase()) 522 | } 523 | } 524 | process.stdout.write('\n') 525 | 526 | if (DEBUG) { 527 | console.log('mnemonic', wallet!.mnemonic.phrase) 528 | console.log('fullPath', wallet!.mnemonic.path) 529 | console.log('privateKey', wallet!.privateKey) 530 | } 531 | 532 | console.log( 533 | `⛏ Account Mined as ${ 534 | wallet!.address 535 | } and set as mnemonic in packages/hardhat` 536 | ) 537 | console.log( 538 | `📜 This will create the first contract: ${chalk.magenta( 539 | contractAddress 540 | )}` 541 | ) 542 | console.log( 543 | "💬 Use 'yarn run account' to get more information about the deployment account." 544 | ) 545 | 546 | fs.writeFileSync( 547 | `./${wallet!.address}_produces${contractAddress}.secret`, 548 | wallet!.mnemonic.phrase 549 | ) 550 | fs.writeFileSync('./mnemonic.secret', wallet!.mnemonic.phrase) 551 | }) 552 | 553 | task( 554 | 'account', 555 | 'Get balance information for the deployment account.', 556 | async (_, { ethers, config }) => { 557 | try { 558 | const mnemonic = getMnemonic() 559 | const wallet = ethers.Wallet.fromMnemonic(mnemonic) 560 | 561 | if (DEBUG) { 562 | console.log('mnemonic', wallet.mnemonic.phrase) 563 | console.log('fullPath', wallet.mnemonic.path) 564 | console.log('privateKey', wallet.privateKey) 565 | } 566 | 567 | const qrcode = require('qrcode-terminal') 568 | qrcode.generate(wallet.address) 569 | console.log(`‍📬 Deployer Account is ${wallet.address}`) 570 | for (const networkName in config.networks) { 571 | const network = config.networks[networkName] 572 | if (!('url' in network)) continue 573 | try { 574 | const provider = new ethers.providers.JsonRpcProvider(network.url) 575 | const balance = await provider.getBalance(wallet.address) 576 | console.log(` -- ${chalk.bold(networkName)} -- -- -- 📡 `) 577 | console.log(` balance: ${ethers.utils.formatEther(balance)}`) 578 | console.log( 579 | ` nonce: ${await provider.getTransactionCount(wallet.address)}` 580 | ) 581 | console.log() 582 | } catch (e) { 583 | if (DEBUG) { 584 | console.log(e) 585 | } 586 | } 587 | } 588 | } catch (err) { 589 | console.log(`--- Looks like there is no mnemonic file created yet.`) 590 | console.log( 591 | `--- Please run ${chalk.greenBright('yarn generate')} to create one` 592 | ) 593 | } 594 | } 595 | ) 596 | 597 | /** 598 | * Get a checksumed address. 599 | * @param ethers {HardhatEthersHelpers} Ethers object from Hardhat. 600 | * @param addr {string | number} The address string to be checksumed or an index in the account's mnemonic. 601 | * @return Promise The checksumed address 602 | */ 603 | async function findFirstAddr( 604 | ethers: HardhatEthersHelpers, 605 | addr: string | number 606 | ): Promise { 607 | if (typeof addr === 'string' && isAddress(addr)) { 608 | return getAddress(addr) 609 | } else if (typeof addr === 'number') { 610 | const accounts = await ethers.provider.listAccounts() 611 | if (accounts[addr] !== undefined) { 612 | return getAddress(accounts[addr]) 613 | } 614 | } 615 | throw new Error(`Could not normalize address: ${addr}`) 616 | } 617 | 618 | task('accounts', 'Prints the list of accounts', async (_, { ethers }) => { 619 | const accounts = await ethers.provider.listAccounts() 620 | accounts.forEach((account) => console.log(account)) 621 | }) 622 | 623 | task('blockNumber', 'Prints the block number', async (_, { ethers }) => { 624 | const blockNumber = await ethers.provider.getBlockNumber() 625 | console.log(blockNumber) 626 | }) 627 | 628 | task('balance', "Prints an account's balance") 629 | .addPositionalParam( 630 | 'account', 631 | "The account's address or index in the mnemonic" 632 | ) 633 | .setAction(async (taskArgs, { ethers }) => { 634 | const balance = await ethers.provider.getBalance( 635 | await findFirstAddr(ethers, taskArgs.account) 636 | ) 637 | console.log(formatUnits(balance, 'ether'), 'ETH') 638 | }) 639 | 640 | async function send( 641 | signer: Signer, 642 | txparams: TransactionRequest 643 | ): Promise { 644 | const response = await signer.sendTransaction(txparams) 645 | debug(`transactionHash: ${response.hash}`) 646 | const waitBlocksForReceipt = 0 // 2 647 | 648 | return await response.wait(waitBlocksForReceipt) 649 | } 650 | 651 | task('send', 'Send ETH') 652 | .addParam('from', 'From address or account index') 653 | .addOptionalParam('to', 'To address or account index') 654 | .addOptionalParam('amount', 'Amount to send in ether') 655 | .addOptionalParam('data', 'Data included in transaction') 656 | .addOptionalParam('gasPrice', 'Price you are willing to pay in gwei') 657 | .addOptionalParam('gasLimit', 'Limit of how much gas to spend') 658 | .setAction(async (taskArgs, { network, ethers }) => { 659 | const from = await findFirstAddr(ethers, taskArgs.from) 660 | debug(`Normalized from address: ${from}`) 661 | const fromSigner = ethers.provider.getSigner(from) 662 | 663 | let to 664 | if (taskArgs.to) { 665 | to = await findFirstAddr(ethers, taskArgs.to) 666 | debug(`Normalized to address: ${to}`) 667 | } 668 | 669 | const txRequest: TransactionRequest = { 670 | from: await fromSigner.getAddress(), 671 | to, 672 | value: parseUnits( 673 | taskArgs.amount ? taskArgs.amount : '0', 674 | 'ether' 675 | ).toHexString(), 676 | nonce: await fromSigner.getTransactionCount(), 677 | gasPrice: parseUnits( 678 | taskArgs.gasPrice ? taskArgs.gasPrice : '1.001', 679 | 'gwei' 680 | ).toHexString(), 681 | gasLimit: taskArgs.gasLimit ? taskArgs.gasLimit : 24000, 682 | chainId: network.config.chainId, 683 | } 684 | 685 | if (taskArgs.data !== undefined) { 686 | txRequest.data = taskArgs.data 687 | debug(`Adding data to payload: ${txRequest.data}`) 688 | } 689 | // eslint-disable-next-line @typescript-eslint/no-base-to-string 690 | debug(formatUnits(txRequest.gasPrice!.toString(), 'gwei')) 691 | debug(JSON.stringify(txRequest, null, 2)) 692 | 693 | return await send(fromSigner, txRequest) 694 | }) 695 | -------------------------------------------------------------------------------- /helpers/chai-helpers.ts: -------------------------------------------------------------------------------- 1 | import chai from 'chai' 2 | import { BigNumber as BN } from 'ethers' 3 | 4 | chai.Assertion.overwriteMethod( 5 | 'eql', 6 | (_super) => 7 | function (this: any, ...args: any[]) { 8 | const obj = chai.util.flag(this, 'object') 9 | if (BN.isBigNumber(obj) || BN.isBigNumber(args[0])) { 10 | new chai.Assertion(obj.toString()).to.eql( 11 | args[0].toString(), 12 | ...args.slice(1) 13 | ) 14 | } else { 15 | _super.apply(this, args) 16 | } 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /helpers/consts.ts: -------------------------------------------------------------------------------- 1 | export const ADDRESSES = { 2 | NULL: '0x0000000000000000000000000000000000000000', 3 | DUMMY: '0x0000000000000000000000000000000000000001', 4 | mainnet: { 5 | UNISWAP_ROUTER_V2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 6 | }, 7 | kovan: { 8 | UNISWAP_ROUTER_V2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 9 | }, 10 | rinkeby: { 11 | UNISWAP_ROUTER_V2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 12 | }, 13 | ropsten: { 14 | UNISWAP_ROUTER_V2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 15 | }, 16 | polygon: { 17 | SUSHISWAP_ROUTER_V2: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 18 | }, 19 | polygon_mumbai: { 20 | SUSHISWAP_ROUTER_V2: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /helpers/deploy-helpers.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { makeNodeDisklet } from 'disklet' 3 | import { Contract } from 'ethers' 4 | import { DeployOptions, DeployResult, Libraries } from 'hardhat-deploy/types' 5 | import { HardhatRuntimeEnvironment } from 'hardhat/types' 6 | 7 | interface CommonDeployArgs extends Omit { 8 | hre: HardhatRuntimeEnvironment 9 | name?: string 10 | libraries?: Libraries 11 | log?: boolean 12 | indent?: number 13 | } 14 | 15 | export interface DeployArgs extends CommonDeployArgs { 16 | contract: string 17 | args?: any[] 18 | skipIfAlreadyDeployed?: boolean 19 | mock?: boolean 20 | } 21 | 22 | type DeployedContract = C & { deployResult: DeployResult } 23 | 24 | export const deploy = async ( 25 | args: DeployArgs 26 | ): Promise> => { 27 | const { hre, skipIfAlreadyDeployed = true, indent = 1 } = args 28 | const { 29 | deployments: { deploy, getOrNull }, 30 | getNamedAccounts, 31 | } = hre 32 | 33 | const { deployer } = await getNamedAccounts() 34 | 35 | // If marked as mock, prepend "Mock" to the contract name 36 | const contractName = `${args.contract}${args.mock ? 'Mock' : ''}` 37 | const contractDeployName = args.name ?? args.contract 38 | 39 | const existingContract = await getOrNull(contractDeployName) 40 | let contractAddress: string 41 | let result: DeployResult 42 | 43 | if (!existingContract || (existingContract && !skipIfAlreadyDeployed)) { 44 | result = await deploy(contractDeployName, { 45 | ...args, 46 | contract: contractName, 47 | from: deployer, 48 | }) 49 | contractAddress = result.address 50 | } else { 51 | result = { ...existingContract, newlyDeployed: false } 52 | contractAddress = existingContract.address 53 | } 54 | 55 | await onDeployResult({ 56 | result, 57 | contract: contractName, 58 | name: contractDeployName, 59 | hre, 60 | indent, 61 | }) 62 | 63 | const contract = (await hre.contracts.get(contractDeployName, { 64 | at: contractAddress, 65 | })) as DeployedContract 66 | contract.deployResult = result 67 | return contract 68 | } 69 | 70 | 71 | 72 | 73 | interface DeployResultArgs { 74 | result: DeployResult 75 | hre: HardhatRuntimeEnvironment 76 | contract: string 77 | name: string 78 | indent?: number 79 | } 80 | 81 | const onDeployResult = async (args: DeployResultArgs): Promise => { 82 | const { result, hre, contract, name, indent = 1 } = args 83 | 84 | let displayName = name 85 | if (contract !== name) { 86 | displayName = `${displayName} (${chalk.bold.italic(contract)})` 87 | } 88 | 89 | 90 | hre.log(`${displayName}:`, { 91 | indent, 92 | star: true, 93 | nl: false, 94 | }) 95 | 96 | if (result.newlyDeployed) { 97 | const gas = chalk.cyan(`${result.receipt!.gasUsed} gas`) 98 | hre.log( 99 | ` ${chalk.green('new')} ${result.address} ${ 100 | result.receipt ? 'with ' + gas : '' 101 | }` 102 | ) 103 | 104 | await saveDeploymentBlock(hre.network.name, result.receipt!.blockNumber) 105 | } else { 106 | hre.log(` ${chalk.yellow('reusing')} ${result.address}`) 107 | } 108 | } 109 | 110 | const saveDeploymentBlock = async ( 111 | networkName: string, 112 | block: number 113 | ): Promise => { 114 | if (networkName === 'hardhat') return 115 | 116 | const disklet = makeNodeDisklet('.') 117 | 118 | const deploymentBlockPath = `deployments/${networkName}/.latestDeploymentBlock` 119 | const lastDeployment = await disklet 120 | .getText(deploymentBlockPath) 121 | .catch(() => {}) 122 | if (!lastDeployment || block > parseInt(lastDeployment)) { 123 | await disklet.setText(deploymentBlockPath, block.toString()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /helpers/formatMsg.ts: -------------------------------------------------------------------------------- 1 | export interface FormatMsgConfig { 2 | indent?: number 3 | star?: boolean 4 | nl?: boolean 5 | } 6 | export const formatMsg = ( 7 | msg: string, 8 | config: FormatMsgConfig = {} 9 | ): string => { 10 | const { indent = 0, star, nl = true } = config 11 | 12 | let result = '' 13 | 14 | result += msg 15 | .split('\n') 16 | .map((m) => { 17 | let r = m 18 | if (star) r = `* ${m}` 19 | r = ' '.repeat(indent) + r 20 | return r 21 | }) 22 | .join('\n') 23 | if (nl) result += '\n' 24 | 25 | return result 26 | } 27 | -------------------------------------------------------------------------------- /helpers/get-funds.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BigNumberish, Signer } from 'ethers' 2 | import { IUniswapV2Router, IWETH } from 'generated/typechain' 3 | import { HardhatRuntimeEnvironment } from 'hardhat/types' 4 | import { ADDRESSES } from '../helpers/consts' 5 | import { Address, TokenSymbol } from '../helpers/types' 6 | 7 | import { getTokens, isEthereumNetwork } from '../config' 8 | 9 | export interface SwapArgs { 10 | to: Address | Signer 11 | tokenSym: TokenSymbol 12 | amount: BigNumberish 13 | hre: HardhatRuntimeEnvironment 14 | } 15 | 16 | export const getFunds = async (args: SwapArgs): Promise => { 17 | const { getNamedSigner, ethers, contracts, network } = args.hre 18 | 19 | const forkingNetworkName = process.env.FORKING_NETWORK 20 | const funder = await getNamedSigner('funder') 21 | //const { all: tokenAddresses } = getTokens(args.hre.network) 22 | 23 | let routerAddress: string 24 | 25 | const toAddress = Signer.isSigner(args.to) 26 | ? await args.to.getAddress() 27 | : args.to 28 | 29 | if (args.tokenSym === 'ETH' || args.tokenSym === 'MATIC') { 30 | await funder.sendTransaction({ 31 | to: toAddress, 32 | value: args.amount, 33 | }) 34 | } 35 | } 36 | 37 | export interface SendNativeTokenArgs { 38 | tokenAddress: string 39 | amount: BigNumberish 40 | toAddress: string 41 | hre: HardhatRuntimeEnvironment 42 | } 43 | 44 | const sendWrappedNativeToken = async ( 45 | args: SendNativeTokenArgs 46 | ): Promise => { 47 | const funder = await args.hre.getNamedSigner('funder') 48 | const weth = await args.hre.contracts.get('IWETH', { 49 | at: args.tokenAddress, 50 | from: funder, 51 | }) 52 | const balanceToSend = args.hre.ethers.BigNumber.from(args.amount).mul(2) 53 | await weth.deposit({ value: balanceToSend, from: funder.getAddress() }) 54 | await weth.transfer(args.toAddress, args.amount, { 55 | from: funder.getAddress(), 56 | }) 57 | return await weth.balanceOf(args.toAddress) 58 | } 59 | -------------------------------------------------------------------------------- /helpers/hre-extensions.ts: -------------------------------------------------------------------------------- 1 | import '@nomiclabs/hardhat-ethers' 2 | import 'hardhat-deploy' 3 | 4 | import { BigNumber, BigNumberish, Contract, Signer } from 'ethers' 5 | import { IERC20 } from '../generated/typechain' 6 | import { extendEnvironment } from 'hardhat/config' 7 | import { HardhatRuntimeEnvironment } from 'hardhat/types' 8 | import moment from 'moment' 9 | 10 | import { getTokens } from '../config' 11 | 12 | import { formatMsg, FormatMsgConfig } from './formatMsg' 13 | 14 | declare module 'hardhat/types/runtime' { 15 | interface HardhatRuntimeEnvironment { 16 | contracts: ContractsExtension 17 | 18 | evm: EVM 19 | getNamedSigner: (name: string) => Promise 20 | toBN: (amount: BigNumberish, decimals?: BigNumberish) => BigNumber 21 | fromBN: (amount: BigNumberish, decimals?: BigNumberish) => BigNumber 22 | log: (msg: string, config?: LogConfig) => void 23 | } 24 | } 25 | 26 | interface LogConfig extends FormatMsgConfig { 27 | disable?: boolean 28 | error?: boolean 29 | } 30 | 31 | interface ContractsExtension { 32 | get: ( 33 | name: string, 34 | config?: ContractsGetConfig 35 | ) => Promise 36 | } 37 | 38 | 39 | interface AdvanceTimeOptions { 40 | /** 41 | * When set to `true`, it ensures that a block is not mined for seconds/block 42 | * internal. 43 | * 44 | * In order for the timestamp to take effect a block must be mined. This means 45 | * that if a view function is called, it will not know about the time update. 46 | * Use the `mine` option to mine a block after updating the timestamp if used 47 | * in conjunction with a view function on the next call. 48 | */ 49 | withoutBlocks?: boolean 50 | 51 | /** 52 | * Ensures that a block is mined after advancing the next blocks timestamp. 53 | * 54 | * This option should be used in conjunction with the `withoutBlocks` option 55 | * when calling a view function to run any necessary checks. 56 | */ 57 | mine?: boolean 58 | } 59 | 60 | interface EVM { 61 | /** 62 | * This sets the timestamp of the next block that executes 63 | * @param timestamp {moment.Moment} The Moment object that represents a timestamp. 64 | */ 65 | setNextBlockTimestamp: (timestamp: moment.Moment) => Promise 66 | 67 | /** 68 | * This increases the next block's timestamp by the specified amount of seconds. 69 | * @param seconds {BigNumberish | moment.Duration} Amount of seconds to increase the next block's timestamp by. 70 | */ 71 | advanceTime: ( 72 | seconds: BigNumberish | moment.Duration, 73 | option?: AdvanceTimeOptions 74 | ) => Promise 75 | 76 | /** 77 | * Will mine the specified number of blocks locally. This is helpful when functionality 78 | * requires a certain number of blocks to be processed for values to change. 79 | * @param blocks {number} Amount of blocks to mine. 80 | * @param secsPerBlock {number} Determines how many seconds to increase time by for 81 | * each block that is mined. Default is 15. 82 | */ 83 | advanceBlocks: (blocks?: number, secsPerBlock?: number) => Promise 84 | 85 | /** 86 | * Mines a new block. 87 | */ 88 | mine: () => Promise 89 | 90 | /** 91 | * Creates a snapshot of the blockchain in its current state. It then returns a function 92 | * that can be used to revert back to the state at which the snapshot was taken. 93 | */ 94 | snapshot: () => Promise<() => Promise> 95 | 96 | /** 97 | * This allows for functionality to executed within the scope of the specified number 98 | * of additional blocks to be mined. Once the supplied function to be called within 99 | * the scope is executed, the blockchain is reverted back to the state it started at. 100 | * @param blocks {number} The number of blocks that should be mined. 101 | * @param fn {function} A function that should be executed once blocks have been mined. 102 | */ 103 | withBlockScope: (blocks: number, fn: () => T) => Promise 104 | 105 | /** 106 | * Impersonates a supplied address. This allows for the execution of transactions 107 | * with the `from` field to be the supplied address. This allows for the context 108 | * of `msg.sender` in a transaction to also be the supplied address. 109 | * 110 | * Once you have completed the action of impersonating an address, you may wish to 111 | * stop impersonating it. To do this, a `stop` function is also returned for 112 | * convenience. This may also be achieved by calling `evm.stopImpersonating`. 113 | * 114 | * To do this: 115 | * 1. Impersonate an address. 116 | * 2. Execute a transaction on a contract by connecting the returned signer. 117 | * Ex: 118 | * const impersonation = await evm.impersonate(0x123) 119 | * await contract.connect(impersonation.signer).functionToCall() 120 | * @param address {string} An address to start impersonating. 121 | * @return {Promise} 122 | */ 123 | impersonate: (address: string) => Promise 124 | 125 | /** 126 | * It stops the ability to impersonate an address on the local blockchain. 127 | * @param address {string} An address to stop impersonating. 128 | */ 129 | stopImpersonating: (address: string) => Promise 130 | } 131 | 132 | interface ImpersonateReturn { 133 | signer: Signer 134 | stop: () => Promise 135 | } 136 | 137 | interface ContractsGetConfig { 138 | from?: string | Signer 139 | at?: string 140 | } 141 | 142 | /** 143 | * Updates the Etherscan API key in the config based on the network being 144 | * used. 145 | * @param hre {HardhatRuntimeEnvironment} Hardhat Environment variable to modify 146 | * directly 147 | */ 148 | const updateEtherscanConfig = (hre: HardhatRuntimeEnvironment): void => { 149 | let apiKey: string | undefined 150 | switch (hre.network.name) { 151 | case 'polygon': 152 | case 'matic': 153 | case 'mumbai': 154 | apiKey = process.env.POLYGONSCAN_API_KEY 155 | break 156 | 157 | default: 158 | apiKey = process.env.ETHERSCAN_API_KEY 159 | } 160 | 161 | hre.config.etherscan.apiKey = apiKey 162 | } 163 | 164 | extendEnvironment((hre) => { 165 | const { deployments, ethers, network } = hre 166 | 167 | updateEtherscanConfig(hre) 168 | 169 | hre.contracts = { 170 | async get( 171 | name: string, 172 | config?: ContractsGetConfig 173 | ): Promise { 174 | const { abi, address } = await deployments 175 | .get(name) 176 | .catch(async () => 177 | hre.network.name === 'localhost' 178 | ? await hre.artifacts.readArtifact(name) 179 | : await deployments.getArtifact(name) 180 | ) 181 | .then((artifact) => ({ 182 | abi: artifact.abi, 183 | address: 184 | config?.at ?? ('address' in artifact ? artifact.address : null), 185 | })) 186 | 187 | if (address == null) 188 | throw new Error( 189 | `No deployment exists for ${name}. If expected, supply an address (config.at)` 190 | ) 191 | 192 | let contract = await ethers.getContractAt(abi, address) 193 | 194 | if (config?.from) { 195 | const signer = Signer.isSigner(config.from) 196 | ? config.from 197 | : ethers.provider.getSigner(config.from) 198 | contract = contract.connect(signer) 199 | } 200 | 201 | return contract as C 202 | }, 203 | } 204 | 205 | 206 | hre.getNamedSigner = async (name: string): Promise => { 207 | const accounts = await hre.getNamedAccounts() 208 | return ethers.provider.getSigner(accounts[name]) 209 | } 210 | 211 | hre.evm = { 212 | async setNextBlockTimestamp(timestamp: moment.Moment): Promise { 213 | await network.provider.send('evm_setNextBlockTimestamp', [ 214 | timestamp.unix(), 215 | ]) 216 | }, 217 | 218 | async advanceTime( 219 | seconds: BigNumberish | moment.Duration, 220 | options?: AdvanceTimeOptions 221 | ): Promise { 222 | const secs = moment.isDuration(seconds) 223 | ? seconds 224 | : moment.duration(BigNumber.from(seconds).toString(), 's') 225 | if (options?.withoutBlocks) { 226 | const block = await ethers.provider.getBlock('latest') 227 | const timestamp = moment( 228 | secs.add(block.timestamp, 's').asMilliseconds() 229 | ) 230 | await this.setNextBlockTimestamp(timestamp) 231 | if (options?.mine) await this.mine() 232 | } else { 233 | const secsPerBlock = 15 234 | const blocks = BigNumber.from(secs.asSeconds()) 235 | .div(secsPerBlock) 236 | .toNumber() 237 | await this.advanceBlocks(blocks, secsPerBlock) 238 | } 239 | }, 240 | 241 | async advanceBlocks(blocks = 1, secsPerBlock = 15): Promise { 242 | for (let block = 0; block < blocks; block++) { 243 | await network.provider.send('evm_increaseTime', [secsPerBlock]) 244 | await this.mine() 245 | } 246 | }, 247 | 248 | async mine(): Promise { 249 | await network.provider.send('evm_mine') 250 | }, 251 | 252 | async snapshot(): Promise<() => Promise> { 253 | const id = await network.provider.send('evm_snapshot') 254 | return async () => { 255 | await network.provider.send('evm_revert', [id]) 256 | } 257 | }, 258 | 259 | async withBlockScope(blocks: number, fn: () => any): Promise { 260 | const revert = await this.snapshot() 261 | await this.advanceBlocks(blocks) 262 | const result = await fn() 263 | await revert() 264 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 265 | return result 266 | }, 267 | 268 | async impersonate(address: string): Promise { 269 | await network.provider.request({ 270 | method: 'hardhat_impersonateAccount', 271 | params: [address], 272 | }) 273 | const signer = ethers.provider.getSigner(address) 274 | return { 275 | signer, 276 | stop: async () => await this.stopImpersonating(address), 277 | } 278 | }, 279 | 280 | async stopImpersonating(address: string): Promise { 281 | await network.provider.request({ 282 | method: 'hardhat_stopImpersonatingAccount', 283 | params: [address], 284 | }) 285 | }, 286 | } 287 | 288 | hre.toBN = (amount: BigNumberish, decimals?: BigNumberish): BigNumber => { 289 | if (typeof amount === 'string') { 290 | return ethers.utils.parseUnits(amount, decimals) 291 | } 292 | 293 | const num = BigNumber.from(amount) 294 | if (decimals) { 295 | return num.mul(BigNumber.from('10').pow(decimals)) 296 | } 297 | return num 298 | } 299 | 300 | hre.fromBN = (amount: BigNumberish, decimals?: BigNumberish): BigNumber => { 301 | const num = BigNumber.from(amount) 302 | if (decimals) { 303 | return num.div(BigNumber.from('10').pow(decimals)) 304 | } 305 | return num 306 | } 307 | 308 | hre.log = (msg: string, config: LogConfig = {}): void => { 309 | const { disable = process.env.DISABLE_LOGS === 'true' } = config 310 | 311 | if (disable) return 312 | const fn = config?.error ? process.stderr : process.stdout 313 | fn.write(formatMsg(msg, config)) 314 | } 315 | }) 316 | -------------------------------------------------------------------------------- /helpers/oz-contract-helpers.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber as BN } from 'ethers' 2 | import { ethers } from 'hardhat' 3 | 4 | export const isInitialized = async (address: string): Promise => { 5 | const storage = await ethers.provider.getStorageAt(address, 0) 6 | return !BN.from(storage).isZero() 7 | } 8 | -------------------------------------------------------------------------------- /helpers/rrequire.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import glob from 'glob' 4 | 5 | const rrequire = (dir: string): void => { 6 | glob.sync(path.resolve(dir, '**', '*.ts')).forEach((file) => { 7 | require(path.resolve(file)) 8 | }) 9 | } 10 | 11 | export default rrequire 12 | -------------------------------------------------------------------------------- /helpers/tasks/test.ts: -------------------------------------------------------------------------------- 1 | import { TASK_TEST_SOLIDITY } from '@mangrovedao/hardhat-test-solidity' 2 | import { TASK_TEST } from 'hardhat/builtin-tasks/task-names' 3 | import { task } from 'hardhat/config' 4 | 5 | task(TASK_TEST) 6 | .addFlag('onlySol', 'Only run tests written in Solidity') 7 | .addFlag('onlyTs', 'Only run tests written in TypeScript') 8 | .setAction(async (args, hre, runSuper) => { 9 | const { onlySol, onlyTs, ...restArgs } = args 10 | const runAll = !onlySol && !onlyTs 11 | 12 | // Disable logging 13 | process.env.DISABLE_LOGS = 'true' 14 | 15 | if (runAll || onlySol) 16 | await hre.run(TASK_TEST_SOLIDITY, { 17 | ...restArgs, 18 | contracts: restArgs.testFiles, 19 | }) 20 | if (runAll || onlyTs) await runSuper(restArgs) 21 | }) 22 | -------------------------------------------------------------------------------- /helpers/types.d.ts: -------------------------------------------------------------------------------- 1 | type Address = string 2 | type TokenSymbol = string 3 | 4 | export interface NetworkTokens { 5 | [protocol: string]: Tokens | undefined 6 | erc20: Tokens 7 | compound?: Tokens 8 | aave?: Tokens 9 | poolTogether?: Tokens 10 | yearn?: Tokens 11 | } 12 | 13 | export interface AllNetworkTokens extends NetworkTokens { 14 | all: Tokens 15 | } 16 | 17 | export interface Tokens { 18 | [tokenSymbol: string]: Address 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "0xbitcointoken", 3 | "version": "1.10.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "disklet": "^0.5.0", 8 | "eslint": "^7.5.0", 9 | "eslint-config-airbnb": "^18.2.0", 10 | "eslint-config-prettier": "^6.11.0", 11 | "eslint-plugin-babel": "^5.3.1", 12 | "eslint-plugin-prettier": "^3.4.0", 13 | "hardhat-contract-sizer": "^2.0.3", 14 | "hardhat-gas-reporter": "^1.0.4" 15 | }, 16 | "dependencies": { 17 | "@eth-optimism/hardhat-ovm": "^0.2.2", 18 | "@nomiclabs/hardhat-ethers": "^2.0.5", 19 | "@nomiclabs/hardhat-etherscan": "^2.1.5", 20 | "@nomiclabs/hardhat-waffle": "^2.0.0", 21 | "@openzeppelin/contracts": "^4.3.0", 22 | "@openzeppelin/contracts-upgradeable": "^4.3.0", 23 | "@tenderly/hardhat-tenderly": "^1.0.10", 24 | "@typechain/ethers-v5": "^7.1.0", 25 | "@typechain/hardhat": "^6.0.0", 26 | "@types/chai": "^4.2.14", 27 | "@types/chai-as-promised": "^7.1.3", 28 | "@types/dotenv": "^8.2.0", 29 | "@types/glob": "^7.2.0", 30 | "@types/mocha": "^9.0.0", 31 | "@types/semver": "^7.3.9", 32 | "chai": "^4.2.0", 33 | "chai-as-promised": "^7.1.1", 34 | "dotenv": "^16.0.1", 35 | "eslint": "^7.14.0", 36 | "eslint-config-standard-kit": "0.15.1", 37 | "eslint-plugin-import": "^2.22.1", 38 | "eslint-plugin-node": "^11.1.0", 39 | "eslint-plugin-prettier": "^3.1.4", 40 | "eslint-plugin-promise": "^4.2.1", 41 | "eslint-plugin-simple-import-sort": "^6.0.1", 42 | "ethereum-waffle": "^3.1.1", 43 | "ethereumjs-util": "^7.1.5", 44 | "ethers": "^5.4.4", 45 | "hardhat": "2.6.0", 46 | "hardhat-deploy": "0.9.3", 47 | "@mangrovedao/hardhat-test-solidity": "^0.0.16", 48 | "mocha": "^7.1.0", 49 | "moment": "^2.29.1", 50 | "node-watch": "^0.7.0", 51 | "payspec-js": "^0.16.0", 52 | "ramda": "^0.27.1", 53 | "ts-node": "^10.3.0", 54 | "typechain": "^5.2.0", 55 | "typescript": "^4.1.3" 56 | }, 57 | "scripts": { 58 | "test": "DISABLE_LOGS=true TS_NODE_TRANSPILE_ONLY=1 TESTING=1 yarn hardhat test", 59 | "coverage": "DISABLE_LOGS=true FORKING_NETWORK=mainnet TS_NODE_TRANSPILE_ONLY=1 TESTING=1 yarn hh coverage", 60 | "fork:mainnet": "FORKING_NETWORK=mainnet TS_NODE_TRANSPILE_ONLY=1 yarn hh fork --network hardhat", 61 | "shell": "yarn solidity-shell", 62 | "chain": "yarn hh node --no-deploy", 63 | "compile": "COMPILING=true yarn hardhat compile", 64 | "build": "yarn compile --force && yarn tsc", 65 | "deploy": "yarn hardhat deploy", 66 | "export": "yarn hardhat export:deployments", 67 | "accounts": "yarn hardhat accounts", 68 | "balance": "yarn hardhat balance", 69 | "send": "yarn hardhat send", 70 | "generate": "yarn hardhat generate", 71 | "account": "yarn hardhat account", 72 | "verify": "yarn hardhat etherscan-verify" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "warn" */ 2 | const fs = require("fs"); 3 | const chalk = require("chalk"); 4 | const { config, ethers, tenderly, run } = require("hardhat"); 5 | const { utils } = require("ethers"); 6 | const R = require("ramda"); 7 | 8 | /* 9 | 10 | _______ _________ _______ _______ 11 | ( ____ \\__ __/( ___ )( ____ ) 12 | | ( \/ ) ( | ( ) || ( )| 13 | | (_____ | | | | | || (____)| 14 | (_____ ) | | | | | || _____) 15 | ) | | | | | | || ( 16 | /\____) | | | | (___) || ) 17 | \_______) )_( (_______)|/ 18 | 19 | This deploy script is no longer in use, but is left for reference purposes! 20 | 21 | scaffold-eth now uses hardhat-deploy to manage deployments, see the /deploy folder 22 | And learn more here: https://www.npmjs.com/package/hardhat-deploy 23 | 24 | */ 25 | 26 | const main = async () => { 27 | console.log("\n\n 📡 Deploying...\n"); 28 | 29 | const yourContract = await deploy("YourContract"); // <-- add in constructor args like line 19 vvvv 30 | // use for local token bridging 31 | // const mockToken = await deploy("MockERC20") // <-- add in constructor args like line 19 vvvv 32 | 33 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 34 | //const secondContract = await deploy("SecondContract") 35 | 36 | // const exampleToken = await deploy("ExampleToken") 37 | // const examplePriceOracle = await deploy("ExamplePriceOracle") 38 | // const smartContractWallet = await deploy("SmartContractWallet",[exampleToken.address,examplePriceOracle.address]) 39 | 40 | /* 41 | //If you want to send value to an address from the deployer 42 | const deployerWallet = ethers.provider.getSigner() 43 | await deployerWallet.sendTransaction({ 44 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 45 | value: ethers.utils.parseEther("0.001") 46 | }) 47 | */ 48 | 49 | /* 50 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 51 | const yourContract = await deploy("YourContract", [], { 52 | value: ethers.utils.parseEther("0.05") 53 | }); 54 | */ 55 | 56 | /* 57 | //If you want to link a library into your contract: 58 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 59 | const yourContract = await deploy("YourContract", [], {}, { 60 | LibraryName: **LibraryAddress** 61 | }); 62 | */ 63 | 64 | //If you want to verify your contract on tenderly.co (see setup details in the scaffold-eth README!) 65 | /* 66 | await tenderlyVerify( 67 | {contractName: "YourContract", 68 | contractAddress: yourContract.address 69 | }) 70 | */ 71 | 72 | console.log( 73 | " 💾 Artifacts (address, abi, and args) saved to: ", 74 | chalk.blue("packages/hardhat/artifacts/"), 75 | "\n\n" 76 | ); 77 | }; 78 | 79 | const deploy = async ( 80 | contractName, 81 | _args = [], 82 | overrides = {}, 83 | libraries = {} 84 | ) => { 85 | console.log(` 🛰 Deploying: ${contractName}`); 86 | 87 | const contractArgs = _args || []; 88 | const contractArtifacts = await ethers.getContractFactory(contractName, { 89 | libraries: libraries, 90 | }); 91 | const deployed = await contractArtifacts.deploy(...contractArgs, overrides); 92 | const encoded = abiEncodeArgs(deployed, contractArgs); 93 | fs.writeFileSync(`artifacts/${contractName}.address`, deployed.address); 94 | 95 | let extraGasInfo = ""; 96 | if (deployed && deployed.deployTransaction) { 97 | const gasUsed = deployed.deployTransaction.gasLimit.mul( 98 | deployed.deployTransaction.gasPrice 99 | ); 100 | extraGasInfo = `${utils.formatEther(gasUsed)} ETH, tx hash ${ 101 | deployed.deployTransaction.hash 102 | }`; 103 | } 104 | 105 | console.log( 106 | " 📄", 107 | chalk.cyan(contractName), 108 | "deployed to:", 109 | chalk.magenta(deployed.address) 110 | ); 111 | console.log(" ⛽", chalk.grey(extraGasInfo)); 112 | 113 | await tenderly.persistArtifacts({ 114 | name: contractName, 115 | address: deployed.address, 116 | }); 117 | 118 | if (!encoded || encoded.length <= 2) return deployed; 119 | fs.writeFileSync(`artifacts/${contractName}.args`, encoded.slice(2)); 120 | 121 | return deployed; 122 | }; 123 | 124 | // ------ utils ------- 125 | 126 | // abi encodes contract arguments 127 | // useful when you want to manually verify the contracts 128 | // for example, on Etherscan 129 | const abiEncodeArgs = (deployed, contractArgs) => { 130 | // not writing abi encoded args if this does not pass 131 | if ( 132 | !contractArgs || 133 | !deployed || 134 | !R.hasPath(["interface", "deploy"], deployed) 135 | ) { 136 | return ""; 137 | } 138 | const encoded = utils.defaultAbiCoder.encode( 139 | deployed.interface.deploy.inputs, 140 | contractArgs 141 | ); 142 | return encoded; 143 | }; 144 | 145 | // checks if it is a Solidity file 146 | const isSolidity = (fileName) => 147 | fileName.indexOf(".sol") >= 0 && 148 | fileName.indexOf(".swp") < 0 && 149 | fileName.indexOf(".swap") < 0; 150 | 151 | const readArgsFile = (contractName) => { 152 | let args = []; 153 | try { 154 | const argsFile = `./contracts/${contractName}.args`; 155 | if (!fs.existsSync(argsFile)) return args; 156 | args = JSON.parse(fs.readFileSync(argsFile)); 157 | } catch (e) { 158 | console.log(e); 159 | } 160 | return args; 161 | }; 162 | 163 | function sleep(ms) { 164 | return new Promise((resolve) => setTimeout(resolve, ms)); 165 | } 166 | 167 | // If you want to verify on https://tenderly.co/ 168 | const tenderlyVerify = async ({ contractName, contractAddress }) => { 169 | let tenderlyNetworks = [ 170 | "kovan", 171 | "goerli", 172 | "mainnet", 173 | "rinkeby", 174 | "ropsten", 175 | "matic", 176 | "mumbai", 177 | "xDai", 178 | "POA", 179 | ]; 180 | let targetNetwork = process.env.HARDHAT_NETWORK || config.defaultNetwork; 181 | 182 | if (tenderlyNetworks.includes(targetNetwork)) { 183 | console.log( 184 | chalk.blue( 185 | ` 📁 Attempting tenderly verification of ${contractName} on ${targetNetwork}` 186 | ) 187 | ); 188 | 189 | await tenderly.persistArtifacts({ 190 | name: contractName, 191 | address: contractAddress, 192 | }); 193 | 194 | let verification = await tenderly.verify({ 195 | name: contractName, 196 | address: contractAddress, 197 | network: targetNetwork, 198 | }); 199 | 200 | return verification; 201 | } else { 202 | console.log( 203 | chalk.grey(` 🧐 Contract verification not supported on ${targetNetwork}`) 204 | ); 205 | } 206 | }; 207 | 208 | main() 209 | .then(() => process.exit(0)) 210 | .catch((error) => { 211 | console.error(error); 212 | process.exit(1); 213 | }); 214 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | 4 | const graphDir = "../subgraph"; 5 | const deploymentsDir = "./deployments"; 6 | const publishDir = "../react-app/src/contracts"; 7 | 8 | function publishContract(contractName, networkName) { 9 | try { 10 | let contract = fs 11 | .readFileSync(`${deploymentsDir}/${networkName}/${contractName}.json`) 12 | .toString(); 13 | contract = JSON.parse(contract); 14 | const graphConfigPath = `${graphDir}/config/config.json`; 15 | let graphConfig; 16 | try { 17 | if (fs.existsSync(graphConfigPath)) { 18 | graphConfig = fs.readFileSync(graphConfigPath).toString(); 19 | } else { 20 | graphConfig = "{}"; 21 | } 22 | } catch (e) { 23 | console.log(e); 24 | } 25 | 26 | graphConfig = JSON.parse(graphConfig); 27 | graphConfig[`${networkName}_${contractName}Address`] = contract.address; 28 | 29 | const folderPath = graphConfigPath.replace("/config.json", ""); 30 | if (!fs.existsSync(folderPath)) { 31 | fs.mkdirSync(folderPath); 32 | } 33 | fs.writeFileSync(graphConfigPath, JSON.stringify(graphConfig, null, 2)); 34 | if (!fs.existsSync(`${graphDir}/abis`)) fs.mkdirSync(`${graphDir}/abis`); 35 | fs.writeFileSync( 36 | `${graphDir}/abis/${networkName}_${contractName}.json`, 37 | JSON.stringify(contract.abi, null, 2) 38 | ); 39 | 40 | //Hardhat Deploy writes a file with all ABIs in react-app/src/contracts/contracts.json 41 | //If you need the bytecodes and/or you want one file per ABIs, un-comment the following block. 42 | //Write the contracts ABI, address and bytecodes in case the front-end needs them 43 | // fs.writeFileSync( 44 | // `${publishDir}/${contractName}.address.js`, 45 | // `module.exports = "${contract.address}";` 46 | // ); 47 | // fs.writeFileSync( 48 | // `${publishDir}/${contractName}.abi.js`, 49 | // `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 50 | // ); 51 | // fs.writeFileSync( 52 | // `${publishDir}/${contractName}.bytecode.js`, 53 | // `module.exports = "${contract.bytecode}";` 54 | // ); 55 | 56 | return true; 57 | } catch (e) { 58 | console.log( 59 | "Failed to publish " + chalk.red(contractName) + " to the subgraph." 60 | ); 61 | console.log(e); 62 | return false; 63 | } 64 | } 65 | 66 | async function main() { 67 | const directories = fs.readdirSync(deploymentsDir); 68 | directories.forEach(function (directory) { 69 | const files = fs.readdirSync(`${deploymentsDir}/${directory}`); 70 | files.forEach(function (file) { 71 | if (file.indexOf(".json") >= 0) { 72 | const contractName = file.replace(".json", ""); 73 | publishContract(contractName, directory); 74 | } 75 | }); 76 | }); 77 | console.log("✅ Published contracts to the subgraph package."); 78 | } 79 | main() 80 | .then(() => process.exit(0)) 81 | .catch((error) => { 82 | console.error(error); 83 | process.exit(1); 84 | }); 85 | -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("🛠 Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /tasks.ts: -------------------------------------------------------------------------------- 1 | import '@nomiclabs/hardhat-ethers' 2 | 3 | import * as fs from 'fs' 4 | import * as chalk from 'chalk' 5 | import { task } from 'hardhat/config' 6 | import { BigNumber as BN, Signer } from 'ethers' 7 | 8 | const DEBUG = false 9 | 10 | function debug(text: string) { 11 | if (DEBUG) { 12 | console.log(text) 13 | } 14 | } 15 | 16 | task("wallet", "Create a wallet (pk) link", async (_, { ethers }) => { 17 | const randomWallet = ethers.Wallet.createRandom() 18 | const privateKey = randomWallet._signingKey().privateKey 19 | console.log("🔐 WALLET Generated as " + randomWallet.address + "") 20 | console.log("🔗 http://localhost:3000/pk#" + privateKey) 21 | }) 22 | 23 | task("fundedwallet", "Create a wallet (pk) link and fund it with deployer?") 24 | .addOptionalParam( 25 | "amount", 26 | "Amount of ETH to send to wallet after generating" 27 | ) 28 | .addOptionalParam("url", "URL to add pk to") 29 | .setAction(async (taskArgs, { network, ethers }) => { 30 | const randomWallet = ethers.Wallet.createRandom() 31 | const privateKey = randomWallet._signingKey().privateKey 32 | console.log("🔐 WALLET Generated as " + randomWallet.address + "") 33 | let url = taskArgs.url ? taskArgs.url : "http://localhost:3000" 34 | 35 | let localDeployerMnemonic 36 | try { 37 | localDeployerMnemonic = fs.readFileSync("./mnemonic.txt") 38 | localDeployerMnemonic = localDeployerMnemonic.toString().trim() 39 | } catch (e) { 40 | /* do nothing - this file isn't always there */ 41 | } 42 | 43 | let amount = taskArgs.amount ? taskArgs.amount : "0.01" 44 | const tx = { 45 | to: randomWallet.address, 46 | value: ethers.utils.parseEther(amount), 47 | } 48 | 49 | //SEND USING LOCAL DEPLOYER MNEMONIC IF THERE IS ONE 50 | // IF NOT SEND USING LOCAL HARDHAT NODE: 51 | if (localDeployerMnemonic) { 52 | let deployerWallet = ethers.Wallet.fromMnemonic( 53 | localDeployerMnemonic.toString() 54 | ) 55 | deployerWallet = deployerWallet.connect(ethers.provider) 56 | console.log( 57 | "💵 Sending " + 58 | amount + 59 | " ETH to " + 60 | randomWallet.address + 61 | " using deployer account" 62 | ) 63 | let sendresult = await deployerWallet.sendTransaction(tx) 64 | console.log("\n" + url + "/pk#" + privateKey + "\n") 65 | return 66 | } else { 67 | console.log( 68 | "💵 Sending " + 69 | amount + 70 | " ETH to " + 71 | randomWallet.address + 72 | " using local node" 73 | ) 74 | console.log("\n" + url + "/pk#" + privateKey + "\n") 75 | return send(ethers.provider.getSigner(), tx) 76 | } 77 | }) 78 | 79 | task( 80 | "generate", 81 | "Create a mnemonic for builder deploys", 82 | async (_, { ethers }) => { 83 | const bip39 = require("bip39") 84 | const hdkey = require("ethereumjs-wallet/hdkey") 85 | const mnemonic = bip39.generateMnemonic() 86 | if (DEBUG) console.log("mnemonic", mnemonic) 87 | const seed = await bip39.mnemonicToSeed(mnemonic) 88 | if (DEBUG) console.log("seed", seed) 89 | const hdwallet = hdkey.fromMasterSeed(seed) 90 | const wallet_hdpath = "m/44'/60'/0'/0/" 91 | const account_index = 0 92 | let fullPath = wallet_hdpath + account_index 93 | if (DEBUG) console.log("fullPath", fullPath) 94 | const wallet = hdwallet.derivePath(fullPath).getWallet() 95 | const privateKey = "0x" + wallet._privKey.toString("hex") 96 | if (DEBUG) console.log("privateKey", privateKey) 97 | var EthUtil = require("ethereumjs-util") 98 | const address = 99 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex") 100 | console.log( 101 | "🔐 Account Generated as " + 102 | address + 103 | " and set as mnemonic in packages/hardhat" 104 | ) 105 | console.log( 106 | "💬 Use 'yarn run account' to get more information about the deployment account." 107 | ) 108 | 109 | fs.writeFileSync("./" + address + ".txt", mnemonic.toString()) 110 | fs.writeFileSync("./mnemonic.txt", mnemonic.toString()) 111 | } 112 | ) 113 | 114 | task( 115 | "mineContractAddress", 116 | "Looks for a deployer account that will give leading zeros" 117 | ) 118 | .addParam("searchFor", "String to search for") 119 | .setAction(async (taskArgs, { network, ethers }) => { 120 | let contract_address = "" 121 | let address 122 | 123 | const bip39 = require("bip39") 124 | const hdkey = require("ethereumjs-wallet/hdkey") 125 | 126 | let mnemonic = "" 127 | while (contract_address.indexOf(taskArgs.searchFor) != 0) { 128 | mnemonic = bip39.generateMnemonic() 129 | if (DEBUG) console.log("mnemonic", mnemonic) 130 | const seed = await bip39.mnemonicToSeed(mnemonic) 131 | if (DEBUG) console.log("seed", seed) 132 | const hdwallet = hdkey.fromMasterSeed(seed) 133 | const wallet_hdpath = "m/44'/60'/0'/0/" 134 | const account_index = 0 135 | let fullPath = wallet_hdpath + account_index 136 | if (DEBUG) console.log("fullPath", fullPath) 137 | const wallet = hdwallet.derivePath(fullPath).getWallet() 138 | const privateKey = "0x" + wallet._privKey.toString("hex") 139 | if (DEBUG) console.log("privateKey", privateKey) 140 | var EthUtil = require("ethereumjs-util") 141 | address = 142 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex") 143 | 144 | const rlp = require("rlp") 145 | const keccak = require("keccak") 146 | 147 | let nonce = 0x00 //The nonce must be a hex literal! 148 | let sender = address 149 | 150 | let input_arr = [sender, nonce] 151 | let rlp_encoded = rlp.encode(input_arr) 152 | 153 | let contract_address_long = keccak("keccak256") 154 | .update(rlp_encoded) 155 | .digest("hex") 156 | 157 | contract_address = contract_address_long.substring(24) //Trim the first 24 characters. 158 | } 159 | 160 | console.log( 161 | "⛏ Account Mined as " + 162 | address + 163 | " and set as mnemonic in packages/hardhat" 164 | ) 165 | console.log( 166 | "📜 This will create the first contract: " + 167 | chalk.magenta("0x" + contract_address) 168 | ) 169 | console.log( 170 | "💬 Use 'yarn run account' to get more information about the deployment account." 171 | ) 172 | 173 | fs.writeFileSync( 174 | "./" + address + "_produces" + contract_address + ".txt", 175 | mnemonic.toString() 176 | ) 177 | fs.writeFileSync("./mnemonic.txt", mnemonic.toString()) 178 | }) 179 | 180 | task( 181 | "account", 182 | "Get balance informations for the deployment account.", 183 | async (_, { ethers, config }) => { 184 | const hdkey = require("ethereumjs-wallet/hdkey") 185 | const bip39 = require("bip39") 186 | let mnemonic = fs.readFileSync("./mnemonic.txt").toString().trim() 187 | if (DEBUG) console.log("mnemonic", mnemonic) 188 | const seed = await bip39.mnemonicToSeed(mnemonic) 189 | if (DEBUG) console.log("seed", seed) 190 | const hdwallet = hdkey.fromMasterSeed(seed) 191 | const wallet_hdpath = "m/44'/60'/0'/0/" 192 | const account_index = 0 193 | let fullPath = wallet_hdpath + account_index 194 | if (DEBUG) console.log("fullPath", fullPath) 195 | const wallet = hdwallet.derivePath(fullPath).getWallet() 196 | const privateKey = "0x" + wallet._privKey.toString("hex") 197 | if (DEBUG) console.log("privateKey", privateKey) 198 | var EthUtil = require("ethereumjs-util") 199 | const address = 200 | "0x" + EthUtil.privateToAddress(wallet._privKey).toString("hex") 201 | 202 | var qrcode = require("qrcode-terminal") 203 | qrcode.generate(address) 204 | console.log("‍📬 Deployer Account is " + address) 205 | for (let n in config.networks) { 206 | //console.log(config.networks[n],n) 207 | try { 208 | let balance = await ethers.provider.getBalance(address) 209 | console.log(" -- " + n + " -- -- -- 📡 ") 210 | console.log(" balance: " + ethers.utils.formatEther(balance)) 211 | console.log( 212 | " nonce: " + (await ethers.provider.getTransactionCount(address)) 213 | ) 214 | } catch (e) { 215 | if (DEBUG) { 216 | console.log(e) 217 | } 218 | } 219 | } 220 | } 221 | ) 222 | 223 | async function addr(ethers: any, addr: string) { 224 | if (ethers.utils.isAddress(addr)) { 225 | return ethers.utils.getAddress(addr) 226 | } 227 | const accounts = await ethers.provider.listAccounts() 228 | if (accounts[addr] !== undefined) { 229 | return accounts[addr] 230 | } 231 | throw `Could not normalize address: ${addr}` 232 | } 233 | 234 | task("accounts", "Prints the list of accounts", async (_, { ethers }) => { 235 | const accounts = await ethers.provider.listAccounts() 236 | accounts.forEach((account) => console.log(account)) 237 | }) 238 | 239 | task("blockNumber", "Prints the block number", async (_, { ethers }) => { 240 | const blockNumber = await ethers.provider.getBlockNumber() 241 | console.log(blockNumber) 242 | }) 243 | 244 | task("balance", "Prints an account's balance") 245 | .addPositionalParam("account", "The account's address") 246 | .setAction(async (taskArgs, { ethers }) => { 247 | const balance = await ethers.provider.getBalance( 248 | await addr(ethers, taskArgs.account) 249 | ) 250 | console.log(ethers.utils.formatUnits(balance, "ether"), "ETH") 251 | }) 252 | 253 | async function send(signer: Signer, params: any) { 254 | let txHash = '' 255 | try { 256 | const { hash } = await signer.sendTransaction(params) 257 | txHash = hash 258 | debug(`transactionHash: ${txHash}`) 259 | // checkForReceipt(2, params, transactionHash, resolve) 260 | } catch (err) { 261 | if (err) { 262 | debug(`Error: ${err}`) 263 | } 264 | } 265 | 266 | return txHash 267 | } 268 | 269 | task("send", "Send ETH") 270 | .addParam("from", "From address or afundWalletccount index") 271 | .addOptionalParam("to", "To address or account index") 272 | .addOptionalParam("amount", "Amount to send in ether") 273 | .addOptionalParam("data", "Data included in transaction") 274 | .addOptionalParam("gasPrice", "Price you are willing to pay in gwei") 275 | .addOptionalParam("gasLimit", "Limit of how much gas to spend") 276 | 277 | .setAction(async (taskArgs, { network, ethers }) => { 278 | const from = await addr(ethers, taskArgs.from) 279 | debug(`Normalized from address: ${from}`) 280 | const fromSigner = await ethers.provider.getSigner(from) 281 | 282 | let to 283 | if (taskArgs.to) { 284 | to = await addr(ethers, taskArgs.to) 285 | debug(`Normalized to address: ${to}`) 286 | } 287 | 288 | const txRequest = { 289 | from: await fromSigner.getAddress(), 290 | to, 291 | value: ethers.utils.parseUnits( 292 | taskArgs.amount ? taskArgs.amount : "0", 293 | "ether" 294 | ).toHexString(), 295 | nonce: await fromSigner.getTransactionCount(), 296 | gasPrice: ethers.utils.parseUnits( 297 | taskArgs.gasPrice ? taskArgs.gasPrice : "1.001", 298 | "gwei" 299 | ).toHexString(), 300 | gasLimit: taskArgs.gasLimit ? taskArgs.gasLimit : 24000, 301 | chainId: network.config.chainId, 302 | data: undefined 303 | } 304 | 305 | if (taskArgs.data !== undefined) { 306 | txRequest.data = taskArgs.data 307 | debug(`Adding data to payload: ${txRequest.data}`) 308 | } 309 | debug(BN.from(txRequest.gasPrice).div(1000000000).toString() + " gwei") 310 | debug(JSON.stringify(txRequest, null, 2)) 311 | 312 | return send(fromSigner, txRequest) 313 | }) 314 | -------------------------------------------------------------------------------- /test-old/solidity-helper.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var leftpad = require('leftpad'); 4 | var web3utils = require('web3-utils'); 5 | 6 | module.exports = { 7 | 8 | 9 | stringToSolidityBytes32(string) 10 | { 11 | var result = "0x"; 12 | var i = 0; 13 | 14 | for(i=0;i<32;i++) 15 | { 16 | if(string.length > i) 17 | { 18 | result += string.charCodeAt(i).toString(16) 19 | }else { 20 | result += "00" 21 | } 22 | } 23 | 24 | 25 | return result; 26 | }, 27 | 28 | //0x6161000000000000000000000000000000000000000000000000000000000000 -> aa 29 | solidityBytes32ToString(bytes32) 30 | { 31 | var result = ""; 32 | var i = 0; 33 | 34 | if(bytes32.startsWith('0x')) 35 | { 36 | var rawcodes = bytes32.substring(2); 37 | }else{ 38 | var rawcodes = bytes32; 39 | } 40 | 41 | for(i=0;i<32;i++) 42 | { 43 | var segment = rawcodes.substring(i*2,i*2+2) 44 | var segment_value = parseInt(segment,16) 45 | if(segment_value != '00') 46 | { 47 | result += String.fromCharCode(segment_value) 48 | } 49 | } 50 | 51 | 52 | return result; 53 | }, 54 | 55 | solidityKeccak256(...args) { 56 | args = args.map(arg => { 57 | if (typeof arg === 'string') { 58 | if (arg.substring(0, 2) === '0x') { 59 | return arg.slice(2) 60 | } else { 61 | return web3utils.toHex(arg).slice(2) 62 | } 63 | } 64 | 65 | if (typeof arg === 'number') { 66 | if (arg < 0) { 67 | return leftpad((arg >>> 0).toString(16), 64, 'F'); 68 | } 69 | return leftpad((arg).toString(16), 64, 0); 70 | } 71 | }) 72 | 73 | args = args.join('') 74 | 75 | return web3utils.sha3(args, { encoding: 'hex' }) 76 | } 77 | 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /test-old/test-main.js: -------------------------------------------------------------------------------- 1 | 2 | // old 3 | //const NametagToken = artifacts.require("NametagToken"); 4 | //const OpenNFTExchange = artifacts.require("OpenNFTExchange"); 5 | 6 | //https://medium.com/@adrianmcli/migrating-your-truffle-project-to-web3-v1-0-ed3a56f11a4 7 | 8 | 9 | 10 | var EthUtil = require('ethereumjs-util') 11 | 12 | // v1.0 13 | const { getWeb3, getContractInstance } = require("./web3helpers") 14 | const web3 = getWeb3() 15 | const getInstance = getContractInstance(web3) 16 | 17 | const PayspecHelper = require('./payspec-helper') 18 | 19 | var web3utils = web3.utils; 20 | 21 | 22 | 23 | var myAccount; 24 | var counterpartyAccount; 25 | var feeAccount; 26 | 27 | 28 | contract('payspecV2',(accounts) => { 29 | 30 | var fixedSupplyToken; 31 | var payspecV2; 32 | 33 | 34 | it(" can deploy ", async () => { 35 | fixedSupplyToken = getInstance("FixedSupplyToken"); 36 | payspecV2 = getInstance("PayspecV2"); 37 | 38 | 39 | console.log('payspec is ',payspecV2.options.address) 40 | 41 | 42 | myAccount = accounts[0]; 43 | counterpartyAccount = accounts[1]; 44 | feeAccount = accounts[2]; 45 | 46 | console.log('my acct ', myAccount ) 47 | console.log('counterparty acct ', counterpartyAccount ) 48 | 49 | 50 | 51 | // await fixedSupplyToken.methods.transfer(counterpartyAccount, 1000000 ).send({from:myAccount}) 52 | 53 | 54 | assert.ok(fixedSupplyToken); 55 | assert.ok(payspecV2); 56 | }), 57 | 58 | 59 | 60 | 61 | it("invoice can be created with proper uuid ", async function () { 62 | 63 | 64 | 65 | balance = await web3.eth.getBalance(myAccount); 66 | console.log('eth balance is ', balance) 67 | 68 | 69 | let newInvoiceData = { 70 | description: 'testtx', 71 | nonce: 1, 72 | token: fixedSupplyToken.options.address, 73 | amountDue: 100, 74 | payTo: myAccount, 75 | feeAddresses: [ feeAccount ], 76 | feePercents: [ 2 ], 77 | expiresAt: 0 78 | } 79 | 80 | 81 | 82 | let getInvoiceUUIDArgsArray = Object.values(newInvoiceData) 83 | let actualInvoiceUUID; 84 | 85 | try { 86 | actualInvoiceUUID= await payspecV2.methods.getInvoiceUUID.apply(this,getInvoiceUUIDArgsArray).call({ from: myAccount }) ; 87 | } catch (error) { 88 | console.trace(error) 89 | } 90 | 91 | 92 | console.log('actualInvoiceUUID',actualInvoiceUUID) 93 | assert.ok(actualInvoiceUUID); 94 | 95 | 96 | newInvoiceData.payspecContractAddress = payspecV2.options.address 97 | 98 | let expecteduuid = PayspecHelper.getExpectedInvoiceUUID( newInvoiceData ) 99 | 100 | 101 | assert.equal(expecteduuid, actualInvoiceUUID); 102 | 103 | 104 | 105 | 106 | }); 107 | 108 | 109 | 110 | it("invoice can be submitted ", async function () { 111 | 112 | 113 | 114 | 115 | let newInvoiceData = { 116 | description: 'testtx2', 117 | nonce: 2, 118 | token: fixedSupplyToken.options.address, 119 | amountDue: 100, 120 | payTo: counterpartyAccount, 121 | feeAddresses: [ feeAccount ], 122 | feePercents: [ 2 ], 123 | expiresAt: 0 124 | } 125 | 126 | let getInvoiceUUIDArgsArray = Object.values(newInvoiceData) 127 | 128 | let actualInvoiceUUID; 129 | 130 | try { 131 | actualInvoiceUUID= await payspecV2.methods.getInvoiceUUID.apply(this,getInvoiceUUIDArgsArray).call({ from: myAccount }) ; 132 | } catch (error) { 133 | console.trace(error) 134 | } 135 | 136 | 137 | 138 | //inject the contract address here just to get the expected UUID in an offchain way 139 | let expecteduuid = PayspecHelper.getExpectedInvoiceUUID( Object.assign( {payspecContractAddress: payspecV2.options.address }, newInvoiceData ) ) 140 | 141 | 142 | assert.equal( expecteduuid,actualInvoiceUUID ); 143 | 144 | let finalInvoiceData = Object.assign( newInvoiceData, {expecteduuid: expecteduuid } ) 145 | 146 | 147 | try { 148 | await fixedSupplyToken.methods.approve(payspecV2.options.address, 10000).send({ from: myAccount, gas:3000000 }) 149 | } catch (error) { 150 | assert.fail("Method Reverted", "approve", error.reason); 151 | } 152 | 153 | 154 | 155 | 156 | let createAndPayArgsArray = Object.values( finalInvoiceData ) 157 | 158 | console.log( 'finalInvoiceData', finalInvoiceData , createAndPayArgsArray) 159 | 160 | let success; 161 | 162 | try { 163 | success = await payspecV2.methods.createAndPayInvoice.apply(this, createAndPayArgsArray ).send({ from: myAccount, gas:3000000 }) ; 164 | } catch (error) { 165 | console.trace( error ) 166 | } 167 | 168 | 169 | assert.ok(success); 170 | 171 | let myBalance = await fixedSupplyToken.methods.balanceOf(myAccount).call( ) 172 | let counterpartyBalance = await fixedSupplyToken.methods.balanceOf(counterpartyAccount).call( ) 173 | let feeBalance = await fixedSupplyToken.methods.balanceOf(feeAccount).call( ) 174 | 175 | 176 | assert.equal(myBalance,2099999999999900); 177 | assert.equal(counterpartyBalance,98); 178 | assert.equal(feeBalance,2); 179 | 180 | }); 181 | 182 | 183 | 184 | 185 | it("invoice can be cancelled ", async function () { 186 | 187 | 188 | 189 | 190 | let newInvoiceData = { 191 | description: 'testtx3', 192 | nonce: 3, 193 | token: fixedSupplyToken.options.address, 194 | amountDue: 100, 195 | payTo: myAccount, 196 | feeAddresses: [ feeAccount ], 197 | feePercents: [ 2 ], 198 | expiresAt: 0 199 | } 200 | 201 | let getInvoiceUUIDArgsArray = Object.values(newInvoiceData) 202 | 203 | let actualInvoiceUUID; 204 | 205 | try { 206 | actualInvoiceUUID= await payspecV2.methods.getInvoiceUUID.apply(this,getInvoiceUUIDArgsArray).call({ from: myAccount }) ; 207 | } catch (error) { 208 | console.trace(error) 209 | } 210 | 211 | 212 | 213 | //inject the contract address here just to get the expected UUID in an offchain way 214 | let expecteduuid = PayspecHelper.getExpectedInvoiceUUID( Object.assign( {payspecContractAddress: payspecV2.options.address }, newInvoiceData ) ) 215 | 216 | 217 | assert.equal( expecteduuid,actualInvoiceUUID ); 218 | 219 | let finalInvoiceData = Object.assign( newInvoiceData, {expecteduuid: expecteduuid } ) 220 | 221 | 222 | try { 223 | await fixedSupplyToken.methods.approve(payspecV2.options.address, 10000).send({ from: myAccount, gas:3000000 }) 224 | } catch (error) { 225 | assert.fail("Method Reverted", "approve", error.reason); 226 | } 227 | 228 | 229 | 230 | 231 | let createAndPayArgsArray = Object.values( finalInvoiceData ) 232 | 233 | console.log( 'finalInvoiceData', finalInvoiceData , createAndPayArgsArray) 234 | 235 | let success; 236 | 237 | try { 238 | success = await payspecV2.methods.cancelInvoice.apply(this, createAndPayArgsArray ).send({ from: myAccount, gas:3000000 }) ; 239 | } catch (error) { 240 | console.trace( error ) 241 | } 242 | 243 | 244 | assert.ok(success); 245 | 246 | 247 | }); 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | }); 259 | 260 | 261 | 262 | 263 | 264 | 265 | async function printBalances(accounts) { 266 | // accounts.forEach(function(ac, i) { 267 | var balance_val = await (web3.eth.getBalance(accounts[0])); 268 | console.log('acct 0 balance', web3utils.fromWei(balance_val.toString() , 'ether') ) 269 | // }) 270 | } 271 | -------------------------------------------------------------------------------- /test-old/web3helpers.js: -------------------------------------------------------------------------------- 1 | const Web3 = require("web3") // import web3 v1.0 constructor 2 | 3 | // use globally injected web3 to find the currentProvider and wrap with web3 v1.0 4 | const getWeb3 = () => { 5 | const myWeb3 = new Web3(web3.currentProvider) 6 | return myWeb3 7 | } 8 | 9 | // assumes passed-in web3 is v1.0 and creates a function to receive contract name 10 | const getContractInstance = (web3) => (contractName) => { 11 | const artifact = artifacts.require(contractName) // globally injected artifacts helper 12 | const deployedAddress = artifact.networks[artifact.network_id].address 13 | const instance = new web3.eth.Contract(artifact.abi, deployedAddress) 14 | return instance 15 | } 16 | 17 | module.exports = { getWeb3, getContractInstance } 18 | -------------------------------------------------------------------------------- /test/lib/EIP2616SDK.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, utils } from "ethers"; 2 | import ethUtil, { bufferToHex, ecrecover, ecsign, pubToAddress, toBuffer } from 'ethereumjs-util' 3 | 4 | export interface PermitApproval{ 5 | 6 | owner: string, 7 | spender: string, 8 | value: string, 9 | deadline: string, 10 | v: number, 11 | r: string, 12 | s: string 13 | 14 | } 15 | 16 | export interface ApprovalInputs { 17 | spender: string, 18 | value: string, 19 | deadline: string, 20 | permitNonce: string 21 | } 22 | 23 | export interface DomainData { 24 | name: string, 25 | version: string, 26 | chainId: number, 27 | resolverAddress: string 28 | } 29 | 30 | export async function signPermitApproval( 31 | approvalInputs: ApprovalInputs, 32 | domainData: DomainData, 33 | permitter: Wallet 34 | ) : Promise{ 35 | 36 | 37 | let abiCoder = new utils.AbiCoder() 38 | 39 | let domainString = utils.keccak256(utils.toUtf8Bytes("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")) 40 | 41 | let domainSeparator = abiCoder.encode( 42 | ["bytes32","bytes32","bytes32","uint","address"], 43 | [ 44 | domainString, 45 | utils.keccak256(utils.toUtf8Bytes(domainData.name)), 46 | utils.keccak256(utils.toUtf8Bytes(domainData.version)), 47 | domainData.chainId, 48 | domainData.resolverAddress] 49 | ) 50 | 51 | let permitTypehash = utils.keccak256(utils.toUtf8Bytes("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")) 52 | 53 | let owner = permitter.address 54 | 55 | let typeHashAndData = abiCoder.encode( 56 | ["bytes32","address","address","uint","uint","uint"], 57 | [ 58 | permitTypehash, 59 | owner, 60 | approvalInputs.spender, 61 | approvalInputs.value, 62 | approvalInputs.permitNonce , 63 | approvalInputs.deadline] 64 | ); 65 | 66 | 67 | let digest = utils.solidityKeccak256( 68 | ["bytes2","bytes32","bytes32"], [ 69 | Buffer.from('1901', 'hex'), 70 | utils.keccak256(domainSeparator), //correct 71 | utils.keccak256(typeHashAndData) //correct 72 | ]) 73 | 74 | 75 | var msgBuffer= toBuffer(digest) 76 | 77 | const sig = ecsign(msgBuffer, toBuffer(permitter.privateKey)) 78 | 79 | var hashBuf = toBuffer(digest) 80 | 81 | const pubKey = ecrecover(hashBuf, sig.v, sig.r, sig.s); 82 | const addrBuf = pubToAddress(pubKey); 83 | const recoveredSignatureSigner = bufferToHex(addrBuf); 84 | 85 | 86 | return { 87 | owner, 88 | spender: approvalInputs.spender, 89 | value: approvalInputs.value, 90 | deadline: approvalInputs.deadline, 91 | v: sig.v, 92 | r: sig.r, 93 | s: sig.s 94 | } 95 | } -------------------------------------------------------------------------------- /test/test-utils.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@ethersproject/abstract-provider' 2 | import { Wallet } from 'ethers' 3 | import hre, { ethers } from 'hardhat' 4 | import { getFunds } from '../helpers/get-funds' 5 | 6 | export async function createAndFundRandomWallet(provider: any): Promise { 7 | 8 | 9 | const wallet = Wallet.createRandom().connect(provider) 10 | 11 | await getFunds({ 12 | to: await wallet.getAddress(), 13 | tokenSym: 'ETH', 14 | amount: hre.ethers.utils.parseEther('1000'), 15 | hre, 16 | }) 17 | 18 | return wallet 19 | } -------------------------------------------------------------------------------- /test/token.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import chai, { expect } from 'chai' 3 | import chaiAsPromised from 'chai-as-promised' 4 | import { BigNumber, Contract, Signer, Wallet } from 'ethers' 5 | import hre, { ethers } from 'hardhat' 6 | //import { deploy } from 'helpers/deploy-helpers' 7 | import { XBitcoinTokenTest, XBitsToken } from '../generated/typechain' 8 | import { getPayspecInvoiceUUID, PayspecInvoice , ETH_ADDRESS} from 'payspec-js' 9 | import { deploy } from '../helpers/deploy-helpers' 10 | import { createAndFundRandomWallet } from './test-utils' 11 | import { ApprovalInputs, DomainData, signPermitApproval } from './lib/EIP2616SDK' 12 | 13 | chai.should() 14 | chai.use(chaiAsPromised) 15 | 16 | const { deployments } = hre 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 19 | interface SetupOptions {} 20 | 21 | interface SetupReturn { 22 | originalTokenContract: XBitcoinTokenTest 23 | upgradeTokenContract: XBitsToken 24 | } 25 | 26 | const setup = deployments.createFixture( 27 | async (hre, _opts) => { 28 | 29 | 30 | await hre.deployments.fixture(['primary'], { 31 | keepExistingDeployments: false, 32 | }) 33 | 34 | const originalTokenContract = await hre.contracts 35 | .get('_0xBitcoinTokenTest') 36 | const upgradeTokenContract = await hre.contracts 37 | .get('xBitsToken') 38 | 39 | 40 | 41 | 42 | return { 43 | originalTokenContract, 44 | upgradeTokenContract 45 | } 46 | } 47 | ) 48 | 49 | 50 | 51 | describe('Upgrade Contract', () => { 52 | 53 | let originalTokenContract: XBitcoinTokenTest 54 | let upgradeTokenContract: XBitcoinTokenV2 55 | 56 | 57 | let miner: Wallet 58 | let permitter: Wallet 59 | 60 | before(async () => { 61 | 62 | 63 | miner = await createAndFundRandomWallet( ethers.provider ) 64 | permitter = await createAndFundRandomWallet( ethers.provider ) 65 | 66 | let minerEth = await miner.getBalance() 67 | 68 | 69 | const result = await setup() 70 | originalTokenContract = result.originalTokenContract 71 | upgradeTokenContract = result.upgradeTokenContract 72 | 73 | 74 | }) 75 | 76 | 77 | 78 | 79 | it('should deposit and withdraw', async () => { 80 | 81 | 82 | 83 | await originalTokenContract.connect(miner).mintTest() 84 | let balance = await originalTokenContract.balanceOf(miner.address) 85 | 86 | expect(balance).to.eql( "5000000000" ) 87 | 88 | await originalTokenContract.connect(miner).approveAndCall(upgradeTokenContract.address, 9000, "0x") 89 | 90 | let upgradeBalance = await upgradeTokenContract.balanceOf(miner.address) 91 | 92 | expect(upgradeBalance).to.eql( "9000" ) 93 | 94 | let depositedAmount = await upgradeTokenContract.amountDeposited( ) 95 | 96 | expect(depositedAmount).to.eql( "9000" ) 97 | 98 | 99 | let totalSupply = await upgradeTokenContract.totalSupply( ) 100 | 101 | expect(totalSupply).to.eql( "2100000000000000" ) 102 | 103 | let tokensMinted = await upgradeTokenContract.tokensMinted( ) 104 | 105 | expect(tokensMinted).to.eql( "30000" ) 106 | 107 | 108 | expect(await upgradeTokenContract.currentMiningReward( )) 109 | .to.eql( "5000000000" ) 110 | 111 | 112 | 113 | let latestDiffStartedAt = await upgradeTokenContract.latestDifficultyPeriodStarted( ) 114 | expect(latestDiffStartedAt).to.eql( "1001" ) 115 | 116 | 117 | 118 | }) 119 | 120 | it('should permit approve', async () => { 121 | 122 | 123 | 124 | 125 | let permitNonce = await upgradeTokenContract.nonces( miner.address ) 126 | expect(permitNonce).to.eql(0) 127 | 128 | let permitNonceString = permitNonce.toString() 129 | 130 | let approvalInputs :ApprovalInputs = { 131 | 132 | spender: miner.address, 133 | value: '10', 134 | deadline: (Date.now() + 80000).toString(), 135 | permitNonce: permitNonceString 136 | 137 | } 138 | 139 | let ethersNetwork = await ethers.provider.getNetwork() 140 | 141 | 142 | let domainData : DomainData = { 143 | name: await upgradeTokenContract.name(), 144 | version: await upgradeTokenContract.version(), 145 | chainId: ethersNetwork.chainId, 146 | resolverAddress: upgradeTokenContract.address 147 | } 148 | 149 | let permitInputs = await signPermitApproval( 150 | approvalInputs, domainData, permitter ) 151 | 152 | 153 | await upgradeTokenContract.connect(miner).permit( 154 | permitInputs.owner, 155 | permitInputs.spender, 156 | permitInputs.value, 157 | permitInputs.deadline, 158 | permitInputs.v, 159 | permitInputs.r, 160 | permitInputs.s 161 | ) 162 | 163 | 164 | }) 165 | 166 | 167 | 168 | }) 169 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": false, 4 | "esModuleInterop": true, 5 | "allowSyntheticDefaultImports": true, 6 | "module": "commonjs", 7 | "resolveJsonModule": true, 8 | "target": "ES6", 9 | "allowJs": false, 10 | "strict": true, 11 | "noEmit": true 12 | }, 13 | "files": ["hardhat.config.ts"], 14 | "include": [ 15 | "./config", 16 | "./deploy", 17 | "./scripts", 18 | "./tasks", 19 | "./test", 20 | "./types/custom", 21 | "./utils" 22 | ], 23 | "exclude": [ 24 | "./artifacts", 25 | "./build", 26 | "./cache", 27 | "./contracts", 28 | "./coverage", 29 | "./docs" 30 | ] 31 | } 32 | --------------------------------------------------------------------------------