diff --git a/packages/contracts-watr/contracts/TrueCurrency.sol b/packages/contracts-watr/contracts/TrueCurrency.sol index a55fc1209..ab8a262b8 100644 --- a/packages/contracts-watr/contracts/TrueCurrency.sol +++ b/packages/contracts-watr/contracts/TrueCurrency.sol @@ -96,6 +96,7 @@ abstract contract TrueCurrency is BurnableTokenWithBounds { */ function setBlacklisted(address account, bool _isBlacklisted) external override onlyOwner { require(uint256(account) >= REDEMPTION_ADDRESS_COUNT, "TrueCurrency: blacklisting of redemption address is not allowed"); + _setBlacklistedXC20(account, _isBlacklisted); isBlacklisted[account] = _isBlacklisted; emit Blacklisted(account, _isBlacklisted); } @@ -179,4 +180,15 @@ abstract contract TrueCurrency is BurnableTokenWithBounds { function isRedemptionAddress(address account) internal pure returns (bool) { return uint256(account) < REDEMPTION_ADDRESS_COUNT && account != address(0); } + + function _setBlacklistedXC20(address account, bool _isBlacklisted) internal { + if (isBlacklisted[account] == _isBlacklisted) { + return; + } + if (_isBlacklisted) { + _freeze(account); + } else { + _thaw(account); + } + } } diff --git a/packages/contracts-watr/contracts/common/XC20Wrapper.sol b/packages/contracts-watr/contracts/common/XC20Wrapper.sol index 2da1b7a70..9fdce63c0 100644 --- a/packages/contracts-watr/contracts/common/XC20Wrapper.sol +++ b/packages/contracts-watr/contracts/common/XC20Wrapper.sol @@ -7,17 +7,17 @@ import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ClaimableOwnable} from "./ClaimableOwnable.sol"; -import {IERC20Plus} from "../interface/IERC20Plus.sol"; +import {IMintableXC20} from "../interface/IMintableXC20.sol"; abstract contract XC20Wrapper is IERC20, ClaimableOwnable, Context { using SafeMath for uint256; function totalSupply() public view virtual override returns (uint256) { - return IERC20Plus(nativeToken).totalSupply(); + return IMintableXC20(nativeToken).totalSupply(); } function balanceOf(address account) external view virtual override returns (uint256) { - return IERC20Plus(nativeToken).balanceOf(account); + return IMintableXC20(nativeToken).balanceOf(account); } function transfer(address recipient, uint256 amount) external virtual override returns (bool) { @@ -54,7 +54,7 @@ abstract contract XC20Wrapper is IERC20, ClaimableOwnable, Context { } function decimals() public view virtual returns (uint8) { - return IERC20Plus(nativeToken).decimals(); + return IMintableXC20(nativeToken).decimals(); } function name() public pure virtual returns (string memory); @@ -63,12 +63,12 @@ abstract contract XC20Wrapper is IERC20, ClaimableOwnable, Context { function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "XC20: mint to the zero address"); - IERC20Plus(nativeToken).mint(account, amount); + IMintableXC20(nativeToken).mint(account, amount); emit Transfer(address(0), account, amount); } function _burn(address account, uint256 amount) internal virtual { - IERC20Plus(nativeToken).burn(account, amount); + IMintableXC20(nativeToken).burn(account, amount); emit Transfer(account, address(0), amount); } @@ -97,8 +97,16 @@ abstract contract XC20Wrapper is IERC20, ClaimableOwnable, Context { address recipient, uint256 amount ) internal virtual { - require(IERC20Plus(nativeToken).balanceOf(sender) >= amount, "XC20: amount exceeds balance"); - IERC20Plus(nativeToken).burn(sender, amount); - IERC20Plus(nativeToken).mint(recipient, amount); + require(IMintableXC20(nativeToken).balanceOf(sender) >= amount, "XC20: amount exceeds balance"); + IMintableXC20(nativeToken).burn(sender, amount); + IMintableXC20(nativeToken).mint(recipient, amount); + } + + function _freeze(address account) internal { + IMintableXC20(nativeToken).freeze(account); + } + + function _thaw(address account) internal { + IMintableXC20(nativeToken).thaw(account); } } diff --git a/packages/contracts-watr/contracts/interface/IMintableXC20.sol b/packages/contracts-watr/contracts/interface/IMintableXC20.sol new file mode 100644 index 000000000..a89476492 --- /dev/null +++ b/packages/contracts-watr/contracts/interface/IMintableXC20.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.10; + +import {IERC20Plus} from "./IERC20Plus.sol"; + +interface IMintableXC20 is IERC20Plus { + function freeze(address account) external returns (bool); + + function thaw(address account) external returns (bool); + + function freezeAsset() external returns (bool); + + function thawAsset() external returns (bool); +} diff --git a/packages/contracts-watr/contracts/mocks/MockXC20.sol b/packages/contracts-watr/contracts/mocks/MockXC20.sol index 9d03d387c..9f33b2ac3 100644 --- a/packages/contracts-watr/contracts/mocks/MockXC20.sol +++ b/packages/contracts-watr/contracts/mocks/MockXC20.sol @@ -6,6 +6,8 @@ contract MockXC20 { mapping(address => uint256) public balanceOf; uint8 public decimals; + mapping(address => bool) public frozen; + constructor(uint8 _decimals) public { decimals = _decimals; } @@ -24,4 +26,16 @@ contract MockXC20 { return true; } + + function freeze(address account) public returns (bool) { + frozen[account] = true; + + return true; + } + + function thaw(address account) public returns (bool) { + frozen[account] = false; + + return true; + } } diff --git a/packages/contracts-watr/test/TrueMintableBurnable.test.ts b/packages/contracts-watr/test/TrueMintableBurnable.test.ts index 2890b7d51..dd232ca01 100644 --- a/packages/contracts-watr/test/TrueMintableBurnable.test.ts +++ b/packages/contracts-watr/test/TrueMintableBurnable.test.ts @@ -1,7 +1,7 @@ import { expect, use } from 'chai' import { solidity } from 'ethereum-waffle' import { BigNumber, BigNumberish, constants, Wallet } from 'ethers' -import { MockXC20__factory, TrueUSD, TrueUSD__factory } from 'contracts' +import { MockXC20, MockXC20__factory, TrueUSD, TrueUSD__factory } from 'contracts' import { beforeEachWithFixture } from 'fixtures/beforeEachWithFixture' import { AddressZero, Zero } from '@ethersproject/constants' import { parseEther } from '@ethersproject/units' @@ -15,13 +15,14 @@ describe('TrueCurrency - Mint/Burn', () => { let owner: Wallet let initialHolder: Wallet let secondAccount: Wallet + let mockXC20: MockXC20 let token: TrueUSD const initialSupply = parseTrueUSD('1000') beforeEachWithFixture(async (wallets) => { [owner, initialHolder, secondAccount] = wallets - const mockXC20 = await new MockXC20__factory(owner).deploy(trueUSDDecimals) + mockXC20 = await new MockXC20__factory(owner).deploy(trueUSDDecimals) token = await new TrueUSD__factory(owner).deploy() await token.initialize(mockXC20.address) await token.connect(owner).mint(initialHolder.address, initialSupply) @@ -382,5 +383,16 @@ describe('TrueCurrency - Mint/Burn', () => { await expect(token.setBlacklisted(AddressZero, true)).to.be.revertedWith('TrueCurrency: blacklisting of redemption address is not allowed') }) }) + + describe('xc-20', () => { + it('calls "freeze" when blacklisting account', async () => { + expect(await mockXC20.frozen(blacklistedAccount.address)).to.be.true + }) + + it('calls "thaw" when de-blacklisting account', async () => { + await token.setBlacklisted(blacklistedAccount.address, false) + expect(await mockXC20.frozen(blacklistedAccount.address)).to.be.false + }) + }) }) }) diff --git a/packages/contracts-watr/test/verifyDeployment/verifyDeployment.test.ts b/packages/contracts-watr/test/verifyDeployment/verifyDeployment.test.ts index ad3397ef0..65ef7ffee 100644 --- a/packages/contracts-watr/test/verifyDeployment/verifyDeployment.test.ts +++ b/packages/contracts-watr/test/verifyDeployment/verifyDeployment.test.ts @@ -71,6 +71,14 @@ describe('verify deployment', () => { await expect(tx).to.changeTokenBalances(token, [deployer, toAccount(otherAddress)], [parseEther('-0.5'), parseEther('0.5')]) }).timeout(100000) + + it('can blacklist', async () => { + const deployer = new ethers.Wallet(process.env['PRIVATE_KEY_DEPLOYER'], provider) + const tokenController = TokenControllerV3__factory.connect(deployments.tokenControllerV3_proxy.address, deployer) + const otherAddress = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984' + + await waitFor(tokenController.setBlacklisted(otherAddress, true)) + }) }) async function waitFor(tx: Promise<{ wait: () => Promise }>) {