├── LICENSE ├── README.md ├── abi ├── Aeolus.json ├── CoinCyclone.json ├── CycloneToken.json ├── ERC20Cyclone.json ├── GovernorAlpha.json ├── IMimoFactory.json ├── Timelock.json └── Verifier.json ├── circuits ├── merkleTree.circom └── withdraw.circom ├── compile-hasher.js ├── contracts ├── Aeolus.sol ├── AeolusV2.sol ├── AeolusV2dot1.sol ├── AeolusV2dot2.sol ├── Cyclone.sol ├── CycloneV2.sol ├── CycloneV2dot1.sol ├── CycloneV2dot2.sol ├── ICycloneV2.sol ├── ICycloneV2dot2.sol ├── Migrations.sol ├── cream │ └── IDelegator.sol ├── governance │ ├── GovernorAlpha.sol │ ├── ITimelock.sol │ └── Timelock.sol ├── lifecycle │ └── Pausable.sol ├── math │ └── SafeMath.sol ├── mimo │ ├── CoinCyclone.sol │ ├── ERC20Cyclone.sol │ ├── IMimoExchange.sol │ └── IMimoFactory.sol ├── mock │ ├── ERC20.sol │ ├── MimoExchange.sol │ ├── MimoFactory.sol │ ├── MockUniswapV2Router.sol │ └── TestCycloneDelegate.sol ├── ownership │ ├── Ownable.sol │ └── Whitelist.sol ├── token │ ├── BasicToken.sol │ ├── CycloneToken.sol │ ├── IERC20.sol │ ├── IERC20Basic.sol │ ├── IMintableToken.sol │ ├── SafeERC20.sol │ ├── ShadowToken.sol │ └── StandardToken.sol ├── uniswapv2 │ ├── IRouter.sol │ └── UniswapV2CycloneRouter.sol ├── utils │ └── Address.sol └── zksnarklib │ ├── IVerifier.sol │ ├── MerkleTreeWithHistory.sol │ └── Verifier.sol ├── lib ├── MerkleTree.js ├── MiMC.js └── Storage.js ├── linker.js ├── package-lock.json ├── package.json ├── production ├── Verifier.sol ├── withdraw.json ├── withdraw_proving_key.bin └── withdraw_verification_key.json └── test ├── Aeolus.test.js ├── CoinCyclone.test.js ├── CycloneToken.test.js ├── ERC20Cyclone.test.js └── GovernorAlpha.test.js /README.md: -------------------------------------------------------------------------------- 1 | # Cyclone Protocol 2 | 3 | **Cyclone is a cross-chain, non-custodial, universal privacy-preserving protocol with the decentralized governance for all DeFi apps**. Cyclone applies zkSNARKs to enable transactional privacy for all DeFi components by breaking the on-chain link between depositor and recipient addresses. It uses a smart contract that accepts coins/tokens deposits, which can be withdrawn by a different address. Whenever an asset is withdrawn from Cyclone, there is no way to link the withdrawal to the deposit for absolute privacy. 4 | 5 | While Cyclone's zkSNARKs part is based on the attested implementation of tornado.cash, it offers unique values in supporting cross-chain and being the universal privacy-preserving layer for almost all DeFi components with the decentralized governance by CYC holders. 6 | 7 | ### Cross-chain 8 | 9 | Cyclone has been launched firstly on IoTeX as it is a fast and EVM-compatible blockchain with an active community. Thanks to anonymity mining and liquidity mining that properly incentive contributions to the anonymity pools, the first four anonymity pools launched got 3+ million USD TVL in a few days, according to https://cyclone.xyz/stats. 10 | 11 | Cyclone aims to launch multiple anonymity pools for various assets on Ethereum, Binance Smart Chain (BSC), and other EVM-compatible public blockchains in Q1/Q2 2021. We will be looking at launching on Polkadot and other non-EVM public blockchains in Q3 2021. CYC will be THE TOKEN for all instances of Cyclone Protocol. 12 | 13 | ### Decentralized Governance 14 | 15 | Cyclone Protocol is governed in a decentralized way. The governance DAO lives on IoTeX blockchain while each anonymity pools live on different blockchains connected to IoTeX blockchain via bridges (such as ioTube). We estimate the governance DAO will be activated in early Q2 2021. 16 | At least the following rights are entitled to CYC holders: 17 | 18 | - Which blockchain to support next 19 | - Launch a new anonymity pool with a certain asset 20 | - Update params of an existing anonymity pool, e.g., CYC to mine per day 21 | - Launch a new liquidity pool 22 | - Update params of an existing liquidity pool, e.g., CYC to mine per day 23 | - Other token economics of CYC 24 | 25 | 26 | ### Universal Privacy-preserving Layer for DeFi with Yield Aggregation 27 | 28 | Cyclone Protocol is much more than a token mixer. Cyclone protocol is the world-first protocol that supports anonymity pools aggregating yield-generating DeFi components, and it is the universal privacy-enhancement layer for all DeFi apps. It is envisioned to be a universal privacy layer for all DeFi components on various blockchains, enabling users to participate in the anonymity/liquidity mining of CYC and harvesting profits from the underlying DeFi components. 29 | 30 | 31 | ## How It Works 32 | 33 | https://docs.cyclone.xyz/how-it-works 34 | 35 | ## Token Economics 36 | 37 | https://docs.cyclone.xyz/cyc-token 38 | 39 | ## Roadmap 40 | 41 | https://docs.cyclone.xyz/cyc-token-roadmap 42 | 43 | ## Deployment and Contract Addresses 44 | 45 | See https://docs.cyclone.xyz/audit 46 | 47 | -------------------------------------------------------------------------------- /abi/Aeolus.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "contract IMintableToken", 6 | "name": "_cycToken", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "contract IERC20", 11 | "name": "_lpToken", 12 | "type": "address" 13 | } 14 | ], 15 | "payable": false, 16 | "stateMutability": "nonpayable", 17 | "type": "constructor" 18 | }, 19 | { 20 | "anonymous": false, 21 | "inputs": [ 22 | { 23 | "indexed": true, 24 | "internalType": "address", 25 | "name": "user", 26 | "type": "address" 27 | }, 28 | { 29 | "indexed": false, 30 | "internalType": "uint256", 31 | "name": "amount", 32 | "type": "uint256" 33 | } 34 | ], 35 | "name": "Deposit", 36 | "type": "event" 37 | }, 38 | { 39 | "anonymous": false, 40 | "inputs": [ 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "user", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "amount", 51 | "type": "uint256" 52 | } 53 | ], 54 | "name": "EmergencyWithdraw", 55 | "type": "event" 56 | }, 57 | { 58 | "anonymous": false, 59 | "inputs": [ 60 | { 61 | "indexed": true, 62 | "internalType": "address", 63 | "name": "previousOwner", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "internalType": "address", 69 | "name": "newOwner", 70 | "type": "address" 71 | } 72 | ], 73 | "name": "OwnershipTransferred", 74 | "type": "event" 75 | }, 76 | { 77 | "anonymous": false, 78 | "inputs": [ 79 | { 80 | "indexed": false, 81 | "internalType": "uint256", 82 | "name": "amount", 83 | "type": "uint256" 84 | }, 85 | { 86 | "indexed": false, 87 | "internalType": "bool", 88 | "name": "isBlockReward", 89 | "type": "bool" 90 | } 91 | ], 92 | "name": "RewardAdded", 93 | "type": "event" 94 | }, 95 | { 96 | "anonymous": false, 97 | "inputs": [ 98 | { 99 | "indexed": false, 100 | "internalType": "address", 101 | "name": "addr", 102 | "type": "address" 103 | } 104 | ], 105 | "name": "WhitelistedAddressAdded", 106 | "type": "event" 107 | }, 108 | { 109 | "anonymous": false, 110 | "inputs": [ 111 | { 112 | "indexed": false, 113 | "internalType": "address", 114 | "name": "addr", 115 | "type": "address" 116 | } 117 | ], 118 | "name": "WhitelistedAddressRemoved", 119 | "type": "event" 120 | }, 121 | { 122 | "anonymous": false, 123 | "inputs": [ 124 | { 125 | "indexed": true, 126 | "internalType": "address", 127 | "name": "user", 128 | "type": "address" 129 | }, 130 | { 131 | "indexed": false, 132 | "internalType": "uint256", 133 | "name": "amount", 134 | "type": "uint256" 135 | } 136 | ], 137 | "name": "Withdraw", 138 | "type": "event" 139 | }, 140 | { 141 | "constant": true, 142 | "inputs": [], 143 | "name": "accCYCPerShare", 144 | "outputs": [ 145 | { 146 | "internalType": "uint256", 147 | "name": "", 148 | "type": "uint256" 149 | } 150 | ], 151 | "payable": false, 152 | "stateMutability": "view", 153 | "type": "function" 154 | }, 155 | { 156 | "constant": false, 157 | "inputs": [ 158 | { 159 | "internalType": "address", 160 | "name": "addr", 161 | "type": "address" 162 | } 163 | ], 164 | "name": "addAddressToWhitelist", 165 | "outputs": [ 166 | { 167 | "internalType": "bool", 168 | "name": "success", 169 | "type": "bool" 170 | } 171 | ], 172 | "payable": false, 173 | "stateMutability": "nonpayable", 174 | "type": "function" 175 | }, 176 | { 177 | "constant": false, 178 | "inputs": [ 179 | { 180 | "internalType": "address[]", 181 | "name": "addrs", 182 | "type": "address[]" 183 | } 184 | ], 185 | "name": "addAddressesToWhitelist", 186 | "outputs": [ 187 | { 188 | "internalType": "bool", 189 | "name": "success", 190 | "type": "bool" 191 | } 192 | ], 193 | "payable": false, 194 | "stateMutability": "nonpayable", 195 | "type": "function" 196 | }, 197 | { 198 | "constant": true, 199 | "inputs": [], 200 | "name": "cycToken", 201 | "outputs": [ 202 | { 203 | "internalType": "contract IMintableToken", 204 | "name": "", 205 | "type": "address" 206 | } 207 | ], 208 | "payable": false, 209 | "stateMutability": "view", 210 | "type": "function" 211 | }, 212 | { 213 | "constant": true, 214 | "inputs": [], 215 | "name": "lastRewardBlock", 216 | "outputs": [ 217 | { 218 | "internalType": "uint256", 219 | "name": "", 220 | "type": "uint256" 221 | } 222 | ], 223 | "payable": false, 224 | "stateMutability": "view", 225 | "type": "function" 226 | }, 227 | { 228 | "constant": true, 229 | "inputs": [], 230 | "name": "owner", 231 | "outputs": [ 232 | { 233 | "internalType": "address", 234 | "name": "", 235 | "type": "address" 236 | } 237 | ], 238 | "payable": false, 239 | "stateMutability": "view", 240 | "type": "function" 241 | }, 242 | { 243 | "constant": false, 244 | "inputs": [ 245 | { 246 | "internalType": "address", 247 | "name": "addr", 248 | "type": "address" 249 | } 250 | ], 251 | "name": "removeAddressFromWhitelist", 252 | "outputs": [ 253 | { 254 | "internalType": "bool", 255 | "name": "success", 256 | "type": "bool" 257 | } 258 | ], 259 | "payable": false, 260 | "stateMutability": "nonpayable", 261 | "type": "function" 262 | }, 263 | { 264 | "constant": false, 265 | "inputs": [ 266 | { 267 | "internalType": "address[]", 268 | "name": "addrs", 269 | "type": "address[]" 270 | } 271 | ], 272 | "name": "removeAddressesFromWhitelist", 273 | "outputs": [ 274 | { 275 | "internalType": "bool", 276 | "name": "success", 277 | "type": "bool" 278 | } 279 | ], 280 | "payable": false, 281 | "stateMutability": "nonpayable", 282 | "type": "function" 283 | }, 284 | { 285 | "constant": true, 286 | "inputs": [], 287 | "name": "rewardPerBlock", 288 | "outputs": [ 289 | { 290 | "internalType": "uint256", 291 | "name": "", 292 | "type": "uint256" 293 | } 294 | ], 295 | "payable": false, 296 | "stateMutability": "view", 297 | "type": "function" 298 | }, 299 | { 300 | "constant": false, 301 | "inputs": [ 302 | { 303 | "internalType": "address", 304 | "name": "newOwner", 305 | "type": "address" 306 | } 307 | ], 308 | "name": "transferOwnership", 309 | "outputs": [], 310 | "payable": false, 311 | "stateMutability": "nonpayable", 312 | "type": "function" 313 | }, 314 | { 315 | "constant": true, 316 | "inputs": [ 317 | { 318 | "internalType": "address", 319 | "name": "", 320 | "type": "address" 321 | } 322 | ], 323 | "name": "userInfo", 324 | "outputs": [ 325 | { 326 | "internalType": "uint256", 327 | "name": "amount", 328 | "type": "uint256" 329 | }, 330 | { 331 | "internalType": "uint256", 332 | "name": "rewardDebt", 333 | "type": "uint256" 334 | } 335 | ], 336 | "payable": false, 337 | "stateMutability": "view", 338 | "type": "function" 339 | }, 340 | { 341 | "constant": true, 342 | "inputs": [ 343 | { 344 | "internalType": "address", 345 | "name": "", 346 | "type": "address" 347 | } 348 | ], 349 | "name": "whitelist", 350 | "outputs": [ 351 | { 352 | "internalType": "bool", 353 | "name": "", 354 | "type": "bool" 355 | } 356 | ], 357 | "payable": false, 358 | "stateMutability": "view", 359 | "type": "function" 360 | }, 361 | { 362 | "constant": false, 363 | "inputs": [ 364 | { 365 | "internalType": "uint256", 366 | "name": "_rewardPerBlock", 367 | "type": "uint256" 368 | } 369 | ], 370 | "name": "setRewardPerBlock", 371 | "outputs": [], 372 | "payable": false, 373 | "stateMutability": "nonpayable", 374 | "type": "function" 375 | }, 376 | { 377 | "constant": true, 378 | "inputs": [ 379 | { 380 | "internalType": "address", 381 | "name": "_user", 382 | "type": "address" 383 | } 384 | ], 385 | "name": "pendingReward", 386 | "outputs": [ 387 | { 388 | "internalType": "uint256", 389 | "name": "", 390 | "type": "uint256" 391 | } 392 | ], 393 | "payable": false, 394 | "stateMutability": "view", 395 | "type": "function" 396 | }, 397 | { 398 | "constant": false, 399 | "inputs": [ 400 | { 401 | "internalType": "uint256", 402 | "name": "_amount", 403 | "type": "uint256" 404 | } 405 | ], 406 | "name": "addReward", 407 | "outputs": [], 408 | "payable": false, 409 | "stateMutability": "nonpayable", 410 | "type": "function" 411 | }, 412 | { 413 | "constant": false, 414 | "inputs": [], 415 | "name": "updateBlockReward", 416 | "outputs": [], 417 | "payable": false, 418 | "stateMutability": "nonpayable", 419 | "type": "function" 420 | }, 421 | { 422 | "constant": false, 423 | "inputs": [ 424 | { 425 | "internalType": "uint256", 426 | "name": "_amount", 427 | "type": "uint256" 428 | } 429 | ], 430 | "name": "deposit", 431 | "outputs": [], 432 | "payable": false, 433 | "stateMutability": "nonpayable", 434 | "type": "function" 435 | }, 436 | { 437 | "constant": false, 438 | "inputs": [ 439 | { 440 | "internalType": "uint256", 441 | "name": "_amount", 442 | "type": "uint256" 443 | } 444 | ], 445 | "name": "withdraw", 446 | "outputs": [], 447 | "payable": false, 448 | "stateMutability": "nonpayable", 449 | "type": "function" 450 | }, 451 | { 452 | "constant": false, 453 | "inputs": [], 454 | "name": "emergencyWithdraw", 455 | "outputs": [], 456 | "payable": false, 457 | "stateMutability": "nonpayable", 458 | "type": "function" 459 | } 460 | ] -------------------------------------------------------------------------------- /abi/IMimoFactory.json: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"exchange","type":"address"}],"name":"NewExchange","type":"event"},{"constant":false,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"createExchange","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getExchange","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getToken","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"token_id","type":"uint256"}],"name":"getTokenWihId","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /abi/Verifier.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "internalType": "bytes", 7 | "name": "proof", 8 | "type": "bytes" 9 | }, 10 | { 11 | "internalType": "uint256[6]", 12 | "name": "input", 13 | "type": "uint256[6]" 14 | } 15 | ], 16 | "name": "verifyProof", 17 | "outputs": [ 18 | { 19 | "internalType": "bool", 20 | "name": "", 21 | "type": "bool" 22 | } 23 | ], 24 | "payable": false, 25 | "stateMutability": "view", 26 | "type": "function" 27 | } 28 | ] -------------------------------------------------------------------------------- /circuits/merkleTree.circom: -------------------------------------------------------------------------------- 1 | include "../node_modules/circomlib/circuits/mimcsponge.circom"; 2 | 3 | // Computes MiMC([left, right]) 4 | template HashLeftRight() { 5 | signal input left; 6 | signal input right; 7 | signal output hash; 8 | 9 | component hasher = MiMCSponge(2, 1); 10 | hasher.ins[0] <== left; 11 | hasher.ins[1] <== right; 12 | hasher.k <== 0; 13 | hash <== hasher.outs[0]; 14 | } 15 | 16 | // if s == 0 returns [in[0], in[1]] 17 | // if s == 1 returns [in[1], in[0]] 18 | template DualMux() { 19 | signal input in[2]; 20 | signal input s; 21 | signal output out[2]; 22 | 23 | s * (1 - s) === 0 24 | out[0] <== (in[1] - in[0])*s + in[0]; 25 | out[1] <== (in[0] - in[1])*s + in[1]; 26 | } 27 | 28 | // Verifies that merkle proof is correct for given merkle root and a leaf 29 | // pathIndices input is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path 30 | template MerkleTreeChecker(levels) { 31 | signal input leaf; 32 | signal input root; 33 | signal input pathElements[levels]; 34 | signal input pathIndices[levels]; 35 | 36 | component selectors[levels]; 37 | component hashers[levels]; 38 | 39 | for (var i = 0; i < levels; i++) { 40 | selectors[i] = DualMux(); 41 | selectors[i].in[0] <== i == 0 ? leaf : hashers[i - 1].hash; 42 | selectors[i].in[1] <== pathElements[i]; 43 | selectors[i].s <== pathIndices[i]; 44 | 45 | hashers[i] = HashLeftRight(); 46 | hashers[i].left <== selectors[i].out[0]; 47 | hashers[i].right <== selectors[i].out[1]; 48 | } 49 | 50 | root === hashers[levels - 1].hash; 51 | } 52 | -------------------------------------------------------------------------------- /circuits/withdraw.circom: -------------------------------------------------------------------------------- 1 | include "../node_modules/circomlib/circuits/bitify.circom"; 2 | include "../node_modules/circomlib/circuits/pedersen.circom"; 3 | include "merkleTree.circom"; 4 | 5 | // computes Pedersen(nullifier + secret) 6 | template CommitmentHasher() { 7 | signal input nullifier; 8 | signal input secret; 9 | signal output commitment; 10 | signal output nullifierHash; 11 | 12 | component commitmentHasher = Pedersen(496); 13 | component nullifierHasher = Pedersen(248); 14 | component nullifierBits = Num2Bits(248); 15 | component secretBits = Num2Bits(248); 16 | nullifierBits.in <== nullifier; 17 | secretBits.in <== secret; 18 | for (var i = 0; i < 248; i++) { 19 | nullifierHasher.in[i] <== nullifierBits.out[i]; 20 | commitmentHasher.in[i] <== nullifierBits.out[i]; 21 | commitmentHasher.in[i + 248] <== secretBits.out[i]; 22 | } 23 | 24 | commitment <== commitmentHasher.out[0]; 25 | nullifierHash <== nullifierHasher.out[0]; 26 | } 27 | 28 | // Verifies that commitment that corresponds to given secret and nullifier is included in the merkle tree of deposits 29 | template Withdraw(levels) { 30 | signal input root; 31 | signal input nullifierHash; 32 | signal input recipient; // not taking part in any computations 33 | signal input relayer; // not taking part in any computations 34 | signal input fee; // not taking part in any computations 35 | signal input refund; // not taking part in any computations 36 | signal private input nullifier; 37 | signal private input secret; 38 | signal private input pathElements[levels]; 39 | signal private input pathIndices[levels]; 40 | 41 | component hasher = CommitmentHasher(); 42 | hasher.nullifier <== nullifier; 43 | hasher.secret <== secret; 44 | hasher.nullifierHash === nullifierHash; 45 | 46 | component tree = MerkleTreeChecker(levels); 47 | tree.leaf <== hasher.commitment; 48 | tree.root <== root; 49 | for (var i = 0; i < levels; i++) { 50 | tree.pathElements[i] <== pathElements[i]; 51 | tree.pathIndices[i] <== pathIndices[i]; 52 | } 53 | 54 | // Add hidden signals to make sure that tampering with recipient or fee will invalidate the snark proof 55 | // Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints 56 | // Squares are used to prevent optimizer from removing those constraints 57 | signal recipientSquare; 58 | signal feeSquare; 59 | signal relayerSquare; 60 | signal refundSquare; 61 | recipientSquare <== recipient * recipient; 62 | feeSquare <== fee * fee; 63 | relayerSquare <== relayer * relayer; 64 | refundSquare <== refund * refund; 65 | } 66 | 67 | component main = Withdraw(20); 68 | -------------------------------------------------------------------------------- /compile-hasher.js: -------------------------------------------------------------------------------- 1 | // Generates Hasher artifact at compile-time using Truffle's external compiler 2 | // mechanism 3 | const path = require('path') 4 | const fs = require('fs') 5 | const genContract = require('circomlib/src/mimcsponge_gencontract.js') 6 | 7 | // where Truffle will expect to find the results of the external compiler 8 | // command 9 | const outputDir = path.join(__dirname, 'build') 10 | const outputPath = path.join(__dirname, 'build', 'Hasher.json') 11 | 12 | function main () { 13 | const contract = { 14 | contractName: 'Hasher', 15 | abi: genContract.abi, 16 | bytecode: genContract.createCode('mimcsponge', 220) 17 | } 18 | 19 | fs.mkdir(outputDir, function(){ 20 | fs.writeFileSync(outputPath, JSON.stringify(contract)) 21 | }) 22 | } 23 | 24 | main() 25 | -------------------------------------------------------------------------------- /contracts/Aeolus.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | 4 | import "./token/IERC20.sol"; 5 | import "./token/SafeERC20.sol"; 6 | import "./math/SafeMath.sol"; 7 | import "./ownership/Whitelist.sol"; 8 | import "./token/IMintableToken.sol"; 9 | 10 | 11 | // Aeolus is the master of Cyclone tokens. He can make CYC and he is a fair guy. 12 | // 13 | // Note that it's ownable and the owner wields tremendous power. The ownership 14 | // will be transferred to a governance smart contract once CYC is sufficiently 15 | // distributed and the community can show to govern itself. 16 | // 17 | // Have fun reading it. Hopefully it's bug-free. God bless. 18 | contract Aeolus is Whitelist { 19 | using SafeMath for uint256; 20 | using SafeERC20 for IERC20; 21 | 22 | // Info of each user. 23 | struct UserInfo { 24 | uint256 amount; // How many LP tokens the user has provided. 25 | uint256 rewardDebt; // Reward debt. See explanation below. 26 | // 27 | // We do some fancy math here. Basically, any point in time, the amount of CYCs 28 | // entitled to a user but is pending to be distributed is: 29 | // 30 | // pending reward = (user.amount * accCYCPerShare) - user.rewardDebt 31 | // 32 | // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: 33 | // 1. Update accCYCPerShare and lastRewardBlock 34 | // 2. User receives the pending reward sent to his/her address. 35 | // 3. User's `amount` gets updated. 36 | // 4. User's `rewardDebt` gets updated. 37 | } 38 | 39 | 40 | // Address of LP token contract. 41 | IERC20 lpToken; 42 | // Accumulated CYCs per share, times 1e12. See below. 43 | uint256 public accCYCPerShare; 44 | // Last block reward block height 45 | uint256 public lastRewardBlock; 46 | // Reward per block 47 | uint256 public rewardPerBlock; 48 | 49 | // The Cyclone TOKEN 50 | IMintableToken public cycToken; 51 | 52 | // Info of each user that stakes LP tokens. 53 | mapping (address => UserInfo) public userInfo; 54 | 55 | event RewardAdded(uint256 amount, bool isBlockReward); 56 | event Deposit(address indexed user, uint256 amount); 57 | event Withdraw(address indexed user, uint256 amount); 58 | event EmergencyWithdraw(address indexed user, uint256 amount); 59 | 60 | constructor(IMintableToken _cycToken, IERC20 _lpToken) public { 61 | cycToken = _cycToken; 62 | lastRewardBlock = block.number; 63 | lpToken = _lpToken; 64 | } 65 | 66 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner { 67 | updateBlockReward(); 68 | rewardPerBlock = _rewardPerBlock; 69 | } 70 | 71 | // View function to see pending reward on frontend. 72 | function pendingReward(address _user) external view returns (uint256) { 73 | UserInfo storage user = userInfo[_user]; 74 | if (rewardPerBlock == 0) { 75 | return 0; 76 | } 77 | uint256 lpSupply = lpToken.balanceOf(address(this)); 78 | if (block.number <= lastRewardBlock || lpSupply == 0) { 79 | return 0; 80 | } 81 | return user.amount.mul( 82 | accCYCPerShare.add(block.number.sub(lastRewardBlock).mul(rewardPerBlock).mul(1e12).div(lpSupply)) 83 | ).div(1e12).sub(user.rewardDebt); 84 | } 85 | 86 | // Add reward variables to be up-to-date. 87 | function addReward(uint256 _amount) public onlyWhitelisted { 88 | uint256 lpSupply = lpToken.balanceOf(address(this)); 89 | if (lpSupply == 0 || _amount == 0) { 90 | return; 91 | } 92 | require(cycToken.mint(address(this), _amount), "failed to mint"); 93 | emit RewardAdded(_amount, false); 94 | accCYCPerShare = accCYCPerShare.add(_amount.mul(1e12).div(lpSupply)); 95 | } 96 | 97 | // Update reward variables to be up-to-date. 98 | function updateBlockReward() public { 99 | if (block.number <= lastRewardBlock || rewardPerBlock == 0) { 100 | return; 101 | } 102 | uint256 lpSupply = lpToken.balanceOf(address(this)); 103 | if (lpSupply == 0) { 104 | lastRewardBlock = block.number; 105 | return; 106 | } 107 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 108 | require(cycToken.mint(address(this), reward), "failed to mint"); 109 | emit RewardAdded(reward, true); 110 | lastRewardBlock = block.number; 111 | accCYCPerShare = accCYCPerShare.add(reward.mul(1e12).div(lpSupply)); 112 | } 113 | 114 | // Deposit LP tokens to Aeolus for CYC allocation. 115 | function deposit(uint256 _amount) public { 116 | updateBlockReward(); 117 | UserInfo storage user = userInfo[msg.sender]; 118 | if (user.amount > 0) { 119 | uint256 pending = user.amount.mul(accCYCPerShare).div(1e12).sub(user.rewardDebt); 120 | if (pending > 0) { 121 | safeCYCTransfer(msg.sender, pending); 122 | } 123 | } 124 | if (_amount > 0) { 125 | lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); 126 | user.amount = user.amount.add(_amount); 127 | } 128 | user.rewardDebt = user.amount.mul(accCYCPerShare).div(1e12); 129 | emit Deposit(msg.sender, _amount); 130 | } 131 | 132 | // Withdraw LP tokens from Aeolus. 133 | function withdraw(uint256 _amount) public { 134 | UserInfo storage user = userInfo[msg.sender]; 135 | require(user.amount >= _amount, "withdraw: not good"); 136 | updateBlockReward(); 137 | uint256 pending = user.amount.mul(accCYCPerShare).div(1e12).sub(user.rewardDebt); 138 | if (pending > 0) { 139 | safeCYCTransfer(msg.sender, pending); 140 | } 141 | if (_amount > 0) { 142 | user.amount = user.amount.sub(_amount); 143 | lpToken.safeTransfer(address(msg.sender), _amount); 144 | } 145 | user.rewardDebt = user.amount.mul(accCYCPerShare).div(1e12); 146 | emit Withdraw(msg.sender, _amount); 147 | } 148 | 149 | // Withdraw without caring about rewards. EMERGENCY ONLY. 150 | function emergencyWithdraw() public { 151 | UserInfo storage user = userInfo[msg.sender]; 152 | uint256 amount = user.amount; 153 | user.amount = 0; 154 | user.rewardDebt = 0; 155 | lpToken.safeTransfer(address(msg.sender), amount); 156 | emit EmergencyWithdraw(msg.sender, amount); 157 | } 158 | 159 | // Safe CYC transfer function, just in case if rounding error causes pool to not have enough CYCs. 160 | function safeCYCTransfer(address _to, uint256 _amount) internal { 161 | uint256 cycBalance = cycToken.balanceOf(address(this)); 162 | if (_amount > cycBalance) { 163 | require(cycToken.transfer(_to, cycBalance), "failed to transfer cyc token"); 164 | } else { 165 | require(cycToken.transfer(_to, _amount), "failed to transfer cyc token"); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /contracts/AeolusV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./ownership/Ownable.sol"; 5 | import "./token/IERC20.sol"; 6 | import "./token/IMintableToken.sol"; 7 | import "./token/SafeERC20.sol"; 8 | import "./uniswapv2/IRouter.sol"; 9 | 10 | // Aeolus is the master of Cyclone tokens. He can distribute CYC and he is a fair guy. 11 | // 12 | // Note that it's ownable and the owner wields tremendous power. The ownership 13 | // will be transferred to a governance smart contract once CYC is sufficiently 14 | // distributed and the community can show to govern itself. 15 | // 16 | // Have fun reading it. Hopefully it's bug-free. God bless. 17 | contract AeolusV2 is Ownable { 18 | using SafeMath for uint256; 19 | using SafeERC20 for IERC20; 20 | 21 | // Info of each user. 22 | struct UserInfo { 23 | uint256 amount; // How many LP tokens the user has provided. 24 | uint256 rewardDebt; // Reward debt. See explanation below. 25 | // 26 | // We do some fancy math here. Basically, any point in time, the amount of CYCs 27 | // entitled to a user but is pending to be distributed is: 28 | // 29 | // pending reward = (user.amount * accCYCPerShare) - user.rewardDebt 30 | // 31 | // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: 32 | // 1. Update accCYCPerShare and lastRewardBlock 33 | // 2. User receives the pending reward sent to his/her address. 34 | // 3. User's `amount` gets updated. 35 | // 4. User's `rewardDebt` gets updated. 36 | } 37 | 38 | 39 | // Address of LP token contract. 40 | IERC20 public lpToken; 41 | // Accumulated CYCs per share, times 1e12. See below. 42 | uint256 public accCYCPerShare; 43 | // Last block reward block height 44 | uint256 public lastRewardBlock; 45 | // Reward per block 46 | uint256 public rewardPerBlock; 47 | // Reward to distribute 48 | uint256 public rewardToDistribute; 49 | // Entrance Fee Rate 50 | uint256 public entranceFeeRate; 51 | 52 | IERC20 public wrappedCoin; 53 | IRouter public router; 54 | // The Cyclone TOKEN 55 | IMintableToken public cycToken; 56 | 57 | // Info of each user that stakes LP tokens. 58 | mapping (address => UserInfo) public userInfo; 59 | 60 | event RewardAdded(uint256 amount, bool isBlockReward); 61 | event Deposit(address indexed user, uint256 amount, uint256 fee); 62 | event Withdraw(address indexed user, uint256 amount); 63 | event EmergencyWithdraw(address indexed user, uint256 amount); 64 | 65 | constructor(IMintableToken _cycToken, IERC20 _lpToken, address _router, IERC20 _wrappedCoin) public { 66 | cycToken = _cycToken; 67 | lastRewardBlock = block.number; 68 | lpToken = _lpToken; 69 | router = IRouter(_router); 70 | wrappedCoin = _wrappedCoin; 71 | } 72 | 73 | function setEntranceFeeRate(uint256 _entranceFeeRate) public onlyOwner { 74 | require(_entranceFeeRate < 10000, "invalid entrance fee rate"); 75 | entranceFeeRate = _entranceFeeRate; 76 | } 77 | 78 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner { 79 | updateBlockReward(); 80 | rewardPerBlock = _rewardPerBlock; 81 | } 82 | 83 | function rewardPending() internal view returns (uint256) { 84 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 85 | uint256 cycBalance = cycToken.balanceOf(address(this)).sub(rewardToDistribute); 86 | if (cycBalance < reward) { 87 | return cycBalance; 88 | } 89 | return reward; 90 | } 91 | 92 | // View function to see pending reward on frontend. 93 | function pendingReward(address _user) external view returns (uint256) { 94 | UserInfo storage user = userInfo[_user]; 95 | uint256 acps = accCYCPerShare; 96 | if (rewardPerBlock > 0) { 97 | uint256 lpSupply = lpToken.balanceOf(address(this)); 98 | if (block.number > lastRewardBlock && lpSupply > 0) { 99 | acps = acps.add(rewardPending().mul(1e12).div(lpSupply)); 100 | } 101 | } 102 | 103 | return user.amount.mul(acps).div(1e12).sub(user.rewardDebt); 104 | } 105 | 106 | // Update reward variables to be up-to-date. 107 | function updateBlockReward() public { 108 | if (block.number <= lastRewardBlock || rewardPerBlock == 0) { 109 | return; 110 | } 111 | uint256 lpSupply = lpToken.balanceOf(address(this)); 112 | uint256 reward = rewardPending(); 113 | if (lpSupply == 0 || reward == 0) { 114 | lastRewardBlock = block.number; 115 | return; 116 | } 117 | rewardToDistribute = rewardToDistribute.add(reward); 118 | emit RewardAdded(reward, true); 119 | lastRewardBlock = block.number; 120 | accCYCPerShare = accCYCPerShare.add(reward.mul(1e12).div(lpSupply)); 121 | } 122 | 123 | // Deposit LP tokens to Aeolus for CYC allocation. 124 | function deposit(uint256 _amount) public { 125 | updateBlockReward(); 126 | UserInfo storage user = userInfo[msg.sender]; 127 | if (user.amount > 0) { 128 | uint256 pending = user.amount.mul(accCYCPerShare).div(1e12).sub(user.rewardDebt); 129 | if (pending > 0) { 130 | safeCYCTransfer(msg.sender, pending); 131 | } 132 | } 133 | uint256 feeInCYC = 0; 134 | if (_amount > 0) { 135 | lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); 136 | uint256 entranceFee = _amount.mul(entranceFeeRate).div(10000); 137 | if (entranceFee > 0) { 138 | IERC20 wct = wrappedCoin; 139 | require(lpToken.approve(address(router), entranceFee), "failed to approve router"); 140 | (uint256 wcAmount, uint256 cycAmount) = router.removeLiquidity(address(wct), address(cycToken), entranceFee, 0, 0, address(this), block.timestamp.mul(2)); 141 | if (wcAmount > 0) { 142 | address[] memory path = new address[](2); 143 | path[0] = address(wct); 144 | path[1] = address(cycToken); 145 | require(wct.approve(address(router), wcAmount), "failed to approve router"); 146 | uint256[] memory amounts = router.swapExactTokensForTokens(wcAmount, 0, path, address(this), block.timestamp.mul(2)); 147 | feeInCYC = cycAmount.add(amounts[1]); 148 | } else { 149 | feeInCYC = cycAmount; 150 | } 151 | if (feeInCYC > 0) { 152 | require(cycToken.burn(feeInCYC), "failed to burn cyc token"); 153 | } 154 | _amount = _amount.sub(entranceFee); 155 | } 156 | user.amount = user.amount.add(_amount); 157 | } 158 | user.rewardDebt = user.amount.mul(accCYCPerShare).div(1e12); 159 | emit Deposit(msg.sender, _amount, feeInCYC); 160 | } 161 | 162 | // Withdraw LP tokens from Aeolus. 163 | function withdraw(uint256 _amount) public { 164 | UserInfo storage user = userInfo[msg.sender]; 165 | require(user.amount >= _amount, "withdraw: not good"); 166 | updateBlockReward(); 167 | uint256 pending = user.amount.mul(accCYCPerShare).div(1e12).sub(user.rewardDebt); 168 | if (pending > 0) { 169 | safeCYCTransfer(msg.sender, pending); 170 | } 171 | if (_amount > 0) { 172 | user.amount = user.amount.sub(_amount); 173 | lpToken.safeTransfer(address(msg.sender), _amount); 174 | } 175 | user.rewardDebt = user.amount.mul(accCYCPerShare).div(1e12); 176 | emit Withdraw(msg.sender, _amount); 177 | } 178 | 179 | // Withdraw without caring about rewards. EMERGENCY ONLY. 180 | function emergencyWithdraw() public { 181 | UserInfo storage user = userInfo[msg.sender]; 182 | uint256 amount = user.amount; 183 | user.amount = 0; 184 | user.rewardDebt = 0; 185 | lpToken.safeTransfer(address(msg.sender), amount); 186 | emit EmergencyWithdraw(msg.sender, amount); 187 | } 188 | 189 | // Safe CYC transfer function, just in case if rounding error causes pool to not have enough CYCs. 190 | function safeCYCTransfer(address _to, uint256 _amount) internal { 191 | uint256 cycBalance = cycToken.balanceOf(address(this)); 192 | if (_amount > cycBalance) { 193 | _amount = cycBalance; 194 | } 195 | rewardToDistribute -= _amount; 196 | require(cycToken.transfer(_to, _amount), "failed to transfer cyc token"); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /contracts/AeolusV2dot1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./ownership/Ownable.sol"; 5 | import "./token/IERC20.sol"; 6 | import "./token/IMintableToken.sol"; 7 | import "./token/SafeERC20.sol"; 8 | import "./uniswapv2/IRouter.sol"; 9 | 10 | // Aeolus is the master of Cyclone tokens. He can distribute CYC and he is a fair guy. 11 | // 12 | // Note that it's ownable and the owner wields tremendous power. The ownership 13 | // will be transferred to a governance smart contract once CYC is sufficiently 14 | // distributed and the community can show to govern itself. 15 | // 16 | // Have fun reading it. Hopefully it's bug-free. God bless. 17 | contract AeolusV2dot1 is Ownable { 18 | using SafeMath for uint256; 19 | using SafeERC20 for IERC20; 20 | 21 | // Info of each user. 22 | struct UserInfo { 23 | uint256 amount; // How many LP tokens the user has provided. 24 | uint256 rewardDebt; // Reward debt. See explanation below. 25 | // 26 | // We do some fancy math here. Basically, any point in time, the amount of CYCs 27 | // entitled to a user but is pending to be distributed is: 28 | // 29 | // pending reward = (user.amount * accCYCPerShare) - user.rewardDebt 30 | // 31 | // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: 32 | // 1. Update accCYCPerShare and lastRewardBlock 33 | // 2. User receives the pending reward sent to his/her address. 34 | // 3. User's `amount` gets updated. 35 | // 4. User's `rewardDebt` gets updated. 36 | } 37 | 38 | 39 | // Address of LP token contract. 40 | IERC20 public lpToken; 41 | // Accumulated CYCs per share, times 1e12. See below. 42 | uint256 public accCYCPerShare; 43 | // Last block reward block height 44 | uint256 public lastRewardBlock; 45 | // Reward per block 46 | uint256 public rewardPerBlock; 47 | // Reward to distribute 48 | uint256 public rewardToDistribute; 49 | // Entrance Fee Rate 50 | uint256 public entranceFeeRate; 51 | 52 | IERC20 public wrappedCoin; 53 | IRouter public router; 54 | // The Cyclone TOKEN 55 | IMintableToken public cycToken; 56 | 57 | // Info of each user that stakes LP tokens. 58 | mapping (address => UserInfo) public userInfo; 59 | 60 | event RewardAdded(uint256 amount, bool isBlockReward); 61 | event Deposit(address indexed user, uint256 amount, uint256 fee); 62 | event Withdraw(address indexed user, uint256 amount); 63 | event EmergencyWithdraw(address indexed user, uint256 amount); 64 | 65 | constructor(IMintableToken _cycToken, IERC20 _lpToken, address _router, IERC20 _wrappedCoin) public { 66 | cycToken = _cycToken; 67 | lastRewardBlock = block.number; 68 | lpToken = _lpToken; 69 | router = IRouter(_router); 70 | wrappedCoin = _wrappedCoin; 71 | require(_lpToken.approve(_router, uint256(-1)), "failed to approve router"); 72 | require(_wrappedCoin.approve(_router, uint256(-1)), "failed to approve router"); 73 | } 74 | 75 | function setEntranceFeeRate(uint256 _entranceFeeRate) public onlyOwner { 76 | require(_entranceFeeRate < 10000, "invalid entrance fee rate"); 77 | entranceFeeRate = _entranceFeeRate; 78 | } 79 | 80 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner { 81 | updateBlockReward(); 82 | rewardPerBlock = _rewardPerBlock; 83 | } 84 | 85 | function rewardPending() internal view returns (uint256) { 86 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 87 | uint256 cycBalance = cycToken.balanceOf(address(this)).sub(rewardToDistribute); 88 | if (cycBalance < reward) { 89 | return cycBalance; 90 | } 91 | return reward; 92 | } 93 | 94 | // View function to see pending reward on frontend. 95 | function pendingReward(address _user) external view returns (uint256) { 96 | UserInfo storage user = userInfo[_user]; 97 | uint256 acps = accCYCPerShare; 98 | if (rewardPerBlock > 0) { 99 | uint256 lpSupply = lpToken.balanceOf(address(this)); 100 | if (block.number > lastRewardBlock && lpSupply > 0) { 101 | acps = acps.add(rewardPending().mul(1e12).div(lpSupply)); 102 | } 103 | } 104 | 105 | return user.amount.mul(acps).div(1e12).sub(user.rewardDebt); 106 | } 107 | 108 | // Update reward variables to be up-to-date. 109 | function updateBlockReward() public { 110 | if (block.number <= lastRewardBlock || rewardPerBlock == 0) { 111 | return; 112 | } 113 | uint256 lpSupply = lpToken.balanceOf(address(this)); 114 | uint256 reward = rewardPending(); 115 | if (lpSupply == 0 || reward == 0) { 116 | lastRewardBlock = block.number; 117 | return; 118 | } 119 | rewardToDistribute = rewardToDistribute.add(reward); 120 | emit RewardAdded(reward, true); 121 | lastRewardBlock = block.number; 122 | accCYCPerShare = accCYCPerShare.add(reward.mul(1e12).div(lpSupply)); 123 | } 124 | 125 | // Deposit LP tokens to Aeolus for CYC allocation. 126 | function deposit(uint256 _amount) public { 127 | updateBlockReward(); 128 | UserInfo storage user = userInfo[msg.sender]; 129 | uint256 originAmount = user.amount; 130 | uint256 acps = accCYCPerShare; 131 | if (originAmount > 0) { 132 | uint256 pending = originAmount.mul(acps).div(1e12).sub(user.rewardDebt); 133 | if (pending > 0) { 134 | safeCYCTransfer(msg.sender, pending); 135 | } 136 | } 137 | uint256 feeInCYC = 0; 138 | if (_amount > 0) { 139 | lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); 140 | uint256 entranceFee = _amount.mul(entranceFeeRate).div(10000); 141 | if (entranceFee > 0) { 142 | IERC20 wct = wrappedCoin; 143 | (uint256 wcAmount, uint256 cycAmount) = router.removeLiquidity(address(wct), address(cycToken), entranceFee, 0, 0, address(this), block.timestamp.mul(2)); 144 | if (wcAmount > 0) { 145 | address[] memory path = new address[](2); 146 | path[0] = address(wct); 147 | path[1] = address(cycToken); 148 | uint256[] memory amounts = router.swapExactTokensForTokens(wcAmount, 0, path, address(this), block.timestamp.mul(2)); 149 | feeInCYC = cycAmount.add(amounts[1]); 150 | } else { 151 | feeInCYC = cycAmount; 152 | } 153 | if (feeInCYC > 0) { 154 | require(cycToken.burn(feeInCYC), "failed to burn cyc token"); 155 | } 156 | _amount = _amount.sub(entranceFee); 157 | } 158 | user.amount = originAmount.add(_amount); 159 | } 160 | user.rewardDebt = user.amount.mul(acps).div(1e12); 161 | emit Deposit(msg.sender, _amount, feeInCYC); 162 | } 163 | 164 | // Withdraw LP tokens from Aeolus. 165 | function withdraw(uint256 _amount) public { 166 | UserInfo storage user = userInfo[msg.sender]; 167 | uint256 originAmount = user.amount; 168 | require(originAmount >= _amount, "withdraw: not good"); 169 | updateBlockReward(); 170 | uint256 acps = accCYCPerShare; 171 | uint256 pending = originAmount.mul(acps).div(1e12).sub(user.rewardDebt); 172 | if (pending > 0) { 173 | safeCYCTransfer(msg.sender, pending); 174 | } 175 | if (_amount > 0) { 176 | user.amount = originAmount.sub(_amount); 177 | lpToken.safeTransfer(address(msg.sender), _amount); 178 | } 179 | user.rewardDebt = user.amount.mul(acps).div(1e12); 180 | emit Withdraw(msg.sender, _amount); 181 | } 182 | 183 | // Withdraw without caring about rewards. EMERGENCY ONLY. 184 | function emergencyWithdraw() public { 185 | UserInfo storage user = userInfo[msg.sender]; 186 | uint256 amount = user.amount; 187 | user.amount = 0; 188 | user.rewardDebt = 0; 189 | lpToken.safeTransfer(address(msg.sender), amount); 190 | emit EmergencyWithdraw(msg.sender, amount); 191 | } 192 | 193 | // Safe CYC transfer function, just in case if rounding error causes pool to not have enough CYCs. 194 | function safeCYCTransfer(address _to, uint256 _amount) internal { 195 | IMintableToken token = cycToken; 196 | uint256 cycBalance = token.balanceOf(address(this)); 197 | if (_amount > cycBalance) { 198 | _amount = cycBalance; 199 | } 200 | rewardToDistribute = rewardToDistribute.sub(_amount); 201 | require(token.transfer(_to, _amount), "failed to transfer cyc token"); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /contracts/AeolusV2dot2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./ownership/Ownable.sol"; 5 | import "./token/IERC20.sol"; 6 | import "./token/IMintableToken.sol"; 7 | import "./token/SafeERC20.sol"; 8 | import "./uniswapv1/IExchange.sol"; 9 | 10 | // Aeolus is the master of Cyclone tokens. He can distribute CYC and he is a fair guy. 11 | // 12 | // Note that it's ownable and the owner wields tremendous power. The ownership 13 | // will be transferred to a governance smart contract once CYC is sufficiently 14 | // distributed and the community can show to govern itself. 15 | // 16 | // Have fun reading it. Hopefully it's bug-free. God bless. 17 | contract AeolusV2dot2 is Ownable { 18 | using SafeMath for uint256; 19 | using SafeERC20 for IERC20; 20 | 21 | // Info of each user. 22 | struct UserInfo { 23 | uint256 amount; // How many LP tokens the user has provided. 24 | uint256 rewardDebt; // Reward debt. See explanation below. 25 | // 26 | // We do some fancy math here. Basically, any point in time, the amount of CYCs 27 | // entitled to a user but is pending to be distributed is: 28 | // 29 | // pending reward = (user.amount * accCYCPerShare) - user.rewardDebt 30 | // 31 | // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: 32 | // 1. Update accCYCPerShare and lastRewardBlock 33 | // 2. User receives the pending reward sent to his/her address. 34 | // 3. User's `amount` gets updated. 35 | // 4. User's `rewardDebt` gets updated. 36 | } 37 | 38 | 39 | // Address of LP token contract. 40 | IERC20 public lpToken; 41 | // Accumulated CYCs per share, times 1e12. See below. 42 | uint256 public accCYCPerShare; 43 | // Last block reward block height 44 | uint256 public lastRewardBlock; 45 | // Reward per block 46 | uint256 public rewardPerBlock; 47 | // Reward to distribute 48 | uint256 public rewardToDistribute; 49 | // Entrance Fee Rate 50 | uint256 public entranceFeeRate; 51 | 52 | IExchange public exchange; 53 | // The Cyclone TOKEN 54 | IMintableToken public cycToken; 55 | 56 | // Info of each user that stakes LP tokens. 57 | mapping (address => UserInfo) public userInfo; 58 | 59 | event RewardAdded(uint256 amount, bool isBlockReward); 60 | event Deposit(address indexed user, uint256 amount, uint256 fee); 61 | event Withdraw(address indexed user, uint256 amount); 62 | event EmergencyWithdraw(address indexed user, uint256 amount); 63 | 64 | constructor(IMintableToken _cycToken, address payable _exchange) public { 65 | cycToken = _cycToken; 66 | lastRewardBlock = block.number; 67 | lpToken = IERC20(_exchange); 68 | exchange = IExchange(_exchange); 69 | } 70 | 71 | function() external payable { 72 | } 73 | 74 | function setEntranceFeeRate(uint256 _entranceFeeRate) public onlyOwner { 75 | require(_entranceFeeRate < 10000, "invalid entrance fee rate"); 76 | entranceFeeRate = _entranceFeeRate; 77 | } 78 | 79 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner { 80 | updateBlockReward(); 81 | rewardPerBlock = _rewardPerBlock; 82 | } 83 | 84 | function rewardPending() internal view returns (uint256) { 85 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 86 | uint256 cycBalance = cycToken.balanceOf(address(this)).sub(rewardToDistribute); 87 | if (cycBalance < reward) { 88 | return cycBalance; 89 | } 90 | return reward; 91 | } 92 | 93 | // View function to see pending reward on frontend. 94 | function pendingReward(address _user) external view returns (uint256) { 95 | UserInfo storage user = userInfo[_user]; 96 | uint256 acps = accCYCPerShare; 97 | if (rewardPerBlock > 0) { 98 | uint256 lpSupply = lpToken.balanceOf(address(this)); 99 | if (block.number > lastRewardBlock && lpSupply > 0) { 100 | acps = acps.add(rewardPending().mul(1e12).div(lpSupply)); 101 | } 102 | } 103 | 104 | return user.amount.mul(acps).div(1e12).sub(user.rewardDebt); 105 | } 106 | 107 | // Update reward variables to be up-to-date. 108 | function updateBlockReward() public { 109 | if (block.number <= lastRewardBlock || rewardPerBlock == 0) { 110 | return; 111 | } 112 | uint256 lpSupply = lpToken.balanceOf(address(this)); 113 | uint256 reward = rewardPending(); 114 | if (lpSupply == 0 || reward == 0) { 115 | lastRewardBlock = block.number; 116 | return; 117 | } 118 | rewardToDistribute = rewardToDistribute.add(reward); 119 | emit RewardAdded(reward, true); 120 | lastRewardBlock = block.number; 121 | accCYCPerShare = accCYCPerShare.add(reward.mul(1e12).div(lpSupply)); 122 | } 123 | 124 | // Deposit LP tokens to Aeolus for CYC allocation. 125 | function deposit(uint256 _amount) public { 126 | updateBlockReward(); 127 | UserInfo storage user = userInfo[msg.sender]; 128 | uint256 originAmount = user.amount; 129 | uint256 acps = accCYCPerShare; 130 | if (originAmount > 0) { 131 | uint256 pending = originAmount.mul(acps).div(1e12).sub(user.rewardDebt); 132 | if (pending > 0) { 133 | safeCYCTransfer(msg.sender, pending); 134 | } 135 | } 136 | uint256 feeInCYC = 0; 137 | if (_amount > 0) { 138 | lpToken.safeTransferFrom(address(msg.sender), address(this), _amount); 139 | uint256 entranceFee = _amount.mul(entranceFeeRate).div(10000); 140 | if (entranceFee > 0) { 141 | (uint256 iotxAmount, uint256 cycAmount) = exchange.removeLiquidity(entranceFee, 1, 1, block.timestamp.mul(2)); 142 | feeInCYC = cycAmount.add(exchange.iotxToTokenSwapInput.value(iotxAmount)(1, block.timestamp.mul(2))); 143 | require(cycToken.burn(feeInCYC), "failed to burn cyc token"); 144 | _amount = _amount.sub(entranceFee); 145 | } 146 | user.amount = originAmount.add(_amount); 147 | } 148 | user.rewardDebt = user.amount.mul(acps).div(1e12); 149 | emit Deposit(msg.sender, _amount, feeInCYC); 150 | } 151 | 152 | // Withdraw LP tokens from Aeolus. 153 | function withdraw(uint256 _amount) public { 154 | UserInfo storage user = userInfo[msg.sender]; 155 | uint256 originAmount = user.amount; 156 | require(originAmount >= _amount, "withdraw: not good"); 157 | updateBlockReward(); 158 | uint256 acps = accCYCPerShare; 159 | uint256 pending = originAmount.mul(acps).div(1e12).sub(user.rewardDebt); 160 | if (pending > 0) { 161 | safeCYCTransfer(msg.sender, pending); 162 | } 163 | if (_amount > 0) { 164 | user.amount = originAmount.sub(_amount); 165 | lpToken.safeTransfer(address(msg.sender), _amount); 166 | } 167 | user.rewardDebt = user.amount.mul(acps).div(1e12); 168 | emit Withdraw(msg.sender, _amount); 169 | } 170 | 171 | // Withdraw without caring about rewards. EMERGENCY ONLY. 172 | function emergencyWithdraw() public { 173 | UserInfo storage user = userInfo[msg.sender]; 174 | uint256 amount = user.amount; 175 | user.amount = 0; 176 | user.rewardDebt = 0; 177 | lpToken.safeTransfer(address(msg.sender), amount); 178 | emit EmergencyWithdraw(msg.sender, amount); 179 | } 180 | 181 | // Safe CYC transfer function, just in case if rounding error causes pool to not have enough CYCs. 182 | function safeCYCTransfer(address _to, uint256 _amount) internal { 183 | IMintableToken token = cycToken; 184 | uint256 cycBalance = token.balanceOf(address(this)); 185 | if (_amount > cycBalance) { 186 | _amount = cycBalance; 187 | } 188 | rewardToDistribute = rewardToDistribute.sub(_amount); 189 | require(token.transfer(_to, _amount), "failed to transfer cyc token"); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /contracts/Cyclone.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./token/IMintableToken.sol"; 5 | import "./utils/Address.sol"; 6 | import "./zksnarklib/MerkleTreeWithHistory.sol"; 7 | import "./zksnarklib/IVerifier.sol"; 8 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 9 | 10 | contract IAeolus { 11 | function addReward(uint256 amount) external; 12 | } 13 | 14 | contract Cyclone is MerkleTreeWithHistory, ReentrancyGuard { 15 | 16 | using SafeMath for uint256; 17 | 18 | uint256 public initDenomination; // (10K or 100k or 1M) * 10^18 19 | uint256 public denominationRound; 20 | mapping(bytes32 => bool) public nullifierHashes; 21 | mapping(bytes32 => bool) public commitments; // we store all commitments just to prevent accidental deposits with the same commitment 22 | IVerifier public verifier; 23 | IMintableToken public cycToken; 24 | IAeolus public aeolus; // liquidity mining pool 25 | address public govDAO; 26 | uint256 public oneCYC; // 10 ** cycToken.decimals 27 | uint256 public numOfShares; 28 | uint256 public maxNumOfShares; // 0 stands for unlimited 29 | uint256 public depositLpIR; // when deposit, a small portion minted CYC is given to the liquidity mining pool 30 | uint256 public withdrawLpIR; // when withdraw, a small portion of bought CYC is donated to the liquidity mining pool 31 | uint256 public cashbackRate; // when deposit, a portion of minted CYC is given to the depositor 32 | uint256 public buybackRate; // when withdraw, a portion of bought CYC will be burnt 33 | uint256 public minCYCPrice; // max amount of CYC minted to the depositor 34 | uint256 public apIncentiveRate; // when withdraw, a certain portion is donated to the pool 35 | 36 | modifier onlyGovDAO { 37 | // Start with an governance DAO address and will transfer to a governance DAO, e.g., Timelock + GovernorAlpha, after launch 38 | require(msg.sender == govDAO, "Only Governance DAO can call this function."); 39 | _; 40 | } 41 | 42 | modifier notContract { 43 | require(msg.sender == tx.origin && !Address.isContract(msg.sender), "Caller cannot be a contract"); 44 | _; 45 | } 46 | 47 | event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp, uint256 denomination); 48 | event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 denomination, uint256 cost, uint256 fee); 49 | event ConfigUpdated(uint256 depositLpIR, uint256 cashbackRate, uint256 withdrawLpIR, uint256 buybackRate, uint256 apIncentiveRate, uint256 minCYCPrice, uint256 maxNumOfShares); 50 | 51 | /** 52 | @dev The constructor 53 | @param _verifier the address of SNARK verifier for this contract 54 | @param _initDenomination transfer amount for each deposit 55 | @param _merkleTreeHeight the height of deposits' Merkle Tree 56 | @param _govDAO governance DAO address 57 | */ 58 | constructor( 59 | IVerifier _verifier, 60 | IMintableToken _cyctoken, 61 | IAeolus _aeolus, 62 | uint256 _initDenomination, 63 | uint256 _denominationRound, 64 | uint32 _merkleTreeHeight, 65 | address _govDAO 66 | ) MerkleTreeWithHistory(_merkleTreeHeight) public { 67 | require(_initDenomination > 0, "initial denomination should be greater than 0"); 68 | require(_denominationRound > 0, "invalid denomination round"); 69 | verifier = _verifier; 70 | cycToken = _cyctoken; 71 | govDAO = _govDAO; 72 | aeolus = _aeolus; 73 | oneCYC = 10 ** 18; 74 | initDenomination = _initDenomination; 75 | denominationRound = _denominationRound; 76 | numOfShares = 0; 77 | maxNumOfShares = 0; 78 | } 79 | 80 | function getDepositParameters() external view returns (uint256, uint256); 81 | 82 | function getWithdrawDenomination() external view returns (uint256); 83 | 84 | /** 85 | @dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance. 86 | @param _commitment the note commitment, which is PedersenHash(nullifier + secret) 87 | @param _minCashbackAmount is the minimum cashback CYCs 88 | */ 89 | function deposit(bytes32 _commitment, uint256 _minCashbackAmount) external payable nonReentrant notContract { 90 | require(!commitments[_commitment], "The commitment has been submitted"); 91 | require(maxNumOfShares == 0 || numOfShares < maxNumOfShares, "hit share limit"); 92 | uint32 insertedIndex = _insert(_commitment); 93 | commitments[_commitment] = true; 94 | uint256 denomination = _processDeposit(_minCashbackAmount); 95 | numOfShares += 1; 96 | emit Deposit(_commitment, insertedIndex, block.timestamp, denomination); 97 | } 98 | 99 | /** @dev this function is defined in a child contract */ 100 | function _processDeposit(uint256 _minCashbackAmount) internal returns (uint256); 101 | 102 | function _weightedAmount(uint256 _amount, uint256 _num) internal view returns (uint256) { 103 | // if maxNumOfShares is 0, return _amount 104 | if (maxNumOfShares == 0) { 105 | return _amount; 106 | } 107 | if (_num.mul(4) < maxNumOfShares) { 108 | return _amount.mul(maxNumOfShares.sub(_num.mul(2))).div(maxNumOfShares); 109 | } 110 | if (_num.mul(2) < maxNumOfShares) { 111 | return _amount.mul(maxNumOfShares.mul(3).sub(_num.mul(4))).div(maxNumOfShares).div(4); 112 | } 113 | 114 | return _amount.mul(maxNumOfShares.sub(_num)).div(maxNumOfShares).div(2); 115 | } 116 | 117 | /** 118 | @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs 119 | `input` array consists of: 120 | - merkle root of all deposits in the contract 121 | - hash of unique deposit nullifier to prevent double spends 122 | - the recipient of funds 123 | - optional fee that goes to the transaction sender (usually a relay) 124 | */ 125 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) external payable nonReentrant notContract { 126 | require(!nullifierHashes[_nullifierHash], "The note has been already spent"); 127 | require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one 128 | require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund]), "Invalid withdraw proof"); 129 | 130 | nullifierHashes[_nullifierHash] = true; 131 | (uint256 denomination, uint256 cost) = _processWithdraw(_recipient, _relayer, _fee, _refund); 132 | numOfShares -= 1; 133 | emit Withdrawal(_recipient, _nullifierHash, _relayer, denomination, cost, _fee); 134 | } 135 | 136 | /** @dev this function is defined in a child contract */ 137 | function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal returns (uint256, uint256); 138 | 139 | /** @dev whether a note is already spent */ 140 | function isSpent(bytes32 _nullifierHash) public view returns(bool) { 141 | return nullifierHashes[_nullifierHash]; 142 | } 143 | 144 | /** @dev whether an array of notes is already spent */ 145 | function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) { 146 | spent = new bool[](_nullifierHashes.length); 147 | for(uint i = 0; i < _nullifierHashes.length; i++) { 148 | if (isSpent(_nullifierHashes[i])) { 149 | spent[i] = true; 150 | } 151 | } 152 | } 153 | 154 | /** 155 | @dev allow governance DAO to update SNARK verification keys. This is needed to 156 | update keys if tornado.cash update their keys in production. 157 | */ 158 | function updateVerifier(address _newVerifier) external onlyGovDAO { 159 | verifier = IVerifier(_newVerifier); 160 | } 161 | 162 | /** @dev governance DAO can change his address */ 163 | function changeGovDAO(address _newGovDAO) external onlyGovDAO { 164 | govDAO = _newGovDAO; 165 | } 166 | 167 | /** @dev governance DAO can update config */ 168 | function updateConfig(uint256 _depositLpIR, uint256 _cashbackRate, uint256 _withdrawLpIR, uint256 _buybackRate, uint256 _apIncentiveRate, uint256 _minCYCPrice, uint256 _maxNumOfShares) external onlyGovDAO { 169 | require(_depositLpIR + _cashbackRate <= 10000, "invalid deposit related rates"); 170 | require(_withdrawLpIR + _buybackRate + _apIncentiveRate <= 10000, "invalid withdraw related rates"); 171 | depositLpIR = _depositLpIR; 172 | cashbackRate = _cashbackRate; 173 | withdrawLpIR = _withdrawLpIR; 174 | buybackRate = _buybackRate; 175 | apIncentiveRate = _apIncentiveRate; 176 | minCYCPrice = _minCYCPrice; 177 | maxNumOfShares = _maxNumOfShares; 178 | emit ConfigUpdated(depositLpIR, cashbackRate, withdrawLpIR, buybackRate, apIncentiveRate, minCYCPrice, maxNumOfShares); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /contracts/CycloneV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./token/IMintableToken.sol"; 5 | import "./utils/Address.sol"; 6 | import "./zksnarklib/MerkleTreeWithHistory.sol"; 7 | import "./zksnarklib/IVerifier.sol"; 8 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 9 | 10 | contract CycloneV2 is MerkleTreeWithHistory, ReentrancyGuard { 11 | 12 | using SafeMath for uint256; 13 | uint256 public tokenDenomination; // (10K or 100k or 1M) * 10^18 14 | uint256 public coinDenomination; 15 | mapping(bytes32 => bool) public nullifierHashes; 16 | mapping(bytes32 => bool) public commitments; // we store all commitments just to prevent accidental deposits with the same commitment 17 | IVerifier public verifier; 18 | IERC20 public token; 19 | IMintableToken public cycToken; 20 | address public govDAO; 21 | uint256 public numOfShares; 22 | uint256 public lastRewardBlock; 23 | uint256 public rewardPerBlock; 24 | uint256 public accumulateCYC; 25 | uint256 public anonymityRate; 26 | 27 | modifier onlyGovDAO { 28 | // Start with an governance DAO address and will transfer to a governance DAO, e.g., Timelock + GovernorAlpha, after launch 29 | require(msg.sender == govDAO, "Only Governance DAO can call this function."); 30 | _; 31 | } 32 | 33 | event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp, uint256 cycDenomination); 34 | event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 reward, uint256 anonymityFee, uint256 relayerFee); 35 | event RewardPerBlockUpdated(uint256 oldValue, uint256 newValue); 36 | 37 | /** 38 | @dev The constructor 39 | @param _verifier the address of SNARK verifier for this contract 40 | @param _merkleTreeHeight the height of deposits' Merkle Tree 41 | @param _govDAO governance DAO address 42 | */ 43 | constructor( 44 | address _govDAO, 45 | IERC20 _token, 46 | IMintableToken _cyctoken, 47 | uint256 _coinDenomination, 48 | uint256 _tokenDenomination, 49 | uint256 _startBlock, 50 | IVerifier _verifier, 51 | uint32 _merkleTreeHeight 52 | ) MerkleTreeWithHistory(_merkleTreeHeight) public { 53 | verifier = _verifier; 54 | cycToken = _cyctoken; 55 | token = _token; 56 | govDAO = _govDAO; 57 | if (_startBlock < block.number) { 58 | lastRewardBlock = block.number; 59 | } else { 60 | lastRewardBlock = _startBlock; 61 | } 62 | coinDenomination = _coinDenomination; 63 | tokenDenomination = _tokenDenomination; 64 | numOfShares = 0; 65 | } 66 | 67 | function calcAccumulateCYC() internal view returns (uint256) { 68 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 69 | uint256 remaining = cycToken.balanceOf(address(this)).sub(accumulateCYC); 70 | if (remaining < reward) { 71 | reward = remaining; 72 | } 73 | return accumulateCYC.add(reward); 74 | } 75 | 76 | function updateBlockReward() public { 77 | uint256 blockNumber = block.number; 78 | if (blockNumber <= lastRewardBlock || rewardPerBlock == 0) { 79 | return; 80 | } 81 | accumulateCYC = calcAccumulateCYC(); 82 | // always update lastRewardBlock no matter there is sufficient reward or not 83 | lastRewardBlock = blockNumber; 84 | } 85 | 86 | function cycDenomination() public view returns (uint256) { 87 | if (numOfShares == 0) { 88 | return 0; 89 | } 90 | uint256 blockNumber = block.number; 91 | uint256 accCYC = accumulateCYC; 92 | if (blockNumber > lastRewardBlock && rewardPerBlock > 0) { 93 | accCYC = calcAccumulateCYC(); 94 | } 95 | return accCYC.add(numOfShares - 1).div(numOfShares); 96 | } 97 | 98 | /** 99 | @dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance. 100 | @param _commitment the note commitment, which is PedersenHash(nullifier + secret) 101 | */ 102 | function deposit(bytes32 _commitment) external payable nonReentrant { 103 | require(!commitments[_commitment], "The commitment has been submitted"); 104 | require(msg.value >= coinDenomination, "insufficient coin amount"); 105 | uint256 refund = msg.value - coinDenomination; 106 | uint32 insertedIndex = _insert(_commitment); 107 | commitments[_commitment] = true; 108 | updateBlockReward(); 109 | uint256 cycDeno = cycDenomination(); 110 | if (cycDeno > 0) { 111 | require(cycToken.transferFrom(msg.sender, address(this), cycDeno), "insufficient CYC allowance"); 112 | } 113 | uint256 td = tokenDenomination; 114 | if (td > 0) { 115 | require(token.transferFrom(msg.sender, address(this), td), "insufficient allowance"); 116 | } 117 | accumulateCYC += cycDeno; 118 | numOfShares += 1; 119 | if (refund > 0) { 120 | (bool success, ) = msg.sender.call.value(refund)(""); 121 | require(success, "failed to refund"); 122 | } 123 | emit Deposit(_commitment, insertedIndex, block.timestamp, cycDeno); 124 | } 125 | 126 | /** 127 | @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs 128 | `input` array consists of: 129 | - merkle root of all deposits in the contract 130 | - hash of unique deposit nullifier to prevent double spends 131 | - the recipient of funds 132 | - optional fee that goes to the transaction sender (usually a relay) 133 | */ 134 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _feeRate, uint256 _refund) external payable nonReentrant { 135 | require(_refund == 0, "refund is not zero"); 136 | require(!Address.isContract(_recipient), "recipient of cannot be contract"); 137 | require(_feeRate.add(anonymityRate) < 10000, "invalid fee rate"); 138 | require(!nullifierHashes[_nullifierHash], "The note has been already spent"); 139 | require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one 140 | require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _feeRate, _refund]), "Invalid withdraw proof"); 141 | 142 | nullifierHashes[_nullifierHash] = true; 143 | uint256 cd = coinDenomination; 144 | if (cd > 0) { 145 | (bool success,) = _recipient.call.value(cd)(""); 146 | require(success, "failed to withdraw coin"); 147 | } 148 | uint256 td = tokenDenomination; 149 | if (td > 0) { 150 | require(safeTransfer(token, _recipient, td), "failed to withdraw token"); 151 | } 152 | updateBlockReward(); 153 | uint256 anonymityFee = 0; 154 | uint256 relayerFee = 0; 155 | // numOfShares should be larger than 0 156 | uint256 cycDeno = accumulateCYC.div(numOfShares); 157 | if (cycDeno > 0) { 158 | accumulateCYC -= cycDeno; 159 | anonymityFee = cycDeno.mul(anonymityRate).div(10000); 160 | if (anonymityFee > 0) { 161 | require(cycToken.burn(anonymityFee), "failed to burn CYC"); 162 | } 163 | relayerFee = cycDeno.mul(_feeRate).div(10000); 164 | if (relayerFee > 0) { 165 | require(safeTransfer(cycToken, _relayer, relayerFee), "failed to send CYC to relayer"); 166 | } 167 | require(safeTransfer(cycToken, _recipient, cycDeno.sub(anonymityFee).sub(relayerFee)), "failed to reward CYC"); 168 | } 169 | numOfShares -= 1; 170 | emit Withdrawal(_recipient, _nullifierHash, _relayer, cycDeno, anonymityFee, relayerFee); 171 | } 172 | 173 | /** @dev whether a note is already spent */ 174 | function isSpent(bytes32 _nullifierHash) public view returns(bool) { 175 | return nullifierHashes[_nullifierHash]; 176 | } 177 | 178 | /** @dev whether an array of notes is already spent */ 179 | function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) { 180 | spent = new bool[](_nullifierHashes.length); 181 | for(uint i = 0; i < _nullifierHashes.length; i++) { 182 | if (isSpent(_nullifierHashes[i])) { 183 | spent[i] = true; 184 | } 185 | } 186 | } 187 | 188 | /** 189 | @dev allow governance DAO to update SNARK verification keys. This is needed to 190 | update keys if tornado.cash update their keys in production. 191 | */ 192 | function updateVerifier(address _newVerifier) external onlyGovDAO { 193 | verifier = IVerifier(_newVerifier); 194 | } 195 | 196 | /** @dev governance DAO can change his address */ 197 | function changeGovDAO(address _newGovDAO) external onlyGovDAO { 198 | govDAO = _newGovDAO; 199 | } 200 | 201 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyGovDAO { 202 | updateBlockReward(); 203 | rewardPerBlock = _rewardPerBlock; 204 | } 205 | 206 | function setAnonymityRate(uint256 _rate) public onlyGovDAO { 207 | require(_rate < 10000, "invalid anonymity rate"); 208 | anonymityRate = _rate; 209 | } 210 | 211 | // Safe transfer function, just in case if rounding error causes pool to not have enough CYCs. 212 | function safeTransfer(IERC20 _token, address _to, uint256 _amount) internal returns (bool) { 213 | uint256 balance = _token.balanceOf(address(this)); 214 | if (_amount > balance) { 215 | return _token.transfer(_to, balance); 216 | } 217 | return _token.transfer(_to, _amount); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /contracts/CycloneV2dot1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./token/IMintableToken.sol"; 5 | import "./utils/Address.sol"; 6 | import "./zksnarklib/MerkleTreeWithHistory.sol"; 7 | import "./zksnarklib/IVerifier.sol"; 8 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 9 | 10 | contract CycloneV2dot1 is MerkleTreeWithHistory, ReentrancyGuard { 11 | 12 | using SafeMath for uint256; 13 | uint256 public tokenDenomination; // (10K or 100k or 1M) * 10^18 14 | uint256 public coinDenomination; 15 | uint256 public initCYCDenomination; 16 | mapping(bytes32 => bool) public nullifierHashes; 17 | mapping(bytes32 => bool) public commitments; // we store all commitments just to prevent accidental deposits with the same commitment 18 | IVerifier public verifier; 19 | IERC20 public token; 20 | IMintableToken public cycToken; 21 | address public govDAO; 22 | uint256 public numOfShares; 23 | uint256 public lastRewardBlock; 24 | uint256 public rewardPerBlock; 25 | uint256 public accumulateCYC; 26 | uint256 public anonymityRate; 27 | 28 | modifier onlyGovDAO { 29 | // Start with an governance DAO address and will transfer to a governance DAO, e.g., Timelock + GovernorAlpha, after launch 30 | require(msg.sender == govDAO, "Only Governance DAO can call this function."); 31 | _; 32 | } 33 | 34 | event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp, uint256 cycDenomination); 35 | event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 reward, uint256 anonymityFee, uint256 relayerFee); 36 | event RewardPerBlockUpdated(uint256 oldValue, uint256 newValue); 37 | 38 | /** 39 | @dev The constructor 40 | @param _verifier the address of SNARK verifier for this contract 41 | @param _merkleTreeHeight the height of deposits' Merkle Tree 42 | @param _govDAO governance DAO address 43 | */ 44 | constructor( 45 | address _govDAO, 46 | IERC20 _token, 47 | IMintableToken _cycToken, 48 | uint256 _initCYCDenomination, 49 | uint256 _coinDenomination, 50 | uint256 _tokenDenomination, 51 | uint256 _startBlock, 52 | IVerifier _verifier, 53 | uint32 _merkleTreeHeight 54 | ) MerkleTreeWithHistory(_merkleTreeHeight) public { 55 | require(address(_token) != address(_cycToken), "token cannot be identical to CYC token"); 56 | verifier = _verifier; 57 | cycToken = _cycToken; 58 | token = _token; 59 | govDAO = _govDAO; 60 | if (_startBlock < block.number) { 61 | lastRewardBlock = block.number; 62 | } else { 63 | lastRewardBlock = _startBlock; 64 | } 65 | initCYCDenomination = _initCYCDenomination; 66 | coinDenomination = _coinDenomination; 67 | tokenDenomination = _tokenDenomination; 68 | numOfShares = 0; 69 | } 70 | 71 | function calcAccumulateCYC() internal view returns (uint256) { 72 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 73 | uint256 remaining = cycToken.balanceOf(address(this)).sub(accumulateCYC); 74 | if (remaining < reward) { 75 | reward = remaining; 76 | } 77 | return accumulateCYC.add(reward); 78 | } 79 | 80 | function updateBlockReward() public { 81 | uint256 blockNumber = block.number; 82 | if (blockNumber <= lastRewardBlock) { 83 | return; 84 | } 85 | if (rewardPerBlock != 0) { 86 | accumulateCYC = calcAccumulateCYC(); 87 | } 88 | // always update lastRewardBlock no matter there is sufficient reward or not 89 | lastRewardBlock = blockNumber; 90 | } 91 | 92 | function cycDenomination() public view returns (uint256) { 93 | if (numOfShares == 0) { 94 | return initCYCDenomination; 95 | } 96 | uint256 blockNumber = block.number; 97 | uint256 accCYC = accumulateCYC; 98 | if (blockNumber > lastRewardBlock && rewardPerBlock > 0) { 99 | accCYC = calcAccumulateCYC(); 100 | } 101 | return accCYC.add(numOfShares - 1).div(numOfShares); 102 | } 103 | 104 | /** 105 | @dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance. 106 | @param _commitment the note commitment, which is PedersenHash(nullifier + secret) 107 | */ 108 | function deposit(bytes32 _commitment) external payable nonReentrant { 109 | require(!commitments[_commitment], "The commitment has been submitted"); 110 | require(msg.value >= coinDenomination, "insufficient coin amount"); 111 | uint256 refund = msg.value - coinDenomination; 112 | uint32 insertedIndex = _insert(_commitment); 113 | commitments[_commitment] = true; 114 | updateBlockReward(); 115 | uint256 cycDeno = cycDenomination(); 116 | if (cycDeno > 0) { 117 | require(cycToken.transferFrom(msg.sender, address(this), cycDeno), "insufficient CYC allowance"); 118 | } 119 | uint256 td = tokenDenomination; 120 | if (td > 0) { 121 | require(token.transferFrom(msg.sender, address(this), td), "insufficient allowance"); 122 | } 123 | accumulateCYC += cycDeno; 124 | numOfShares += 1; 125 | if (refund > 0) { 126 | (bool success, ) = msg.sender.call.value(refund)(""); 127 | require(success, "failed to refund"); 128 | } 129 | emit Deposit(_commitment, insertedIndex, block.timestamp, cycDeno); 130 | } 131 | 132 | /** 133 | @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs 134 | `input` array consists of: 135 | - merkle root of all deposits in the contract 136 | - hash of unique deposit nullifier to prevent double spends 137 | - the recipient of funds 138 | - optional fee that goes to the transaction sender (usually a relay) 139 | */ 140 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _feeRate, uint256 _refund) external payable nonReentrant { 141 | require(_refund == 0, "refund is not zero"); 142 | require(!Address.isContract(_recipient), "recipient of cannot be contract"); 143 | require(_feeRate.add(anonymityRate) < 10000, "invalid fee rate"); 144 | require(!nullifierHashes[_nullifierHash], "The note has been already spent"); 145 | require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one 146 | require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _feeRate, _refund]), "Invalid withdraw proof"); 147 | 148 | nullifierHashes[_nullifierHash] = true; 149 | uint256 cd = coinDenomination; 150 | if (cd > 0) { 151 | (bool success,) = _recipient.call.value(cd)(""); 152 | require(success, "failed to withdraw coin"); 153 | } 154 | uint256 td = tokenDenomination; 155 | if (td > 0) { 156 | require(safeTransfer(token, _recipient, td), "failed to withdraw token"); 157 | } 158 | updateBlockReward(); 159 | uint256 anonymityFee = 0; 160 | uint256 relayerFee = 0; 161 | // numOfShares should be larger than 0 162 | uint256 cycDeno = accumulateCYC.div(numOfShares); 163 | if (cycDeno > 0) { 164 | accumulateCYC -= cycDeno; 165 | anonymityFee = cycDeno.mul(anonymityRate).div(10000); 166 | if (anonymityFee > 0) { 167 | require(cycToken.burn(anonymityFee), "failed to burn CYC"); 168 | } 169 | relayerFee = cycDeno.mul(_feeRate).div(10000); 170 | if (relayerFee > 0) { 171 | require(safeTransfer(cycToken, _relayer, relayerFee), "failed to send CYC to relayer"); 172 | } 173 | require(safeTransfer(cycToken, _recipient, cycDeno.sub(anonymityFee).sub(relayerFee)), "failed to reward CYC"); 174 | } 175 | numOfShares -= 1; 176 | emit Withdrawal(_recipient, _nullifierHash, _relayer, cycDeno, anonymityFee, relayerFee); 177 | } 178 | 179 | /** @dev whether a note is already spent */ 180 | function isSpent(bytes32 _nullifierHash) public view returns(bool) { 181 | return nullifierHashes[_nullifierHash]; 182 | } 183 | 184 | /** @dev whether an array of notes is already spent */ 185 | function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) { 186 | spent = new bool[](_nullifierHashes.length); 187 | for(uint i = 0; i < _nullifierHashes.length; i++) { 188 | if (isSpent(_nullifierHashes[i])) { 189 | spent[i] = true; 190 | } 191 | } 192 | } 193 | 194 | /** 195 | @dev allow governance DAO to update SNARK verification keys. This is needed to 196 | update keys if tornado.cash update their keys in production. 197 | */ 198 | function updateVerifier(address _newVerifier) external onlyGovDAO { 199 | verifier = IVerifier(_newVerifier); 200 | } 201 | 202 | /** @dev governance DAO can change his address */ 203 | function changeGovDAO(address _newGovDAO) external onlyGovDAO { 204 | govDAO = _newGovDAO; 205 | } 206 | 207 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyGovDAO { 208 | updateBlockReward(); 209 | rewardPerBlock = _rewardPerBlock; 210 | } 211 | 212 | function setAnonymityRate(uint256 _rate) public onlyGovDAO { 213 | require(_rate < 10000, "invalid anonymity rate"); 214 | anonymityRate = _rate; 215 | } 216 | 217 | // Safe transfer function, just in case if rounding error causes pool to not have enough CYCs. 218 | function safeTransfer(IERC20 _token, address _to, uint256 _amount) internal returns (bool) { 219 | uint256 balance = _token.balanceOf(address(this)); 220 | if (_amount > balance) { 221 | return _token.transfer(_to, balance); 222 | } 223 | return _token.transfer(_to, _amount); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /contracts/CycloneV2dot2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./math/SafeMath.sol"; 4 | import "./token/IMintableToken.sol"; 5 | import "./utils/Address.sol"; 6 | import "./zksnarklib/MerkleTreeWithHistory.sol"; 7 | import "./zksnarklib/IVerifier.sol"; 8 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 9 | 10 | contract CycloneV2dot2 is MerkleTreeWithHistory, ReentrancyGuard { 11 | 12 | using SafeMath for uint256; 13 | uint256 public tokenDenomination; // (10K or 100k or 1M) * 10^18 14 | uint256 public coinDenomination; 15 | uint256 public initCYCDenomination; 16 | mapping(bytes32 => bool) public nullifierHashes; 17 | mapping(bytes32 => bool) public commitments; // we store all commitments just to prevent accidental deposits with the same commitment 18 | IVerifier public verifier; 19 | IERC20 public token; 20 | IMintableToken public cycToken; 21 | address public treasury; 22 | address public govDAO; 23 | uint256 public numOfShares; 24 | uint256 public lastRewardBlock; 25 | uint256 public rewardPerBlock; 26 | uint256 public accumulateCYC; 27 | uint256 public anonymityFee; 28 | 29 | modifier onlyGovDAO { 30 | // Start with an governance DAO address and will transfer to a governance DAO, e.g., Timelock + GovernorAlpha, after launch 31 | require(msg.sender == govDAO, "Only Governance DAO can call this function."); 32 | _; 33 | } 34 | 35 | event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp, uint256 cycDenomination, uint256 anonymityFee); 36 | event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 reward, uint256 relayerFee); 37 | event RewardPerBlockUpdated(uint256 oldValue, uint256 newValue); 38 | event AnonymityFeeUpdated(uint256 oldValue, uint256 newValue); 39 | 40 | /** 41 | @dev The constructor 42 | @param _verifier the address of SNARK verifier for this contract 43 | @param _merkleTreeHeight the height of deposits' Merkle Tree 44 | @param _govDAO governance DAO address 45 | */ 46 | constructor( 47 | address _govDAO, 48 | IERC20 _token, 49 | IMintableToken _cycToken, 50 | address _treasury, 51 | uint256 _initCYCDenomination, 52 | uint256 _coinDenomination, 53 | uint256 _tokenDenomination, 54 | uint256 _startBlock, 55 | IVerifier _verifier, 56 | uint32 _merkleTreeHeight 57 | ) MerkleTreeWithHistory(_merkleTreeHeight) public { 58 | require(address(_token) != address(_cycToken), "token cannot be identical to CYC token"); 59 | verifier = _verifier; 60 | treasury = _treasury; 61 | cycToken = _cycToken; 62 | token = _token; 63 | govDAO = _govDAO; 64 | if (_startBlock < block.number) { 65 | lastRewardBlock = block.number; 66 | } else { 67 | lastRewardBlock = _startBlock; 68 | } 69 | initCYCDenomination = _initCYCDenomination; 70 | coinDenomination = _coinDenomination; 71 | tokenDenomination = _tokenDenomination; 72 | numOfShares = 0; 73 | } 74 | 75 | function calcAccumulateCYC() internal view returns (uint256) { 76 | uint256 reward = block.number.sub(lastRewardBlock).mul(rewardPerBlock); 77 | uint256 remaining = cycToken.balanceOf(address(this)).sub(accumulateCYC); 78 | if (remaining < reward) { 79 | reward = remaining; 80 | } 81 | return accumulateCYC.add(reward); 82 | } 83 | 84 | function updateBlockReward() public { 85 | uint256 blockNumber = block.number; 86 | if (blockNumber <= lastRewardBlock) { 87 | return; 88 | } 89 | if (rewardPerBlock != 0) { 90 | accumulateCYC = calcAccumulateCYC(); 91 | } 92 | // always update lastRewardBlock no matter there is sufficient reward or not 93 | lastRewardBlock = blockNumber; 94 | } 95 | 96 | function cycDenomination() public view returns (uint256) { 97 | if (numOfShares == 0) { 98 | return initCYCDenomination; 99 | } 100 | uint256 blockNumber = block.number; 101 | uint256 accCYC = accumulateCYC; 102 | if (blockNumber > lastRewardBlock && rewardPerBlock > 0) { 103 | accCYC = calcAccumulateCYC(); 104 | } 105 | return accCYC.add(numOfShares - 1).div(numOfShares); 106 | } 107 | 108 | /** 109 | @dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance. 110 | @param _commitment the note commitment, which is PedersenHash(nullifier + secret) 111 | */ 112 | function deposit(bytes32 _commitment) external payable nonReentrant { 113 | require(!commitments[_commitment], "The commitment has been submitted"); 114 | require(msg.value >= coinDenomination, "insufficient coin amount"); 115 | uint256 refund = msg.value - coinDenomination; 116 | uint32 insertedIndex = _insert(_commitment); 117 | commitments[_commitment] = true; 118 | updateBlockReward(); 119 | uint256 cycDeno = cycDenomination(); 120 | uint256 fee = anonymityFee; 121 | if (cycDeno.add(fee) > 0) { 122 | require(cycToken.transferFrom(msg.sender, address(this), cycDeno.add(fee)), "insufficient CYC allowance"); 123 | } 124 | if (fee > 0) { 125 | address t = treasury; 126 | if (t == address(0)) { 127 | require(cycToken.burn(fee), "failed to burn anonymity fee"); 128 | } else { 129 | require(safeTransfer(cycToken, t, fee), "failed to transfer anonymity fee"); 130 | } 131 | } 132 | uint256 td = tokenDenomination; 133 | if (td > 0) { 134 | require(token.transferFrom(msg.sender, address(this), td), "insufficient allowance"); 135 | } 136 | accumulateCYC += cycDeno; 137 | numOfShares += 1; 138 | if (refund > 0) { 139 | (bool success, ) = msg.sender.call.value(refund)(""); 140 | require(success, "failed to refund"); 141 | } 142 | emit Deposit(_commitment, insertedIndex, block.timestamp, cycDeno, fee); 143 | } 144 | 145 | /** 146 | @dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs 147 | `input` array consists of: 148 | - merkle root of all deposits in the contract 149 | - hash of unique deposit nullifier to prevent double spends 150 | - the recipient of funds 151 | - optional fee that goes to the transaction sender (usually a relay) 152 | */ 153 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _relayerFee, uint256 _refund) external payable nonReentrant { 154 | require(_refund == 0, "refund is not zero"); 155 | require(!Address.isContract(_recipient), "recipient of cannot be contract"); 156 | require(!nullifierHashes[_nullifierHash], "The note has been already spent"); 157 | require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one 158 | require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _relayerFee, _refund]), "Invalid withdraw proof"); 159 | 160 | nullifierHashes[_nullifierHash] = true; 161 | uint256 td = tokenDenomination; 162 | if (td > 0) { 163 | require(safeTransfer(token, _recipient, td), "failed to withdraw token"); 164 | } 165 | updateBlockReward(); 166 | uint256 relayerFee = 0; 167 | // numOfShares should be larger than 0 168 | uint256 cycDeno = accumulateCYC.div(numOfShares); 169 | if (cycDeno > 0) { 170 | accumulateCYC -= cycDeno; 171 | require(safeTransfer(cycToken, _recipient, cycDeno), "failed to reward CYC"); 172 | } 173 | uint256 cd = coinDenomination; 174 | if (_relayerFee > cd) { 175 | _relayerFee = cd; 176 | } 177 | if (_relayerFee > 0) { 178 | (bool success,) = _relayer.call.value(_relayerFee)(""); 179 | require(success, "failed to send relayer fee"); 180 | cd -= _relayerFee; 181 | } 182 | if (cd > 0) { 183 | (bool success,) = _recipient.call.value(cd)(""); 184 | require(success, "failed to withdraw coin"); 185 | } 186 | numOfShares -= 1; 187 | emit Withdrawal(_recipient, _nullifierHash, _relayer, cycDeno, relayerFee); 188 | } 189 | 190 | /** @dev whether a note is already spent */ 191 | function isSpent(bytes32 _nullifierHash) public view returns(bool) { 192 | return nullifierHashes[_nullifierHash]; 193 | } 194 | 195 | /** @dev whether an array of notes is already spent */ 196 | function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) { 197 | spent = new bool[](_nullifierHashes.length); 198 | for(uint i = 0; i < _nullifierHashes.length; i++) { 199 | if (isSpent(_nullifierHashes[i])) { 200 | spent[i] = true; 201 | } 202 | } 203 | } 204 | 205 | /** 206 | @dev allow governance DAO to update SNARK verification keys. This is needed to 207 | update keys if tornado.cash update their keys in production. 208 | */ 209 | function updateVerifier(address _newVerifier) external onlyGovDAO { 210 | verifier = IVerifier(_newVerifier); 211 | } 212 | 213 | /** @dev governance DAO can change his address */ 214 | function changeGovDAO(address _newGovDAO) external onlyGovDAO { 215 | govDAO = _newGovDAO; 216 | } 217 | 218 | function setRewardPerBlock(uint256 _rewardPerBlock) public onlyGovDAO { 219 | updateBlockReward(); 220 | emit RewardPerBlockUpdated(rewardPerBlock, _rewardPerBlock); 221 | rewardPerBlock = _rewardPerBlock; 222 | } 223 | 224 | function setAnonymityFee(uint256 _fee) public onlyGovDAO { 225 | emit AnonymityFeeUpdated(anonymityFee, _fee); 226 | anonymityFee = _fee; 227 | } 228 | 229 | // Safe transfer function, just in case if rounding error causes pool to not have enough CYCs. 230 | function safeTransfer(IERC20 _token, address _to, uint256 _amount) internal returns (bool) { 231 | uint256 balance = _token.balanceOf(address(this)); 232 | if (_amount > balance) { 233 | return _token.transfer(_to, balance); 234 | } 235 | return _token.transfer(_to, _amount); 236 | } 237 | 238 | function version() public pure returns(string memory) { 239 | return "2.2"; 240 | } 241 | 242 | } 243 | -------------------------------------------------------------------------------- /contracts/ICycloneV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./token/IERC20.sol"; 4 | 5 | interface ICycloneV2 { 6 | function coinDenomination() external view returns (uint256); 7 | function tokenDenomination() external view returns (uint256); 8 | function cycDenomination() external view returns (uint256); 9 | function token() external view returns (IERC20); 10 | function cycToken() external view returns (IERC20); 11 | function deposit(bytes32 _commitment) external payable; 12 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) external payable; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/ICycloneV2dot2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./token/IERC20.sol"; 4 | 5 | interface ICycloneV2dot2 { 6 | 7 | function coinDenomination() external view returns (uint256); 8 | function tokenDenomination() external view returns (uint256); 9 | function cycDenomination() external view returns (uint256); 10 | function token() external view returns (IERC20); 11 | function cycToken() external view returns (IERC20); 12 | function deposit(bytes32 _commitment) external payable; 13 | function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) external payable; 14 | function anonymityFee() external view returns (uint256); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.22 <0.8.0; 3 | 4 | contract Migrations { 5 | address public owner = msg.sender; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | require( 10 | msg.sender == owner, 11 | "This function is restricted to the contract's owner" 12 | ); 13 | _; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/cream/IDelegator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.16; 2 | 3 | interface IDelegator { 4 | function expScale() external view returns (uint); 5 | function balanceOf(address owner) external view returns (uint); 6 | function balanceOfUnderlying(address owner) external returns (uint); 7 | function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint); 8 | function borrowRatePerBlock() external view returns (uint); 9 | function supplyRatePerBlock() external view returns (uint); 10 | function exchangeRateCurrent() external returns (uint); 11 | function exchangeRateStored() external view returns (uint); 12 | function getCash() external view returns (uint); 13 | function accrueInterest() external returns (uint); 14 | function underlying() external view returns (address); 15 | function mint(uint mintAmount) external returns (uint); 16 | function redeem(uint redeemTokens) external returns (uint); 17 | function redeemUnderlying(uint redeemAmount) external returns (uint); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/governance/ITimelock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.17; 2 | 3 | interface ITimelock { 4 | function delay() external view returns (uint); 5 | function GRACE_PERIOD() external view returns (uint); 6 | function acceptAdmin() external; 7 | function queuedTransactions(bytes32 hash) external view returns (bool); 8 | function queueTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external returns (bytes32); 9 | function cancelTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external; 10 | function executeTransaction(address target, uint value, string calldata signature, bytes calldata data, uint eta) external payable returns (bytes memory); 11 | } -------------------------------------------------------------------------------- /contracts/governance/Timelock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.17; 2 | 3 | import "../math/SafeMath.sol"; 4 | 5 | contract Timelock { 6 | using SafeMath for uint; 7 | 8 | event NewAdmin(address indexed newAdmin); 9 | event NewPendingAdmin(address indexed newPendingAdmin); 10 | event NewDelay(uint indexed newDelay); 11 | event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 12 | event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 13 | event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 14 | 15 | uint public constant GRACE_PERIOD = 14 days; 16 | uint public constant MINIMUM_DELAY = 2 days; 17 | uint public constant MAXIMUM_DELAY = 30 days; 18 | 19 | address public admin; 20 | address public pendingAdmin; 21 | uint public delay; 22 | bool public admin_initialized; 23 | 24 | mapping (bytes32 => bool) public queuedTransactions; 25 | 26 | 27 | constructor(address admin_, uint delay_) public { 28 | require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); 29 | require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); 30 | 31 | admin = admin_; 32 | delay = delay_; 33 | admin_initialized = false; 34 | } 35 | 36 | function() external payable { } 37 | 38 | function setDelay(uint delay_) public { 39 | require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); 40 | require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); 41 | require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); 42 | delay = delay_; 43 | 44 | emit NewDelay(delay); 45 | } 46 | 47 | function acceptAdmin() public { 48 | require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); 49 | admin = msg.sender; 50 | pendingAdmin = address(0); 51 | 52 | emit NewAdmin(admin); 53 | } 54 | 55 | function setPendingAdmin(address pendingAdmin_) public { 56 | // allows one time setting of admin for deployment purposes 57 | if (admin_initialized) { 58 | require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); 59 | } else { 60 | require(msg.sender == admin, "Timelock::setPendingAdmin: First call must come from admin."); 61 | admin_initialized = true; 62 | } 63 | pendingAdmin = pendingAdmin_; 64 | 65 | emit NewPendingAdmin(pendingAdmin); 66 | } 67 | 68 | function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) { 69 | require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); 70 | require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay."); 71 | 72 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 73 | queuedTransactions[txHash] = true; 74 | 75 | emit QueueTransaction(txHash, target, value, signature, data, eta); 76 | return txHash; 77 | } 78 | 79 | function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public { 80 | require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); 81 | 82 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 83 | queuedTransactions[txHash] = false; 84 | 85 | emit CancelTransaction(txHash, target, value, signature, data, eta); 86 | } 87 | 88 | function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) { 89 | require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); 90 | 91 | bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); 92 | require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); 93 | require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); 94 | require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale."); 95 | 96 | queuedTransactions[txHash] = false; 97 | 98 | bytes memory callData; 99 | 100 | if (bytes(signature).length == 0) { 101 | callData = data; 102 | } else { 103 | callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); 104 | } 105 | 106 | // solium-disable-next-line security/no-call-value 107 | (bool success, bytes memory returnData) = target.call.value(value)(callData); 108 | require(success, "Timelock::executeTransaction: Transaction execution reverted."); 109 | 110 | emit ExecuteTransaction(txHash, target, value, signature, data, eta); 111 | 112 | return returnData; 113 | } 114 | 115 | function getBlockTimestamp() internal view returns (uint) { 116 | // solium-disable-next-line security/no-block-members 117 | return block.timestamp; 118 | } 119 | } -------------------------------------------------------------------------------- /contracts/lifecycle/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "../ownership/Ownable.sol"; 4 | 5 | /** 6 | * @title Pausable 7 | * @dev Base contract which allows children to implement an emergency stop mechanism. 8 | */ 9 | contract Pausable is Ownable { 10 | event Pause(); 11 | event Unpause(); 12 | 13 | bool public paused = false; 14 | 15 | 16 | /** 17 | * @dev Modifier to make a function callable only when the contract is not paused. 18 | */ 19 | modifier whenNotPaused() { 20 | require(!paused); 21 | _; 22 | } 23 | 24 | /** 25 | * @dev Modifier to make a function callable only when the contract is paused. 26 | */ 27 | modifier whenPaused() { 28 | require(paused); 29 | _; 30 | } 31 | 32 | /** 33 | * @dev called by the owner to pause, triggers stopped state 34 | */ 35 | function pause() onlyOwner whenNotPaused public { 36 | paused = true; 37 | emit Pause(); 38 | } 39 | 40 | /** 41 | * @dev called by the owner to unpause, returns to normal state 42 | */ 43 | function unpause() onlyOwner whenPaused public { 44 | paused = false; 45 | emit Unpause(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.21; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error 7 | */ 8 | library SafeMath { 9 | 10 | /** 11 | * @dev Multiplies two numbers, throws on overflow. 12 | */ 13 | 14 | /*@CTK SafeMath_mul 15 | @tag spec 16 | @post __reverted == __has_assertion_failure 17 | @post __has_assertion_failure == __has_overflow 18 | @post __reverted == false -> c == a * b 19 | @post msg == msg__post 20 | */ 21 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 22 | function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { 23 | if (a == 0) { 24 | return 0; 25 | } 26 | c = a * b; 27 | assert(c / a == b); 28 | return c; 29 | } 30 | 31 | /** 32 | * @dev Integer division of two numbers, truncating the quotient. 33 | */ 34 | /*@CTK SafeMath_div 35 | @tag spec 36 | @pre b != 0 37 | @post __reverted == __has_assertion_failure 38 | @post __has_overflow == true -> __has_assertion_failure == true 39 | @post __reverted == false -> __return == a / b 40 | @post msg == msg__post 41 | */ 42 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 43 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 44 | // assert(b > 0); // Solidity automatically throws when dividing by 0 45 | // uint256 c = a / b; 46 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 47 | return a / b; 48 | } 49 | 50 | /** 51 | * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). 52 | */ 53 | /*@CTK SafeMath_sub 54 | @tag spec 55 | @post __reverted == __has_assertion_failure 56 | @post __has_overflow == true -> __has_assertion_failure == true 57 | @post __reverted == false -> __return == a - b 58 | @post msg == msg__post 59 | */ 60 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 61 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 62 | assert(b <= a); 63 | return a - b; 64 | } 65 | 66 | /** 67 | * @dev Adds two numbers, throws on overflow. 68 | */ 69 | /*@CTK SafeMath_add 70 | @tag spec 71 | @post __reverted == __has_assertion_failure 72 | @post __has_assertion_failure == __has_overflow 73 | @post __reverted == false -> c == a + b 74 | @post msg == msg__post 75 | */ 76 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 77 | function add(uint256 a, uint256 b) internal pure returns (uint256 c) { 78 | c = a + b; 79 | assert(c >= a); 80 | return c; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/mimo/CoinCyclone.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "../Cyclone.sol"; 4 | import "./IMimoFactory.sol"; 5 | import "./IMimoExchange.sol"; 6 | 7 | contract CoinCyclone is Cyclone { 8 | IMimoExchange public mimoExchange; 9 | constructor( 10 | IVerifier _verifier, 11 | IMintableToken _cyctoken, 12 | IMimoFactory _mimoFactory, 13 | IAeolus _aeolus, 14 | uint256 _initDenomination, 15 | uint256 _denominationRound, 16 | uint32 _merkleTreeHeight, 17 | address _operator 18 | ) Cyclone(_verifier, _cyctoken, _aeolus, _initDenomination, _denominationRound, _merkleTreeHeight, _operator) public { 19 | mimoExchange = IMimoExchange(_mimoFactory.getExchange(address(_cyctoken))); 20 | } 21 | 22 | function getDepositParameters() external view returns (uint256, uint256) { 23 | uint256 denomination = _getDepositDenomination(address(this).balance); 24 | 25 | return (denomination, _weightedAmount(_cycPerDenomination(denomination).mul(cashbackRate), numOfShares).div(10000)); 26 | } 27 | 28 | function getWithdrawDenomination() external view returns (uint256) { 29 | return _getWithdrawDenomination(); 30 | } 31 | 32 | function _getWithdrawDenomination() private view returns (uint256) { 33 | if (numOfShares > 0) { 34 | return address(this).balance.div(numOfShares).div(denominationRound).mul(denominationRound); 35 | } 36 | return initDenomination + address(this).balance; 37 | } 38 | 39 | function _getDepositDenomination(uint256 totalBalance) private view returns (uint256) { 40 | if (numOfShares > 0) { 41 | return totalBalance.div(numOfShares).add(denominationRound - 1).div(denominationRound).mul(denominationRound); 42 | } 43 | return initDenomination + totalBalance; 44 | } 45 | 46 | function _cycPerDenomination(uint256 _denomination) internal view returns (uint256) { 47 | uint256 cycPrice; 48 | if (cycToken.balanceOf(address(mimoExchange)) != 0 && address(mimoExchange).balance != 0) { 49 | cycPrice = address(mimoExchange).balance.mul(oneCYC).div(cycToken.balanceOf(address(mimoExchange))); 50 | } 51 | if (cycPrice < minCYCPrice) { 52 | cycPrice = minCYCPrice; 53 | } 54 | if (cycPrice == 0) { 55 | return 0; 56 | } 57 | return _denomination.mul(oneCYC).div(cycPrice); 58 | } 59 | 60 | function _processDeposit(uint256 _minCashbackAmount) internal returns (uint256) { 61 | uint256 denomination = _getDepositDenomination(address(this).balance - msg.value); // this is for excluding current msg.value from total balance 62 | require (msg.value >= denomination, "amount should not be smaller than denomination"); 63 | uint256 remaining = msg.value.sub(denomination); 64 | if (remaining > 0) { 65 | (bool success, ) = msg.sender.call.value(remaining)(""); 66 | require(success, "transfer of change failed"); 67 | } 68 | uint256 cycPerDenomination = _cycPerDenomination(denomination); 69 | aeolus.addReward(cycPerDenomination.mul(depositLpIR).div(10000)); 70 | uint256 cashbackAmount = _weightedAmount(cycPerDenomination.mul(cashbackRate), numOfShares).div(10000); 71 | require(cashbackAmount >= _minCashbackAmount, "insufficient cashback amount"); 72 | if (cashbackAmount > 0) { 73 | require(cycToken.mint(msg.sender, cashbackAmount), "mint failure"); 74 | } 75 | 76 | return denomination; 77 | } 78 | 79 | function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal returns (uint256, uint256) { 80 | // sanity checks 81 | require(msg.value == 0, "Message value is supposed to be zero for IOTX Cyclone"); 82 | require(_refund == 0, "Refund value is supposed to be zero for IOTX Cyclone"); 83 | 84 | uint256 denomination = _getWithdrawDenomination(); 85 | uint256 iotxToSpend = denomination.mul(withdrawLpIR).add(_weightedAmount(denomination.mul(buybackRate), numOfShares.sub(1))).div(10000); 86 | if (iotxToSpend != 0) { 87 | uint256 cycBought = mimoExchange.iotxToTokenSwapInput.value(iotxToSpend)(mimoExchange.getIotxToTokenInputPrice(iotxToSpend), block.timestamp.mul(2)); 88 | uint256 lpIncentive = cycBought.mul(withdrawLpIR).div(withdrawLpIR.add(buybackRate)); 89 | aeolus.addReward(lpIncentive); 90 | require(cycToken.burn(cycBought.sub(lpIncentive)), "burn failure"); 91 | } 92 | (bool success, ) = _recipient.call.value(denomination.mul(10000 - apIncentiveRate).div(10000).sub(iotxToSpend).sub(_fee))(""); 93 | require(success, "payment to _recipient did not go thru"); 94 | if (_fee > 0) { 95 | (success, ) = _relayer.call.value(_fee)(""); 96 | require(success, "payment to _relayer did not go thru"); 97 | } 98 | 99 | return (denomination, iotxToSpend); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /contracts/mimo/ERC20Cyclone.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "../Cyclone.sol"; 4 | import "../token/IERC20.sol"; 5 | import "./IMimoFactory.sol"; 6 | import "./IMimoExchange.sol"; 7 | 8 | contract ERC20Cyclone is Cyclone { 9 | IERC20 public xrc20Token; 10 | IMimoExchange public xrc20Exchange; 11 | IMimoExchange public mimoExchange; 12 | constructor( 13 | IVerifier _verifier, 14 | IMintableToken _cyctoken, 15 | IMimoFactory _mimoFactory, 16 | IAeolus _aeolus, 17 | uint256 _initDenomination, 18 | uint256 _denominationRound, 19 | uint32 _merkleTreeHeight, 20 | address _operator, 21 | IERC20 _xrc20Token 22 | ) Cyclone(_verifier, _cyctoken, _aeolus, _initDenomination, _denominationRound, _merkleTreeHeight, _operator) public { 23 | xrc20Token = _xrc20Token; 24 | xrc20Exchange = IMimoExchange(_mimoFactory.getExchange(address(_xrc20Token))); 25 | mimoExchange = IMimoExchange(_mimoFactory.getExchange(address(_cyctoken))); 26 | } 27 | 28 | function getDepositParameters() external view returns (uint256, uint256) { 29 | uint256 denomination = _getDepositDenomination(); 30 | return (denomination, _weightedAmount(_cycPerDenomination(denomination).mul(cashbackRate), numOfShares).div(10000)); 31 | } 32 | 33 | function _getDepositDenomination() private view returns (uint256) { 34 | if (numOfShares > 0) { 35 | return xrc20Token.balanceOf(address(this)).div(numOfShares).add(denominationRound - 1).div(denominationRound).mul(denominationRound); 36 | } 37 | return initDenomination + xrc20Token.balanceOf(address(this)); 38 | } 39 | 40 | function getWithdrawDenomination() external view returns (uint256) { 41 | return _getWithdrawDenomination(); 42 | } 43 | 44 | function _getWithdrawDenomination() private view returns (uint256) { 45 | if (numOfShares > 0) { 46 | return xrc20Token.balanceOf(address(this)).div(numOfShares).div(denominationRound).mul(denominationRound); 47 | } 48 | return initDenomination + xrc20Token.balanceOf(address(this)); 49 | } 50 | 51 | function _cycPerDenomination(uint256 denomination) internal view returns (uint256) { 52 | uint256 cycPrice; 53 | if ( 54 | address(xrc20Exchange).balance != 0 && 55 | address(mimoExchange).balance != 0 && 56 | xrc20Token.balanceOf(address(xrc20Exchange)) != 0 && 57 | cycToken.balanceOf(address(mimoExchange)) != 0 58 | ) { 59 | cycPrice = oneCYC.mul(address(xrc20Exchange).balance) 60 | .mul(cycToken.balanceOf(address(mimoExchange))) 61 | .div(xrc20Token.balanceOf(address(xrc20Exchange))) 62 | .div(address(mimoExchange).balance); 63 | } 64 | if (cycPrice < minCYCPrice) { 65 | cycPrice = minCYCPrice; 66 | } 67 | if (cycPrice == 0) { 68 | return 0; 69 | } 70 | return denomination.mul(oneCYC).div(cycPrice); 71 | } 72 | 73 | function _processDeposit(uint256 _minCashbackAmount) internal returns (uint256) { 74 | require(msg.value == 0, "Coin value is supposed to be 0 for ERC20 instance"); 75 | uint256 denomination = _getDepositDenomination(); 76 | _safeXRC20TransferFrom(msg.sender, address(this), denomination); 77 | uint256 cycPerDenomination = _cycPerDenomination(denomination); 78 | aeolus.addReward(cycPerDenomination.mul(depositLpIR).div(10000)); 79 | uint256 cashbackAmount = _weightedAmount(cycPerDenomination.mul(cashbackRate), numOfShares).div(10000); 80 | require(cashbackAmount >= _minCashbackAmount, "insufficient cashback amount"); 81 | if (cashbackAmount > 0) { 82 | require(cycToken.mint(msg.sender, cashbackAmount), "mint failure"); 83 | } 84 | 85 | return denomination; 86 | } 87 | 88 | function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal returns (uint256, uint256) { 89 | require(msg.value == _refund, "Incorrect refund amount received by the contract"); 90 | uint256 denomination = _getWithdrawDenomination(); 91 | uint256 xrc20ToSell = denomination.mul(withdrawLpIR).add(_weightedAmount(denomination.mul(buybackRate), numOfShares.sub(1))).div(10000); 92 | if (xrc20ToSell != 0) { 93 | require(xrc20Token.approve(address(xrc20Exchange), xrc20ToSell), "failed to approve xrc20 token to xrc20 exchanges"); 94 | // ERC20(sell) => iotx(buy) => iotx(sell)=> CYC token(buy) 95 | uint256 iotxToBuy = xrc20Exchange.getTokenToIotxInputPrice(xrc20ToSell); 96 | uint256 cycBought = xrc20Exchange.tokenToTokenSwapInput( 97 | xrc20ToSell, 98 | mimoExchange.getIotxToTokenInputPrice(iotxToBuy), 99 | iotxToBuy, 100 | block.timestamp.mul(2), 101 | address(cycToken) 102 | ); 103 | uint256 lpIncentive = cycBought.mul(withdrawLpIR).div(withdrawLpIR.add(buybackRate)); 104 | aeolus.addReward(lpIncentive); 105 | require(cycToken.burn(cycBought.sub(lpIncentive)), "burn failure"); 106 | } 107 | _safeXRC20Transfer(_recipient, denomination.mul(10000 - apIncentiveRate).div(10000).sub(xrc20ToSell).sub(_fee)); 108 | if (_fee > 0) { 109 | _safeXRC20Transfer(_relayer, _fee); 110 | } 111 | 112 | if (_refund > 0) { 113 | (bool success, ) = _recipient.call.value(_refund)(""); 114 | if (!success) { 115 | // let's return _refund back to the relayer 116 | _relayer.transfer(_refund); 117 | } 118 | } 119 | 120 | return (denomination, xrc20ToSell); 121 | } 122 | 123 | function _safeXRC20TransferFrom(address _from, address _to, uint256 _amount) internal { 124 | // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))) 125 | (bool success, bytes memory data) = address(xrc20Token).call(abi.encodeWithSelector(0x23b872dd, _from, _to, _amount)); 126 | require(success && (data.length == 0 || abi.decode(data, (bool))), "failed to call transferFrom"); 127 | } 128 | 129 | function _safeXRC20Transfer(address _to, uint256 _amount) internal { 130 | // bytes4(keccak256(bytes('transfer(address,uint256)'))) 131 | (bool success, bytes memory data) = address(xrc20Token).call(abi.encodeWithSelector(0xa9059cbb, _to, _amount)); 132 | require(success && (data.length == 0 || abi.decode(data, (bool))), "failed to call transfer"); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/mimo/IMimoFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | interface IMimoFactory { 4 | event NewExchange(address indexed token, address indexed exchange); 5 | 6 | function createExchange(address token) external returns (address payable); 7 | 8 | function getExchange(address token) external view returns (address payable); 9 | 10 | function getToken(address token) external view returns (address); 11 | 12 | function getTokenWithId(uint256 token_id) external view returns (address); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mock/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "../math/SafeMath.sol"; 3 | 4 | 5 | /** 6 | * @title Standard ERC20 token 7 | * 8 | * @dev Implementation of the basic standard token. 9 | * https://eips.ethereum.org/EIPS/eip-20 10 | * Originally based on code by FirstBlood: 11 | * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 12 | * 13 | * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for 14 | * all accounts just by listening to said events. Note that this isn't required by the specification, and other 15 | * compliant implementations may not do it. 16 | */ 17 | contract ERC20 { 18 | using SafeMath for uint256; 19 | 20 | mapping (address => uint256) internal _balances; 21 | mapping (address => mapping (address => uint256)) internal _allowed; 22 | 23 | event Transfer(address indexed from, address indexed to, uint256 value); 24 | event Approval(address indexed owner, address indexed spender, uint256 value); 25 | 26 | uint256 internal _totalSupply; 27 | 28 | /** 29 | * @dev Total number of tokens in existence 30 | */ 31 | function totalSupply() public view returns (uint256) { 32 | return _totalSupply; 33 | } 34 | 35 | /** 36 | * @dev Gets the balance of the specified address. 37 | * @param owner The address to query the balance of. 38 | * @return A uint256 representing the amount owned by the passed address. 39 | */ 40 | function balanceOf(address owner) public view returns (uint256) { 41 | return _balances[owner]; 42 | } 43 | 44 | /** 45 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 46 | * @param owner address The address which owns the funds. 47 | * @param spender address The address which will spend the funds. 48 | * @return A uint256 specifying the amount of tokens still available for the spender. 49 | */ 50 | function allowance(address owner, address spender) public view returns (uint256) { 51 | return _allowed[owner][spender]; 52 | } 53 | 54 | /** 55 | * @dev Transfer token to a specified address 56 | * @param to The address to transfer to. 57 | * @param value The amount to be transferred. 58 | */ 59 | function transfer(address to, uint256 value) public returns (bool) { 60 | _transfer(msg.sender, to, value); 61 | return true; 62 | } 63 | 64 | /** 65 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 66 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 67 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 68 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 69 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 70 | * @param spender The address which will spend the funds. 71 | * @param value The amount of tokens to be spent. 72 | */ 73 | function approve(address spender, uint256 value) public returns (bool) { 74 | _approve(msg.sender, spender, value); 75 | return true; 76 | } 77 | 78 | /** 79 | * @dev Transfer tokens from one address to another. 80 | * Note that while this function emits an Approval event, this is not required as per the specification, 81 | * and other compliant implementations may not emit the event. 82 | * @param from address The address which you want to send tokens from 83 | * @param to address The address which you want to transfer to 84 | * @param value uint256 the amount of tokens to be transferred 85 | */ 86 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 87 | _transfer(from, to, value); 88 | _approve(from, msg.sender, _allowed[from][msg.sender].sub(value)); 89 | return true; 90 | } 91 | 92 | /** 93 | * @dev Increase the amount of tokens that an owner allowed to a spender. 94 | * approve should be called when _allowed[msg.sender][spender] == 0. To increment 95 | * allowed value is better to use this function to avoid 2 calls (and wait until 96 | * the first transaction is mined) 97 | * From MonolithDAO Token.sol 98 | * Emits an Approval event. 99 | * @param spender The address which will spend the funds. 100 | * @param addedValue The amount of tokens to increase the allowance by. 101 | */ 102 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 103 | _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue)); 104 | return true; 105 | } 106 | 107 | /** 108 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 109 | * approve should be called when _allowed[msg.sender][spender] == 0. To decrement 110 | * allowed value is better to use this function to avoid 2 calls (and wait until 111 | * the first transaction is mined) 112 | * From MonolithDAO Token.sol 113 | * Emits an Approval event. 114 | * @param spender The address which will spend the funds. 115 | * @param subtractedValue The amount of tokens to decrease the allowance by. 116 | */ 117 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 118 | _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue)); 119 | return true; 120 | } 121 | 122 | /** 123 | * @dev Transfer token for a specified addresses 124 | * @param from The address to transfer from. 125 | * @param to The address to transfer to. 126 | * @param value The amount to be transferred. 127 | */ 128 | function _transfer(address from, address to, uint256 value) internal { 129 | require(to != address(0)); 130 | 131 | _balances[from] = _balances[from].sub(value); 132 | _balances[to] = _balances[to].add(value); 133 | emit Transfer(from, to, value); 134 | } 135 | 136 | /** 137 | * @dev Internal function that mints an amount of the token and assigns it to 138 | * an account. This encapsulates the modification of balances such that the 139 | * proper events are emitted. 140 | * @param account The account that will receive the created tokens. 141 | * @param value The amount that will be created. 142 | */ 143 | function _mint(address account, uint256 value) internal { 144 | require(account != address(0)); 145 | 146 | _totalSupply = _totalSupply.add(value); 147 | _balances[account] = _balances[account].add(value); 148 | emit Transfer(address(0), account, value); 149 | } 150 | 151 | /** 152 | * @dev Internal function that burns an amount of the token of a given 153 | * account. 154 | * @param account The account whose tokens will be burnt. 155 | * @param value The amount that will be burnt. 156 | */ 157 | function _burn(address account, uint256 value) internal { 158 | require(account != address(0)); 159 | 160 | _totalSupply = _totalSupply.sub(value); 161 | _balances[account] = _balances[account].sub(value); 162 | emit Transfer(account, address(0), value); 163 | } 164 | 165 | /** 166 | * @dev Approve an address to spend another addresses' tokens. 167 | * @param owner The address that owns the tokens. 168 | * @param spender The address that will spend the tokens. 169 | * @param value The number of tokens that can be spent. 170 | */ 171 | function _approve(address owner, address spender, uint256 value) internal { 172 | require(spender != address(0)); 173 | require(owner != address(0)); 174 | 175 | _allowed[owner][spender] = value; 176 | emit Approval(owner, spender, value); 177 | } 178 | 179 | /** 180 | * @dev Internal function that burns an amount of the token of a given 181 | * account, deducting from the sender's allowance for said account. Uses the 182 | * internal burn function. 183 | * Emits an Approval event (reflecting the reduced allowance). 184 | * @param account The account whose tokens will be burnt. 185 | * @param value The amount that will be burnt. 186 | */ 187 | function _burnFrom(address account, uint256 value) internal { 188 | _burn(account, value); 189 | _approve(account, msg.sender, _allowed[account][msg.sender].sub(value)); 190 | } 191 | } -------------------------------------------------------------------------------- /contracts/mock/MimoFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./MimoExchange.sol"; 4 | 5 | contract MimoFactory { 6 | /***********************************| 7 | | Events And Variables | 8 | |__________________________________*/ 9 | event NewExchange(address indexed token, address indexed exchange); 10 | 11 | uint256 public tokenCount; 12 | mapping(address => address) internal token_to_exchange; 13 | mapping(address => address) internal exchange_to_token; 14 | mapping(uint256 => address) internal id_to_token; 15 | 16 | /***********************************| 17 | | Factory Functions | 18 | |__________________________________*/ 19 | 20 | function createExchange(address token) public returns (address) { 21 | require(token != address(0)); 22 | require(token_to_exchange[token] == address(0)); 23 | MimoExchange exchange = new MimoExchange(); 24 | exchange.setup(token); 25 | token_to_exchange[token] = address(exchange); 26 | exchange_to_token[address(exchange)] = token; 27 | uint256 token_id = tokenCount + 1; 28 | tokenCount = token_id; 29 | id_to_token[token_id] = token; 30 | emit NewExchange(token, address(exchange)); 31 | return address(exchange); 32 | } 33 | 34 | /***********************************| 35 | | Getter Functions | 36 | |__________________________________*/ 37 | 38 | function getExchange(address token) public view returns (address) { 39 | return token_to_exchange[token]; 40 | } 41 | 42 | function getToken(address exchange) public view returns (address) { 43 | return exchange_to_token[exchange]; 44 | } 45 | 46 | function getTokenWithId(uint256 token_id) public view returns (address) { 47 | return id_to_token[token_id]; 48 | } 49 | } -------------------------------------------------------------------------------- /contracts/mock/MockUniswapV2Router.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.8.0; 2 | 3 | import "../math/SafeMath.sol"; 4 | import "../token/IMintableToken.sol"; 5 | import "../uniswapv2/IRouter.sol"; 6 | 7 | contract MockUniswapV2Router is IRouter { 8 | using SafeMath for uint256; 9 | 10 | uint256[] public amountsIn; 11 | IMintableToken public lpToken; 12 | constructor(address _lpToken) public { 13 | lpToken = IMintableToken(_lpToken); 14 | } 15 | 16 | function setAmountsIn(uint256[] memory _amounts) public { 17 | require(_amounts.length == 2); 18 | amountsIn = _amounts; 19 | } 20 | 21 | function removeLiquidity( 22 | address tokenA, 23 | address tokenB, 24 | uint256 liquidity, 25 | uint256 amountAMin, 26 | uint256 amountBMin, 27 | address to, 28 | uint256 29 | ) external returns (uint256 amountA, uint256 amountB) { 30 | require(liquidity > 0, "liquidity cannot be zero"); 31 | require(amountsIn[0] >= amountAMin && amountsIn[1] >= amountBMin, "invalid amounts"); 32 | require(lpToken.transferFrom(msg.sender, address(this), liquidity), "failed to transfer lp token"); 33 | require(lpToken.burn(liquidity), "failed to burn lp token"); 34 | require(IMintableToken(tokenA).mint(to, amountsIn[0]), "failed to mint tokenA"); 35 | require(IMintableToken(tokenB).mint(to, amountsIn[1]), "failed to mint tokenB"); 36 | return (amountsIn[0], amountsIn[1]); 37 | } 38 | 39 | function swapExactTokensForTokens( 40 | uint256 amountIn, 41 | uint256 amountOutMin, 42 | address[] calldata path, 43 | address to, 44 | uint256 45 | ) external returns (uint256[] memory amounts) { 46 | amounts = new uint256[](2); 47 | amounts[0] = amountIn; 48 | amounts[1] = amountsIn[1].mul(amountIn).div(amountsIn[0]); 49 | require(amounts[1] >= amountOutMin, "invalid amount out"); 50 | require(path.length == 2, "invalid path length"); 51 | IMintableToken token0 = IMintableToken(path[0]); 52 | IMintableToken token1 = IMintableToken(path[1]); 53 | require(token0.transferFrom(msg.sender, address(this), amounts[0]), "failed to transfer token"); 54 | require(token0.burn(amounts[0]), "failed to burn token"); 55 | require(token1.mint(to, amounts[1]), "failed to mint token"); 56 | } 57 | 58 | function swapETHForExactTokens( 59 | uint256 amountOut, 60 | address[] calldata path, 61 | address to, 62 | uint256 63 | ) external payable returns (uint[] memory amounts) { 64 | amounts = new uint256[](2); 65 | amounts[0] = amountsIn[0].mul(amountOut).div(amountsIn[1]); 66 | amounts[1] = amountOut; 67 | require(msg.value >= amounts[0], "invalid amount in"); 68 | require(path.length == 2, "invalid path length"); 69 | IMintableToken token1 = IMintableToken(path[1]); 70 | require(token1.mint(to, amounts[1]), "failed to mint token"); 71 | if (msg.value - amounts[0] > 0) { 72 | (bool success,) = msg.sender.call.value(msg.value - amounts[0])(""); 73 | require(success, 'failed to return change'); 74 | } 75 | } 76 | 77 | function getAmountsIn(uint256, address[] calldata) external view returns (uint256[] memory amounts) { 78 | return amountsIn; 79 | } 80 | } -------------------------------------------------------------------------------- /contracts/mock/TestCycloneDelegate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <6.0 >=0.4.0; 2 | 3 | import "../Cyclone.sol"; 4 | 5 | 6 | contract TestCycloneDelegate { 7 | Cyclone public cyclone; 8 | constructor(Cyclone _cyclone) public { 9 | cyclone = _cyclone; 10 | } 11 | 12 | function deposit(bytes32 _commitment) public payable { 13 | cyclone.deposit(_commitment, 0); 14 | } 15 | 16 | function withdraw(bytes memory _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) public payable { 17 | cyclone.withdraw(_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund); 18 | } 19 | } -------------------------------------------------------------------------------- /contracts/ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <6.0 >=0.4.0; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | address public owner; 11 | 12 | 13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 14 | 15 | 16 | /** 17 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 18 | * account. 19 | */ 20 | constructor() public { 21 | owner = msg.sender; 22 | } 23 | 24 | /** 25 | * @dev Throws if called by any account other than the owner. 26 | */ 27 | modifier onlyOwner() { 28 | require(msg.sender == owner); 29 | _; 30 | } 31 | 32 | /** 33 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 34 | * @param newOwner The address to transfer ownership to. 35 | */ 36 | function transferOwnership(address newOwner) public onlyOwner { 37 | require(newOwner != address(0)); 38 | emit OwnershipTransferred(owner, newOwner); 39 | owner = newOwner; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /contracts/ownership/Whitelist.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <6.0 >=0.4.0; 2 | 3 | import "./Ownable.sol"; 4 | 5 | /** 6 | * @title Whitelist 7 | * @dev The Whitelist contract has a whitelist of addresses, and provides basic authorization control functions. 8 | * @dev This simplifies the implementation of "user permissions". 9 | */ 10 | contract Whitelist is Ownable { 11 | mapping(address => bool) public whitelist; 12 | 13 | event WhitelistedAddressAdded(address addr); 14 | event WhitelistedAddressRemoved(address addr); 15 | 16 | /** 17 | * @dev Throws if called by any account that's not whitelisted. 18 | */ 19 | modifier onlyWhitelisted() { 20 | require(whitelist[msg.sender]); 21 | _; 22 | } 23 | 24 | /** 25 | * @dev add an address to the whitelist 26 | * @param addr address 27 | * @return true if the address was added to the whitelist, false if the address was already in the whitelist 28 | */ 29 | function addAddressToWhitelist(address addr) onlyOwner public returns(bool success) { 30 | if (!whitelist[addr]) { 31 | whitelist[addr] = true; 32 | emit WhitelistedAddressAdded(addr); 33 | success = true; 34 | } 35 | } 36 | 37 | /** 38 | * @dev add addresses to the whitelist 39 | * @param addrs addresses 40 | * @return true if at least one address was added to the whitelist, 41 | * false if all addresses were already in the whitelist 42 | */ 43 | function addAddressesToWhitelist(address[] memory addrs) onlyOwner public returns(bool success) { 44 | for (uint256 i = 0; i < addrs.length; i++) { 45 | if (addAddressToWhitelist(addrs[i])) { 46 | success = true; 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * @dev remove an address from the whitelist 53 | * @param addr address 54 | * @return true if the address was removed from the whitelist, 55 | * false if the address wasn't in the whitelist in the first place 56 | */ 57 | function removeAddressFromWhitelist(address addr) onlyOwner public returns(bool success) { 58 | if (whitelist[addr]) { 59 | whitelist[addr] = false; 60 | emit WhitelistedAddressRemoved(addr); 61 | success = true; 62 | } 63 | } 64 | 65 | /** 66 | * @dev remove addresses from the whitelist 67 | * @param addrs addresses 68 | * @return true if at least one address was removed from the whitelist, 69 | * false if all addresses weren't in the whitelist in the first place 70 | */ 71 | function removeAddressesFromWhitelist(address[] memory addrs) onlyOwner public returns(bool success) { 72 | for (uint256 i = 0; i < addrs.length; i++) { 73 | if (removeAddressFromWhitelist(addrs[i])) { 74 | success = true; 75 | } 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /contracts/token/BasicToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.21; 2 | 3 | 4 | import "./IERC20Basic.sol"; 5 | import "../math/SafeMath.sol"; 6 | 7 | 8 | /** 9 | * @title Basic token 10 | * @dev Basic version of StandardToken, with no allowances. 11 | */ 12 | contract BasicToken is IERC20Basic { 13 | using SafeMath for uint256; 14 | 15 | mapping(address => uint256) balances; 16 | 17 | uint256 totalSupply_; 18 | 19 | /** 20 | * @dev total number of tokens in existence 21 | */ 22 | function totalSupply() public view returns (uint256) { 23 | return totalSupply_; 24 | } 25 | 26 | /** 27 | * @dev transfer token for a specified address 28 | * @param _to The address to transfer to. 29 | * @param _value The amount to be transferred. 30 | */ 31 | /*@CTK transfer_success 32 | @pre _to != address(0) 33 | @pre balances[msg.sender] >= _value 34 | @pre __reverted == false 35 | @post __reverted == false 36 | @post __return == true 37 | */ 38 | /*@CTK transfer_same_address 39 | @tag no_overflow 40 | @pre _to == msg.sender 41 | @post this == __post 42 | */ 43 | /*@CTK transfer_conditions 44 | @tag assume_completion 45 | @pre _to != msg.sender 46 | @post __post.balances[_to] == balances[_to] + _value 47 | @post __post.balances[msg.sender] == balances[msg.sender] - _value 48 | */ 49 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 50 | function transfer(address _to, uint256 _value) public returns (bool) { 51 | require(_to != address(0)); 52 | require(_value <= balances[msg.sender]); 53 | 54 | balances[msg.sender] = balances[msg.sender].sub(_value); 55 | balances[_to] = balances[_to].add(_value); 56 | emit Transfer(msg.sender, _to, _value); 57 | return true; 58 | } 59 | 60 | /** 61 | * @dev Gets the balance of the specified address. 62 | * @param _owner The address to query the the balance of. 63 | * @return An uint256 representing the amount owned by the passed address. 64 | */ 65 | /*@CTK balanceOf 66 | @post __reverted == false 67 | @post __return == balances[_owner] 68 | */ 69 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 70 | function balanceOf(address _owner) public view returns (uint256) { 71 | return balances[_owner]; 72 | } 73 | } -------------------------------------------------------------------------------- /contracts/token/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.21; 2 | 3 | import "./IERC20Basic.sol"; 4 | 5 | 6 | /** 7 | * @title ERC20 interface 8 | * @dev see https://github.com/ethereum/EIPs/issues/20 9 | */ 10 | contract IERC20 is IERC20Basic { 11 | function name() external view returns (string memory); 12 | function symbol() external view returns (string memory); 13 | function allowance(address owner, address spender) public view returns (uint256); 14 | function transferFrom(address from, address to, uint256 value) public returns (bool); 15 | function approve(address spender, uint256 value) public returns (bool); 16 | event Approval(address indexed owner, address indexed spender, uint256 value); 17 | } -------------------------------------------------------------------------------- /contracts/token/IERC20Basic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.21; 2 | 3 | 4 | /** 5 | * @title ERC20Basic 6 | * @dev Simpler version of ERC20 interface 7 | * @dev see https://github.com/ethereum/EIPs/issues/179 8 | */ 9 | contract IERC20Basic { 10 | function totalSupply() public view returns (uint256); 11 | function balanceOf(address who) public view returns (uint256); 12 | function transfer(address to, uint256 value) public returns (bool); 13 | event Transfer(address indexed from, address indexed to, uint256 value); 14 | } -------------------------------------------------------------------------------- /contracts/token/IMintableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./IERC20.sol"; 4 | 5 | contract IMintableToken is IERC20 { 6 | function mint(address, uint) external returns (bool); 7 | function burn(uint) external returns (bool); 8 | 9 | event Minted(address indexed to, uint256 amount); 10 | event Burned(address indexed from, uint256 amount); 11 | event MinterAdded(address indexed minter); 12 | event MinterRemoved(address indexed minter); 13 | } -------------------------------------------------------------------------------- /contracts/token/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./IERC20.sol"; 4 | import "../math/SafeMath.sol"; 5 | import "../utils/Address.sol"; 6 | 7 | /** 8 | * @title SafeERC20 9 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 10 | * contract returns false). Tokens that return no value (and instead revert or 11 | * throw on failure) are also supported, non-reverting calls are assumed to be 12 | * successful. 13 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 14 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 15 | */ 16 | library SafeERC20 { 17 | using SafeMath for uint256; 18 | using Address for address; 19 | 20 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 21 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 22 | } 23 | 24 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 25 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 26 | } 27 | 28 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 29 | // safeApprove should only be called when setting an initial allowance, 30 | // or when resetting it to zero. To increase and decrease it, use 31 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 32 | // solhint-disable-next-line max-line-length 33 | require((value == 0) || (token.allowance(address(this), spender) == 0), 34 | "SafeERC20: approve from non-zero to non-zero allowance" 35 | ); 36 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 37 | } 38 | 39 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { 40 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 41 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 42 | } 43 | 44 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { 45 | uint256 newAllowance = token.allowance(address(this), spender).sub(value); 46 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 47 | } 48 | 49 | /** 50 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 51 | * on the return value: the return value is optional (but if data is returned, it must not be false). 52 | * @param token The token targeted by the call. 53 | * @param data The call data (encoded using abi.encode or one of its variants). 54 | */ 55 | function callOptionalReturn(IERC20 token, bytes memory data) private { 56 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 57 | // we're implementing it ourselves. 58 | 59 | // A Solidity high level call has three parts: 60 | // 1. The target address is checked to verify it contains contract code 61 | // 2. The call itself is made, and success asserted 62 | // 3. The return value is decoded, which in turn checks the size of the returned data. 63 | // solhint-disable-next-line max-line-length 64 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 65 | 66 | // solhint-disable-next-line avoid-low-level-calls 67 | (bool success, bytes memory returndata) = address(token).call(data); 68 | require(success, "SafeERC20: low-level call failed"); 69 | 70 | if (returndata.length > 0) { // Return data is optional 71 | // solhint-disable-next-line max-line-length 72 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/token/ShadowToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "./IMintableToken.sol"; 4 | import "./StandardToken.sol"; 5 | import "../lifecycle/Pausable.sol"; 6 | 7 | contract ShadowToken is StandardToken, IMintableToken, Pausable { 8 | event Minted(address indexed to, uint256 amount); 9 | event Burned(address indexed from, uint256 amount); 10 | event MinterAdded(address indexed minter); 11 | event MinterRemoved(address indexed minter); 12 | 13 | modifier onlyMinter() { 14 | require(minter == msg.sender, "not the minter"); 15 | _; 16 | } 17 | 18 | address public coToken; 19 | address public minter; 20 | string public name; 21 | string public symbol; 22 | uint8 public decimals; 23 | 24 | constructor(address _minter, address _coToken, string memory _name, string memory _symbol, uint8 _decimals) public { 25 | minter = _minter; 26 | coToken = _coToken; 27 | name = _name; 28 | symbol = _symbol; 29 | decimals = _decimals; 30 | emit MinterAdded(_minter); 31 | } 32 | 33 | function mint(address _to, uint256 _amount) public onlyMinter whenNotPaused returns (bool) { 34 | totalSupply_ = totalSupply_.add(_amount); 35 | balances[_to] = balances[_to].add(_amount); 36 | emit Minted(_to, _amount); 37 | emit Transfer(address(0), _to, _amount); 38 | return true; 39 | } 40 | 41 | // user can also burn by sending token to address(0), but this function will emit Burned event 42 | function burn(uint256 _amount) public returns (bool) { 43 | require(balances[msg.sender] >= _amount); 44 | totalSupply_ = totalSupply_.sub(_amount); 45 | balances[msg.sender] = balances[msg.sender].sub(_amount); 46 | emit Burned(msg.sender, _amount); 47 | emit Transfer(msg.sender, address(0), _amount); 48 | return true; 49 | } 50 | } -------------------------------------------------------------------------------- /contracts/token/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.21; 2 | 3 | import "./BasicToken.sol"; 4 | import "./IERC20.sol"; 5 | 6 | 7 | /** 8 | * @title Standard ERC20 token 9 | * 10 | * @dev Implementation of the basic standard token. 11 | * @dev https://github.com/ethereum/EIPs/issues/20 12 | * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol 13 | */ 14 | contract StandardToken is IERC20, BasicToken { 15 | 16 | mapping (address => mapping (address => uint256)) internal allowed; 17 | 18 | 19 | /** 20 | * @dev Transfer tokens from one address to another 21 | * @param _from address The address which you want to send tokens from 22 | * @param _to address The address which you want to transfer to 23 | * @param _value uint256 the amount of tokens to be transferred 24 | */ 25 | /*@CTK transferFrom 26 | @tag assume_completion 27 | @pre _from != _to 28 | @post __return == true 29 | @post __post.balances[_to] == balances[_to] + _value 30 | @post __post.balances[_from] == balances[_from] - _value 31 | @post __has_overflow == false 32 | */ 33 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 34 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 35 | require(_to != address(0)); 36 | require(_value <= balances[_from]); 37 | require(_value <= allowed[_from][msg.sender]); 38 | 39 | balances[_from] = balances[_from].sub(_value); 40 | balances[_to] = balances[_to].add(_value); 41 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); 42 | emit Transfer(_from, _to, _value); 43 | return true; 44 | } 45 | 46 | /** 47 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 48 | * 49 | * Beware that changing an allowance with this method brings the risk that someone may use both the old 50 | * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 51 | * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 52 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 53 | * @param _spender The address which will spend the funds. 54 | * @param _value The amount of tokens to be spent. 55 | */ 56 | /*@CTK approve_success 57 | @post _value == 0 -> __reverted == false 58 | @post allowed[msg.sender][_spender] == 0 -> __reverted == false 59 | */ 60 | /*@CTK approve 61 | @tag assume_completion 62 | @post __post.allowed[msg.sender][_spender] == _value 63 | */ 64 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 65 | function approve(address _spender, uint256 _value) public returns (bool) { 66 | allowed[msg.sender][_spender] = _value; 67 | emit Approval(msg.sender, _spender, _value); 68 | return true; 69 | } 70 | 71 | /** 72 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 73 | * @param _owner address The address which owns the funds. 74 | * @param _spender address The address which will spend the funds. 75 | * @return A uint256 specifying the amount of tokens still available for the spender. 76 | */ 77 | function allowance(address _owner, address _spender) public view returns (uint256) { 78 | return allowed[_owner][_spender]; 79 | } 80 | 81 | /** 82 | * @dev Increase the amount of tokens that an owner allowed to a spender. 83 | * 84 | * approve should be called when allowed[_spender] == 0. To increment 85 | * allowed value is better to use this function to avoid 2 calls (and wait until 86 | * the first transaction is mined) 87 | * From MonolithDAO Token.sol 88 | * @param _spender The address which will spend the funds. 89 | * @param _addedValue The amount of tokens to increase the allowance by. 90 | */ 91 | /*@CTK CtkIncreaseApprovalEffect 92 | @tag assume_completion 93 | @post __post.allowed[msg.sender][_spender] == allowed[msg.sender][_spender] + _addedValue 94 | @post __has_overflow == false 95 | */ 96 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 97 | function increaseApproval(address _spender, uint _addedValue) public returns (bool) { 98 | allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); 99 | emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 100 | return true; 101 | } 102 | 103 | /** 104 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 105 | * 106 | * approve should be called when allowed[_spender] == 0. To decrement 107 | * allowed value is better to use this function to avoid 2 calls (and wait until 108 | * the first transaction is mined) 109 | * From MonolithDAO Token.sol 110 | * @param _spender The address which will spend the funds. 111 | * @param _subtractedValue The amount of tokens to decrease the allowance by. 112 | */ 113 | /*@CTK CtkDecreaseApprovalEffect_1 114 | @pre allowed[msg.sender][_spender] >= _subtractedValue 115 | @tag assume_completion 116 | @post __post.allowed[msg.sender][_spender] == allowed[msg.sender][_spender] - _subtractedValue 117 | @post __has_overflow == false 118 | */ 119 | /*@CTK CtkDecreaseApprovalEffect_2 120 | @pre allowed[msg.sender][_spender] < _subtractedValue 121 | @tag assume_completion 122 | @post __post.allowed[msg.sender][_spender] == 0 123 | @post __has_overflow == false 124 | */ 125 | /* CertiK Smart Labelling, for more details visit: https://certik.org */ 126 | function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { 127 | uint oldValue = allowed[msg.sender][_spender]; 128 | if (_subtractedValue > oldValue) { 129 | allowed[msg.sender][_spender] = 0; 130 | } else { 131 | allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); 132 | } 133 | emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 134 | return true; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /contracts/uniswapv2/IRouter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0 <0.8.0; 2 | 3 | interface IRouter { 4 | function removeLiquidity( 5 | address tokenA, 6 | address tokenB, 7 | uint liquidity, 8 | uint amountAMin, 9 | uint amountBMin, 10 | address to, 11 | uint deadline 12 | ) external returns (uint amountA, uint amountB); 13 | function swapETHForExactTokens( 14 | uint amountOut, 15 | address[] calldata path, 16 | address to, 17 | uint deadline 18 | ) external payable returns (uint[] memory amounts); 19 | function swapExactTokensForTokens( 20 | uint amountIn, 21 | uint amountOutMin, 22 | address[] calldata path, 23 | address to, 24 | uint deadline 25 | ) external returns (uint[] memory amounts); 26 | function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts); 27 | } -------------------------------------------------------------------------------- /contracts/uniswapv2/UniswapV2CycloneRouter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | import "../ICycloneV2.sol"; 4 | import "../math/SafeMath.sol"; 5 | import "../token/IERC20.sol"; 6 | import "./IRouter.sol"; 7 | 8 | contract UniswapV2CycloneRouter { 9 | using SafeMath for uint256; 10 | IRouter public router; 11 | address public wrappedCoin; 12 | 13 | constructor(IRouter _router, address _wrappedCoin) public { 14 | router = _router; 15 | wrappedCoin = _wrappedCoin; 16 | } 17 | 18 | function () external payable {} 19 | 20 | function purchaseCost(ICycloneV2 _cyclone) external view returns (uint256) { 21 | uint256 cycAmount = _cyclone.cycDenomination(); 22 | if (cycAmount == 0) { 23 | return 0; 24 | } 25 | address[] memory paths = new address[](2); 26 | paths[0] = wrappedCoin; 27 | paths[1] = address(_cyclone.cycToken()); 28 | uint256[] memory amounts = router.getAmountsIn(cycAmount, paths); 29 | return amounts[0]; 30 | } 31 | 32 | function deposit(ICycloneV2 _cyclone, bytes32 _commitment, bool _buyCYC) external payable { 33 | uint256 coinAmount = _cyclone.coinDenomination(); 34 | uint256 tokenAmount = _cyclone.tokenDenomination(); 35 | uint256 cycAmount = _cyclone.cycDenomination(); 36 | require(msg.value >= coinAmount, "UniswapV2CycloneRouter: insufficient coin amount"); 37 | uint256 remainingCoin = msg.value - coinAmount; 38 | if (tokenAmount > 0) { 39 | IERC20 token = _cyclone.token(); 40 | require(token.transferFrom(msg.sender, address(this), tokenAmount), "UniswapV2CycloneRouter: failed to transfer token"); 41 | require(token.approve(address(_cyclone), tokenAmount), "UniswapV2CycloneRouter: failed to approve allowance"); 42 | } 43 | if (cycAmount > 0) { 44 | IERC20 cycToken = _cyclone.cycToken(); 45 | if (_buyCYC) { 46 | address[] memory path = new address[](2); 47 | path[0] = wrappedCoin; 48 | path[1] = address(cycToken); 49 | uint256[] memory amounts = router.swapETHForExactTokens.value(remainingCoin)(cycAmount, path, address(this), block.timestamp.mul(2)); 50 | require(remainingCoin >= amounts[0], "UniswapV2CycloneRouter: unexpected status"); 51 | remainingCoin -= amounts[0]; 52 | } else { 53 | require(cycToken.transferFrom(msg.sender, address(this), cycAmount), "UniswapV2CycloneRouter: failed to transfer CYC token"); 54 | } 55 | require(cycToken.approve(address(_cyclone), cycAmount), "UniswapV2CycloneRouter: failed to approve CYC token allowance"); 56 | } 57 | _cyclone.deposit.value(coinAmount)(_commitment); 58 | if (remainingCoin > 0) { 59 | (bool success,) = msg.sender.call.value(remainingCoin)(""); 60 | require(success, 'UniswapV2CycloneRouter: failed to refund'); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /contracts/utils/Address.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * @dev Collection of functions related to the address type, 5 | */ 6 | library Address { 7 | /** 8 | * @dev Returns true if `account` is a contract. 9 | * 10 | * This test is non-exhaustive, and there may be false-negatives: during the 11 | * execution of a contract's constructor, its address will be reported as 12 | * not containing a contract. 13 | * 14 | * > It is unsafe to assume that an address for which this function returns 15 | * false is an externally-owned account (EOA) and not a contract. 16 | */ 17 | function isContract(address account) internal view returns (bool) { 18 | // This method relies in extcodesize, which returns 0 for contracts in 19 | // construction, since the code is only stored at the end of the 20 | // constructor execution. 21 | 22 | uint256 size; 23 | // solhint-disable-next-line no-inline-assembly 24 | assembly { size := extcodesize(account) } 25 | return size > 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/zksnarklib/IVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | contract IVerifier { 4 | function verifyProof(bytes memory _proof, uint256[6] memory _input) public returns(bool); 5 | } -------------------------------------------------------------------------------- /contracts/zksnarklib/MerkleTreeWithHistory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity <0.6 >=0.4.24; 2 | 3 | library Hasher { 4 | function MiMCSponge(uint256 in_xL, uint256 in_xR) public pure returns (uint256 xL, uint256 xR); 5 | } 6 | 7 | contract MerkleTreeWithHistory { 8 | uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 9 | uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE 10 | 11 | uint32 public levels; 12 | 13 | // the following variables are made public for easier testing and debugging and 14 | // are not supposed to be accessed in regular code 15 | bytes32[] public filledSubtrees; 16 | bytes32[] public zeros; 17 | uint32 public currentRootIndex = 0; 18 | uint32 public nextIndex = 0; 19 | uint32 public constant ROOT_HISTORY_SIZE = 100; 20 | bytes32[ROOT_HISTORY_SIZE] public roots; 21 | 22 | constructor(uint32 _treeLevels) public { 23 | require(_treeLevels > 0, "_treeLevels should be greater than zero"); 24 | require(_treeLevels < 32, "_treeLevels should be less than 32"); 25 | levels = _treeLevels; 26 | 27 | bytes32 currentZero = bytes32(ZERO_VALUE); 28 | zeros.push(currentZero); 29 | filledSubtrees.push(currentZero); 30 | 31 | for (uint32 i = 1; i < levels; i++) { 32 | currentZero = hashLeftRight(currentZero, currentZero); 33 | zeros.push(currentZero); 34 | filledSubtrees.push(currentZero); 35 | } 36 | 37 | roots[0] = hashLeftRight(currentZero, currentZero); 38 | } 39 | 40 | /** 41 | @dev Hash 2 tree leaves, returns MiMC(_left, _right) 42 | */ 43 | function hashLeftRight(bytes32 _left, bytes32 _right) public pure returns (bytes32) { 44 | require(uint256(_left) < FIELD_SIZE, "_left should be inside the field"); 45 | require(uint256(_right) < FIELD_SIZE, "_right should be inside the field"); 46 | uint256 R = uint256(_left); 47 | uint256 C = 0; 48 | (R, C) = Hasher.MiMCSponge(R, C); 49 | R = addmod(R, uint256(_right), FIELD_SIZE); 50 | (R, C) = Hasher.MiMCSponge(R, C); 51 | return bytes32(R); 52 | } 53 | 54 | function _insert(bytes32 _leaf) internal returns(uint32 index) { 55 | uint32 currentIndex = nextIndex; 56 | require(currentIndex != uint32(2)**levels, "Merkle tree is full. No more leafs can be added"); 57 | nextIndex += 1; 58 | bytes32 currentLevelHash = _leaf; 59 | bytes32 left; 60 | bytes32 right; 61 | 62 | for (uint32 i = 0; i < levels; i++) { 63 | if (currentIndex % 2 == 0) { 64 | left = currentLevelHash; 65 | right = zeros[i]; 66 | 67 | filledSubtrees[i] = currentLevelHash; 68 | } else { 69 | left = filledSubtrees[i]; 70 | right = currentLevelHash; 71 | } 72 | 73 | currentLevelHash = hashLeftRight(left, right); 74 | 75 | currentIndex /= 2; 76 | } 77 | 78 | currentRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE; 79 | roots[currentRootIndex] = currentLevelHash; 80 | return nextIndex - 1; 81 | } 82 | 83 | /** 84 | @dev Whether the root is present in the root history 85 | */ 86 | function isKnownRoot(bytes32 _root) public view returns(bool) { 87 | if (_root == 0) { 88 | return false; 89 | } 90 | uint32 i = currentRootIndex; 91 | do { 92 | if (_root == roots[i]) { 93 | return true; 94 | } 95 | if (i == 0) { 96 | i = ROOT_HISTORY_SIZE; 97 | } 98 | i--; 99 | } while (i != currentRootIndex); 100 | return false; 101 | } 102 | 103 | /** 104 | @dev Returns the last root 105 | */ 106 | function getLastRoot() public view returns(bytes32) { 107 | return roots[currentRootIndex]; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /contracts/zksnarklib/Verifier.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2020-05-12 3 | */ 4 | 5 | // https://tornado.cash Verifier.sol generated by trusted setup ceremony. 6 | /* 7 | * d888888P dP a88888b. dP 8 | * 88 88 d8' `88 88 9 | * 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b. 10 | * 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88 11 | * 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88 12 | * dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP 13 | * ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo 14 | */ 15 | // Copyright 2017 Christian Reitwiessner 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to 18 | // deal in the Software without restriction, including without limitation the 19 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 20 | // sell copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // The above copyright notice and this permission notice shall be included in 23 | // all copies or substantial portions of the Software. 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 30 | // IN THE SOFTWARE. 31 | 32 | // 2019 OKIMS 33 | 34 | pragma solidity <0.6 >=0.4.24; 35 | 36 | library Pairing { 37 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 38 | 39 | struct G1Point { 40 | uint256 X; 41 | uint256 Y; 42 | } 43 | 44 | // Encoding of field elements is: X[0] * z + X[1] 45 | struct G2Point { 46 | uint256[2] X; 47 | uint256[2] Y; 48 | } 49 | 50 | /* 51 | * @return The negation of p, i.e. p.plus(p.negate()) should be zero. 52 | */ 53 | function negate(G1Point memory p) internal pure returns (G1Point memory) { 54 | // The prime q in the base field F_q for G1 55 | if (p.X == 0 && p.Y == 0) { 56 | return G1Point(0, 0); 57 | } else { 58 | return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q)); 59 | } 60 | } 61 | 62 | /* 63 | * @return r the sum of two points of G1 64 | */ 65 | function plus( 66 | G1Point memory p1, 67 | G1Point memory p2 68 | ) internal view returns (G1Point memory r) { 69 | uint256[4] memory input; 70 | input[0] = p1.X; 71 | input[1] = p1.Y; 72 | input[2] = p2.X; 73 | input[3] = p2.Y; 74 | bool success; 75 | 76 | // solium-disable-next-line security/no-inline-assembly 77 | assembly { 78 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 79 | // Use "invalid" to make gas estimation work 80 | switch success case 0 { invalid() } 81 | } 82 | 83 | require(success, "pairing-add-failed"); 84 | } 85 | 86 | /* 87 | * @return r the product of a point on G1 and a scalar, i.e. 88 | * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all 89 | * points p. 90 | */ 91 | function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { 92 | uint256[3] memory input; 93 | input[0] = p.X; 94 | input[1] = p.Y; 95 | input[2] = s; 96 | bool success; 97 | // solium-disable-next-line security/no-inline-assembly 98 | assembly { 99 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 100 | // Use "invalid" to make gas estimation work 101 | switch success case 0 { invalid() } 102 | } 103 | require(success, "pairing-mul-failed"); 104 | } 105 | 106 | /* @return The result of computing the pairing check 107 | * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 108 | * For example, 109 | * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. 110 | */ 111 | function pairing( 112 | G1Point memory a1, 113 | G2Point memory a2, 114 | G1Point memory b1, 115 | G2Point memory b2, 116 | G1Point memory c1, 117 | G2Point memory c2, 118 | G1Point memory d1, 119 | G2Point memory d2 120 | ) internal view returns (bool) { 121 | G1Point[4] memory p1 = [a1, b1, c1, d1]; 122 | G2Point[4] memory p2 = [a2, b2, c2, d2]; 123 | 124 | uint256 inputSize = 24; 125 | uint256[] memory input = new uint256[](inputSize); 126 | 127 | for (uint256 i = 0; i < 4; i++) { 128 | uint256 j = i * 6; 129 | input[j + 0] = p1[i].X; 130 | input[j + 1] = p1[i].Y; 131 | input[j + 2] = p2[i].X[0]; 132 | input[j + 3] = p2[i].X[1]; 133 | input[j + 4] = p2[i].Y[0]; 134 | input[j + 5] = p2[i].Y[1]; 135 | } 136 | 137 | uint256[1] memory out; 138 | bool success; 139 | 140 | // solium-disable-next-line security/no-inline-assembly 141 | assembly { 142 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 143 | // Use "invalid" to make gas estimation work 144 | switch success case 0 { invalid() } 145 | } 146 | 147 | require(success, "pairing-opcode-failed"); 148 | 149 | return out[0] != 0; 150 | } 151 | } 152 | 153 | contract Verifier { 154 | uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 155 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 156 | using Pairing for *; 157 | 158 | struct VerifyingKey { 159 | Pairing.G1Point alfa1; 160 | Pairing.G2Point beta2; 161 | Pairing.G2Point gamma2; 162 | Pairing.G2Point delta2; 163 | Pairing.G1Point[7] IC; 164 | } 165 | 166 | struct Proof { 167 | Pairing.G1Point A; 168 | Pairing.G2Point B; 169 | Pairing.G1Point C; 170 | } 171 | 172 | function verifyingKey() internal pure returns (VerifyingKey memory vk) { 173 | vk.alfa1 = Pairing.G1Point(uint256(20692898189092739278193869274495556617788530808486270118371701516666252877969), uint256(11713062878292653967971378194351968039596396853904572879488166084231740557279)); 174 | vk.beta2 = Pairing.G2Point([uint256(12168528810181263706895252315640534818222943348193302139358377162645029937006), uint256(281120578337195720357474965979947690431622127986816839208576358024608803542)], [uint256(16129176515713072042442734839012966563817890688785805090011011570989315559913), uint256(9011703453772030375124466642203641636825223906145908770308724549646909480510)]); 175 | vk.gamma2 = Pairing.G2Point([uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781)], [uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930)]); 176 | vk.delta2 = Pairing.G2Point([uint256(21280594949518992153305586783242820682644996932183186320680800072133486887432), uint256(150879136433974552800030963899771162647715069685890547489132178314736470662)], [uint256(1081836006956609894549771334721413187913047383331561601606260283167615953295), uint256(11434086686358152335540554643130007307617078324975981257823476472104616196090)]); 177 | vk.IC[0] = Pairing.G1Point(uint256(16225148364316337376768119297456868908427925829817748684139175309620217098814), uint256(5167268689450204162046084442581051565997733233062478317813755636162413164690)); 178 | vk.IC[1] = Pairing.G1Point(uint256(12882377842072682264979317445365303375159828272423495088911985689463022094260), uint256(19488215856665173565526758360510125932214252767275816329232454875804474844786)); 179 | vk.IC[2] = Pairing.G1Point(uint256(13083492661683431044045992285476184182144099829507350352128615182516530014777), uint256(602051281796153692392523702676782023472744522032670801091617246498551238913)); 180 | vk.IC[3] = Pairing.G1Point(uint256(9732465972180335629969421513785602934706096902316483580882842789662669212890), uint256(2776526698606888434074200384264824461688198384989521091253289776235602495678)); 181 | vk.IC[4] = Pairing.G1Point(uint256(8586364274534577154894611080234048648883781955345622578531233113180532234842), uint256(21276134929883121123323359450658320820075698490666870487450985603988214349407)); 182 | vk.IC[5] = Pairing.G1Point(uint256(4910628533171597675018724709631788948355422829499855033965018665300386637884), uint256(20532468890024084510431799098097081600480376127870299142189696620752500664302)); 183 | vk.IC[6] = Pairing.G1Point(uint256(15335858102289947642505450692012116222827233918185150176888641903531542034017), uint256(5311597067667671581646709998171703828965875677637292315055030353779531404812)); 184 | 185 | } 186 | 187 | /* 188 | * @returns Whether the proof is valid given the hardcoded verifying key 189 | * above and the public inputs 190 | */ 191 | function verifyProof( 192 | bytes memory proof, 193 | uint256[6] memory input 194 | ) public view returns (bool) { 195 | uint256[8] memory p = abi.decode(proof, (uint256[8])); 196 | 197 | // Make sure that each element in the proof is less than the prime q 198 | for (uint8 i = 0; i < p.length; i++) { 199 | require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q"); 200 | } 201 | 202 | Proof memory _proof; 203 | _proof.A = Pairing.G1Point(p[0], p[1]); 204 | _proof.B = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]); 205 | _proof.C = Pairing.G1Point(p[6], p[7]); 206 | 207 | VerifyingKey memory vk = verifyingKey(); 208 | 209 | // Compute the linear combination vk_x 210 | Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); 211 | vk_x = Pairing.plus(vk_x, vk.IC[0]); 212 | 213 | // Make sure that every input is less than the snark scalar field 214 | for (uint256 i = 0; i < input.length; i++) { 215 | require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field"); 216 | vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); 217 | } 218 | 219 | return Pairing.pairing( 220 | Pairing.negate(_proof.A), 221 | _proof.B, 222 | vk.alfa1, 223 | vk.beta2, 224 | vk_x, 225 | vk.gamma2, 226 | _proof.C, 227 | vk.delta2 228 | ); 229 | } 230 | } -------------------------------------------------------------------------------- /lib/MerkleTree.js: -------------------------------------------------------------------------------- 1 | const jsStorage = require('./Storage') 2 | const hasherImpl = require('./MiMC') 3 | 4 | class MerkleTree { 5 | 6 | constructor(n_levels, defaultElements, prefix, storage, hasher) { 7 | this.prefix = prefix 8 | this.storage = storage || new jsStorage() 9 | this.hasher = hasher || new hasherImpl() 10 | this.n_levels = n_levels 11 | this.zero_values = [] 12 | this.totalElements = 0 13 | 14 | let current_zero_value = '21663839004416932945382355908790599225266501822907911457504978515578255421292' 15 | this.zero_values.push(current_zero_value) 16 | for (let i = 0; i < n_levels; i++) { 17 | current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value) 18 | this.zero_values.push( 19 | current_zero_value.toString(), 20 | ) 21 | } 22 | if (defaultElements) { 23 | let level = 0 24 | this.totalElements = defaultElements.length 25 | defaultElements.forEach((element, i) => { 26 | this.storage.put(MerkleTree.index_to_key(prefix, level, i), element) 27 | }) 28 | level++ 29 | let numberOfElementsInLevel = Math.ceil(defaultElements.length / 2) 30 | for (level; level <= this.n_levels; level++) { 31 | for(let i = 0; i < numberOfElementsInLevel; i++) { 32 | const leftKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i) 33 | const rightKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i + 1) 34 | 35 | const left = this.storage.get(leftKey) 36 | const right = this.storage.get_or_element(rightKey, this.zero_values[level - 1]) 37 | 38 | const subRoot = this.hasher.hash(null, left, right) 39 | this.storage.put(MerkleTree.index_to_key(prefix, level, i), subRoot) 40 | } 41 | numberOfElementsInLevel = Math.ceil(numberOfElementsInLevel / 2) 42 | } 43 | } 44 | } 45 | 46 | static index_to_key(prefix, level, index) { 47 | const key = `${prefix}_tree_${level}_${index}` 48 | return key 49 | } 50 | 51 | async root() { 52 | let root = await this.storage.get_or_element( 53 | MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 54 | this.zero_values[this.n_levels], 55 | ) 56 | 57 | return root 58 | } 59 | 60 | async path(index) { 61 | class PathTraverser { 62 | constructor(prefix, storage, zero_values) { 63 | this.prefix = prefix 64 | this.storage = storage 65 | this.zero_values = zero_values 66 | this.path_elements = [] 67 | this.path_index = [] 68 | } 69 | 70 | async handle_index(level, element_index, sibling_index) { 71 | const sibling = await this.storage.get_or_element( 72 | MerkleTree.index_to_key(this.prefix, level, sibling_index), 73 | this.zero_values[level], 74 | ) 75 | this.path_elements.push(sibling) 76 | this.path_index.push(element_index % 2) 77 | } 78 | } 79 | index = Number(index) 80 | let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values) 81 | const root = await this.storage.get_or_element( 82 | MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 83 | this.zero_values[this.n_levels], 84 | ) 85 | 86 | const element = await this.storage.get_or_element( 87 | MerkleTree.index_to_key(this.prefix, 0, index), 88 | this.zero_values[0], 89 | ) 90 | 91 | await this.traverse(index, traverser) 92 | return { 93 | root, 94 | path_elements: traverser.path_elements, 95 | path_index: traverser.path_index, 96 | element 97 | } 98 | } 99 | 100 | async update(index, element, insert = false) { 101 | if (!insert && index >= this.totalElements) { 102 | throw Error('Use insert method for new elements.') 103 | } else if(insert && index < this.totalElements) { 104 | throw Error('Use update method for existing elements.') 105 | } 106 | try { 107 | class UpdateTraverser { 108 | constructor(prefix, storage, hasher, element, zero_values) { 109 | this.prefix = prefix 110 | this.current_element = element 111 | this.zero_values = zero_values 112 | this.storage = storage 113 | this.hasher = hasher 114 | this.key_values_to_put = [] 115 | } 116 | 117 | async handle_index(level, element_index, sibling_index) { 118 | if (level == 0) { 119 | this.original_element = await this.storage.get_or_element( 120 | MerkleTree.index_to_key(this.prefix, level, element_index), 121 | this.zero_values[level], 122 | ) 123 | } 124 | const sibling = await this.storage.get_or_element( 125 | MerkleTree.index_to_key(this.prefix, level, sibling_index), 126 | this.zero_values[level], 127 | ) 128 | let left, right 129 | if (element_index % 2 == 0) { 130 | left = this.current_element 131 | right = sibling 132 | } else { 133 | left = sibling 134 | right = this.current_element 135 | } 136 | 137 | this.key_values_to_put.push({ 138 | key: MerkleTree.index_to_key(this.prefix, level, element_index), 139 | value: this.current_element, 140 | }) 141 | this.current_element = this.hasher.hash(level, left, right) 142 | } 143 | } 144 | let traverser = new UpdateTraverser( 145 | this.prefix, 146 | this.storage, 147 | this.hasher, 148 | element, 149 | this.zero_values 150 | ) 151 | 152 | await this.traverse(index, traverser) 153 | traverser.key_values_to_put.push({ 154 | key: MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 155 | value: traverser.current_element, 156 | }) 157 | 158 | await this.storage.put_batch(traverser.key_values_to_put) 159 | } catch(e) { 160 | console.error(e) 161 | } 162 | } 163 | 164 | async insert(element) { 165 | const index = this.totalElements 166 | await this.update(index, element, true) 167 | this.totalElements++ 168 | } 169 | 170 | async traverse(index, handler) { 171 | let current_index = index 172 | for (let i = 0; i < this.n_levels; i++) { 173 | let sibling_index = current_index 174 | if (current_index % 2 == 0) { 175 | sibling_index += 1 176 | } else { 177 | sibling_index -= 1 178 | } 179 | await handler.handle_index(i, current_index, sibling_index) 180 | current_index = Math.floor(current_index / 2) 181 | } 182 | } 183 | 184 | getIndexByElement(element) { 185 | for(let i = this.totalElements - 1; i >= 0; i--) { 186 | const elementFromTree = this.storage.get(MerkleTree.index_to_key(this.prefix, 0, i)) 187 | if (elementFromTree === element) { 188 | return i 189 | } 190 | } 191 | return false 192 | } 193 | } 194 | 195 | module.exports = MerkleTree 196 | -------------------------------------------------------------------------------- /lib/MiMC.js: -------------------------------------------------------------------------------- 1 | const circomlib = require('circomlib') 2 | const mimcsponge = circomlib.mimcsponge 3 | const snarkjs = require('snarkjs') 4 | 5 | const bigInt = snarkjs.bigInt 6 | 7 | class MimcSpongeHasher { 8 | hash(level, left, right) { 9 | return mimcsponge.multiHash([bigInt(left), bigInt(right)]).toString() 10 | } 11 | } 12 | 13 | module.exports = MimcSpongeHasher 14 | -------------------------------------------------------------------------------- /lib/Storage.js: -------------------------------------------------------------------------------- 1 | class JsStorage { 2 | constructor() { 3 | this.db = {} 4 | } 5 | 6 | get(key) { 7 | return this.db[key] 8 | } 9 | 10 | get_or_element(key, defaultElement) { 11 | const element = this.db[key] 12 | if (element === undefined) { 13 | return defaultElement 14 | } else { 15 | return element 16 | } 17 | } 18 | 19 | put(key, value) { 20 | if (key === undefined || value === undefined) { 21 | throw Error('key or value is undefined') 22 | } 23 | this.db[key] = value 24 | } 25 | 26 | del(key) { 27 | delete this.db[key] 28 | } 29 | 30 | put_batch(key_values) { 31 | key_values.forEach(element => { 32 | this.db[element.key] = element.value 33 | }) 34 | } 35 | } 36 | 37 | module.exports = JsStorage 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cyclone-contracts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "build:circuit:compile": "npx circom circuits/withdraw.circom -o build/circuits/withdraw.json && npx snarkjs info -c build/circuits/withdraw.json", 11 | "build:circuit:setup": "npx snarkjs setup --protocol groth -c build/circuits/withdraw.json --pk build/circuits/withdraw_proving_key.json --vk build/circuits/withdraw_verification_key.json", 12 | "build:circuit:bin": "node node_modules/websnark/tools/buildpkey.js -i build/circuits/withdraw_proving_key.json -o build/circuits/withdraw_proving_key.bin", 13 | "build:circuit:contract": "npx snarkjs generateverifier -v build/circuits/Verifier.sol --vk build/circuits/withdraw_verification_key.json", 14 | "build:circuit": "mkdir -p build/circuits && npm run build:circuit:compile && npm run build:circuit:setup && npm run build:circuit:bin && npm run build:circuit:contract", 15 | "build:contract": "truffle compile", 16 | "build:local": "npm run build:circuit && npm run build:contract", 17 | "build": "mkdir -p build/circuits && cp ./production/* ./build/circuits/ && npm run build:contract", 18 | "test": "truffle test" 19 | }, 20 | "author": "", 21 | "license": "ISC", 22 | "dependencies": { 23 | "@openzeppelin/contracts": "^2.4.0", 24 | "@openzeppelin/test-helpers": "^0.5.9", 25 | "@truffle/hdwallet-provider": "^1.2.0", 26 | "axios": "^0.21.0", 27 | "circom": "0.0.35", 28 | "circomlib": "git+https://github.com/tornadocash/circomlib.git#c372f14d324d57339c88451834bf2824e73bbdbc", 29 | "commander": "^4.1.1", 30 | "dotenv": "^8.2.0", 31 | "iotex-antenna": "^0.30.4", 32 | "snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5", 33 | "solc": "^0.7.4", 34 | "web3-eth-abi": "^1.3.0", 35 | "websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /production/Verifier.sol: -------------------------------------------------------------------------------- 1 | /** 2 | *Submitted for verification at Etherscan.io on 2020-05-12 3 | */ 4 | 5 | // https://tornado.cash Verifier.sol generated by trusted setup ceremony. 6 | /* 7 | * d888888P dP a88888b. dP 8 | * 88 88 d8' `88 88 9 | * 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b. 10 | * 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88 11 | * 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88 12 | * dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP 13 | * ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo 14 | */ 15 | // Copyright 2017 Christian Reitwiessner 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to 18 | // deal in the Software without restriction, including without limitation the 19 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 20 | // sell copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | // The above copyright notice and this permission notice shall be included in 23 | // all copies or substantial portions of the Software. 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 30 | // IN THE SOFTWARE. 31 | 32 | // 2019 OKIMS 33 | 34 | pragma solidity 0.5.17; 35 | 36 | library Pairing { 37 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 38 | 39 | struct G1Point { 40 | uint256 X; 41 | uint256 Y; 42 | } 43 | 44 | // Encoding of field elements is: X[0] * z + X[1] 45 | struct G2Point { 46 | uint256[2] X; 47 | uint256[2] Y; 48 | } 49 | 50 | /* 51 | * @return The negation of p, i.e. p.plus(p.negate()) should be zero. 52 | */ 53 | function negate(G1Point memory p) internal pure returns (G1Point memory) { 54 | // The prime q in the base field F_q for G1 55 | if (p.X == 0 && p.Y == 0) { 56 | return G1Point(0, 0); 57 | } else { 58 | return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q)); 59 | } 60 | } 61 | 62 | /* 63 | * @return r the sum of two points of G1 64 | */ 65 | function plus( 66 | G1Point memory p1, 67 | G1Point memory p2 68 | ) internal view returns (G1Point memory r) { 69 | uint256[4] memory input; 70 | input[0] = p1.X; 71 | input[1] = p1.Y; 72 | input[2] = p2.X; 73 | input[3] = p2.Y; 74 | bool success; 75 | 76 | // solium-disable-next-line security/no-inline-assembly 77 | assembly { 78 | success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60) 79 | // Use "invalid" to make gas estimation work 80 | switch success case 0 { invalid() } 81 | } 82 | 83 | require(success, "pairing-add-failed"); 84 | } 85 | 86 | /* 87 | * @return r the product of a point on G1 and a scalar, i.e. 88 | * p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all 89 | * points p. 90 | */ 91 | function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) { 92 | uint256[3] memory input; 93 | input[0] = p.X; 94 | input[1] = p.Y; 95 | input[2] = s; 96 | bool success; 97 | // solium-disable-next-line security/no-inline-assembly 98 | assembly { 99 | success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60) 100 | // Use "invalid" to make gas estimation work 101 | switch success case 0 { invalid() } 102 | } 103 | require(success, "pairing-mul-failed"); 104 | } 105 | 106 | /* @return The result of computing the pairing check 107 | * e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1 108 | * For example, 109 | * pairing([P1(), P1().negate()], [P2(), P2()]) should return true. 110 | */ 111 | function pairing( 112 | G1Point memory a1, 113 | G2Point memory a2, 114 | G1Point memory b1, 115 | G2Point memory b2, 116 | G1Point memory c1, 117 | G2Point memory c2, 118 | G1Point memory d1, 119 | G2Point memory d2 120 | ) internal view returns (bool) { 121 | G1Point[4] memory p1 = [a1, b1, c1, d1]; 122 | G2Point[4] memory p2 = [a2, b2, c2, d2]; 123 | 124 | uint256 inputSize = 24; 125 | uint256[] memory input = new uint256[](inputSize); 126 | 127 | for (uint256 i = 0; i < 4; i++) { 128 | uint256 j = i * 6; 129 | input[j + 0] = p1[i].X; 130 | input[j + 1] = p1[i].Y; 131 | input[j + 2] = p2[i].X[0]; 132 | input[j + 3] = p2[i].X[1]; 133 | input[j + 4] = p2[i].Y[0]; 134 | input[j + 5] = p2[i].Y[1]; 135 | } 136 | 137 | uint256[1] memory out; 138 | bool success; 139 | 140 | // solium-disable-next-line security/no-inline-assembly 141 | assembly { 142 | success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) 143 | // Use "invalid" to make gas estimation work 144 | switch success case 0 { invalid() } 145 | } 146 | 147 | require(success, "pairing-opcode-failed"); 148 | 149 | return out[0] != 0; 150 | } 151 | } 152 | 153 | contract Verifier { 154 | uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 155 | uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; 156 | using Pairing for *; 157 | 158 | struct VerifyingKey { 159 | Pairing.G1Point alfa1; 160 | Pairing.G2Point beta2; 161 | Pairing.G2Point gamma2; 162 | Pairing.G2Point delta2; 163 | Pairing.G1Point[7] IC; 164 | } 165 | 166 | struct Proof { 167 | Pairing.G1Point A; 168 | Pairing.G2Point B; 169 | Pairing.G1Point C; 170 | } 171 | 172 | function verifyingKey() internal pure returns (VerifyingKey memory vk) { 173 | vk.alfa1 = Pairing.G1Point(uint256(20692898189092739278193869274495556617788530808486270118371701516666252877969), uint256(11713062878292653967971378194351968039596396853904572879488166084231740557279)); 174 | vk.beta2 = Pairing.G2Point([uint256(12168528810181263706895252315640534818222943348193302139358377162645029937006), uint256(281120578337195720357474965979947690431622127986816839208576358024608803542)], [uint256(16129176515713072042442734839012966563817890688785805090011011570989315559913), uint256(9011703453772030375124466642203641636825223906145908770308724549646909480510)]); 175 | vk.gamma2 = Pairing.G2Point([uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781)], [uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930)]); 176 | vk.delta2 = Pairing.G2Point([uint256(21280594949518992153305586783242820682644996932183186320680800072133486887432), uint256(150879136433974552800030963899771162647715069685890547489132178314736470662)], [uint256(1081836006956609894549771334721413187913047383331561601606260283167615953295), uint256(11434086686358152335540554643130007307617078324975981257823476472104616196090)]); 177 | vk.IC[0] = Pairing.G1Point(uint256(16225148364316337376768119297456868908427925829817748684139175309620217098814), uint256(5167268689450204162046084442581051565997733233062478317813755636162413164690)); 178 | vk.IC[1] = Pairing.G1Point(uint256(12882377842072682264979317445365303375159828272423495088911985689463022094260), uint256(19488215856665173565526758360510125932214252767275816329232454875804474844786)); 179 | vk.IC[2] = Pairing.G1Point(uint256(13083492661683431044045992285476184182144099829507350352128615182516530014777), uint256(602051281796153692392523702676782023472744522032670801091617246498551238913)); 180 | vk.IC[3] = Pairing.G1Point(uint256(9732465972180335629969421513785602934706096902316483580882842789662669212890), uint256(2776526698606888434074200384264824461688198384989521091253289776235602495678)); 181 | vk.IC[4] = Pairing.G1Point(uint256(8586364274534577154894611080234048648883781955345622578531233113180532234842), uint256(21276134929883121123323359450658320820075698490666870487450985603988214349407)); 182 | vk.IC[5] = Pairing.G1Point(uint256(4910628533171597675018724709631788948355422829499855033965018665300386637884), uint256(20532468890024084510431799098097081600480376127870299142189696620752500664302)); 183 | vk.IC[6] = Pairing.G1Point(uint256(15335858102289947642505450692012116222827233918185150176888641903531542034017), uint256(5311597067667671581646709998171703828965875677637292315055030353779531404812)); 184 | 185 | } 186 | 187 | /* 188 | * @returns Whether the proof is valid given the hardcoded verifying key 189 | * above and the public inputs 190 | */ 191 | function verifyProof( 192 | bytes memory proof, 193 | uint256[6] memory input 194 | ) public view returns (bool) { 195 | uint256[8] memory p = abi.decode(proof, (uint256[8])); 196 | 197 | // Make sure that each element in the proof is less than the prime q 198 | for (uint8 i = 0; i < p.length; i++) { 199 | require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q"); 200 | } 201 | 202 | Proof memory _proof; 203 | _proof.A = Pairing.G1Point(p[0], p[1]); 204 | _proof.B = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]); 205 | _proof.C = Pairing.G1Point(p[6], p[7]); 206 | 207 | VerifyingKey memory vk = verifyingKey(); 208 | 209 | // Compute the linear combination vk_x 210 | Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); 211 | vk_x = Pairing.plus(vk_x, vk.IC[0]); 212 | 213 | // Make sure that every input is less than the snark scalar field 214 | for (uint256 i = 0; i < input.length; i++) { 215 | require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field"); 216 | vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i])); 217 | } 218 | 219 | return Pairing.pairing( 220 | Pairing.negate(_proof.A), 221 | _proof.B, 222 | vk.alfa1, 223 | vk.beta2, 224 | vk_x, 225 | vk.gamma2, 226 | _proof.C, 227 | vk.delta2 228 | ); 229 | } 230 | } -------------------------------------------------------------------------------- /production/withdraw_proving_key.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cycloneprotocol/cyclone-contracts/1c756369f5788526b483e7d60480bf2916947894/production/withdraw_proving_key.bin -------------------------------------------------------------------------------- /production/withdraw_verification_key.json: -------------------------------------------------------------------------------- 1 | {"IC":[["16225148364316337376768119297456868908427925829817748684139175309620217098814","5167268689450204162046084442581051565997733233062478317813755636162413164690","1"],["12882377842072682264979317445365303375159828272423495088911985689463022094260","19488215856665173565526758360510125932214252767275816329232454875804474844786","1"],["13083492661683431044045992285476184182144099829507350352128615182516530014777","602051281796153692392523702676782023472744522032670801091617246498551238913","1"],["9732465972180335629969421513785602934706096902316483580882842789662669212890","2776526698606888434074200384264824461688198384989521091253289776235602495678","1"],["8586364274534577154894611080234048648883781955345622578531233113180532234842","21276134929883121123323359450658320820075698490666870487450985603988214349407","1"],["4910628533171597675018724709631788948355422829499855033965018665300386637884","20532468890024084510431799098097081600480376127870299142189696620752500664302","1"],["15335858102289947642505450692012116222827233918185150176888641903531542034017","5311597067667671581646709998171703828965875677637292315055030353779531404812","1"]],"vk_alfa_1":["20692898189092739278193869274495556617788530808486270118371701516666252877969","11713062878292653967971378194351968039596396853904572879488166084231740557279","1"],"vk_beta_2":[["281120578337195720357474965979947690431622127986816839208576358024608803542","12168528810181263706895252315640534818222943348193302139358377162645029937006"],["9011703453772030375124466642203641636825223906145908770308724549646909480510","16129176515713072042442734839012966563817890688785805090011011570989315559913"],["1","0"]],"vk_gamma_2":[["10857046999023057135944570762232829481370756359578518086990519993285655852781","11559732032986387107991004021392285783925812861821192530917403151452391805634"],["8495653923123431417604973247489272438418190587263600148770280649306958101930","4082367875863433681332203403145435568316851327593401208105741076214120093531"],["1","0"]],"vk_delta_2":[["150879136433974552800030963899771162647715069685890547489132178314736470662","21280594949518992153305586783242820682644996932183186320680800072133486887432"],["11434086686358152335540554643130007307617078324975981257823476472104616196090","1081836006956609894549771334721413187913047383331561601606260283167615953295"],["1","0"]],"vk_alfabeta_12":[[["21365812195485565970449951947073093780266368302919758441364015846541070649889","13899346400535634506212110533298291334607392402427556671205120154633328392938"],["9176241749501911223800029754421468853554325976185319022570714890557573371905","16897971416518120303319064173781767941602184168568029998374699041158871223961"],["6994180461635063460601079479427022670717591990958754250719881638298446317234","9179026066977875182617507817914233459615244337807200435373777462238862437279"]],[["12505271218650914527710842094803759916526791354425688234288687823807495160149","21079529389819175768418925187724856376731616978516023985093353832725307191172"],["17191503117818720549103434672697266076462686591305810428253469897937793366640","1460614254014044318793914579722356842222906714283817829623699739748310555420"],["8156206509351086444845867552906130156524383450240899694386232124211773153798","9170252275562998399982022993666567730500187520875477300499051353566398457339"]]],"protocol":"groth","nPublic":6} -------------------------------------------------------------------------------- /test/Aeolus.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const Aeolus = artifacts.require('Aeolus'); 3 | const CycloneToken = artifacts.require('CycloneToken'); 4 | const ShadowToken = artifacts.require('ShadowToken'); 5 | 6 | contract('Aeolus', function ([operator, minter, alice, bob, carol]) { 7 | beforeEach(async function () { 8 | this.cycToken = await CycloneToken.new(operator, carol, { from: alice }); 9 | this.lpToken = await ShadowToken.new(minter, this.cycToken.address, "lp token", "lp", 18, { from: alice }); 10 | this.aeolus = await Aeolus.new(this.cycToken.address, this.lpToken.address, { from: alice }); 11 | await this.aeolus.addAddressToWhitelist(operator, { from: alice }); 12 | await this.cycToken.addMinter(this.aeolus.address, {from: operator}); 13 | await this.lpToken.mint(bob, '10000000000', { from: minter }); 14 | assert.equal((await this.lpToken.balanceOf(bob)).valueOf(), '10000000000') 15 | }); 16 | 17 | describe('set block reward', function() { 18 | it("check initial reward per block", async function() { 19 | assert.equal((await this.aeolus.rewardPerBlock()).valueOf(), '0'); 20 | }); 21 | it('set block reward from stranger', async function () { 22 | await expectRevert.unspecified(this.aeolus.setRewardPerBlock('30000000', { from: bob })); 23 | assert.equal((await this.aeolus.rewardPerBlock()).valueOf(), '0'); 24 | }); 25 | describe("set block reward from a whitelisted address", function() { 26 | it('set block reward once', async function () { 27 | await this.aeolus.setRewardPerBlock('30000000', { from: alice }); 28 | assert.equal((await this.aeolus.rewardPerBlock()).valueOf(), '30000000'); 29 | }); 30 | it('set block reward twice', async function () { 31 | await this.aeolus.setRewardPerBlock('50000000', { from: alice }); 32 | await this.aeolus.setRewardPerBlock('60000000', { from: alice }); 33 | assert.equal((await this.aeolus.rewardPerBlock()).valueOf(), '60000000'); 34 | }); 35 | }); 36 | }); 37 | 38 | describe("update block reward", function() { 39 | it("reward per block is zero", async function() { 40 | const result = await this.aeolus.updateBlockReward(); 41 | assert.equal(result.receipt.rawLogs.length, 0); 42 | assert.notEqual(result.receipt.blockNumber, this.aeolus.lastRewardBlock); 43 | }); 44 | describe("reward per block is not zero", function() { 45 | beforeEach(async function() { 46 | await this.aeolus.setRewardPerBlock("123456789", { from: alice }); 47 | }); 48 | it("lp supply is zero", async function() { 49 | const result = await this.aeolus.updateBlockReward(); 50 | assert.equal(result.receipt.rawLogs.length, 0); 51 | assert.equal(result.receipt.blockNumber, await this.aeolus.lastRewardBlock()); 52 | }); 53 | it("lp supply is not zero", async function() { 54 | await this.lpToken.approve(this.aeolus.address, '8000000', { from: bob }); 55 | await this.aeolus.deposit("4000000", { from: bob }); 56 | const result = await this.aeolus.updateBlockReward(); 57 | assert.equal(result.receipt.rawLogs.length, 3); 58 | assert.equal(result.receipt.logs[0].logIndex, 2); 59 | assert.equal(result.receipt.logs[0].event, "RewardAdded"); 60 | assert.equal(result.receipt.logs[0].args[0].valueOf(), "123456789"); 61 | assert.equal(result.receipt.logs[0].args[1], true); 62 | assert.equal(result.receipt.blockNumber, await this.aeolus.lastRewardBlock()); 63 | }); 64 | }); 65 | }) 66 | 67 | describe('deposit and withdraw', function() { 68 | describe('not enough approve lp token', function() { 69 | it('no approve lp token', async function() { 70 | await expectRevert.unspecified(this.aeolus.deposit('8000000', { from: bob })); 71 | }); 72 | it('no approve lp token', async function() { 73 | await this.lpToken.approve(this.aeolus.address, '7000000', { from: bob }); 74 | await expectRevert.unspecified(this.aeolus.deposit('8000000', { from: bob })); 75 | }); 76 | it('emergency withdraw', async function() { 77 | const result = await this.aeolus.emergencyWithdraw({from: bob}); 78 | assert.equal(result.receipt.rawLogs.length, 2); 79 | assert.equal(result.receipt.logs[0].event, "EmergencyWithdraw"); 80 | assert.equal(result.receipt.logs[0].args[0], bob); 81 | assert.equal(result.receipt.logs[0].args[1].valueOf(), "0"); 82 | }) 83 | }); 84 | describe('enough approve lp token', function() { 85 | describe("zero reward per block", function() { 86 | it("deposit", async function() { 87 | await this.lpToken.approve(this.aeolus.address, '8000000', { from: bob }); 88 | const depositResult = await this.aeolus.deposit('8000000', { from: bob }); 89 | assert.equal(depositResult.receipt.rawLogs.length, 2); 90 | }); 91 | }); 92 | describe("non-zero reward per block", function() { 93 | beforeEach(async function () { 94 | await this.aeolus.setRewardPerBlock('4000000', { from: alice }); 95 | await this.lpToken.approve(this.aeolus.address, '8000000', { from: bob }); 96 | }); 97 | describe('deposit', function() { 98 | it('deposit without pending', async function() { 99 | }); 100 | describe('deposit with pending', function() { 101 | beforeEach(async function () { 102 | assert.equal((await this.aeolus.pendingReward(bob)).valueOf(), "0"); 103 | assert.equal((await this.lpToken.balanceOf(this.aeolus.address)).valueOf(), '0'); 104 | const depositResult = await this.aeolus.deposit('8000000', { from: bob }); 105 | assert.equal(depositResult.receipt.rawLogs.length, 2); 106 | assert.equal(depositResult.receipt.logs[0].event, "Deposit"); 107 | assert.equal(depositResult.receipt.logs[0].args[0], bob); 108 | assert.equal(depositResult.receipt.logs[0].args[1].valueOf(), "8000000"); 109 | assert.equal((await this.lpToken.balanceOf(this.aeolus.address)).valueOf(), '8000000'); 110 | }); 111 | it('check pending reward', async function() { 112 | await time.advanceBlock(); 113 | assert.equal((await this.aeolus.pendingReward(bob)).valueOf(), "4000000"); 114 | assert.equal((await this.aeolus.pendingReward(alice)).valueOf(), "0"); 115 | await time.advanceBlock(); 116 | assert.equal((await this.aeolus.pendingReward(bob)).valueOf(), "8000000"); 117 | assert.equal((await this.aeolus.pendingReward(alice)).valueOf(), "0"); 118 | }); 119 | it('withdraw', async function() { 120 | const withdrawResult = await this.aeolus.withdraw('4000000', { from: bob }); 121 | assert.equal(withdrawResult.receipt.rawLogs.length, 6); 122 | assert.equal(withdrawResult.receipt.logs[1].event, "Withdraw"); 123 | assert.equal(withdrawResult.receipt.logs[1].args[0], bob); 124 | assert.equal(withdrawResult.receipt.logs[1].args[1].valueOf(), "4000000"); 125 | assert.equal((await this.lpToken.balanceOf(this.aeolus.address)).valueOf(), '4000000'); 126 | assert.equal((await this.lpToken.balanceOf(bob)).valueOf(), '9996000000'); 127 | assert.equal((await this.cycToken.balanceOf(bob)).valueOf(), '4000000'); 128 | }); 129 | it('emergency withdraw', async function() { 130 | const result = await this.aeolus.emergencyWithdraw({from: bob}); 131 | assert.equal(result.receipt.rawLogs.length, 2); 132 | assert.equal(result.receipt.logs[0].event, "EmergencyWithdraw"); 133 | assert.equal(result.receipt.logs[0].args[0], bob); 134 | assert.equal(result.receipt.logs[0].args[1].valueOf(), "8000000"); 135 | }); 136 | }); 137 | }); 138 | }); 139 | }); 140 | }); 141 | describe('add reward', function() { 142 | it('from a not-whitelisted address', async function() { 143 | await expectRevert.unspecified(this.aeolus.addReward("300000", { from: bob })); 144 | }); 145 | describe('from a whitelisted address', function() { 146 | beforeEach(async function() { 147 | assert.equal((await this.aeolus.accCYCPerShare()).valueOf(), "0"); 148 | }); 149 | it("no lp supply", async function() { 150 | const result = await this.aeolus.addReward("300000", { from: operator }); 151 | assert.equal(result.receipt.rawLogs.length, 0); 152 | }); 153 | describe("with lp supply", function() { 154 | beforeEach(async function() { 155 | await this.lpToken.approve(this.aeolus.address, '8000000', { from: bob }); 156 | await this.aeolus.deposit('8000000', { from: bob }); 157 | }); 158 | it("zero amount", async function() { 159 | const result = await this.aeolus.addReward("0", { from: operator }); 160 | assert.equal(result.receipt.rawLogs.length, 0); 161 | }); 162 | it("success operation", async function() { 163 | const result = await this.aeolus.addReward("3000", { from: operator }); 164 | assert.equal(result.receipt.rawLogs.length, 3); 165 | }); 166 | }); 167 | }); 168 | }); 169 | }); -------------------------------------------------------------------------------- /test/CycloneToken.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert } = require('@openzeppelin/test-helpers'); 2 | const CycloneToken = artifacts.require('CycloneToken'); 3 | const ShadowToken = artifacts.require('ShadowToken'); 4 | 5 | contract('CycloneToken', ([alice, bob, carol]) => { 6 | beforeEach(async () => { 7 | this.cycToken = await CycloneToken.new(alice, carol, { from: alice }); 8 | await this.cycToken.addMinter(alice, {from: alice}); 9 | }); 10 | 11 | it('should have correct name and symbol and decimal and initial airdrop', async () => { 12 | const name = await this.cycToken.name(); 13 | const symbol = await this.cycToken.symbol(); 14 | const decimals = await this.cycToken.decimals(); 15 | assert.equal(name.valueOf(), 'Cyclone'); 16 | assert.equal(symbol.valueOf(), 'CYC'); 17 | assert.equal(decimals.valueOf(), '18'); 18 | 19 | const CarolBal = await this.cycToken.balanceOf(carol); 20 | assert.equal(CarolBal.valueOf(), '1200000000000000000000'); 21 | }); 22 | 23 | it('should only allow owner to mint token', async () => { 24 | await this.cycToken.mint(alice, '100', { from: alice }); 25 | await this.cycToken.mint(bob, '1000', { from: alice }); 26 | await expectRevert( 27 | this.cycToken.mint(carol, '1000', { from: bob }), 28 | 'not the minter', 29 | ); 30 | const totalSupply = await this.cycToken.totalSupply(); 31 | const aliceBal = await this.cycToken.balanceOf(alice); 32 | const bobBal = await this.cycToken.balanceOf(bob); 33 | const carolBal = await this.cycToken.balanceOf(carol); 34 | assert.equal(totalSupply.toString(), '1200000000000000001100'); 35 | assert.equal(aliceBal.toString(), '100'); 36 | assert.equal(bobBal.toString(), '1000'); 37 | assert.equal(carolBal.toString(), '1200000000000000000000'); 38 | }); 39 | 40 | it('should supply token transfers properly', async () => { 41 | await this.cycToken.mint(alice, '100', { from: alice }); 42 | await this.cycToken.mint(bob, '1000', { from: alice }); 43 | await this.cycToken.transfer(carol, '10', { from: alice }); 44 | await this.cycToken.transfer(carol, '100', { from: bob }); 45 | const totalSupply = await this.cycToken.totalSupply(); 46 | const aliceBal = await this.cycToken.balanceOf(alice); 47 | const bobBal = await this.cycToken.balanceOf(bob); 48 | const carolBal = await this.cycToken.balanceOf(carol); 49 | assert.equal(totalSupply.toString(), '1200000000000000001100'); 50 | assert.equal(aliceBal.toString(), '90'); 51 | assert.equal(bobBal.toString(), '900'); 52 | assert.equal(carolBal.toString(), '1200000000000000000110'); 53 | }); 54 | 55 | it('should transfer entails move delegate properly', async () => { 56 | await this.cycToken.mint(alice, '100', { from: alice }); 57 | await this.cycToken.mint(bob, '10', { from: alice }); 58 | await this.cycToken.delegate(carol, { from: alice }); 59 | await this.cycToken.delegate(alice, { from: bob }); 60 | await this.cycToken.transfer(bob, '100', { from: alice }); 61 | 62 | const aliceBal = await this.cycToken.balanceOf(alice); 63 | assert.equal(aliceBal.toString(), '0'); 64 | const carolVotes = await this.cycToken.getCurrentVotes(carol); 65 | const aliceVotes = await this.cycToken.getCurrentVotes(alice); 66 | 67 | assert.equal(carolVotes.toString(), '0'); 68 | assert.equal(aliceVotes.toString(), '110'); 69 | }); 70 | 71 | it('should burnShadowToMint works', async () => { 72 | this.shadow = await ShadowToken.new(alice, this.cycToken.address, "Cyclone Shadow Token", "CYC-I'", 18, { from: alice }); 73 | await this.cycToken.setShadowToken(this.shadow.address, { from: alice }); 74 | 75 | await this.shadow.mint(bob, '100'); 76 | await this.shadow.approve(this.cycToken.address, '100', { from: bob }); 77 | await this.cycToken.burnShadowToMint(bob, '100', {from : bob }); 78 | 79 | const shadowBalance = await this.shadow.balanceOf(bob); 80 | const cycTokenBalance = await this.cycToken.balanceOf(bob); 81 | assert.equal(shadowBalance.toString(), '0'); 82 | assert.equal(cycTokenBalance.toString(), '100'); 83 | }); 84 | }); -------------------------------------------------------------------------------- /test/GovernorAlpha.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const ethers = require('ethers'); 3 | const CycloneToken = artifacts.require('CycloneToken'); 4 | const CoinCyclone = artifacts.require('CoinCyclone'); 5 | const Timelock = artifacts.require('Timelock'); 6 | const GovernorAlpha = artifacts.require('GovernorAlpha'); 7 | const Hasher = artifacts.require('Hasher'); 8 | const Verifier = artifacts.require('Verifier'); 9 | const MimoFactory = artifacts.require('MimoFactory'); 10 | const MimoExchange = artifacts.require('MimoExchange'); 11 | const Aeolus = artifacts.require('Aeolus'); 12 | 13 | function encodeParameters(types, values) { 14 | const abi = new ethers.utils.AbiCoder(); 15 | return abi.encode(types, values); 16 | } 17 | 18 | contract('Governor', ([cycOperator, initialLP, alice, bob]) => { 19 | it('should work', async () => { 20 | // deploy cyclone token 21 | this.cycToken = await CycloneToken.new(cycOperator, initialLP, { from: cycOperator }); 22 | await this.cycToken.addMinter(cycOperator, { from: cycOperator }); 23 | await this.cycToken.delegate(initialLP, { from: initialLP }); 24 | this.cycToken.mint(bob, '4000000000000000000000', {from: cycOperator}) // 4000 CYC 25 | await this.cycToken.delegate(bob, { from: bob }); 26 | 27 | // deploy timelock 28 | this.timelock = await Timelock.new(alice, time.duration.days(2), { from: alice }); 29 | this.gov = await GovernorAlpha.new(this.timelock.address, this.cycToken.address, alice, 100, { from: alice }); 30 | await this.timelock.setPendingAdmin(this.gov.address, { from: alice }); 31 | await this.gov.__acceptAdmin({ from: alice }); 32 | 33 | // deploy Mimos 34 | this.mimoFactory = await MimoFactory.new(); 35 | this.poolToken = new MimoExchange(await this.mimoFactory.getExchange(this.cycToken.address)); 36 | 37 | // deploy Aeolus 38 | this.als = await Aeolus.new(this.cycToken.address, this.poolToken.address, { from: alice }); 39 | 40 | // deploy CoinCyclone 41 | this.hasher = await Hasher.new({from: alice}); 42 | this.verifier = await Verifier.new({from: alice}); 43 | await CoinCyclone.link("Hasher", this.hasher.address); 44 | this.cyclone = await CoinCyclone.new( 45 | this.verifier.address, 46 | this.cycToken.address, 47 | this.mimoFactory.address, 48 | this.als.address, 49 | "1000000000000000000", 50 | "10000000000", 51 | 20, 52 | this.timelock.address, 53 | { from: alice } 54 | ); 55 | 56 | // test "updateTax" through Governor 57 | await expectRevert( 58 | this.cyclone.updateConfig('100', '200', '300', '400', '500', '1000000000000000000', 5, { from: alice }), 59 | 'Only Governance DAO can call this function.', 60 | ); 61 | await expectRevert( 62 | this.gov.propose( 63 | [this.cyclone.address], ['0'], ['updateConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256)'], 64 | [encodeParameters(['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256'], ['100', '200', '300', '400', '500', '1000000000000000000', 5])], 65 | 'Update tax to 100', 66 | { from: alice }, 67 | ), 68 | 'GovernorAlpha::propose: proposer votes below proposal threshold', 69 | ); 70 | await this.gov.propose( 71 | [this.cyclone.address], ['0'], ['updateConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256)'], 72 | [encodeParameters(['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256'], ['100', '200', '300', '400', '500', '1000000000000000000', 5])], 73 | 'Update tax to 100', 74 | { from: initialLP }, 75 | ); 76 | await time.advanceBlock(); 77 | await this.gov.castVote('1', true, { from: bob }); 78 | await expectRevert(this.gov.queue('1'), "GovernorAlpha::queue: proposal can only be queued if it is succeeded"); 79 | console.log("Advancing 100 blocks. Will take a while..."); 80 | for (let i = 0; i < 100; ++i) { 81 | await time.advanceBlock(); 82 | } 83 | await this.gov.queue('1'); 84 | await expectRevert(this.gov.execute('1'), "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); 85 | await time.increase(time.duration.days(3)); 86 | assert.equal((await this.cyclone.depositLpIR()).toString(), '0'); 87 | assert.equal((await this.cyclone.cashbackRate()).toString(), '0'); 88 | assert.equal((await this.cyclone.withdrawLpIR()).toString(), '0'); 89 | assert.equal((await this.cyclone.buybackRate()).toString(), '0'); 90 | assert.equal((await this.cyclone.apIncentiveRate()).toString(), '0'); 91 | assert.equal((await this.cyclone.minCYCPrice()).toString(), '0'); 92 | assert.equal((await this.cyclone.maxNumOfShares()).toString(), '0'); 93 | await this.gov.execute('1'); 94 | assert.equal((await this.cyclone.depositLpIR()).toString(), '100'); 95 | assert.equal((await this.cyclone.cashbackRate()).toString(), '200'); 96 | assert.equal((await this.cyclone.withdrawLpIR()).toString(), '300'); 97 | assert.equal((await this.cyclone.buybackRate()).toString(), '400'); 98 | assert.equal((await this.cyclone.apIncentiveRate()).toString(), '500'); 99 | assert.equal((await this.cyclone.minCYCPrice()).toString(), '1000000000000000000'); 100 | assert.equal((await this.cyclone.maxNumOfShares()).toString(), '5'); 101 | }); 102 | }); --------------------------------------------------------------------------------