pragma solidity ^0.5.16; // Inheritance import "./Owned.sol"; import "./ExternStateToken.sol"; import "./MixinResolver.sol"; import "./interfaces/ISynth.sol"; import "./interfaces/IERC20.sol"; // Internal references import "./interfaces/ISystemStatus.sol"; import "./interfaces/IFeePool.sol"; import "./interfaces/IExchanger.sol"; import "./interfaces/IIssuer.sol"; // https://docs.synthetix.io/contracts/source/contracts/synth contract Synth is Owned, IERC20, ExternStateToken, MixinResolver, ISynth { /* ========== STATE VARIABLES ========== */ // Currency key which identifies this Synth to the Synthetix system bytes32 public currencyKey; uint8 public constant DECIMALS = 18; // Where fees are pooled in sUSD address public constant FEE_ADDRESS = 0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus"; bytes32 private constant CONTRACT_EXCHANGER = "Exchanger"; bytes32 private constant CONTRACT_ISSUER = "Issuer"; bytes32 private constant CONTRACT_FEEPOOL = "FeePool"; /* ========== CONSTRUCTOR ========== */ constructor( address payable _proxy, TokenState _tokenState, string memory _tokenName, string memory _tokenSymbol, address _owner, bytes32 _currencyKey, uint _totalSupply, address _resolver ) public ExternStateToken(_proxy, _tokenState, _tokenName, _tokenSymbol, _totalSupply, DECIMALS, _owner) MixinResolver(_resolver) { require(_proxy != address(0), "_proxy cannot be 0"); require(_owner != address(0), "_owner cannot be 0"); currencyKey = _currencyKey; } /* ========== MUTATIVE FUNCTIONS ========== */ function transfer(address to, uint value) public optionalProxy returns (bool) { _ensureCanTransfer(messageSender, value); // transfers to FEE_ADDRESS will be exchanged into sUSD and recorded as fee if (to == FEE_ADDRESS) { return _transferToFeeAddress(to, value); } // transfers to 0x address will be burned if (to == address(0)) { return _internalBurn(messageSender, value); } return super._internalTransfer(messageSender, to, value); } function transferAndSettle(address to, uint value) public optionalProxy returns (bool) { // Exchanger.settle ensures synth is active (, , uint numEntriesSettled) = exchanger().settle(messageSender, currencyKey); // Save gas instead of calling transferableSynths uint balanceAfter = value; if (numEntriesSettled > 0) { balanceAfter = tokenState.balanceOf(messageSender); } // Reduce the value to transfer if balance is insufficient after reclaimed value = value > balanceAfter ? balanceAfter : value; return super._internalTransfer(messageSender, to, value); } function transferFrom( address from, address to, uint value ) public optionalProxy returns (bool) { _ensureCanTransfer(from, value); return _internalTransferFrom(from, to, value); } function transferFromAndSettle( address from, address to, uint value ) public optionalProxy returns (bool) { // Exchanger.settle() ensures synth is active (, , uint numEntriesSettled) = exchanger().settle(from, currencyKey); // Save gas instead of calling transferableSynths uint balanceAfter = value; if (numEntriesSettled > 0) { balanceAfter = tokenState.balanceOf(from); } // Reduce the value to transfer if balance is insufficient after reclaimed value = value >= balanceAfter ? balanceAfter : value; return _internalTransferFrom(from, to, value); } /** * @notice _transferToFeeAddress function * non-sUSD synths are exchanged into sUSD via synthInitiatedExchange * notify feePool to record amount as fee paid to feePool */ function _transferToFeeAddress(address to, uint value) internal returns (bool) { uint amountInUSD; // sUSD can be transferred to FEE_ADDRESS directly if (currencyKey == "sUSD") { amountInUSD = value; super._internalTransfer(messageSender, to, value); } else { // else exchange synth into sUSD and send to FEE_ADDRESS amountInUSD = exchanger().exchange(messageSender, currencyKey, value, "sUSD", FEE_ADDRESS); } // Notify feePool to record sUSD to distribute as fees feePool().recordFeePaid(amountInUSD); return true; } function issue(address account, uint amount) external onlyInternalContracts { _internalIssue(account, amount); } function burn(address account, uint amount) external onlyInternalContracts { _internalBurn(account, amount); } function _internalIssue(address account, uint amount) internal { tokenState.setBalanceOf(account, tokenState.balanceOf(account).add(amount)); totalSupply = totalSupply.add(amount); emitTransfer(address(0), account, amount); emitIssued(account, amount); } function _internalBurn(address account, uint amount) internal returns (bool) { tokenState.setBalanceOf(account, tokenState.balanceOf(account).sub(amount)); totalSupply = totalSupply.sub(amount); emitTransfer(account, address(0), amount); emitBurned(account, amount); return true; } // Allow owner to set the total supply on import. function setTotalSupply(uint amount) external optionalProxy_onlyOwner { totalSupply = amount; } /* ========== VIEWS ========== */ // Note: use public visibility so that it can be invoked in a subclass function resolverAddressesRequired() public view returns (bytes32[] memory addresses) { addresses = new bytes32[](4); addresses[0] = CONTRACT_SYSTEMSTATUS; addresses[1] = CONTRACT_EXCHANGER; addresses[2] = CONTRACT_ISSUER; addresses[3] = CONTRACT_FEEPOOL; } function systemStatus() internal view returns (ISystemStatus) { return ISystemStatus(requireAndGetAddress(CONTRACT_SYSTEMSTATUS)); } function feePool() internal view returns (IFeePool) { return IFeePool(requireAndGetAddress(CONTRACT_FEEPOOL)); } function exchanger() internal view returns (IExchanger) { return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER)); } function issuer() internal view returns (IIssuer) { return IIssuer(requireAndGetAddress(CONTRACT_ISSUER)); } function _ensureCanTransfer(address from, uint value) internal view { require(exchanger().maxSecsLeftInWaitingPeriod(from, currencyKey) == 0, "Cannot transfer during waiting period"); require(transferableSynths(from) >= value, "Insufficient balance after any settlement owing"); systemStatus().requireSynthActive(currencyKey); } function transferableSynths(address account) public view returns (uint) { (uint reclaimAmount, , ) = exchanger().settlementOwing(account, currencyKey); // Note: ignoring rebate amount here because a settle() is required in order to // allow the transfer to actually work uint balance = tokenState.balanceOf(account); if (reclaimAmount > balance) { return 0; } else { return balance.sub(reclaimAmount); } } /* ========== INTERNAL FUNCTIONS ========== */ function _internalTransferFrom( address from, address to, uint value ) internal returns (bool) { // Skip allowance update in case of infinite allowance if (tokenState.allowance(from, messageSender) != uint(-1)) { // Reduce the allowance by the amount we're transferring. // The safeSub call will handle an insufficient allowance. tokenState.setAllowance(from, messageSender, tokenState.allowance(from, messageSender).sub(value)); } return super._internalTransfer(from, to, value); } /* ========== MODIFIERS ========== */ modifier onlyInternalContracts() { bool isFeePool = msg.sender == address(feePool()); bool isExchanger = msg.sender == address(exchanger()); bool isIssuer = msg.sender == address(issuer()); require(isFeePool || isExchanger || isIssuer, "Only FeePool, Exchanger or Issuer contracts allowed"); _; } /* ========== EVENTS ========== */ event Issued(address indexed account, uint value); bytes32 private constant ISSUED_SIG = keccak256("Issued(address,uint256)"); function emitIssued(address account, uint value) internal { proxy._emit(abi.encode(value), 2, ISSUED_SIG, addressToBytes32(account), 0, 0); } event Burned(address indexed account, uint value); bytes32 private constant BURNED_SIG = keccak256("Burned(address,uint256)"); function emitBurned(address account, uint value) internal { proxy._emit(abi.encode(value), 2, BURNED_SIG, addressToBytes32(account), 0, 0); } }