pragma solidity ^0.5.0; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol"; import "@openzeppelin/contracts/lifecycle/Pausable.sol"; import "@openzeppelin/contracts/ownership/Ownable.sol"; import "./ExtendedMath.sol"; import "./interfaces/IKToken.sol"; contract KToken is IKToken, ERC20, ERC20Detailed, ReentrancyGuard, ExtendedMath, Ownable { using SafeMath for uint256; event Mint(address minter, uint256 tokenAmount, uint256 kTokenAmount); event Redeem(address redeemer, uint256 tokenAmount, uint256 kTokenAmount); address internal _tokenAddress; address internal _invokerAddress; uint256 internal _lock; constructor (address tokenAddress, address invokerAddress, string memory name, string memory symbol, uint8 decimals) internal ERC20Detailed(name, symbol, decimals) { _tokenAddress = tokenAddress; _invokerAddress = invokerAddress; _lock = 0; } /* * PUBLIC STATE CHANGING */ function redeem(uint256 kTokenAmount) external returns (bool) { require(totalSupply() > 0, "KToken: no supply"); return redeemInternal(kTokenAmountToTokenAmount(kTokenAmount), kTokenAmount); } function redeemUnderlying(uint256 tokenAmount) external returns (bool) { require(totalReserve() > 0, "KToken: no reserve"); return redeemInternal(tokenAmount, tokenAmountToKTokenAmountInternal(tokenAmount, true, false)); } function mintInternal(uint256 amount) internal whenUnlocked nonReentrant returns (bool) { uint256 kTokenAmount; if (totalReserve().sub(amount) > 0) { kTokenAmount = tokenAmountToKTokenAmountInternal(amount, false, true); } else { kTokenAmount = amount; } _mint(msg.sender, kTokenAmount); emit Mint(msg.sender, amount, kTokenAmount); return true; } function redeemInternal(uint256 tokenAmount, uint256 kTokenAmount) internal whenUnlocked nonReentrant returns (bool) { _burn(msg.sender, kTokenAmount); require(transferUnderlyingInternal(msg.sender, tokenAmount), "KToken: token transfer failed"); emit Redeem(msg.sender, tokenAmount, kTokenAmount); return true; } /* * INVOKER STATE-CHANGING HOOKS */ function transferUnderlying(address to, uint256 amount) external onlyInvoker whenUnlocked nonReentrant returns (bool) { return transferUnderlyingInternal(to, amount); } function transferUnderlyingInternal(address to, uint256 amount) internal returns (bool); /* Lock automatically clears after block in case of any unintended locking attacks */ function lock() external onlyInvoker whenUnlocked { _lock = block.number; } function unlock() external onlyInvoker whenLocked { _lock = 0; } /* Adds unlimited allowance functionality to standard transferFrom implementation */ function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { _transfer(sender, recipient, amount); if (allowance(sender, _msgSender()) != uint256(-1)) { _approve(sender, _msgSender(), allowance(sender, _msgSender()).sub(amount, "ERC20: transfer amount exceeds allowance")); } return true; } /* * VIEWS */ function tokenAmountToKTokenAmount(uint256 tokenAmount, bool ceiling) public view returns (uint256) { return tokenAmountToKTokenAmountInternal(tokenAmount, ceiling, false); } function tokenAmountToKTokenAmountInternal(uint256 tokenAmount, bool ceiling, bool alreadyDeposited) internal view returns (uint256) { if (totalReserve() == 0) { return 0; } /* mint() pulls in funds before calling mintInternal() - normalize for pre-funding amount */ uint256 adjustedTotalReserve = alreadyDeposited ? totalReserve() - tokenAmount: totalReserve(); /* round mint() down and redeemUnderlying() up to avoid over-ownership exploits */ return divAndRound(tokenAmount.mul(totalSupply()), adjustedTotalReserve, ceiling); } function kTokenAmountToTokenAmount(uint256 kTokenAmount) public view returns (uint256) { if (totalSupply() == 0) { return 0; } return kTokenAmount.mul(totalReserve()).div(totalSupply()); } function isKEther() public view returns (bool); function totalReserve() public view returns (uint256); function balanceOfUnderlying(address owner) public view returns (uint256) { return kTokenAmountToTokenAmount(balanceOf(owner)); } function tokenAddress() external view returns (address) { return _tokenAddress; } function invokerAddress() external view returns (address) { return _invokerAddress; } /* * MODIFIERS */ modifier onlyInvoker() { require( msg.sender == _invokerAddress && !Pausable(_invokerAddress).paused(), "KToken: only can be called by unpaused invoker"); _; } modifier whenLocked() { require(_lock >= block.number, "KToken: not locked"); _; } modifier whenUnlocked() { require(_lock < block.number, "KToken: locked"); _; } /* * ADMIN FUNCTIONS */ /* KToken ownership will be relinquished before public launch. */ function setInvokerAddress(address invokerAddress) external onlyOwner { _invokerAddress = invokerAddress; } }