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/IOikos.sol"; import "./interfaces/IExchanger.sol"; import "./interfaces/IIssuer.sol"; import "./interfaces/IEtherCollateraloUSD.sol"; contract Synth is Owned, IERC20, ExternStateToken, MixinResolver, ISynth { /* ========== STATE VARIABLES ========== */ // Currency key which identifies this Synth to the Oikos system bytes32 public currencyKey; uint8 public constant DECIMALS = 18; // Where fees are pooled in oUSD address public constant FEE_ADDRESS = 0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF; /* ========== ADDRESS RESOLVER CONFIGURATION ========== */ bytes32 private constant CONTRACT_SYSTEMSTATUS = "SystemStatus"; bytes32 private constant CONTRACT_OIKOS = "Oikos"; bytes32 private constant CONTRACT_EXCHANGER = "Exchanger"; bytes32 private constant CONTRACT_ISSUER = "Issuer"; bytes32 private constant CONTRACT_FEEPOOL = "FeePool"; bytes32 private constant CONTRACT_ETHERCOLLATERALOUSD = "EtherCollateraloUSD"; bytes32[24] internal addressesToCache = [ CONTRACT_SYSTEMSTATUS, CONTRACT_OIKOS, CONTRACT_EXCHANGER, CONTRACT_ISSUER, CONTRACT_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, addressesToCache) { 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 oUSD 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) { systemStatus().requireSynthActive(currencyKey); (, , 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) { systemStatus().requireSynthActive(currencyKey); (, , 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-oUSD synths are exchanged into oUSD via synthInitiatedExchange * notify feePool to record amount as fee paid to feePool */ function _transferToFeeAddress(address to, uint value) internal returns (bool) { uint amountInUSD; // oUSD can be transferred to FEE_ADDRESS directly if (currencyKey == "oUSD") { amountInUSD = value; super._internalTransfer(messageSender, to, value); } else { // else exchange synth into oUSD and send to FEE_ADDRESS amountInUSD = exchanger().exchange(messageSender, currencyKey, value, "oUSD", FEE_ADDRESS); } // Notify feePool to record oUSD to distribute as fees feePool().recordFeePaid(amountInUSD); return true; } // Allow oikos to issue a certain number of synths from an account. // forward call to _internalIssue function issue(address account, uint amount) external onlyInternalContracts { _internalIssue(account, amount); } // Allow oikos or another synth contract to burn a certain number of synths from an account. // forward call to _internalBurn 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 _emitTransfer(address account, uint amount) onlyOwner external { emitTransfer(address(0), account, amount); } function _emitIssued(address account, uint amount) onlyOwner external { 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 ========== */ function systemStatus() internal view returns (ISystemStatus) { return ISystemStatus(resolver.requireAndGetAddress(CONTRACT_SYSTEMSTATUS, "Missing SystemStatus address")); } function oikos() internal view returns (IOikos) { return IOikos(resolver.requireAndGetAddress(CONTRACT_OIKOS, "Missing Oikos address")); } function feePool() internal view returns (IFeePool) { return IFeePool(resolver.requireAndGetAddress(CONTRACT_FEEPOOL, "Missing FeePool address")); } function exchanger() internal view returns (IExchanger) { return IExchanger(resolver.requireAndGetAddress(CONTRACT_EXCHANGER, "Missing Exchanger address")); } function issuer() internal view returns (IIssuer) { return IIssuer(resolver.requireAndGetAddress(CONTRACT_ISSUER, "Missing Issuer address")); } function etherCollateraloUSD() internal view returns (IEtherCollateraloUSD) { return IEtherCollateraloUSD(resolver.requireAndGetAddress(CONTRACT_ETHERCOLLATERALOUSD, "Missing EtherCollateraloUSD address")); } 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 isOikos = msg.sender == address(oikos()); bool isFeePool = msg.sender == address(feePool()); bool isExchanger = msg.sender == address(exchanger()); bool isIssuer = msg.sender == address(issuer()); bool isEtherCollateraloUSD = msg.sender == address(etherCollateraloUSD()); require( isOikos || isFeePool || isExchanger || isIssuer || isEtherCollateraloUSD, "Only Oikos, FeePool, Exchanger, Issuer or EtherCollateraloUSD 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); } }