// SPDX-License-Identifier: MIT pragma solidity 0.6.11; import "./Interfaces/IZUSDToken.sol"; import "./Dependencies/SafeMath.sol"; import "./Dependencies/Ownable.sol"; import "./Dependencies/CheckContract.sol"; import "./Dependencies/console.sol"; import "./ZUSDTokenStorage.sol"; /** * * Based upon OpenZeppelin's ERC20 contract: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol * * and their EIP2612 (ERC20Permit / ERC712) functionality: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol * * * --- Functionality added specific to the ZUSDToken --- * * 1) Transfer protection: blacklist of addresses that are invalid recipients (i.e. core Zero contracts) in external * transfer() and transferFrom() calls. The purpose is to protect users from losing tokens by mistakenly sending ZUSD directly to a Zero * core contract, when they should rather call the right function. * * 2) sendToPool() and returnFromPool(): functions callable only Zero core contracts, which move ZUSD tokens between Zero <-> user. */ contract ZUSDToken is ZUSDTokenStorage, CheckContract, IZUSDToken, Ownable { using SafeMath for uint256; // --- Events --- event TroveManagerAddressChanged(address _troveManagerAddress); event StabilityPoolAddressChanged(address _newStabilityPoolAddress); event BorrowerOperationsAddressChanged(address _newBorrowerOperationsAddress); function initialize( address _troveManagerAddress, address _stabilityPoolAddress, address _borrowerOperationsAddress ) public virtual initializer onlyOwner { _initialize(_troveManagerAddress, _stabilityPoolAddress, _borrowerOperationsAddress); } function _initialize( address _troveManagerAddress, address _stabilityPoolAddress, address _borrowerOperationsAddress ) internal { checkContract(_troveManagerAddress); checkContract(_stabilityPoolAddress); checkContract(_borrowerOperationsAddress); troveManagerAddress = _troveManagerAddress; emit TroveManagerAddressChanged(_troveManagerAddress); stabilityPoolAddress = _stabilityPoolAddress; emit StabilityPoolAddressChanged(_stabilityPoolAddress); borrowerOperationsAddress = _borrowerOperationsAddress; emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress); bytes32 hashedName = keccak256(bytes(_NAME)); bytes32 hashedVersion = keccak256(bytes(_VERSION)); _HASHED_NAME = hashedName; _HASHED_VERSION = hashedVersion; _CACHED_CHAIN_ID = _chainID(); _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion); } // --- Functions for intra-Zero calls --- function mint(address _account, uint256 _amount) external override { _requireCallerIsBorrowerOperations(); _mint(_account, _amount); } function burn(address _account, uint256 _amount) external override { _requireCallerIsBOorTroveMorSP(); _burn(_account, _amount); } function sendToPool( address _sender, address _poolAddress, uint256 _amount ) external override { _requireCallerIsStabilityPool(); _transfer(_sender, _poolAddress, _amount); } function returnFromPool( address _poolAddress, address _receiver, uint256 _amount ) external override { _requireCallerIsTroveMorSP(); _transfer(_poolAddress, _receiver, _amount); } // --- External functions --- function totalSupply() external view override returns (uint256) { return _totalSupply; } function balanceOf(address account) external view override returns (uint256) { return _balances[account]; } function transfer(address recipient, uint256 amount) external override returns (bool) { _requireValidRecipient(recipient); _transfer(msg.sender, recipient, amount); return true; } function allowance(address owner, address spender) external view override returns (uint256) { return _allowances[owner][spender]; } function approve(address spender, uint256 amount) external override returns (bool) { _approve(msg.sender, spender, amount); return true; } function transferFrom( address sender, address recipient, uint256 amount ) external override returns (bool) { _requireValidRecipient(recipient); _transfer(sender, recipient, amount); _approve( sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance") ); return true; } function increaseAllowance(address spender, uint256 addedValue) external override returns (bool) { _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); return true; } function decreaseAllowance(address spender, uint256 subtractedValue) external override returns (bool) { _approve( msg.sender, spender, _allowances[msg.sender][spender].sub( subtractedValue, "ERC20: decreased allowance below zero" ) ); return true; } // --- EIP 2612 Functionality --- function domainSeparator() public view override returns (bytes32) { if (_chainID() == _CACHED_CHAIN_ID) { return _CACHED_DOMAIN_SEPARATOR; } else { return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); } } function permit( address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external override { require(deadline >= now, "ZUSD: expired deadline"); bytes32 digest = keccak256( abi.encodePacked( "\x19\x01", domainSeparator(), keccak256( abi.encode( _PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline ) ) ) ); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress == owner, "ZUSD: invalid signature"); _approve(owner, spender, amount); } function nonces(address owner) external view override returns (uint256) { // FOR EIP 2612 return _nonces[owner]; } // --- Internal operations --- function _chainID() private pure returns (uint256 chainID) { assembly { chainID := chainid() } } function _buildDomainSeparator( bytes32 typeHash, bytes32 name, bytes32 version ) private view returns (bytes32) { return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this))); } // --- Internal operations --- // Warning: sanity checks (for sender and recipient) should have been done before calling these internal functions function _transfer( address sender, address recipient, uint256 amount ) internal { assert(sender != address(0)); assert(recipient != address(0)); _balances[sender] = _balances[sender].sub( amount, "ERC20: transfer amount exceeds balance" ); _balances[recipient] = _balances[recipient].add(amount); emit Transfer(sender, recipient, amount); } function _mint(address account, uint256 amount) internal { assert(account != address(0)); _totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount); emit Transfer(address(0), account, amount); } function _burn(address account, uint256 amount) internal { assert(account != address(0)); _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); _totalSupply = _totalSupply.sub(amount); emit Transfer(account, address(0), amount); } function _approve( address owner, address spender, uint256 amount ) internal { assert(owner != address(0)); assert(spender != address(0)); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } // --- 'require' functions --- function _requireValidRecipient(address _recipient) internal view { require( _recipient != address(0) && _recipient != address(this), "ZUSD: Cannot transfer tokens directly to the ZUSD token contract or the zero address" ); } function _requireCallerIsBorrowerOperations() internal view { require( msg.sender == borrowerOperationsAddress, "ZUSDToken: Caller is not BorrowerOperations" ); } function _requireCallerIsBOorTroveMorSP() internal view { require( msg.sender == borrowerOperationsAddress || msg.sender == troveManagerAddress || msg.sender == stabilityPoolAddress, "ZUSD: Caller is neither BorrowerOperations nor TroveManager nor StabilityPool" ); } function _requireCallerIsStabilityPool() internal view { require(msg.sender == stabilityPoolAddress, "ZUSD: Caller is not the StabilityPool"); } function _requireCallerIsTroveMorSP() internal view { require( msg.sender == troveManagerAddress || msg.sender == stabilityPoolAddress, "ZUSD: Caller is neither TroveManager nor StabilityPool" ); } // --- Optional functions --- function name() external view override returns (string memory) { return _NAME; } function symbol() external view override returns (string memory) { return _SYMBOL; } function decimals() external view override returns (uint8) { return _DECIMALS; } function version() external view override returns (string memory) { return _VERSION; } function permitTypeHash() external view override returns (bytes32) { return _PERMIT_TYPEHASH; } }