From a08e502c538e4006f625bd9e1c31fac9420d8fe2 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Fri, 29 Sep 2023 17:38:40 +0300 Subject: [PATCH] migration contract and migration verifier --- script/scripts/BOBPoolMigration.s.sol | 286 ++++ script/scripts/infra/FiatTokenV2.sol | 1754 +++++++++++++++++++++++++ src/zkbob/ZkBobPoolUSDCMigrated.sol | 29 + 3 files changed, 2069 insertions(+) create mode 100644 script/scripts/BOBPoolMigration.s.sol create mode 100644 script/scripts/infra/FiatTokenV2.sol diff --git a/script/scripts/BOBPoolMigration.s.sol b/script/scripts/BOBPoolMigration.s.sol new file mode 100644 index 0000000..9891590 --- /dev/null +++ b/script/scripts/BOBPoolMigration.s.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.15; + +import "forge-std/Script.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; +import "forge-std/Vm.sol"; +import "./Env.s.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import "../../src/interfaces/IERC20Permit.sol"; +import "../../src/proxy/EIP1967Proxy.sol"; +import "../../src/zkbob/ZkBobPoolBOB.sol"; +import "../../src/zkbob/ZkBobPoolUSDCMigrated.sol"; +import "../../src/zkbob/utils/ZkBobAccounting.sol"; +import "../../src/zkbob/ZkBobDirectDepositQueue.sol"; +import "../../src/utils/UniswapV3Seller.sol"; + +contract BOBPoolMigration is Script, StdCheats { + ZkBobPoolBOB pool = ZkBobPoolBOB(0x1CA8C2B9B20E18e86d5b9a72370fC6c91814c97C); + ZkBobDirectDepositQueue queue_proxy = ZkBobDirectDepositQueue(0x15B8C75c024acba8c114C21F42eb515A762c0014); + address bob_addr = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B); + address usdc_addr = address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85); + address relayer_addr = address(0xb9CD01c0b417b4e9095f620aE2f849A84a9B1690); + + struct VerificationValues { + uint256 withdrawalDiff; + uint256 depositDiff; + uint256 fees; + uint256 ddIn; + uint256 ddOutDiff; + ZkBobAccounting.Limits limits; + } + + function migrate() internal { + ITransferVerifier transferVerifier = pool.transfer_verifier(); + ITreeVerifier treeVerifier = pool.tree_verifier(); + IBatchDepositVerifier batchDepositVerifier = pool.batch_deposit_verifier(); + uint256 pool_id = pool.pool_id(); + require(queue_proxy == pool.direct_deposit_queue(), "Incorrect Direct Depoist queue proxy"); + + vm.startPrank(deployer); + ZkBobPoolUSDCMigrated poolImpl = new ZkBobPoolUSDCMigrated( + pool_id, usdc_addr, + transferVerifier, treeVerifier, batchDepositVerifier, + address(queue_proxy) + ); + UniswapV3Seller seller = new UniswapV3Seller(uniV3Router, uniV3Quoter, usdc_addr, 100, address(0x7F5c764cBc14f9669B88837ca1490cCa17c31607), 500); + ZkBobDirectDepositQueue queueImpl = new ZkBobDirectDepositQueue(address(pool), usdc_addr, 1); + vm.stopPrank(); + + bytes memory migrationData = abi.encodePacked(poolImpl.migrationToUSDC.selector); + + deal(usdc_addr, address(owner), 1000_000_000); + vm.startPrank(owner); + IERC20(usdc_addr).approve(address(pool), type(uint256).max); + EIP1967Proxy(payable(address(pool))).upgradeToAndCall(address(poolImpl), migrationData); + IERC20(usdc_addr).approve(address(pool), 0); + EIP1967Proxy(payable(address(queue_proxy))).upgradeTo(address(queueImpl)); + pool.setTokenSeller(address(seller)); + vm.stopPrank(); + } + + function makeFeesWithdrawal() internal { + vm.startPrank(relayer_addr); + pool.withdrawFee(relayer_addr, relayer_addr); + vm.stopPrank(); + } + + function makeWithdrawal() internal { + // fork block: 110_198_152 + // tx: 0x4bea90b237e81940be79eb8589b71500f3c6f8b08bca3cdb1500d9a3a0f1ef79 + // hex"1dd6f9921ddaf1ccd3d16f2f662114f22e0d9e9579a583b848943bf1d42779f507789cfaf3b1e9a0e69fabdebc20f13046ed911413a336d0e8a9ff5384788b0d0000000487800000000000000000000000000000fffffffb42730e0012f77d7d40afb199bf5d9c8f62d0fc2869d3fcf313bd73f20e70842addc0e52f1e07cebe0608262f77b8d4e1ca8c64c1ee4726953e32fbbda27a7fe2eb823c281fb76d8e66a4ec9b2257c91fa80df7acef8dae9267a45f0f4d81339940a6d46a238d406c374584026c7a3bbf7475eb1bb543f96f72148c716e498112cb2d60fe2561f65dabc3cf88a032d7819cda0a590953895171a6485aeaf41cdcaa5ba0580317d2226b98295f2cac7e16b5d24d52d9b1e994769e84583e66e50ceac90b782279cf5a5a4ca7de56c99e19a2f5b3e834f34c367ce605c5fa551b5c3dce9b2d0dae21b9a5cfe19b28ae9510085ff4b633865185db5fd9ca997c9bf2ebee73250f955c6a4325cd724f0ce732e6d3058b6e66fbc887fc18ea5fc588729595356022327f8de0dc1a7c6de88500ab5ba19d010d0864fc61a23c23f2fc174f84715b29f20ae261b3c9023e8c2a67c67cbb9192b50d58c3dc8760cbc45dda83f5ca5a2fd440e1740f11903d6fac2c72dec3b7d628b2d22e7efc0986cf36d9eb2d210b17d70dcb495805aa90c1f2f67159f916b0a1ff8f557ef340b006b73afaa5d76b20016f87e7a713ddf2eba6f20620f0f2dc6ece52e6c2f39a4e21e338672f67351396c1959a679f2a6a9e1af777868275be05283b6ca12265aa532c8587f0e7e5188c2968cf9bf0b7f42c00d8ce2e1cd01e586fa60eb0a7114163acec87b320ab292c304decf7034ef68cb38d0a8e9253a181f2138781c2bfc98f7eb22135996c000200ee0000000015752a00000000012a05f20039f0bd56c1439a22ee90b4972c16b7868d16198101000000fa2cce4e0c4c31fefdc7fa89c2c1d1c4663c2d7d1f14aa03e2feeccef437e227658f5f77a35f6afb74a1fd9b29bb1be034ff21eb7696793ad0e7d4a30a4dd30d1963d72a2e98e40d89e3e905424a0a688c67f78e540d0d92477f00c424ab7e0183eb9c40e9223310a224d4cc0fd529699aa9fa76a85323a84ecdad2e635a7e85672a24f09aebf9c2dced55812b4087cd0363b9db256982c52286af541e270dbb3c23bc281cfc4282facf0b1779c7a3adcb7f76983f2e30d50dc7a93951c6ca27723610195122" + vm.startPrank(relayer_addr); + address(pool).call( + abi.encodePacked( + pool.transact.selector, + hex"1dd6f9921ddaf1ccd3d16f2f662114f22e0d9e9579a583b848943bf1d42779f507789cfaf3b1e9a0e69fabdebc20f13046ed911413a336d0e8a9ff5384788b0d0000000487800000000000000000000000000000fffffffb42730e0012f77d7d40afb199bf5d9c8f62d0fc2869d3fcf313bd73f20e70842addc0e52f1e07cebe0608262f77b8d4e1ca8c64c1ee4726953e32fbbda27a7fe2eb823c281fb76d8e66a4ec9b2257c91fa80df7acef8dae9267a45f0f4d81339940a6d46a238d406c374584026c7a3bbf7475eb1bb543f96f72148c716e498112cb2d60fe2561f65dabc3cf88a032d7819cda0a590953895171a6485aeaf41cdcaa5ba0580317d2226b98295f2cac7e16b5d24d52d9b1e994769e84583e66e50ceac90b782279cf5a5a4ca7de56c99e19a2f5b3e834f34c367ce605c5fa551b5c3dce9b2d0dae21b9a5cfe19b28ae9510085ff4b633865185db5fd9ca997c9bf2ebee73250f955c6a4325cd724f0ce732e6d3058b6e66fbc887fc18ea5fc588729595356022327f8de0dc1a7c6de88500ab5ba19d010d0864fc61a23c23f2fc174f84715b29f20ae261b3c9023e8c2a67c67cbb9192b50d58c3dc8760cbc45dda83f5ca5a2fd440e1740f11903d6fac2c72dec3b7d628b2d22e7efc0986cf36d9eb2d210b17d70dcb495805aa90c1f2f67159f916b0a1ff8f557ef340b006b73afaa5d76b20016f87e7a713ddf2eba6f20620f0f2dc6ece52e6c2f39a4e21e338672f67351396c1959a679f2a6a9e1af777868275be05283b6ca12265aa532c8587f0e7e5188c2968cf9bf0b7f42c00d8ce2e1cd01e586fa60eb0a7114163acec87b320ab292c304decf7034ef68cb38d0a8e9253a181f2138781c2bfc98f7eb22135996c000200ee0000000015752a00000000012a05f20039f0bd56c1439a22ee90b4972c16b7868d16198101000000fa2cce4e0c4c31fefdc7fa89c2c1d1c4663c2d7d1f14aa03e2feeccef437e227658f5f77a35f6afb74a1fd9b29bb1be034ff21eb7696793ad0e7d4a30a4dd30d1963d72a2e98e40d89e3e905424a0a688c67f78e540d0d92477f00c424ab7e0183eb9c40e9223310a224d4cc0fd529699aa9fa76a85323a84ecdad2e635a7e85672a24f09aebf9c2dced55812b4087cd0363b9db256982c52286af541e270dbb3c23bc281cfc4282facf0b1779c7a3adcb7f76983f2e30d50dc7a93951c6ca27723610195122" + ) + ); + vm.stopPrank(); + } + + function makeDeposit(bool migrated) internal { + // fork block: 110_198_152 + // tx: 0x0fe989fb4b37e69cbd7b32c71fa90cadd75bc01f9681fc2cfcbf3d82472755cd + // hex"1f5bf731b361368ec5d621072138465c77c4f868b47cdbc719b2aa0f67a4bc6c0a825b40be1aeae4f5f77a01d48034fdc4800221fee465b2694ab5fb10f9a45b000000048800000000000000000000000000000000000002540be4002e56b2600eba70524189068e4017d7592a9219ec52d352a77cd6d8a78aea9c91173e44c1ded009051f822b0c3e39349d6cb0e26a85f1abee66c56b1b327157ef0e73c6335469f5b74904155f5ec4415d8cdaf74870d82560752fb4c03016ae790acb51380d0e6fa27aebf2f06b5c308625efaf678ebfae35100d530f1137b9fc0b352e5039c59e965967390151a9d60c1e9ae8992ad033f82ee69d4def97781c259aa0be6b31162571c11fc231358c74e3e9a53dcfac89db8102a49f04e4f736184b36ae5a520bc57d69811ea019995173d7100b4abea1a7f75b5a4655b186ea0d820a3c6d747d94e3379e20943d82800a7502a4849c7c0846f054d814de8ac91fbe0697f995309ecafb7bc3302a99581bd8602c63fa18543cf24a5fe1ec37472caecff5c46955a582702a548c09e6f15f5eae4434962b2f0bb3a5dbfd6414df0ee2b0a343502a2f6a4c5556ac99c29d6d604f2a6f164eaabe4857b73e54abc60116bc9cb1031fdd46d7eb37a94263e27258a40a5f73f12ede2bc028da3f67ec1569c9c26befb3fbe843f767fb73967e95202353687424991cdcc1b3164b0a582314fc6d7ede4d33f0a569bce9e530f30aafc34f30d17775bec09a300c8c0618111a856e96dc7318c2d6c7f650f74ab8bdc507c42dddadac9947184397cbd92702714030ac97ea63f5b31ea59c41ca410eef162c4bc806ee842fd9025908d9b026bc7a55b210c32732908838acefccbe7f4d3bf4027422f6c7a7ee2137437b1a000300ee00000000173eed80000000006516da0b39f0bd56c1439a22ee90b4972c16b7868d1619810100000094d9ac2eb4cbd1757ebc9314d2e2cb7138d370500aab6d5ad402006da78f8f1abc1a7d244119a1ab107f27ec85025facd57d18821eefc30777ec31b6251b5d1eb58b7f2f37beb834b544f5474ff9a4284bdc3e3b9f32f78596044850fc9a6faaa3ba456343260839922c3f18fe5bf805562ea915771af825e63b2c7dd6a9989a5f8cc25be40160155a36d75a8bff971142729c70e3ba32f3dfb429d33efcffe261023af04234e0a7e7c9fdc92755b8555ca046ba77dc57487ed1ccc0a8c4a6e2e1821900ffef441154ec44e2f4bfe760fae618bd84157a49f9a72a0a79c41110395c79bf55502e7219e51daee469fdc14810beb9debd429a620cf39e17c7b497e94571d78122" + if (migrated) { + deal(usdc_addr, address(0x39F0bD56c1439a22Ee90b4972c16b7868D161981), 10_400_000); + } else { + deal(bob_addr, address(0x39F0bD56c1439a22Ee90b4972c16b7868D161981), 10_400_000_000_000_000_000); + } + vm.startPrank(relayer_addr); + address(pool).call( + abi.encodePacked( + pool.transact.selector, + hex"1f5bf731b361368ec5d621072138465c77c4f868b47cdbc719b2aa0f67a4bc6c0a825b40be1aeae4f5f77a01d48034fdc4800221fee465b2694ab5fb10f9a45b000000048800000000000000000000000000000000000002540be4002e56b2600eba70524189068e4017d7592a9219ec52d352a77cd6d8a78aea9c91173e44c1ded009051f822b0c3e39349d6cb0e26a85f1abee66c56b1b327157ef0e73c6335469f5b74904155f5ec4415d8cdaf74870d82560752fb4c03016ae790acb51380d0e6fa27aebf2f06b5c308625efaf678ebfae35100d530f1137b9fc0b352e5039c59e965967390151a9d60c1e9ae8992ad033f82ee69d4def97781c259aa0be6b31162571c11fc231358c74e3e9a53dcfac89db8102a49f04e4f736184b36ae5a520bc57d69811ea019995173d7100b4abea1a7f75b5a4655b186ea0d820a3c6d747d94e3379e20943d82800a7502a4849c7c0846f054d814de8ac91fbe0697f995309ecafb7bc3302a99581bd8602c63fa18543cf24a5fe1ec37472caecff5c46955a582702a548c09e6f15f5eae4434962b2f0bb3a5dbfd6414df0ee2b0a343502a2f6a4c5556ac99c29d6d604f2a6f164eaabe4857b73e54abc60116bc9cb1031fdd46d7eb37a94263e27258a40a5f73f12ede2bc028da3f67ec1569c9c26befb3fbe843f767fb73967e95202353687424991cdcc1b3164b0a582314fc6d7ede4d33f0a569bce9e530f30aafc34f30d17775bec09a300c8c0618111a856e96dc7318c2d6c7f650f74ab8bdc507c42dddadac9947184397cbd92702714030ac97ea63f5b31ea59c41ca410eef162c4bc806ee842fd9025908d9b026bc7a55b210c32732908838acefccbe7f4d3bf4027422f6c7a7ee2137437b1a000300ee00000000173eed80000000006516da0b39f0bd56c1439a22ee90b4972c16b7868d1619810100000094d9ac2eb4cbd1757ebc9314d2e2cb7138d370500aab6d5ad402006da78f8f1abc1a7d244119a1ab107f27ec85025facd57d18821eefc30777ec31b6251b5d1eb58b7f2f37beb834b544f5474ff9a4284bdc3e3b9f32f78596044850fc9a6faaa3ba456343260839922c3f18fe5bf805562ea915771af825e63b2c7dd6a9989a5f8cc25be40160155a36d75a8bff971142729c70e3ba32f3dfb429d33efcffe261023af04234e0a7e7c9fdc92755b8555ca046ba77dc57487ed1ccc0a8c4a6e2e1821900ffef441154ec44e2f4bfe760fae618bd84157a49f9a72a0a79c41110395c79bf55502e7219e51daee469fdc14810beb9debd429a620cf39e17c7b497e94571d78122" + ) + ); + vm.stopPrank(); + } + + function makeDirectDeposit(bool migrated) internal returns (uint64 retval) { + // fork block: 110_198_152 + // tx: 0xa0e620d0bf8c85474bf42d955be64e25ef452265855f572f7bfc5ed336170dd4 + address actor = address(0x39F0bD56c1439a22Ee90b4972c16b7868D161981); + uint256 amount; + address token_addr; + string memory zk_addr = "EnjxfGpbEGhryjoyLdQKRTm5JEEqpaaqHtgydJiDeKgaDuChHtUJcaDPZ875bYu"; + if (migrated) { + amount = 4_000_000; + token_addr = usdc_addr; + } else { + amount = 4_000_000_000_000_000_000; + token_addr = bob_addr; + } + if (IERC20(token_addr).balanceOf(actor) < amount) { + deal(token_addr, actor, amount); + } + vm.startPrank(actor); + IERC20(token_addr).approve(address(queue_proxy), amount); + vm.recordLogs(); + queue_proxy.directDeposit(actor, amount, zk_addr); + Vm.Log[] memory entries = vm.getRecordedLogs(); + uint8 log_index = 255; + for(uint8 i=0; i= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +pragma solidity ^0.6.0; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +pragma solidity ^0.6.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +pragma solidity ^0.6.0; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +pragma solidity 0.6.12; + +/** + * @title EIP712 Domain + */ +contract EIP712Domain { + /** + * @dev EIP712 Domain Separator + */ + bytes32 public DOMAIN_SEPARATOR; +} + +pragma solidity 0.6.12; + +abstract contract AbstractFiatTokenV1 is IERC20 { + function _approve( + address owner, + address spender, + uint256 value + ) internal virtual; + + function _transfer( + address from, + address to, + uint256 value + ) internal virtual; +} + +pragma solidity 0.6.12; + +abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 { + function _increaseAllowance( + address owner, + address spender, + uint256 increment + ) internal virtual; + + function _decreaseAllowance( + address owner, + address spender, + uint256 decrement + ) internal virtual; +} + +pragma solidity 0.6.12; + +/** + * @title EIP-3009 + * @notice Provide internal implementation for gas-abstracted transfers + * @dev Contracts that inherit from this must wrap these with publicly + * accessible functions, optionally adding modifiers where necessary + */ +abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain { + // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 + public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; + + // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 + public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; + + // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") + bytes32 + public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; + + /** + * @dev authorizer address => nonce => bool (true if nonce is used) + */ + mapping(address => mapping(bytes32 => bool)) private _authorizationStates; + + event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); + event AuthorizationCanceled( + address indexed authorizer, + bytes32 indexed nonce + ); + + /** + * @notice Returns the state of an authorization + * @dev Nonces are randomly generated 32-byte data unique to the + * authorizer's address + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @return True if the nonce is used + */ + function authorizationState(address authorizer, bytes32 nonce) + external + view + returns (bool) + { + return _authorizationStates[authorizer][nonce]; + } + + /** + * @notice Execute a transfer with a signed authorization + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function _transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + _requireValidAuthorization(from, nonce, validAfter, validBefore); + + bytes memory data = abi.encode( + TRANSFER_WITH_AUTHORIZATION_TYPEHASH, + from, + to, + value, + validAfter, + validBefore, + nonce + ); + // require( + // EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, + // "FiatTokenV2: invalid signature" + // ); + + _markAuthorizationAsUsed(from, nonce); + _transfer(from, to, value); + } + + /** + * @notice Receive a transfer with a signed authorization from the payer + * @dev This has an additional check to ensure that the payee's address + * matches the caller of this function to prevent front-running attacks. + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function _receiveWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + require(to == msg.sender, "FiatTokenV2: caller must be the payee"); + _requireValidAuthorization(from, nonce, validAfter, validBefore); + + bytes memory data = abi.encode( + RECEIVE_WITH_AUTHORIZATION_TYPEHASH, + from, + to, + value, + validAfter, + validBefore, + nonce + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, + "FiatTokenV2: invalid signature" + ); + + _markAuthorizationAsUsed(from, nonce); + _transfer(from, to, value); + } + + /** + * @notice Attempt to cancel an authorization + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function _cancelAuthorization( + address authorizer, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + _requireUnusedAuthorization(authorizer, nonce); + + bytes memory data = abi.encode( + CANCEL_AUTHORIZATION_TYPEHASH, + authorizer, + nonce + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer, + "FiatTokenV2: invalid signature" + ); + + _authorizationStates[authorizer][nonce] = true; + emit AuthorizationCanceled(authorizer, nonce); + } + + /** + * @notice Check that an authorization is unused + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + */ + function _requireUnusedAuthorization(address authorizer, bytes32 nonce) + private + view + { + require( + !_authorizationStates[authorizer][nonce], + "FiatTokenV2: authorization is used or canceled" + ); + } + + /** + * @notice Check that authorization is valid + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + */ + function _requireValidAuthorization( + address authorizer, + bytes32 nonce, + uint256 validAfter, + uint256 validBefore + ) private view { + require( + now > validAfter, + "FiatTokenV2: authorization is not yet valid" + ); + require(now < validBefore, "FiatTokenV2: authorization is expired"); + _requireUnusedAuthorization(authorizer, nonce); + } + + /** + * @notice Mark an authorization as used + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + */ + function _markAuthorizationAsUsed(address authorizer, bytes32 nonce) + private + { + _authorizationStates[authorizer][nonce] = true; + emit AuthorizationUsed(authorizer, nonce); + } +} + +pragma solidity 0.6.12; + +/** + * @title EIP-2612 + * @notice Provide internal implementation for gas-abstracted approvals + */ +abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain { + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + bytes32 + public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + mapping(address => uint256) private _permitNonces; + + /** + * @notice Nonces for permit + * @param owner Token owner's address (Authorizer) + * @return Next nonce + */ + function nonces(address owner) external view returns (uint256) { + return _permitNonces[owner]; + } + + /** + * @notice Verify a signed approval permit and execute if valid + * @param owner Token owner's address (Authorizer) + * @param spender Spender's address + * @param value Amount of allowance + * @param deadline The time at which this expires (unix time) + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function _permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + require(deadline >= now, "FiatTokenV2: permit is expired"); + + bytes memory data = abi.encode( + PERMIT_TYPEHASH, + owner, + spender, + value, + _permitNonces[owner]++, + deadline + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner, + "EIP2612: invalid signature" + ); + + _approve(owner, spender, value); + } +} + +pragma solidity 0.6.12; + +/** + * @notice The Ownable contract has an owner address, and provides basic + * authorization control functions + * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol + * Modifications: + * 1. Consolidate OwnableStorage into this contract (7/13/18) + * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20) + * 3. Make public functions external (5/27/20) + */ +contract Ownable { + // Owner of the contract + address private _owner; + + /** + * @dev Event to show ownership has been transferred + * @param previousOwner representing the address of the previous owner + * @param newOwner representing the address of the new owner + */ + event OwnershipTransferred(address previousOwner, address newOwner); + + /** + * @dev The constructor sets the original owner of the contract to the sender account. + */ + constructor() public { + setOwner(msg.sender); + } + + /** + * @dev Tells the address of the owner + * @return the address of the owner + */ + function owner() external view returns (address) { + return _owner; + } + + /** + * @dev Sets a new owner address + */ + function setOwner(address newOwner) internal { + _owner = newOwner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == _owner, "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) external onlyOwner { + require( + newOwner != address(0), + "Ownable: new owner is the zero address" + ); + emit OwnershipTransferred(_owner, newOwner); + setOwner(newOwner); + } +} + +pragma solidity 0.6.12; + +/** + * @notice Base contract which allows children to implement an emergency stop + * mechanism + * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol + * Modifications: + * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018) + * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018) + * 3. Removed whenPaused (6/14/2018) + * 4. Switches ownable library to use ZeppelinOS (7/12/18) + * 5. Remove constructor (7/13/18) + * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20) + * 7. Make public functions external (5/27/20) + */ +contract Pausable is Ownable { + event Pause(); + event Unpause(); + event PauserChanged(address indexed newAddress); + + address public pauser; + bool public paused = false; + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused, "Pausable: paused"); + _; + } + + /** + * @dev throws if called by any account other than the pauser + */ + modifier onlyPauser() { + require(msg.sender == pauser, "Pausable: caller is not the pauser"); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() external onlyPauser { + paused = true; + emit Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() external onlyPauser { + paused = false; + emit Unpause(); + } + + /** + * @dev update the pauser role + */ + function updatePauser(address _newPauser) external onlyOwner { + require( + _newPauser != address(0), + "Pausable: new pauser is the zero address" + ); + pauser = _newPauser; + emit PauserChanged(pauser); + } +} + +pragma solidity 0.6.12; + +/** + * @title Blacklistable Token + * @dev Allows accounts to be blacklisted by a "blacklister" role + */ +contract Blacklistable is Ownable { + address public blacklister; + mapping(address => bool) internal blacklisted; + + event Blacklisted(address indexed _account); + event UnBlacklisted(address indexed _account); + event BlacklisterChanged(address indexed newBlacklister); + + /** + * @dev Throws if called by any account other than the blacklister + */ + modifier onlyBlacklister() { + require( + msg.sender == blacklister, + "Blacklistable: caller is not the blacklister" + ); + _; + } + + /** + * @dev Throws if argument account is blacklisted + * @param _account The address to check + */ + modifier notBlacklisted(address _account) { + require( + !blacklisted[_account], + "Blacklistable: account is blacklisted" + ); + _; + } + + /** + * @dev Checks if account is blacklisted + * @param _account The address to check + */ + function isBlacklisted(address _account) external view returns (bool) { + return blacklisted[_account]; + } + + /** + * @dev Adds account to blacklist + * @param _account The address to blacklist + */ + function blacklist(address _account) external onlyBlacklister { + blacklisted[_account] = true; + emit Blacklisted(_account); + } + + /** + * @dev Removes account from blacklist + * @param _account The address to remove from the blacklist + */ + function unBlacklist(address _account) external onlyBlacklister { + blacklisted[_account] = false; + emit UnBlacklisted(_account); + } + + function updateBlacklister(address _newBlacklister) external onlyOwner { + require( + _newBlacklister != address(0), + "Blacklistable: new blacklister is the zero address" + ); + blacklister = _newBlacklister; + emit BlacklisterChanged(blacklister); + } +} + +pragma solidity 0.6.12; + +/** + * @title FiatToken + * @dev ERC20 Token backed by fiat reserves + */ +contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable { + using SafeMath for uint256; + + string public name; + string public symbol; + uint8 public decimals; + string public currency; + address public masterMinter; + bool internal initialized; + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + uint256 internal totalSupply_ = 0; + mapping(address => bool) internal minters; + mapping(address => uint256) internal minterAllowed; + + event Mint(address indexed minter, address indexed to, uint256 amount); + event Burn(address indexed burner, uint256 amount); + event MinterConfigured(address indexed minter, uint256 minterAllowedAmount); + event MinterRemoved(address indexed oldMinter); + event MasterMinterChanged(address indexed newMasterMinter); + + function initialize( + string memory tokenName, + string memory tokenSymbol, + string memory tokenCurrency, + uint8 tokenDecimals, + address newMasterMinter, + address newPauser, + address newBlacklister, + address newOwner + ) public { + require(!initialized, "FiatToken: contract is already initialized"); + require( + newMasterMinter != address(0), + "FiatToken: new masterMinter is the zero address" + ); + require( + newPauser != address(0), + "FiatToken: new pauser is the zero address" + ); + require( + newBlacklister != address(0), + "FiatToken: new blacklister is the zero address" + ); + require( + newOwner != address(0), + "FiatToken: new owner is the zero address" + ); + + name = tokenName; + symbol = tokenSymbol; + currency = tokenCurrency; + decimals = tokenDecimals; + masterMinter = newMasterMinter; + pauser = newPauser; + blacklister = newBlacklister; + setOwner(newOwner); + initialized = true; + } + + /** + * @dev Throws if called by any account other than a minter + */ + modifier onlyMinters() { + require(minters[msg.sender], "FiatToken: caller is not a minter"); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. Must be less than or equal + * to the minterAllowance of the caller. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) + external + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + notBlacklisted(_to) + returns (bool) + { + require(_to != address(0), "FiatToken: mint to the zero address"); + require(_amount > 0, "FiatToken: mint amount not greater than 0"); + + uint256 mintingAllowedAmount = minterAllowed[msg.sender]; + require( + _amount <= mintingAllowedAmount, + "FiatToken: mint amount exceeds minterAllowance" + ); + + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount); + emit Mint(msg.sender, _to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Throws if called by any account other than the masterMinter + */ + modifier onlyMasterMinter() { + require( + msg.sender == masterMinter, + "FiatToken: caller is not the masterMinter" + ); + _; + } + + /** + * @dev Get minter allowance for an account + * @param minter The address of the minter + */ + function minterAllowance(address minter) external view returns (uint256) { + return minterAllowed[minter]; + } + + /** + * @dev Checks if account is a minter + * @param account The address to check + */ + function isMinter(address account) external view returns (bool) { + return minters[account]; + } + + /** + * @notice Amount of remaining tokens spender is allowed to transfer on + * behalf of the token owner + * @param owner Token owner's address + * @param spender Spender's address + * @return Allowance amount + */ + function allowance(address owner, address spender) + external + override + view + returns (uint256) + { + return allowed[owner][spender]; + } + + /** + * @dev Get totalSupply of token + */ + function totalSupply() external override view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Get token balance of an account + * @param account address The account + */ + function balanceOf(address account) + external + override + view + returns (uint256) + { + return balances[account]; + } + + /** + * @notice Set spender's allowance over the caller's tokens to be a given + * value. + * @param spender Spender's address + * @param value Allowance amount + * @return True if successful + */ + function approve(address spender, uint256 value) + external + override + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(spender) + returns (bool) + { + _approve(msg.sender, spender, value); + return true; + } + + /** + * @dev Internal function to set allowance + * @param owner Token owner's address + * @param spender Spender's address + * @param value Allowance amount + */ + function _approve( + address owner, + address spender, + uint256 value + ) internal override { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + allowed[owner][spender] = value; + emit Approval(owner, spender, value); + } + + /** + * @notice Transfer tokens by spending allowance + * @param from Payer's address + * @param to Payee's address + * @param value Transfer amount + * @return True if successful + */ + function transferFrom( + address from, + address to, + uint256 value + ) + external + override + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(from) + notBlacklisted(to) + returns (bool) + { + require( + value <= allowed[from][msg.sender], + "ERC20: transfer amount exceeds allowance" + ); + _transfer(from, to, value); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(value); + return true; + } + + /** + * @notice Transfer tokens from the caller + * @param to Payee's address + * @param value Transfer amount + * @return True if successful + */ + function transfer(address to, uint256 value) + external + override + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(to) + returns (bool) + { + _transfer(msg.sender, to, value); + return true; + } + + /** + * @notice Internal function to process transfers + * @param from Payer's address + * @param to Payee's address + * @param value Transfer amount + */ + function _transfer( + address from, + address to, + uint256 value + ) internal override { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + require( + value <= balances[from], + "ERC20: transfer amount exceeds balance" + ); + + balances[from] = balances[from].sub(value); + balances[to] = balances[to].add(value); + emit Transfer(from, to, value); + } + + /** + * @dev Function to add/update a new minter + * @param minter The address of the minter + * @param minterAllowedAmount The minting amount allowed for the minter + * @return True if the operation was successful. + */ + function configureMinter(address minter, uint256 minterAllowedAmount) + external + whenNotPaused + onlyMasterMinter + returns (bool) + { + minters[minter] = true; + minterAllowed[minter] = minterAllowedAmount; + emit MinterConfigured(minter, minterAllowedAmount); + return true; + } + + /** + * @dev Function to remove a minter + * @param minter The address of the minter to remove + * @return True if the operation was successful. + */ + function removeMinter(address minter) + external + onlyMasterMinter + returns (bool) + { + minters[minter] = false; + minterAllowed[minter] = 0; + emit MinterRemoved(minter); + return true; + } + + /** + * @dev allows a minter to burn some of its own tokens + * Validates that caller is a minter and that sender is not blacklisted + * amount is less than or equal to the minter's account balance + * @param _amount uint256 the amount of tokens to be burned + */ + function burn(uint256 _amount) + external + whenNotPaused + onlyMinters + notBlacklisted(msg.sender) + { + uint256 balance = balances[msg.sender]; + require(_amount > 0, "FiatToken: burn amount not greater than 0"); + require(balance >= _amount, "FiatToken: burn amount exceeds balance"); + + totalSupply_ = totalSupply_.sub(_amount); + balances[msg.sender] = balance.sub(_amount); + emit Burn(msg.sender, _amount); + emit Transfer(msg.sender, address(0), _amount); + } + + function updateMasterMinter(address _newMasterMinter) external onlyOwner { + require( + _newMasterMinter != address(0), + "FiatToken: new masterMinter is the zero address" + ); + masterMinter = _newMasterMinter; + emit MasterMinterChanged(masterMinter); + } +} + +pragma solidity 0.6.12; + +contract Rescuable is Ownable { + using SafeERC20 for IERC20; + + address private _rescuer; + + event RescuerChanged(address indexed newRescuer); + + /** + * @notice Returns current rescuer + * @return Rescuer's address + */ + function rescuer() external view returns (address) { + return _rescuer; + } + + /** + * @notice Revert if called by any account other than the rescuer. + */ + modifier onlyRescuer() { + require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer"); + _; + } + + /** + * @notice Rescue ERC20 tokens locked up in this contract. + * @param tokenContract ERC20 token contract address + * @param to Recipient address + * @param amount Amount to withdraw + */ + function rescueERC20( + IERC20 tokenContract, + address to, + uint256 amount + ) external onlyRescuer { + tokenContract.safeTransfer(to, amount); + } + + /** + * @notice Assign the rescuer role to a given address. + * @param newRescuer New rescuer's address + */ + function updateRescuer(address newRescuer) external onlyOwner { + require( + newRescuer != address(0), + "Rescuable: new rescuer is the zero address" + ); + _rescuer = newRescuer; + emit RescuerChanged(newRescuer); + } +} + +pragma solidity 0.6.12; + +/** + * @title FiatTokenV1_1 + * @dev ERC20 Token backed by fiat reserves + */ +contract FiatTokenV1_1 is FiatTokenV1, Rescuable { + +} + +pragma solidity 0.6.12; + +/** + * @title EIP712 + * @notice A library that provides EIP712 helper functions + */ +library EIP712 { + /** + * @notice Make EIP712 domain separator + * @param name Contract name + * @param version Contract version + * @return Domain separator + */ + function makeDomainSeparator(string memory name, string memory version) + internal + view + returns (bytes32) + { + uint256 chainId; + assembly { + chainId := chainid() + } + return + keccak256( + abi.encode( + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, + keccak256(bytes(name)), + keccak256(bytes(version)), + chainId, + address(this) + ) + ); + } + + /** + * @notice Recover signer's address from a EIP712 signature + * @param domainSeparator Domain separator + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + * @param typeHashAndData Type hash concatenated with data + * @return Signer's address + */ + function recover( + bytes32 domainSeparator, + uint8 v, + bytes32 r, + bytes32 s, + bytes memory typeHashAndData + ) internal pure returns (address) { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256(typeHashAndData) + ) + ); + return ECRecover.recover(digest, v, r, s); + } +} + +pragma solidity 0.6.12; + +/** + * @title ECRecover + * @notice A library that provides a safe ECDSA recovery function + */ +library ECRecover { + /** + * @notice Recover signer's address from a signed message + * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol + * Modifications: Accept v, r, and s as separate arguments + * @param digest Keccak-256 hash digest of the signed message + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + * @return Signer address + */ + function recover( + bytes32 digest, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + if ( + uint256(s) > + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + ) { + revert("ECRecover: invalid signature 's' value"); + } + + if (v != 27 && v != 28) { + revert("ECRecover: invalid signature 'v' value"); + } + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(digest, v, r, s); + require(signer != address(0), "ECRecover: invalid signature"); + + return signer; + } +} + +pragma solidity 0.6.12; + +/** + * @title FiatToken V2 + * @notice ERC20 Token backed by fiat reserves, version 2 + */ +contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 { + uint8 internal _initializedVersion; + + /** + * @notice Initialize v2 + * @param newName New token name + */ + function initializeV2(string calldata newName) external { + // solhint-disable-next-line reason-string + require(initialized && _initializedVersion == 0); + name = newName; + DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2"); + _initializedVersion = 1; + } + + /** + * @notice Increase the allowance by a given increment + * @param spender Spender's address + * @param increment Amount of increase in allowance + * @return True if successful + */ + function increaseAllowance(address spender, uint256 increment) + external + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(spender) + returns (bool) + { + _increaseAllowance(msg.sender, spender, increment); + return true; + } + + /** + * @notice Decrease the allowance by a given decrement + * @param spender Spender's address + * @param decrement Amount of decrease in allowance + * @return True if successful + */ + function decreaseAllowance(address spender, uint256 decrement) + external + whenNotPaused + notBlacklisted(msg.sender) + notBlacklisted(spender) + returns (bool) + { + _decreaseAllowance(msg.sender, spender, decrement); + return true; + } + + /** + * @notice Execute a transfer with a signed authorization + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) { + _transferWithAuthorization( + from, + to, + value, + validAfter, + validBefore, + nonce, + v, + r, + s + ); + } + + /** + * @notice Receive a transfer with a signed authorization from the payer + * @dev This has an additional check to ensure that the payee's address + * matches the caller of this function to prevent front-running attacks. + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function receiveWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) { + _receiveWithAuthorization( + from, + to, + value, + validAfter, + validBefore, + nonce, + v, + r, + s + ); + } + + /** + * @notice Attempt to cancel an authorization + * @dev Works only if the authorization is not yet used. + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function cancelAuthorization( + address authorizer, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused { + _cancelAuthorization(authorizer, nonce, v, r, s); + } + + /** + * @notice Update allowance with a signed permit + * @param owner Token owner's address (Authorizer) + * @param spender Spender's address + * @param value Amount of allowance + * @param deadline Expiration time, seconds since the epoch + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) { + _permit(owner, spender, value, deadline, v, r, s); + } + + /** + * @notice Internal function to increase the allowance by a given increment + * @param owner Token owner's address + * @param spender Spender's address + * @param increment Amount of increase + */ + function _increaseAllowance( + address owner, + address spender, + uint256 increment + ) internal override { + _approve(owner, spender, allowed[owner][spender].add(increment)); + } + + /** + * @notice Internal function to decrease the allowance by a given decrement + * @param owner Token owner's address + * @param spender Spender's address + * @param decrement Amount of decrease + */ + function _decreaseAllowance( + address owner, + address spender, + uint256 decrement + ) internal override { + _approve( + owner, + spender, + allowed[owner][spender].sub( + decrement, + "ERC20: decreased allowance below zero" + ) + ); + } +} + +pragma solidity 0.6.12; + +// solhint-disable func-name-mixedcase + +/** + * @title FiatToken V2.1 + * @notice ERC20 Token backed by fiat reserves, version 2.1 + */ +contract FiatTokenV2_1 is FiatTokenV2 { + /** + * @notice Initialize v2.1 + * @param lostAndFound The address to which the locked funds are sent + */ + function initializeV2_1(address lostAndFound) external { + // solhint-disable-next-line reason-string + require(_initializedVersion == 1); + + uint256 lockedAmount = balances[address(this)]; + if (lockedAmount > 0) { + _transfer(address(this), lostAndFound, lockedAmount); + } + blacklisted[address(this)] = true; + + _initializedVersion = 2; + } + + /** + * @notice Version string for the EIP712 domain separator + * @return Version string + */ + function version() external view returns (string memory) { + return "2"; + } +} \ No newline at end of file diff --git a/src/zkbob/ZkBobPoolUSDCMigrated.sol b/src/zkbob/ZkBobPoolUSDCMigrated.sol index 409c0a3..60ccbc7 100644 --- a/src/zkbob/ZkBobPoolUSDCMigrated.sol +++ b/src/zkbob/ZkBobPoolUSDCMigrated.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.15; +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "./ZkBobPool.sol"; import "./ZkBobTokenSellerMixin.sol"; import "./ZkBobUSDCPermitMixin.sol"; @@ -32,4 +33,32 @@ contract ZkBobPoolUSDCMigrated is ZkBobPool, ZkBobTokenSellerMixin, ZkBobUSDCPer 1_000_000_000 ) {} + + function migrationToUSDC() external { + require(msg.sender == address(this), "Incorrect invoker"); + require(token == address(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85), "Incorrect token"); + + address bob_addr = address(0xB0B195aEFA3650A6908f15CdaC7D92F8a5791B0B); + uint256 cur_bob_balance = IERC20(bob_addr).balanceOf(address(this)); + uint256 bob_decimals = IERC20Metadata(bob_addr).decimals(); + + uint256 usdc_decimals = IERC20Metadata(token).decimals(); + + ISwapRouter swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + bool retval = IERC20(bob_addr).approve(address(swapRouter), cur_bob_balance); + uint256 usdc_received = swapRouter.exactInput( + ISwapRouter.ExactInputParams({ + path: abi.encodePacked(bob_addr, uint24(100), 0x7F5c764cBc14f9669B88837ca1490cCa17c31607, uint24(500), token), + recipient: address(this), + deadline: block.timestamp, + amountIn: cur_bob_balance, + amountOutMinimum: 0 + }) + ); + require(IERC20(bob_addr).balanceOf(address(this)) == 0, "Incorrect swap"); + + uint256 spent_on_fees = (cur_bob_balance / (10 ** (bob_decimals - usdc_decimals))) - usdc_received; + + retval = IERC20(token).transferFrom(owner(), address(this), spent_on_fees); + } }