From 3fddf1a22ffcae99a4278b4cef7b75e1c5729b02 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Tue, 3 Sep 2024 09:24:59 +0800 Subject: [PATCH 01/10] Add burner role for burn functions --- package.json | 4 ++-- script/UpgradeFiatToken.s.sol | 2 +- src/v1/FiatTokenV1.sol | 5 +++-- src/v2/FiatTokenV2.sol | 21 +++++++++++++++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 src/v2/FiatTokenV2.sol diff --git a/package.json b/package.json index 20a9bc1..dd2f463 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "@trillion-x/trillion-contracts", + "name": "trillion-contracts", "version": "1.0.0", "license": "MIT", - "repository": "git@github.com:royal-markets/royal-contracts.git", + "repository": "git@github.com:trillion-network/trillion-contracts.git", "files": [ "out", "ts-types" diff --git a/script/UpgradeFiatToken.s.sol b/script/UpgradeFiatToken.s.sol index bb1e470..53dc05c 100644 --- a/script/UpgradeFiatToken.s.sol +++ b/script/UpgradeFiatToken.s.sol @@ -7,7 +7,7 @@ import {Script} from "forge-std/Script.sol"; import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {FiatTokenV1} from "../src/v1/FiatTokenV1.sol"; -contract DeployFiatToken is Script { +contract UpgradeFiatToken is Script { function run() public { vm.startBroadcast(); address fiatTokenProxyAddress = vm.envAddress("FIAT_TOKEN_PROXY_ADDRESS"); diff --git a/src/v1/FiatTokenV1.sol b/src/v1/FiatTokenV1.sol index 5899521..27ffe51 100644 --- a/src/v1/FiatTokenV1.sol +++ b/src/v1/FiatTokenV1.sol @@ -32,6 +32,7 @@ contract FiatTokenV1 is bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); bytes32 public constant RESCUER_ROLE = keccak256("RESCUER_ROLE"); bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); /// @dev reserve storage slots so that future upgrades do not affect storage layout of child contracts /// when extra variables are added, reduce the appropriate slots from the storage gap @@ -93,11 +94,11 @@ contract FiatTokenV1 is _mint(to, amount); } - function burn(uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(MINTER_ROLE) { + function burn(uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(BURNER_ROLE) { super.burn(value); } - function burnFrom(address account, uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(MINTER_ROLE) { + function burnFrom(address account, uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(BURNER_ROLE) { super.burnFrom(account, value); } diff --git a/src/v2/FiatTokenV2.sol b/src/v2/FiatTokenV2.sol new file mode 100644 index 0000000..c9164d2 --- /dev/null +++ b/src/v2/FiatTokenV2.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import "../v1/FiatTokenV1.sol"; + +/// @custom:security-contact snggeng@gmail.com +contract FiatTokenV2 is FiatTokenV1 { + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + function burn(uint256 value) public override(FiatTokenV1) onlyRole(BURNER_ROLE) { + super.burn(value); + } + + function burnFrom(address account, uint256 value) public override(FiatTokenV1) onlyRole(BURNER_ROLE) { + super.burnFrom(account, value); + } + + function version() public pure virtual override(FiatTokenV1) returns (string memory) { + return "2"; + } +} From 4b88776eeabbda270e816d11cf4cc0bfa7c16132 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Wed, 4 Sep 2024 16:35:52 +0800 Subject: [PATCH 02/10] add test for FiatTokenV2 to test burn with BURNER_ROLE --- src/v1/FiatTokenV1.sol | 5 +- src/v2/FiatTokenV2.sol | 19 +- test/v2/FiatTokenV2.t.sol | 597 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 614 insertions(+), 7 deletions(-) create mode 100644 test/v2/FiatTokenV2.t.sol diff --git a/src/v1/FiatTokenV1.sol b/src/v1/FiatTokenV1.sol index 27ffe51..5899521 100644 --- a/src/v1/FiatTokenV1.sol +++ b/src/v1/FiatTokenV1.sol @@ -32,7 +32,6 @@ contract FiatTokenV1 is bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); bytes32 public constant RESCUER_ROLE = keccak256("RESCUER_ROLE"); bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE"); - bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); /// @dev reserve storage slots so that future upgrades do not affect storage layout of child contracts /// when extra variables are added, reduce the appropriate slots from the storage gap @@ -94,11 +93,11 @@ contract FiatTokenV1 is _mint(to, amount); } - function burn(uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(BURNER_ROLE) { + function burn(uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(MINTER_ROLE) { super.burn(value); } - function burnFrom(address account, uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(BURNER_ROLE) { + function burnFrom(address account, uint256 value) public override(ERC20BurnableUpgradeable) onlyRole(MINTER_ROLE) { super.burnFrom(account, value); } diff --git a/src/v2/FiatTokenV2.sol b/src/v2/FiatTokenV2.sol index c9164d2..eea80ba 100644 --- a/src/v2/FiatTokenV2.sol +++ b/src/v2/FiatTokenV2.sol @@ -5,14 +5,25 @@ import "../v1/FiatTokenV1.sol"; /// @custom:security-contact snggeng@gmail.com contract FiatTokenV2 is FiatTokenV1 { + uint8 internal _initializedVersion; bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - function burn(uint256 value) public override(FiatTokenV1) onlyRole(BURNER_ROLE) { - super.burn(value); + function initializeV2(address burner) external { + // solhint-disable-next-line reason-string + require(_initializedVersion == 0); + + _grantRole(BURNER_ROLE, burner); + + _initializedVersion = 1; + } + + function burnBurner(uint256 value) public onlyRole(BURNER_ROLE) { + _burn(_msgSender(), value); } - function burnFrom(address account, uint256 value) public override(FiatTokenV1) onlyRole(BURNER_ROLE) { - super.burnFrom(account, value); + function burnFromBurner(address account, uint256 value) public onlyRole(BURNER_ROLE) { + _spendAllowance(account, _msgSender(), value); + _burn(account, value); } function version() public pure virtual override(FiatTokenV1) returns (string memory) { diff --git a/test/v2/FiatTokenV2.t.sol b/test/v2/FiatTokenV2.t.sol new file mode 100644 index 0000000..183942f --- /dev/null +++ b/test/v2/FiatTokenV2.t.sol @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; + +import {Test, console2} from "forge-std/Test.sol"; +import {UnauthorizedInitialization} from "../../src/v1/FiatTokenV1.sol"; +import {FiatTokenV2} from "../../src/v2/FiatTokenV2.sol"; + +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +import {ERC20CappedUpgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol"; +import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {CallerBlacklisted} from "../../src/v1/BlacklistableV1.sol"; +import {Ramen} from "../../src/mocks/Ramen.sol"; +// import {FiatTokenV99} from "../../src/mocks/FiatTokenV99.sol"; + +// mock contract to test upgrades +contract FiatTokenV99 is FiatTokenV2 { + // solhint-disable-next-line foundry-test-functions + function version() public pure virtual override(FiatTokenV2) returns (string memory) { + return "99"; + } +} + +contract FiatTokenV2Test is Test { + FiatTokenV2 public fiatTokenV2; + ERC1967Proxy public proxy; + address public owner; + address public defaultAdmin; + address public pauser; + address public minter; + address public upgrader; + address public rescuer; + address public blacklister; + address public unauthorized; + address public burner; + address public trustedAddress; + string public tokenName = "FiatTokenV2"; + string public tokenSymbol = "FIAT"; + + // events + event Blacklisted(address indexed account); + event UnBlacklisted(address indexed account); + + function setUp() public { + owner = address(this); + defaultAdmin = vm.addr(1); + pauser = vm.addr(2); + minter = vm.addr(3); + upgrader = vm.addr(4); + rescuer = vm.addr(5); + blacklister = vm.addr(6); + unauthorized = vm.addr(7); + burner = vm.addr(8); + trustedAddress = address(0x66787300CCc33F17643a02635ca96d54301aE2a8); + + // Deploy the token implementation + fiatTokenV2 = new FiatTokenV2(); + + // Deploy the proxy and initialize the contract through the proxy + vm.prank(trustedAddress); + proxy = new ERC1967Proxy( + address(fiatTokenV2), + abi.encodeCall( + fiatTokenV2.initialize, + (defaultAdmin, pauser, minter, upgrader, rescuer, blacklister, tokenName, tokenSymbol) + ) + ); + + // Attach the FiatTokenV2 interface to the deployed proxy + fiatTokenV2 = FiatTokenV2(address(proxy)); + + // Grant BURNER_ROLE to burner + fiatTokenV2.initializeV2(burner); + } + + function testUnauthorizedInitialization() public { + // redeploy new proxy and try to initialize implementation with unauthorized address + fiatTokenV2 = new FiatTokenV2(); + vm.expectRevert(abi.encodeWithSelector(UnauthorizedInitialization.selector, unauthorized)); + vm.prank(unauthorized); + proxy = new ERC1967Proxy( + address(fiatTokenV2), + abi.encodeCall( + fiatTokenV2.initialize, + (defaultAdmin, pauser, minter, upgrader, rescuer, blacklister, tokenName, tokenSymbol) + ) + ); + } + + // Initialization grants roles + + function testInitializedRoles() public { + assertEq(fiatTokenV2.hasRole(fiatTokenV2.DEFAULT_ADMIN_ROLE(), defaultAdmin), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.PAUSER_ROLE(), pauser), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.MINTER_ROLE(), minter), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.UPGRADER_ROLE(), upgrader), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.RESCUER_ROLE(), rescuer), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.BLACKLISTER_ROLE(), blacklister), true); + assertEq(fiatTokenV2.hasRole(fiatTokenV2.BURNER_ROLE(), burner), true); + } + + // ERC 20 behavior + + function testVersion() public { + assertEq(fiatTokenV2.version(), "2"); + } + + function testName() public { + assertEq(fiatTokenV2.name(), tokenName); + } + + function testSymbol() public { + assertEq(fiatTokenV2.symbol(), tokenSymbol); + } + + function testDecimals() public { + assertEq(fiatTokenV2.decimals(), 6); + } + + function testBalanceOf() public { + assertEq(fiatTokenV2.balanceOf(owner), 0); + vm.prank(minter); + fiatTokenV2.mint(owner, 100); + assertEq(fiatTokenV2.balanceOf(owner), 100); + } + + function testTotalSupply() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(owner, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + } + + function testApprove() public { + assertEq(fiatTokenV2.allowance(owner, unauthorized), 0); + vm.prank(owner); + fiatTokenV2.approve(unauthorized, 100); + assertEq(fiatTokenV2.allowance(owner, unauthorized), 100); + } + + function testMint() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(owner, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(owner), 100); + } + + function testMintUnauthorized() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.MINTER_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.mint(owner, 100); + } + + function testMintAboveCap() public { + assertEq(fiatTokenV2.cap(), 1e30); + assertEq(fiatTokenV2.totalSupply(), 0); + vm.expectRevert(abi.encodeWithSelector(ERC20CappedUpgradeable.ERC20ExceededCap.selector, 1e31, 1e30)); + vm.prank(minter); + fiatTokenV2.mint(owner, 1e31); + + // nothing minted + assertEq(fiatTokenV2.totalSupply(), 0); + } + + function testBurn() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(burner, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(burner), 100); + vm.prank(burner); + fiatTokenV2.burnBurner(100); + assertEq(fiatTokenV2.totalSupply(), 0); + assertEq(fiatTokenV2.balanceOf(burner), 0); + } + + function testBurnMustBeLessThanBalance() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(burner, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(burner), 100); + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InsufficientBalance.selector, + burner, // from + 100, // fromBalance + 101 // value + ) + ); + vm.prank(burner); + fiatTokenV2.burnBurner(101); + } + + function testBurnUnauthorized() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(owner, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(owner), 100); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, + unauthorized, // address + fiatTokenV2.BURNER_ROLE() // role + ) + ); + vm.prank(unauthorized); + fiatTokenV2.burnBurner(100); + } + + function testBurnFrom() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(unauthorized, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(unauthorized), 100); + vm.prank(unauthorized); + // needs to be burner as only account with BURNER_ROLE allowed to burn + fiatTokenV2.approve(burner, 100); + assertEq(fiatTokenV2.allowance(unauthorized, burner), 100); + vm.prank(burner); + fiatTokenV2.burnFromBurner(unauthorized, 100); + assertEq(fiatTokenV2.totalSupply(), 0); + assertEq(fiatTokenV2.balanceOf(unauthorized), 0); + } + + function testTransfer() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(minter), 100); + vm.prank(minter); + fiatTokenV2.transfer(unauthorized, 100); + assertEq(fiatTokenV2.balanceOf(minter), 0); + assertEq(fiatTokenV2.balanceOf(unauthorized), 100); + } + + function testTransferMustBeAtLeaseBalance() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(minter), 100); + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InsufficientBalance.selector, + minter, // from + 100, // fromBalance + 101 // value + ) + ); + vm.prank(minter); + fiatTokenV2.transfer(unauthorized, 101); + } + + function testTransferCannotBeToZeroAddress() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(minter), 100); + vm.expectRevert( + abi.encodeWithSelector( + IERC20Errors.ERC20InvalidReceiver.selector, + address(0) // to + ) + ); + vm.prank(minter); + fiatTokenV2.transfer(address(0), 100); + } + + function testTransferFrom() public { + assertEq(fiatTokenV2.totalSupply(), 0); + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + assertEq(fiatTokenV2.totalSupply(), 100); + assertEq(fiatTokenV2.balanceOf(minter), 100); + vm.prank(minter); + fiatTokenV2.approve(unauthorized, 100); + vm.prank(unauthorized); + fiatTokenV2.transferFrom(minter, unauthorized, 100); + assertEq(fiatTokenV2.balanceOf(minter), 0); + assertEq(fiatTokenV2.balanceOf(unauthorized), 100); + } + + function testPause() public { + assertEq(fiatTokenV2.paused(), false); + vm.prank(pauser); + fiatTokenV2.pause(); + assertEq(fiatTokenV2.paused(), true); + // when contract is paused, not allowed to mint, burn, or transfer + vm.expectRevert(Pausable.EnforcedPause.selector); + vm.prank(minter); + fiatTokenV2.mint(owner, 100); + vm.expectRevert(Pausable.EnforcedPause.selector); + vm.prank(minter); + fiatTokenV2.transfer(unauthorized, 100); + vm.expectRevert(Pausable.EnforcedPause.selector); + vm.prank(burner); + fiatTokenV2.burnBurner(100); + } + + function testPauseUnauthorized() public { + assertEq(fiatTokenV2.paused(), false); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.PAUSER_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.pause(); + } + + function testUnpause() public { + assertEq(fiatTokenV2.paused(), false); + vm.prank(pauser); + fiatTokenV2.pause(); + assertEq(fiatTokenV2.paused(), true); + vm.prank(pauser); + fiatTokenV2.unpause(); + assertEq(fiatTokenV2.paused(), false); + } + + function testUnpauseUnauthorized() public { + assertEq(fiatTokenV2.paused(), false); + vm.prank(pauser); + fiatTokenV2.pause(); + assertEq(fiatTokenV2.paused(), true); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.PAUSER_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.unpause(); + } + + function testPaused() public { + assertEq(fiatTokenV2.paused(), false); + vm.prank(pauser); + fiatTokenV2.pause(); + assertEq(fiatTokenV2.paused(), true); + } + + function testRescue() public { + Ramen gld = new Ramen(100); + gld.transfer(address(fiatTokenV2), 100); + assertEq(gld.balanceOf(address(fiatTokenV2)), 100); + vm.prank(rescuer); + fiatTokenV2.rescue(gld, unauthorized, 100); + assertEq(gld.balanceOf(address(fiatTokenV2)), 0); + } + + function testBlacklistMinter() public { + // mint tokens to minter account + // for simplicity, we blacklist the minter account since it has permissions to transfer and mint + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + assertEq(fiatTokenV2.balanceOf(minter), 100); + + // blacklist minter account + assertEq(fiatTokenV2.isBlacklisted(minter), false); + vm.expectEmit(); + emit Blacklisted(minter); + vm.prank(blacklister); + fiatTokenV2.blacklist(minter); + assertEq(fiatTokenV2.isBlacklisted(minter), true); + // once blacklisted, not allowed to transfer + vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, minter)); + vm.prank(minter); + fiatTokenV2.transfer(minter, 100); + // not allowed to mint + vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, minter)); + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + } + + function testBlacklistBurner() public { + // mint tokens to burner account + // for simplicity, we blacklist the burner account since it has permissions to burn + vm.prank(minter); + fiatTokenV2.mint(burner, 100); + assertEq(fiatTokenV2.balanceOf(burner), 100); + + // blacklist minter account + assertEq(fiatTokenV2.isBlacklisted(burner), false); + vm.expectEmit(); + emit Blacklisted(burner); + vm.prank(blacklister); + fiatTokenV2.blacklist(burner); + assertEq(fiatTokenV2.isBlacklisted(burner), true); + // once blacklisted, not allowed to transfer + vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, burner)); + vm.prank(burner); + fiatTokenV2.transfer(burner, 100); + // not allowed to burn + vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, burner)); + vm.prank(burner); + fiatTokenV2.burnBurner(100); + } + + function testUnblacklistMinter() public { + // blacklist minter account + assertEq(fiatTokenV2.isBlacklisted(minter), false); + vm.expectEmit(); + emit Blacklisted(minter); + vm.prank(blacklister); + fiatTokenV2.blacklist(minter); + assertEq(fiatTokenV2.isBlacklisted(minter), true); + // unblacklist minter account + vm.expectEmit(); + emit UnBlacklisted(minter); + vm.prank(blacklister); + fiatTokenV2.unBlacklist(minter); + assertEq(fiatTokenV2.isBlacklisted(minter), false); + // once unblacklisted, allowed to mint + vm.prank(minter); + fiatTokenV2.mint(minter, 100); + // allowed to transfer + vm.prank(minter); + fiatTokenV2.transfer(unauthorized, 100); + // no balance left after transferring and burning + assertEq(fiatTokenV2.balanceOf(minter), 0); + } + + function testUnblacklistBurner() public { + // mint 100 for to burner first + vm.prank(minter); + fiatTokenV2.mint(burner, 100); + // blacklist burner account + assertEq(fiatTokenV2.isBlacklisted(burner), false); + vm.expectEmit(); + emit Blacklisted(burner); + vm.prank(blacklister); + fiatTokenV2.blacklist(burner); + assertEq(fiatTokenV2.isBlacklisted(burner), true); + // unblacklist burner account + vm.expectEmit(); + emit UnBlacklisted(burner); + vm.prank(blacklister); + fiatTokenV2.unBlacklist(burner); + assertEq(fiatTokenV2.isBlacklisted(burner), false); + // once unblacklisted, allowed to burn + vm.prank(burner); + fiatTokenV2.burnBurner(100); + // no balance left after transferring and burning + assertEq(fiatTokenV2.balanceOf(burner), 0); + } + + function testBlacklistUnauthorized() public { + assertEq(fiatTokenV2.isBlacklisted(minter), false); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.BLACKLISTER_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.blacklist(minter); + } + + function testUnblacklistUnauthorized() public { + assertEq(fiatTokenV2.isBlacklisted(minter), false); + vm.prank(blacklister); + fiatTokenV2.blacklist(minter); + assertEq(fiatTokenV2.isBlacklisted(minter), true); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.BLACKLISTER_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.unBlacklist(minter); + } + + // Access control + + function testGrantRole() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + vm.prank(defaultAdmin); + fiatTokenV2.grantRole(upgraderRole, unauthorized); + assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), true); + } + + function testGrantBurnerRole() public { + bytes32 burnerRole = fiatTokenV2.BURNER_ROLE(); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), false); + vm.prank(defaultAdmin); + fiatTokenV2.grantRole(burnerRole, unauthorized); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), true); + } + + function testGrantRoleUnauthorized() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.DEFAULT_ADMIN_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.grantRole(upgraderRole, unauthorized); + assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + } + + function testHasRole() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); + } + + function testGetRoleAdmin() public { + assertEq(fiatTokenV2.getRoleAdmin(fiatTokenV2.UPGRADER_ROLE()), fiatTokenV2.DEFAULT_ADMIN_ROLE()); + } + + function testRevokeRole() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); + vm.prank(defaultAdmin); + fiatTokenV2.revokeRole(upgraderRole, upgrader); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), false); + } + + function testRevokeRoleUnauthorized() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); + vm.expectRevert( + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.DEFAULT_ADMIN_ROLE() + ) + ); + vm.prank(unauthorized); + fiatTokenV2.revokeRole(upgraderRole, unauthorized); + } + + function testRenounceRole() public { + bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); + vm.prank(upgrader); // caller needs to be the one renouncing their own role + fiatTokenV2.renounceRole(upgraderRole, upgrader); + assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), false); + } + + // Upgradeability + + function testUpgradeToAndCall() public { + // new implementation contract + FiatTokenV99 fiatTokenV99 = new FiatTokenV99(); + address newImplementationAddress = address(fiatTokenV99); + assertEq(fiatTokenV99.version(), "99"); + assertEq(fiatTokenV2.version(), "2"); + // upgrade contract + vm.prank(upgrader); + fiatTokenV2.upgradeToAndCall(newImplementationAddress, ""); + address updatedImplementationAddress = Upgrades.getImplementationAddress(address(proxy)); + // verify implementation address is updated + assertEq(newImplementationAddress, updatedImplementationAddress); + // verify version() function implementation is updated + assertEq(fiatTokenV2.version(), "99"); + } + + // Trusted addresses + + function testAddTrustedAddress() public { + assertEq(fiatTokenV2.isTrustedAddress(unauthorized), false); + vm.prank(defaultAdmin); + fiatTokenV2.addTrustedAddress(unauthorized); + assertEq(fiatTokenV2.isTrustedAddress(unauthorized), true); + } + + function testRemoveTrustedAddress() public { + assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), true); + vm.prank(defaultAdmin); + fiatTokenV2.removeTrustedAddress(trustedAddress); + assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), false); + } + + function testIsTrustedAddress() public { + assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), true); + assertEq(fiatTokenV2.isTrustedAddress(unauthorized), false); + } +} From 56fe8d4a5d55f74d59d0378eb35986809542666f Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Wed, 4 Sep 2024 16:40:33 +0800 Subject: [PATCH 03/10] minor: commit updated .gas-snapshot file --- .gas-snapshot | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/.gas-snapshot b/.gas-snapshot index e3a6297..39a50f9 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -41,4 +41,48 @@ FiatTokenV1Test:testUnpause() (gas: 35210) FiatTokenV1Test:testUnpauseUnauthorized() (gas: 52752) FiatTokenV1Test:testUpgradeToAndCall() (gas: 2336296) FiatTokenV1Test:testVersion() (gas: 11934) +FiatTokenV2Test:testAddTrustedAddress() (gas: 45693) +FiatTokenV2Test:testApprove() (gas: 46131) +FiatTokenV2Test:testBalanceOf() (gas: 79093) +FiatTokenV2Test:testBlacklistBurner() (gas: 120181) +FiatTokenV2Test:testBlacklistMinter() (gas: 116535) +FiatTokenV2Test:testBlacklistUnauthorized() (gas: 26275) +FiatTokenV2Test:testBurn() (gas: 68360) +FiatTokenV2Test:testBurnFrom() (gas: 93612) +FiatTokenV2Test:testBurnMustBeLessThanBalance() (gas: 86595) +FiatTokenV2Test:testBurnUnauthorized() (gas: 88699) +FiatTokenV2Test:testDecimals() (gas: 10462) +FiatTokenV2Test:testGetRoleAdmin() (gas: 15008) +FiatTokenV2Test:testGrantBurnerRole() (gas: 51990) +FiatTokenV2Test:testGrantRole() (gas: 51955) +FiatTokenV2Test:testGrantRoleUnauthorized() (gas: 29719) +FiatTokenV2Test:testHasRole() (gas: 22200) +FiatTokenV2Test:testInitializedRoles() (gas: 57974) +FiatTokenV2Test:testIsTrustedAddress() (gas: 20707) +FiatTokenV2Test:testMint() (gas: 80029) +FiatTokenV2Test:testMintAboveCap() (gas: 80683) +FiatTokenV2Test:testMintUnauthorized() (gas: 25860) +FiatTokenV2Test:testName() (gas: 17382) +FiatTokenV2Test:testPause() (gas: 79157) +FiatTokenV2Test:testPauseUnauthorized() (gas: 23834) +FiatTokenV2Test:testPaused() (gas: 44256) +FiatTokenV2Test:testRemoveTrustedAddress() (gas: 23721) +FiatTokenV2Test:testRenounceRole() (gas: 23448) +FiatTokenV2Test:testRescue() (gas: 524582) +FiatTokenV2Test:testRevokeRole() (gas: 30015) +FiatTokenV2Test:testRevokeRoleUnauthorized() (gas: 29844) +FiatTokenV2Test:testSymbol() (gas: 17371) +FiatTokenV2Test:testTotalSupply() (gas: 78407) +FiatTokenV2Test:testTransfer() (gas: 93174) +FiatTokenV2Test:testTransferCannotBeToZeroAddress() (gas: 81041) +FiatTokenV2Test:testTransferFrom() (gas: 108184) +FiatTokenV2Test:testTransferMustBeAtLeaseBalance() (gas: 86566) +FiatTokenV2Test:testUnauthorizedInitialization() (gas: 2538698) +FiatTokenV2Test:testUnblacklistBurner() (gas: 95846) +FiatTokenV2Test:testUnblacklistMinter() (gas: 113440) +FiatTokenV2Test:testUnblacklistUnauthorized() (gas: 55710) +FiatTokenV2Test:testUnpause() (gas: 35247) +FiatTokenV2Test:testUnpauseUnauthorized() (gas: 52884) +FiatTokenV2Test:testUpgradeToAndCall() (gas: 2408169) +FiatTokenV2Test:testVersion() (gas: 11956) RescuableV1Test:testRescue() (gas: 42236) \ No newline at end of file From 49eb3e7d06cbaa0b9779b10c1aa32a853e24749c Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Wed, 4 Sep 2024 17:16:53 +0800 Subject: [PATCH 04/10] minor: update UpgradeFiatToken script --- script/UpgradeFiatToken.s.sol | 4 ++-- src/v2/FiatTokenV2.sol | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/script/UpgradeFiatToken.s.sol b/script/UpgradeFiatToken.s.sol index 53dc05c..9fb8d14 100644 --- a/script/UpgradeFiatToken.s.sol +++ b/script/UpgradeFiatToken.s.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import "forge-std/console2.sol"; import {Script} from "forge-std/Script.sol"; import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import {FiatTokenV1} from "../src/v1/FiatTokenV1.sol"; +import {FiatTokenV2} from "../src/v1/FiatTokenV2.sol"; contract UpgradeFiatToken is Script { function run() public { @@ -16,7 +16,7 @@ contract UpgradeFiatToken is Script { opts.referenceContract = "FiatTokenV1.sol"; // upgrade contract Upgrades.upgradeProxy(fiatTokenProxyAddress, "FiatTokenV2.sol", "", opts); - console2.log("FiatTokenV1 upgraded to version 2"); + console2.log("FiatTokenV1 upgraded to FiatTokenV2"); vm.stopBroadcast(); } } diff --git a/src/v2/FiatTokenV2.sol b/src/v2/FiatTokenV2.sol index eea80ba..c5eca80 100644 --- a/src/v2/FiatTokenV2.sol +++ b/src/v2/FiatTokenV2.sol @@ -3,14 +3,17 @@ pragma solidity 0.8.20; import "../v1/FiatTokenV1.sol"; +error InvalidReInitialization(address addr); + /// @custom:security-contact snggeng@gmail.com contract FiatTokenV2 is FiatTokenV1 { uint8 internal _initializedVersion; bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); function initializeV2(address burner) external { - // solhint-disable-next-line reason-string - require(_initializedVersion == 0); + if (_initializedVersion > 0) { + revert InvalidReInitialization(msg.sender); + } _grantRole(BURNER_ROLE, burner); From 64a883caabfdd35fffa0d416351cca978e7d77fc Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Wed, 4 Sep 2024 17:24:49 +0800 Subject: [PATCH 05/10] fix: wrong import path --- .gas-snapshot | 4 ++-- script/UpgradeFiatToken.s.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 39a50f9..1a94b7e 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -77,12 +77,12 @@ FiatTokenV2Test:testTransfer() (gas: 93174) FiatTokenV2Test:testTransferCannotBeToZeroAddress() (gas: 81041) FiatTokenV2Test:testTransferFrom() (gas: 108184) FiatTokenV2Test:testTransferMustBeAtLeaseBalance() (gas: 86566) -FiatTokenV2Test:testUnauthorizedInitialization() (gas: 2538698) +FiatTokenV2Test:testUnauthorizedInitialization() (gas: 2543113) FiatTokenV2Test:testUnblacklistBurner() (gas: 95846) FiatTokenV2Test:testUnblacklistMinter() (gas: 113440) FiatTokenV2Test:testUnblacklistUnauthorized() (gas: 55710) FiatTokenV2Test:testUnpause() (gas: 35247) FiatTokenV2Test:testUnpauseUnauthorized() (gas: 52884) -FiatTokenV2Test:testUpgradeToAndCall() (gas: 2408169) +FiatTokenV2Test:testUpgradeToAndCall() (gas: 2412584) FiatTokenV2Test:testVersion() (gas: 11956) RescuableV1Test:testRescue() (gas: 42236) \ No newline at end of file diff --git a/script/UpgradeFiatToken.s.sol b/script/UpgradeFiatToken.s.sol index 9fb8d14..cfcb55a 100644 --- a/script/UpgradeFiatToken.s.sol +++ b/script/UpgradeFiatToken.s.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import "forge-std/console2.sol"; import {Script} from "forge-std/Script.sol"; import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; -import {FiatTokenV2} from "../src/v1/FiatTokenV2.sol"; +import {FiatTokenV2} from "../src/v2/FiatTokenV2.sol"; contract UpgradeFiatToken is Script { function run() public { From 109744d55702f520cfcab1b406fc2235d4e8ba57 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Thu, 5 Sep 2024 11:36:29 +0800 Subject: [PATCH 06/10] refactor: minimum updates --- src/v2/FiatTokenV2.sol | 20 +- test/v1/FiatTokenV1.t.sol | 3 +- test/v2/FiatTokenV2.t.sol | 454 +++++--------------------------------- 3 files changed, 54 insertions(+), 423 deletions(-) diff --git a/src/v2/FiatTokenV2.sol b/src/v2/FiatTokenV2.sol index c5eca80..5a93743 100644 --- a/src/v2/FiatTokenV2.sol +++ b/src/v2/FiatTokenV2.sol @@ -3,32 +3,14 @@ pragma solidity 0.8.20; import "../v1/FiatTokenV1.sol"; -error InvalidReInitialization(address addr); - /// @custom:security-contact snggeng@gmail.com contract FiatTokenV2 is FiatTokenV1 { - uint8 internal _initializedVersion; bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - function initializeV2(address burner) external { - if (_initializedVersion > 0) { - revert InvalidReInitialization(msg.sender); - } - - _grantRole(BURNER_ROLE, burner); - - _initializedVersion = 1; - } - - function burnBurner(uint256 value) public onlyRole(BURNER_ROLE) { + function burnByBurner(uint256 value) public onlyRole(BURNER_ROLE) { _burn(_msgSender(), value); } - function burnFromBurner(address account, uint256 value) public onlyRole(BURNER_ROLE) { - _spendAllowance(account, _msgSender(), value); - _burn(account, value); - } - function version() public pure virtual override(FiatTokenV1) returns (string memory) { return "2"; } diff --git a/test/v1/FiatTokenV1.t.sol b/test/v1/FiatTokenV1.t.sol index 064c38e..1440cac 100644 --- a/test/v1/FiatTokenV1.t.sol +++ b/test/v1/FiatTokenV1.t.sol @@ -12,11 +12,10 @@ import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.so import {ERC20CappedUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; -import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {CallerBlacklisted} from "../../src/v1/BlacklistableV1.sol"; import {Ramen} from "../../src/mocks/Ramen.sol"; -// import {FiatTokenV99} from "../../src/mocks/FiatTokenV99.sol"; // mock contract to test upgrades contract FiatTokenV99 is FiatTokenV1 { diff --git a/test/v2/FiatTokenV2.t.sol b/test/v2/FiatTokenV2.t.sol index 183942f..f2934bc 100644 --- a/test/v2/FiatTokenV2.t.sol +++ b/test/v2/FiatTokenV2.t.sol @@ -5,27 +5,15 @@ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; import {Test, console2} from "forge-std/Test.sol"; -import {UnauthorizedInitialization} from "../../src/v1/FiatTokenV1.sol"; +import {FiatTokenV1} from "../../src/v1/FiatTokenV1.sol"; import {FiatTokenV2} from "../../src/v2/FiatTokenV2.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; -import {ERC20CappedUpgradeable} from - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol"; import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol"; -import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {CallerBlacklisted} from "../../src/v1/BlacklistableV1.sol"; -import {Ramen} from "../../src/mocks/Ramen.sol"; -// import {FiatTokenV99} from "../../src/mocks/FiatTokenV99.sol"; - -// mock contract to test upgrades -contract FiatTokenV99 is FiatTokenV2 { - // solhint-disable-next-line foundry-test-functions - function version() public pure virtual override(FiatTokenV2) returns (string memory) { - return "99"; - } -} contract FiatTokenV2Test is Test { FiatTokenV2 public fiatTokenV2; @@ -75,34 +63,10 @@ contract FiatTokenV2Test is Test { // Attach the FiatTokenV2 interface to the deployed proxy fiatTokenV2 = FiatTokenV2(address(proxy)); - // Grant BURNER_ROLE to burner - fiatTokenV2.initializeV2(burner); - } - - function testUnauthorizedInitialization() public { - // redeploy new proxy and try to initialize implementation with unauthorized address - fiatTokenV2 = new FiatTokenV2(); - vm.expectRevert(abi.encodeWithSelector(UnauthorizedInitialization.selector, unauthorized)); - vm.prank(unauthorized); - proxy = new ERC1967Proxy( - address(fiatTokenV2), - abi.encodeCall( - fiatTokenV2.initialize, - (defaultAdmin, pauser, minter, upgrader, rescuer, blacklister, tokenName, tokenSymbol) - ) - ); - } - - // Initialization grants roles - - function testInitializedRoles() public { - assertEq(fiatTokenV2.hasRole(fiatTokenV2.DEFAULT_ADMIN_ROLE(), defaultAdmin), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.PAUSER_ROLE(), pauser), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.MINTER_ROLE(), minter), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.UPGRADER_ROLE(), upgrader), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.RESCUER_ROLE(), rescuer), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.BLACKLISTER_ROLE(), blacklister), true); - assertEq(fiatTokenV2.hasRole(fiatTokenV2.BURNER_ROLE(), burner), true); + // Assign BURNER_ROLE to burner + bytes32 burnerRole = fiatTokenV2.BURNER_ROLE(); + vm.prank(defaultAdmin); + fiatTokenV2.grantRole(burnerRole, burner); } // ERC 20 behavior @@ -111,82 +75,19 @@ contract FiatTokenV2Test is Test { assertEq(fiatTokenV2.version(), "2"); } - function testName() public { - assertEq(fiatTokenV2.name(), tokenName); - } - - function testSymbol() public { - assertEq(fiatTokenV2.symbol(), tokenSymbol); - } - - function testDecimals() public { - assertEq(fiatTokenV2.decimals(), 6); - } - - function testBalanceOf() public { - assertEq(fiatTokenV2.balanceOf(owner), 0); - vm.prank(minter); - fiatTokenV2.mint(owner, 100); - assertEq(fiatTokenV2.balanceOf(owner), 100); - } - - function testTotalSupply() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(owner, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - } - - function testApprove() public { - assertEq(fiatTokenV2.allowance(owner, unauthorized), 0); - vm.prank(owner); - fiatTokenV2.approve(unauthorized, 100); - assertEq(fiatTokenV2.allowance(owner, unauthorized), 100); - } - - function testMint() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(owner, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(owner), 100); - } - - function testMintUnauthorized() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.MINTER_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.mint(owner, 100); - } - - function testMintAboveCap() public { - assertEq(fiatTokenV2.cap(), 1e30); - assertEq(fiatTokenV2.totalSupply(), 0); - vm.expectRevert(abi.encodeWithSelector(ERC20CappedUpgradeable.ERC20ExceededCap.selector, 1e31, 1e30)); - vm.prank(minter); - fiatTokenV2.mint(owner, 1e31); - - // nothing minted - assertEq(fiatTokenV2.totalSupply(), 0); - } - - function testBurn() public { + function testBurnByBurner() public { assertEq(fiatTokenV2.totalSupply(), 0); vm.prank(minter); fiatTokenV2.mint(burner, 100); assertEq(fiatTokenV2.totalSupply(), 100); assertEq(fiatTokenV2.balanceOf(burner), 100); vm.prank(burner); - fiatTokenV2.burnBurner(100); + fiatTokenV2.burnByBurner(100); assertEq(fiatTokenV2.totalSupply(), 0); assertEq(fiatTokenV2.balanceOf(burner), 0); } - function testBurnMustBeLessThanBalance() public { + function testBurnByBurnerMustBeLessThanBalance() public { assertEq(fiatTokenV2.totalSupply(), 0); vm.prank(minter); fiatTokenV2.mint(burner, 100); @@ -201,10 +102,10 @@ contract FiatTokenV2Test is Test { ) ); vm.prank(burner); - fiatTokenV2.burnBurner(101); + fiatTokenV2.burnByBurner(101); } - function testBurnUnauthorized() public { + function testBurnByBurnerUnauthorized() public { assertEq(fiatTokenV2.totalSupply(), 0); vm.prank(minter); fiatTokenV2.mint(owner, 100); @@ -218,175 +119,18 @@ contract FiatTokenV2Test is Test { ) ); vm.prank(unauthorized); - fiatTokenV2.burnBurner(100); - } - - function testBurnFrom() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(unauthorized, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(unauthorized), 100); - vm.prank(unauthorized); - // needs to be burner as only account with BURNER_ROLE allowed to burn - fiatTokenV2.approve(burner, 100); - assertEq(fiatTokenV2.allowance(unauthorized, burner), 100); - vm.prank(burner); - fiatTokenV2.burnFromBurner(unauthorized, 100); - assertEq(fiatTokenV2.totalSupply(), 0); - assertEq(fiatTokenV2.balanceOf(unauthorized), 0); - } - - function testTransfer() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(minter), 100); - vm.prank(minter); - fiatTokenV2.transfer(unauthorized, 100); - assertEq(fiatTokenV2.balanceOf(minter), 0); - assertEq(fiatTokenV2.balanceOf(unauthorized), 100); - } - - function testTransferMustBeAtLeaseBalance() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(minter), 100); - vm.expectRevert( - abi.encodeWithSelector( - IERC20Errors.ERC20InsufficientBalance.selector, - minter, // from - 100, // fromBalance - 101 // value - ) - ); - vm.prank(minter); - fiatTokenV2.transfer(unauthorized, 101); - } - - function testTransferCannotBeToZeroAddress() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(minter), 100); - vm.expectRevert( - abi.encodeWithSelector( - IERC20Errors.ERC20InvalidReceiver.selector, - address(0) // to - ) - ); - vm.prank(minter); - fiatTokenV2.transfer(address(0), 100); - } - - function testTransferFrom() public { - assertEq(fiatTokenV2.totalSupply(), 0); - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - assertEq(fiatTokenV2.totalSupply(), 100); - assertEq(fiatTokenV2.balanceOf(minter), 100); - vm.prank(minter); - fiatTokenV2.approve(unauthorized, 100); - vm.prank(unauthorized); - fiatTokenV2.transferFrom(minter, unauthorized, 100); - assertEq(fiatTokenV2.balanceOf(minter), 0); - assertEq(fiatTokenV2.balanceOf(unauthorized), 100); + fiatTokenV2.burnByBurner(100); } - function testPause() public { + function testBurnerPause() public { assertEq(fiatTokenV2.paused(), false); vm.prank(pauser); fiatTokenV2.pause(); assertEq(fiatTokenV2.paused(), true); - // when contract is paused, not allowed to mint, burn, or transfer - vm.expectRevert(Pausable.EnforcedPause.selector); - vm.prank(minter); - fiatTokenV2.mint(owner, 100); - vm.expectRevert(Pausable.EnforcedPause.selector); - vm.prank(minter); - fiatTokenV2.transfer(unauthorized, 100); + // when contract is paused, not allowed to burn vm.expectRevert(Pausable.EnforcedPause.selector); vm.prank(burner); - fiatTokenV2.burnBurner(100); - } - - function testPauseUnauthorized() public { - assertEq(fiatTokenV2.paused(), false); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.PAUSER_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.pause(); - } - - function testUnpause() public { - assertEq(fiatTokenV2.paused(), false); - vm.prank(pauser); - fiatTokenV2.pause(); - assertEq(fiatTokenV2.paused(), true); - vm.prank(pauser); - fiatTokenV2.unpause(); - assertEq(fiatTokenV2.paused(), false); - } - - function testUnpauseUnauthorized() public { - assertEq(fiatTokenV2.paused(), false); - vm.prank(pauser); - fiatTokenV2.pause(); - assertEq(fiatTokenV2.paused(), true); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.PAUSER_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.unpause(); - } - - function testPaused() public { - assertEq(fiatTokenV2.paused(), false); - vm.prank(pauser); - fiatTokenV2.pause(); - assertEq(fiatTokenV2.paused(), true); - } - - function testRescue() public { - Ramen gld = new Ramen(100); - gld.transfer(address(fiatTokenV2), 100); - assertEq(gld.balanceOf(address(fiatTokenV2)), 100); - vm.prank(rescuer); - fiatTokenV2.rescue(gld, unauthorized, 100); - assertEq(gld.balanceOf(address(fiatTokenV2)), 0); - } - - function testBlacklistMinter() public { - // mint tokens to minter account - // for simplicity, we blacklist the minter account since it has permissions to transfer and mint - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - assertEq(fiatTokenV2.balanceOf(minter), 100); - - // blacklist minter account - assertEq(fiatTokenV2.isBlacklisted(minter), false); - vm.expectEmit(); - emit Blacklisted(minter); - vm.prank(blacklister); - fiatTokenV2.blacklist(minter); - assertEq(fiatTokenV2.isBlacklisted(minter), true); - // once blacklisted, not allowed to transfer - vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, minter)); - vm.prank(minter); - fiatTokenV2.transfer(minter, 100); - // not allowed to mint - vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, minter)); - vm.prank(minter); - fiatTokenV2.mint(minter, 100); + fiatTokenV2.burnByBurner(100); } function testBlacklistBurner() public { @@ -410,31 +154,7 @@ contract FiatTokenV2Test is Test { // not allowed to burn vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, burner)); vm.prank(burner); - fiatTokenV2.burnBurner(100); - } - - function testUnblacklistMinter() public { - // blacklist minter account - assertEq(fiatTokenV2.isBlacklisted(minter), false); - vm.expectEmit(); - emit Blacklisted(minter); - vm.prank(blacklister); - fiatTokenV2.blacklist(minter); - assertEq(fiatTokenV2.isBlacklisted(minter), true); - // unblacklist minter account - vm.expectEmit(); - emit UnBlacklisted(minter); - vm.prank(blacklister); - fiatTokenV2.unBlacklist(minter); - assertEq(fiatTokenV2.isBlacklisted(minter), false); - // once unblacklisted, allowed to mint - vm.prank(minter); - fiatTokenV2.mint(minter, 100); - // allowed to transfer - vm.prank(minter); - fiatTokenV2.transfer(unauthorized, 100); - // no balance left after transferring and burning - assertEq(fiatTokenV2.balanceOf(minter), 0); + fiatTokenV2.burnByBurner(100); } function testUnblacklistBurner() public { @@ -456,46 +176,13 @@ contract FiatTokenV2Test is Test { assertEq(fiatTokenV2.isBlacklisted(burner), false); // once unblacklisted, allowed to burn vm.prank(burner); - fiatTokenV2.burnBurner(100); + fiatTokenV2.burnByBurner(100); // no balance left after transferring and burning assertEq(fiatTokenV2.balanceOf(burner), 0); } - function testBlacklistUnauthorized() public { - assertEq(fiatTokenV2.isBlacklisted(minter), false); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.BLACKLISTER_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.blacklist(minter); - } - - function testUnblacklistUnauthorized() public { - assertEq(fiatTokenV2.isBlacklisted(minter), false); - vm.prank(blacklister); - fiatTokenV2.blacklist(minter); - assertEq(fiatTokenV2.isBlacklisted(minter), true); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.BLACKLISTER_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.unBlacklist(minter); - } - // Access control - function testGrantRole() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); - vm.prank(defaultAdmin); - fiatTokenV2.grantRole(upgraderRole, unauthorized); - assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), true); - } - function testGrantBurnerRole() public { bytes32 burnerRole = fiatTokenV2.BURNER_ROLE(); assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), false); @@ -504,94 +191,57 @@ contract FiatTokenV2Test is Test { assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), true); } - function testGrantRoleUnauthorized() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + function testRevokeBurnerRole() public { + bytes32 burnerRole = fiatTokenV2.BURNER_ROLE(); + vm.prank(defaultAdmin); + fiatTokenV2.grantRole(burnerRole, unauthorized); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), true); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.DEFAULT_ADMIN_ROLE() - ) - ); - vm.prank(unauthorized); - fiatTokenV2.grantRole(upgraderRole, unauthorized); - assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); + vm.prank(defaultAdmin); + fiatTokenV2.revokeRole(burnerRole, unauthorized); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), false); } - function testHasRole() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, unauthorized), false); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); - } + function testRenounceBurnerRole() public { + bytes32 burnerRole = fiatTokenV2.BURNER_ROLE(); + vm.prank(defaultAdmin); + fiatTokenV2.grantRole(burnerRole, unauthorized); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), true); - function testGetRoleAdmin() public { - assertEq(fiatTokenV2.getRoleAdmin(fiatTokenV2.UPGRADER_ROLE()), fiatTokenV2.DEFAULT_ADMIN_ROLE()); + vm.prank(unauthorized); // caller needs to be the one renouncing their own role + fiatTokenV2.renounceRole(burnerRole, unauthorized); + assertEq(fiatTokenV2.hasRole(burnerRole, unauthorized), false); } - function testRevokeRole() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); - vm.prank(defaultAdmin); - fiatTokenV2.revokeRole(upgraderRole, upgrader); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), false); - } + // Upgradeability - function testRevokeRoleUnauthorized() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); - vm.expectRevert( - abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, unauthorized, fiatTokenV2.DEFAULT_ADMIN_ROLE() + function testUpgradeToAndCall() public { + // new implementation contract + FiatTokenV1 fiatTokenV1 = new FiatTokenV1(); + vm.prank(trustedAddress); + ERC1967Proxy proxyV1 = new ERC1967Proxy( + address(fiatTokenV1), + abi.encodeCall( + fiatTokenV1.initialize, + (defaultAdmin, pauser, minter, upgrader, rescuer, blacklister, tokenName, tokenSymbol) ) ); - vm.prank(unauthorized); - fiatTokenV2.revokeRole(upgraderRole, unauthorized); - } - function testRenounceRole() public { - bytes32 upgraderRole = fiatTokenV2.UPGRADER_ROLE(); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), true); - vm.prank(upgrader); // caller needs to be the one renouncing their own role - fiatTokenV2.renounceRole(upgraderRole, upgrader); - assertEq(fiatTokenV2.hasRole(upgraderRole, upgrader), false); - } + // Attach the FiatTokenV1 interface to the deployed proxy + fiatTokenV1 = FiatTokenV1(address(proxyV1)); - // Upgradeability + FiatTokenV2 fiatTokenV2New = new FiatTokenV2(); + address newImplementationAddress = address(fiatTokenV2New); + assertEq(fiatTokenV1.version(), "1"); + assertEq(fiatTokenV2New.version(), "2"); - function testUpgradeToAndCall() public { - // new implementation contract - FiatTokenV99 fiatTokenV99 = new FiatTokenV99(); - address newImplementationAddress = address(fiatTokenV99); - assertEq(fiatTokenV99.version(), "99"); - assertEq(fiatTokenV2.version(), "2"); // upgrade contract vm.prank(upgrader); - fiatTokenV2.upgradeToAndCall(newImplementationAddress, ""); - address updatedImplementationAddress = Upgrades.getImplementationAddress(address(proxy)); + fiatTokenV1.upgradeToAndCall(newImplementationAddress, ""); + address updatedImplementationAddress = Upgrades.getImplementationAddress(address(proxyV1)); // verify implementation address is updated assertEq(newImplementationAddress, updatedImplementationAddress); // verify version() function implementation is updated - assertEq(fiatTokenV2.version(), "99"); - } - - // Trusted addresses - - function testAddTrustedAddress() public { - assertEq(fiatTokenV2.isTrustedAddress(unauthorized), false); - vm.prank(defaultAdmin); - fiatTokenV2.addTrustedAddress(unauthorized); - assertEq(fiatTokenV2.isTrustedAddress(unauthorized), true); - } - - function testRemoveTrustedAddress() public { - assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), true); - vm.prank(defaultAdmin); - fiatTokenV2.removeTrustedAddress(trustedAddress); - assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), false); - } - - function testIsTrustedAddress() public { - assertEq(fiatTokenV2.isTrustedAddress(trustedAddress), true); - assertEq(fiatTokenV2.isTrustedAddress(unauthorized), false); + assertEq(fiatTokenV1.version(), "2"); } } From 329ed3c0dd425ea7ece100ae859296fa333b13e9 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Thu, 5 Sep 2024 11:38:15 +0800 Subject: [PATCH 07/10] minor: update gas-snapshot --- .gas-snapshot | 55 +++++++++++---------------------------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 1a94b7e..3a0c593 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -41,48 +41,15 @@ FiatTokenV1Test:testUnpause() (gas: 35210) FiatTokenV1Test:testUnpauseUnauthorized() (gas: 52752) FiatTokenV1Test:testUpgradeToAndCall() (gas: 2336296) FiatTokenV1Test:testVersion() (gas: 11934) -FiatTokenV2Test:testAddTrustedAddress() (gas: 45693) -FiatTokenV2Test:testApprove() (gas: 46131) -FiatTokenV2Test:testBalanceOf() (gas: 79093) -FiatTokenV2Test:testBlacklistBurner() (gas: 120181) -FiatTokenV2Test:testBlacklistMinter() (gas: 116535) -FiatTokenV2Test:testBlacklistUnauthorized() (gas: 26275) -FiatTokenV2Test:testBurn() (gas: 68360) -FiatTokenV2Test:testBurnFrom() (gas: 93612) -FiatTokenV2Test:testBurnMustBeLessThanBalance() (gas: 86595) -FiatTokenV2Test:testBurnUnauthorized() (gas: 88699) -FiatTokenV2Test:testDecimals() (gas: 10462) -FiatTokenV2Test:testGetRoleAdmin() (gas: 15008) -FiatTokenV2Test:testGrantBurnerRole() (gas: 51990) -FiatTokenV2Test:testGrantRole() (gas: 51955) -FiatTokenV2Test:testGrantRoleUnauthorized() (gas: 29719) -FiatTokenV2Test:testHasRole() (gas: 22200) -FiatTokenV2Test:testInitializedRoles() (gas: 57974) -FiatTokenV2Test:testIsTrustedAddress() (gas: 20707) -FiatTokenV2Test:testMint() (gas: 80029) -FiatTokenV2Test:testMintAboveCap() (gas: 80683) -FiatTokenV2Test:testMintUnauthorized() (gas: 25860) -FiatTokenV2Test:testName() (gas: 17382) -FiatTokenV2Test:testPause() (gas: 79157) -FiatTokenV2Test:testPauseUnauthorized() (gas: 23834) -FiatTokenV2Test:testPaused() (gas: 44256) -FiatTokenV2Test:testRemoveTrustedAddress() (gas: 23721) -FiatTokenV2Test:testRenounceRole() (gas: 23448) -FiatTokenV2Test:testRescue() (gas: 524582) -FiatTokenV2Test:testRevokeRole() (gas: 30015) -FiatTokenV2Test:testRevokeRoleUnauthorized() (gas: 29844) -FiatTokenV2Test:testSymbol() (gas: 17371) -FiatTokenV2Test:testTotalSupply() (gas: 78407) -FiatTokenV2Test:testTransfer() (gas: 93174) -FiatTokenV2Test:testTransferCannotBeToZeroAddress() (gas: 81041) -FiatTokenV2Test:testTransferFrom() (gas: 108184) -FiatTokenV2Test:testTransferMustBeAtLeaseBalance() (gas: 86566) -FiatTokenV2Test:testUnauthorizedInitialization() (gas: 2543113) -FiatTokenV2Test:testUnblacklistBurner() (gas: 95846) -FiatTokenV2Test:testUnblacklistMinter() (gas: 113440) -FiatTokenV2Test:testUnblacklistUnauthorized() (gas: 55710) -FiatTokenV2Test:testUnpause() (gas: 35247) -FiatTokenV2Test:testUnpauseUnauthorized() (gas: 52884) -FiatTokenV2Test:testUpgradeToAndCall() (gas: 2412584) -FiatTokenV2Test:testVersion() (gas: 11956) +FiatTokenV2Test:testBlacklistBurner() (gas: 119873) +FiatTokenV2Test:testBurnByBurner() (gas: 68300) +FiatTokenV2Test:testBurnByBurnerMustBeLessThanBalance() (gas: 86473) +FiatTokenV2Test:testBurnByBurnerUnauthorized() (gas: 88516) +FiatTokenV2Test:testBurnerPause() (gas: 55503) +FiatTokenV2Test:testGrantBurnerRole() (gas: 51863) +FiatTokenV2Test:testRenounceBurnerRole() (gas: 41034) +FiatTokenV2Test:testRevokeBurnerRole() (gas: 41436) +FiatTokenV2Test:testUnblacklistBurner() (gas: 95652) +FiatTokenV2Test:testUpgradeToAndCall() (gas: 5104336) +FiatTokenV2Test:testVersion() (gas: 12000) RescuableV1Test:testRescue() (gas: 42236) \ No newline at end of file From 82aed5d3eeda573541144269b9b9b62caaa47a9b Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Mon, 9 Sep 2024 10:47:14 +0800 Subject: [PATCH 08/10] minor: update function name to burnByBurnerOnly --- src/v2/FiatTokenV2.sol | 4 +++- test/v2/FiatTokenV2.t.sol | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/v2/FiatTokenV2.sol b/src/v2/FiatTokenV2.sol index 5a93743..7aaf04d 100644 --- a/src/v2/FiatTokenV2.sol +++ b/src/v2/FiatTokenV2.sol @@ -7,7 +7,9 @@ import "../v1/FiatTokenV1.sol"; contract FiatTokenV2 is FiatTokenV1 { bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - function burnByBurner(uint256 value) public onlyRole(BURNER_ROLE) { + // burnByBurnerOnly is a token burn operation that can only be called by BURNER_ROLE + // this is to separate the operation from MINTER_ROLE that can call mint and burn function + function burnByBurnerOnly(uint256 value) public virtual onlyRole(BURNER_ROLE) { _burn(_msgSender(), value); } diff --git a/test/v2/FiatTokenV2.t.sol b/test/v2/FiatTokenV2.t.sol index f2934bc..bc8cf5e 100644 --- a/test/v2/FiatTokenV2.t.sol +++ b/test/v2/FiatTokenV2.t.sol @@ -82,7 +82,7 @@ contract FiatTokenV2Test is Test { assertEq(fiatTokenV2.totalSupply(), 100); assertEq(fiatTokenV2.balanceOf(burner), 100); vm.prank(burner); - fiatTokenV2.burnByBurner(100); + fiatTokenV2.burnByBurnerOnly(100); assertEq(fiatTokenV2.totalSupply(), 0); assertEq(fiatTokenV2.balanceOf(burner), 0); } @@ -102,7 +102,7 @@ contract FiatTokenV2Test is Test { ) ); vm.prank(burner); - fiatTokenV2.burnByBurner(101); + fiatTokenV2.burnByBurnerOnly(101); } function testBurnByBurnerUnauthorized() public { @@ -119,7 +119,7 @@ contract FiatTokenV2Test is Test { ) ); vm.prank(unauthorized); - fiatTokenV2.burnByBurner(100); + fiatTokenV2.burnByBurnerOnly(100); } function testBurnerPause() public { @@ -130,7 +130,7 @@ contract FiatTokenV2Test is Test { // when contract is paused, not allowed to burn vm.expectRevert(Pausable.EnforcedPause.selector); vm.prank(burner); - fiatTokenV2.burnByBurner(100); + fiatTokenV2.burnByBurnerOnly(100); } function testBlacklistBurner() public { @@ -154,7 +154,7 @@ contract FiatTokenV2Test is Test { // not allowed to burn vm.expectRevert(abi.encodeWithSelector(CallerBlacklisted.selector, burner)); vm.prank(burner); - fiatTokenV2.burnByBurner(100); + fiatTokenV2.burnByBurnerOnly(100); } function testUnblacklistBurner() public { @@ -176,7 +176,7 @@ contract FiatTokenV2Test is Test { assertEq(fiatTokenV2.isBlacklisted(burner), false); // once unblacklisted, allowed to burn vm.prank(burner); - fiatTokenV2.burnByBurner(100); + fiatTokenV2.burnByBurnerOnly(100); // no balance left after transferring and burning assertEq(fiatTokenV2.balanceOf(burner), 0); } From f71e28d084496deb22acac4cab3f1879aac27730 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Mon, 9 Sep 2024 10:53:03 +0800 Subject: [PATCH 09/10] minor: update log message in UpgradeFiatToken --- script/UpgradeFiatToken.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/UpgradeFiatToken.s.sol b/script/UpgradeFiatToken.s.sol index cfcb55a..d07a99a 100644 --- a/script/UpgradeFiatToken.s.sol +++ b/script/UpgradeFiatToken.s.sol @@ -16,7 +16,7 @@ contract UpgradeFiatToken is Script { opts.referenceContract = "FiatTokenV1.sol"; // upgrade contract Upgrades.upgradeProxy(fiatTokenProxyAddress, "FiatTokenV2.sol", "", opts); - console2.log("FiatTokenV1 upgraded to FiatTokenV2"); + console2.log("FiatToken upgrade successful"); vm.stopBroadcast(); } } From c72bab630cda416d13f81d5b252cc064505bb7c6 Mon Sep 17 00:00:00 2001 From: Stanley Gunawan Date: Mon, 9 Sep 2024 11:08:43 +0800 Subject: [PATCH 10/10] minor: update gas-snapshot --- .gas-snapshot | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 3a0c593..9e67cb6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -41,15 +41,15 @@ FiatTokenV1Test:testUnpause() (gas: 35210) FiatTokenV1Test:testUnpauseUnauthorized() (gas: 52752) FiatTokenV1Test:testUpgradeToAndCall() (gas: 2336296) FiatTokenV1Test:testVersion() (gas: 11934) -FiatTokenV2Test:testBlacklistBurner() (gas: 119873) -FiatTokenV2Test:testBurnByBurner() (gas: 68300) -FiatTokenV2Test:testBurnByBurnerMustBeLessThanBalance() (gas: 86473) -FiatTokenV2Test:testBurnByBurnerUnauthorized() (gas: 88516) -FiatTokenV2Test:testBurnerPause() (gas: 55503) -FiatTokenV2Test:testGrantBurnerRole() (gas: 51863) -FiatTokenV2Test:testRenounceBurnerRole() (gas: 41034) -FiatTokenV2Test:testRevokeBurnerRole() (gas: 41436) -FiatTokenV2Test:testUnblacklistBurner() (gas: 95652) -FiatTokenV2Test:testUpgradeToAndCall() (gas: 5104336) -FiatTokenV2Test:testVersion() (gas: 12000) +FiatTokenV2Test:testBlacklistBurner() (gas: 120114) +FiatTokenV2Test:testBurnByBurner() (gas: 68598) +FiatTokenV2Test:testBurnByBurnerMustBeLessThanBalance() (gas: 86736) +FiatTokenV2Test:testBurnByBurnerUnauthorized() (gas: 88757) +FiatTokenV2Test:testBurnerPause() (gas: 55480) +FiatTokenV2Test:testGrantBurnerRole() (gas: 52173) +FiatTokenV2Test:testRenounceBurnerRole() (gas: 41264) +FiatTokenV2Test:testRevokeBurnerRole() (gas: 41667) +FiatTokenV2Test:testUnblacklistBurner() (gas: 95862) +FiatTokenV2Test:testUpgradeToAndCall() (gas: 5104270) +FiatTokenV2Test:testVersion() (gas: 11978) RescuableV1Test:testRescue() (gas: 42236) \ No newline at end of file