diff --git a/packages/contracts-por/contracts/TrueCurrency.sol b/packages/contracts-por/contracts/TrueCurrency.sol index b064f660b..c795b24f6 100644 --- a/packages/contracts-por/contracts/TrueCurrency.sol +++ b/packages/contracts-por/contracts/TrueCurrency.sol @@ -86,6 +86,13 @@ 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"); + require(isBlacklisted[account] != _isBlacklisted, "TrueCurrency: blacklisted status would not change"); + + if (_isBlacklisted) { + blacklistedAmount += _balances[account]; + } else { + blacklistedAmount -= _balances[account]; + } isBlacklisted[account] = _isBlacklisted; emit Blacklisted(account, _isBlacklisted); } diff --git a/packages/contracts-por/contracts/common/ERC20.sol b/packages/contracts-por/contracts/common/ERC20.sol index 4b52bf193..b672f9e86 100644 --- a/packages/contracts-por/contracts/common/ERC20.sol +++ b/packages/contracts-por/contracts/common/ERC20.sol @@ -83,7 +83,7 @@ abstract contract ERC20 is ClaimableOwnable, Context, IERC20 { * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; + return _totalSupply - blacklistedAmount; } /** diff --git a/packages/contracts-por/contracts/common/ProxyStorage.sol b/packages/contracts-por/contracts/common/ProxyStorage.sol index 5de13cd74..7503ac461 100644 --- a/packages/contracts-por/contracts/common/ProxyStorage.sol +++ b/packages/contracts-por/contracts/common/ProxyStorage.sol @@ -61,6 +61,9 @@ contract ProxyStorage { address public chainReserveFeed; bool public proofOfReserveEnabled; + // Blacklisted accounts are excluded from total supply until they are un-blacklisted. + uint256 public blacklistedAmount; + /* Additionally, we have several keccak-based storage locations. * If you add more keccak-based storage mappings, such as mappings, you must document them here. * If the length of the keccak input is the same as an existing mapping, it is possible there could be a preimage collision. diff --git a/packages/contracts-por/test/TokenController.test.ts b/packages/contracts-por/test/TokenController.test.ts index 853205952..6a172c6cc 100644 --- a/packages/contracts-por/test/TokenController.test.ts +++ b/packages/contracts-por/test/TokenController.test.ts @@ -589,10 +589,21 @@ describe('TokenController', () => { describe('setBlacklisted', () => { it('sets blacklisted status for the account', async () => { + await token.connect(thirdWallet).transfer(otherWallet.address, parseEther('10')) + + const initialTotalSupply = await token.totalSupply() + await expect(controller.setBlacklisted(otherWallet.address, true)).to.emit(token, 'Blacklisted') .withArgs(otherWallet.address, true) + + const reducedTotalSupply = await token.totalSupply() + expect(reducedTotalSupply === initialTotalSupply - await token.balanceOf(otherWallet.address)) + await expect(controller.setBlacklisted(otherWallet.address, false)).to.emit(token, 'Blacklisted') .withArgs(otherWallet.address, false) + + const increasedTotalSupply = await token.totalSupply() + expect(increasedTotalSupply === initialTotalSupply) }) it('rejects when called by non owner', async () => {