diff --git a/contracts/v1/FiatTokenV1_b.sol b/contracts/v1/FiatTokenV1_b.sol new file mode 100644 index 000000000..1cc0cec1b --- /dev/null +++ b/contracts/v1/FiatTokenV1_b.sol @@ -0,0 +1,33 @@ +/** + * SPDX-License-Identifier: MIT + */ + +pragma solidity 0.6.12; + +import { FiatTokenV1 } from "./FiatTokenV1.sol"; + +contract FiatTokenV1b is FiatTokenV1 { + /** + * @dev allows a minter to burn some tokens from a blacklisted address + * Validates that caller is a minter and that From address is blacklisted + * amount is less than or equal to the From address account balance + * @param from The blacklisted address to burn tokens from + * @param _amount uint256 The amount of tokens to be burned + */ + function burnFrom(address from, uint256 _amount) + external + whenNotPaused + onlyMinters + { + uint256 balance = balances[from]; + require(blacklisted[from] == true, "FiatToken: address is not blacklisted"); + require(_amount > 0, "FiatToken: burn amount not greater than 0"); + require(balance >= _amount, "FiatToken: burn amount exceeds balance"); + + totalSupply_ = totalSupply_.sub(_amount); + balances[from] = balance.sub(_amount); + + emit Burn(from, _amount); + emit Transfer(from, address(0), _amount); + } +} diff --git a/migrations/3_deploy_v1b.js b/migrations/3_deploy_v1b.js new file mode 100644 index 000000000..2cfaf509e --- /dev/null +++ b/migrations/3_deploy_v1b.js @@ -0,0 +1,54 @@ +const fs = require("fs"); +const path = require("path"); +const some = require("lodash/some"); + +const FiatTokenV1b = artifacts.require("FiatTokenV1b"); + +let proxyAdminAddress = ""; +let ownerAddress = ""; +let masterMinterAddress = ""; +let pauserAddress = ""; +let blacklisterAddress = ""; + +/** --------------------------- + * Setup + --------------------------- */ + +// Read config file if it exists +if (fs.existsSync(path.join(__dirname, "..", "config.js"))) { + ({ + PROXY_ADMIN_ADDRESS: proxyAdminAddress, + OWNER_ADDRESS: ownerAddress, + MASTERMINTER_ADDRESS: masterMinterAddress, + PAUSER_ADDRESS: pauserAddress, + BLACKLISTER_ADDRESS: blacklisterAddress, + } = require("../config.js")); +} + +module.exports = async (deployer, network) => { + if (some(["development", "coverage"], (v) => network.includes(v))) { + // DO NOT USE THESE ADDRESSES IN PRODUCTION - these are the deterministic + // addresses from ganache, so the private keys are well known and match the + // values we use in the tests + proxyAdminAddress = "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598"; + ownerAddress = "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d"; + masterMinterAddress = "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9"; + pauserAddress = "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E"; + blacklisterAddress = "0xd03ea8624C8C5987235048901fB614fDcA89b117"; + } + + console.log(`Proxy Admin: ${proxyAdminAddress}`); + console.log(`Owner: ${ownerAddress}`); + console.log(`Master Minter: ${masterMinterAddress}`); + console.log(`Pauser: ${pauserAddress}`); + console.log(`Blacklister: ${blacklisterAddress}`); + + /** --------------------------- + * Deploying v1b + --------------------------- */ + + console.log("Deploying implementation contract..."); + await deployer.deploy(FiatTokenV1b); + const fiatTokenV1b = await FiatTokenV1b.deployed(); + console.log("Deployed implementation contract at", fiatTokenV1b.address); +}; diff --git a/migrations/4_update_proxy.js_skip b/migrations/4_update_proxy.js_skip new file mode 100644 index 000000000..9c42f9d6d --- /dev/null +++ b/migrations/4_update_proxy.js_skip @@ -0,0 +1,44 @@ +const fs = require("fs"); +const path = require("path"); +const some = require("lodash/some"); + +const FiatTokenV1b = artifacts.require("FiatTokenV1b"); +const FiatTokenProxy = artifacts.require("FiatTokenProxy"); + +let proxyContractAddress = ""; +let proxyAdminAddress = ""; + +/** --------------------------- + * Setup + --------------------------- */ + +// Read config file if it exists +if (fs.existsSync(path.join(__dirname, "..", "config.js"))) { + ({ + PROXY_CONTRACT_ADDRESS: proxyContractAddress, + PROXY_ADMIN_ADDRESS: proxyAdminAddress, + } = require("../config.js")); +} + +module.exports = async (deployer, network) => { + if (some(["development", "coverage"], (v) => network.includes(v))) { + proxyAdminAddress = "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598"; + const fiatTokenProxy = await FiatTokenProxy.deployed(); + console.log("Deployed proxy contract at", fiatTokenProxy.address); + proxyContractAddress = fiatTokenProxy.address; + } + + /** --------------------------- + * Updating proxy + --------------------------- */ + + const proxy = await FiatTokenProxy.at(proxyContractAddress); + const v1b = await FiatTokenV1b.deployed(); + console.log("Deployed v1b contract at", v1b.address); + const res = await proxy.upgradeTo(v1b.address, { + from: proxyAdminAddress, + }); + + console.log(`FiatTokenProxy: ${proxy.address}`); + console.log({ res }); +}; diff --git a/migrations_backup/1_initial_migration.js b/migrations_backup/1_initial_migration.js new file mode 100644 index 000000000..4d19593c3 --- /dev/null +++ b/migrations_backup/1_initial_migration.js @@ -0,0 +1,5 @@ +const Migrations = artifacts.require("Migrations"); + +module.exports = async (deployer) => { + await deployer.deploy(Migrations); +}; diff --git a/migrations_backup/2_deploy_v1.js b/migrations_backup/2_deploy_v1.js new file mode 100644 index 000000000..f6d2865ca --- /dev/null +++ b/migrations_backup/2_deploy_v1.js @@ -0,0 +1,98 @@ +const fs = require("fs"); +const path = require("path"); +const some = require("lodash/some"); + +const FiatTokenV1 = artifacts.require("FiatTokenV1"); +const FiatTokenProxy = artifacts.require("FiatTokenProxy"); + +const THROWAWAY_ADDRESS = "0x0000000000000000000000000000000000000001"; + +let proxyAdminAddress = ""; +let ownerAddress = ""; +let masterMinterAddress = ""; +let pauserAddress = ""; +let blacklisterAddress = ""; + +// Read config file if it exists +if (fs.existsSync(path.join(__dirname, "..", "config.js"))) { + ({ + PROXY_ADMIN_ADDRESS: proxyAdminAddress, + OWNER_ADDRESS: ownerAddress, + MASTERMINTER_ADDRESS: masterMinterAddress, + PAUSER_ADDRESS: pauserAddress, + BLACKLISTER_ADDRESS: blacklisterAddress, + } = require("../config.js")); +} + +module.exports = async (deployer, network) => { + if (some(["development", "coverage"], (v) => network.includes(v))) { + // DO NOT USE THESE ADDRESSES IN PRODUCTION - these are the deterministic + // addresses from ganache, so the private keys are well known and match the + // values we use in the tests + proxyAdminAddress = "0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598"; + ownerAddress = "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d"; + masterMinterAddress = "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9"; + pauserAddress = "0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E"; + blacklisterAddress = "0xd03ea8624C8C5987235048901fB614fDcA89b117"; + } + + console.log(`Proxy Admin: ${proxyAdminAddress}`); + console.log(`Owner: ${ownerAddress}`); + console.log(`Master Minter: ${masterMinterAddress}`); + console.log(`Pauser: ${pauserAddress}`); + console.log(`Blacklister: ${blacklisterAddress}`); + + if ( + !proxyAdminAddress || + !ownerAddress || + !masterMinterAddress || + !pauserAddress || + !blacklisterAddress + ) { + throw new Error( + "PROXY_ADMIN_ADDRESS, OWNER_ADDRESS, MASTERMINTER_ADDRESS, PAUSER_ADDRESS, and BLACKLISTER_ADDRESS must be provided in config.js" + ); + } + + console.log("Deploying implementation contract..."); + await deployer.deploy(FiatTokenV1); + const fiatTokenV1 = await FiatTokenV1.deployed(); + console.log("Deployed implementation contract at", FiatTokenV1.address); + + console.log("Initializing implementation contract with dummy values..."); + await fiatTokenV1.initialize( + "", + "", + "", + 0, + THROWAWAY_ADDRESS, + THROWAWAY_ADDRESS, + THROWAWAY_ADDRESS, + THROWAWAY_ADDRESS + ); + + console.log("Deploying proxy contract..."); + await deployer.deploy(FiatTokenProxy, FiatTokenV1.address); + const fiatTokenProxy = await FiatTokenProxy.deployed(); + console.log("Deployed proxy contract at", FiatTokenProxy.address); + + console.log("Reassigning proxy contract admin..."); + // need to change admin first, or the call to initialize won't work + // since admin can only call methods in the proxy, and not forwarded methods + await fiatTokenProxy.changeAdmin(proxyAdminAddress); + + console.log("Initializing proxy contract..."); + // Pretend that the proxy address is a FiatTokenV1 - this is fine because the + // proxy will forward all the calls to the FiatTokenV1 impl + const proxyAsV1 = await FiatTokenV1.at(FiatTokenProxy.address); + await proxyAsV1.initialize( + "USD//C", + "USDC", + "USD", + 6, + masterMinterAddress, + pauserAddress, + blacklisterAddress, + ownerAddress + ); +}; diff --git a/migrations/3_deploy_v2.js b/migrations_backup/3_deploy_v2.js similarity index 100% rename from migrations/3_deploy_v2.js rename to migrations_backup/3_deploy_v2.js diff --git a/migrations/4_deploy_v2_upgrader.js b/migrations_backup/4_deploy_v2_upgrader.js similarity index 100% rename from migrations/4_deploy_v2_upgrader.js rename to migrations_backup/4_deploy_v2_upgrader.js diff --git a/migrations/5_deploy_v2_1.js b/migrations_backup/5_deploy_v2_1.js similarity index 100% rename from migrations/5_deploy_v2_1.js rename to migrations_backup/5_deploy_v2_1.js diff --git a/migrations/6_deploy_v2_1_upgrader.js b/migrations_backup/6_deploy_v2_1_upgrader.js similarity index 100% rename from migrations/6_deploy_v2_1_upgrader.js rename to migrations_backup/6_deploy_v2_1_upgrader.js diff --git a/package.json b/package.json index e0e7219fd..b3e72444b 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "solhint": "^3.1.0", "solidity-coverage": "^0.7.9", "truffle": "^5.1.35", + "truffle-plugin-verify": "^0.6.1", "ts-node": "^8.10.2", "typechain": "^2.0.0", "typescript": "^3.9.7", @@ -74,4 +75,4 @@ "node": ">= 12.0.0", "yarn": ">= 1.21.1" } -} +} \ No newline at end of file diff --git a/truffle-config.js b/truffle-config.js index 42120ea16..4e8d15312 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -44,15 +44,19 @@ module.exports = { provider: infuraProvider("ropsten"), network_id: 3, }, + goerli: { + provider: infuraProvider("goerli"), + network_id: 5, + }, }, mocha: { timeout: 60000, // prevents tests from failing when pc is under heavy load reporter: "Spec", }, - plugins: ["solidity-coverage"], + plugins: ["solidity-coverage", "truffle-plugin-verify"], }; -function infuraProvider(network) { +function infuraProvider (network) { return () => { if (!config.MNEMONIC) { console.error("A valid MNEMONIC must be provided in config.js");