pragma solidity ^0.5.0; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/ownership/Ownable.sol"; import "@openzeppelin/contracts/lifecycle/Pausable.sol"; import "./KToken.sol"; import "./interfaces/IInvoker.sol"; contract Invoker is IInvoker, ReentrancyGuard, Pausable, Ownable { using SafeMath for uint256; event Invocation(address invokeAddress, bytes32 invokeDataHash, address kTokenAddress, uint256 tokenAmount); event Reward(uint256 poolReward, uint256 platformReward, address tokenAddress); /* Registered kTokens */ mapping(address => address) internal _kTokenToToken; /* Reward (in bips) distributed to pool per transaction */ uint256 internal _poolRewardBips; /* Reward (in bips) distributed to platform per transaction */ uint256 internal _platformRewardBips; /* Address that collects the platform reward disbursements */ address internal _platformVaultAddress; /* Helper - store called token amount for retrieval */ uint256 internal _currentTokenAmount; /* Helper - store expected balance for currently executing transaction */ uint256 internal _currentExpectedBalance; /* Helper - store kToken address for currently executing transaction */ address internal _currentKTokenAddress; constructor () public { } function invoke(address invokeAddress, bytes calldata invokeData, address kTokenAddress, uint256 tokenAmount) external payable nonReentrant whenNotPaused { require(invokeAddress != address(this), "Invoker: cannot invoke this contract"); require(_kTokenToToken[invokeAddress] == address(0), "Invoker: cannot invoke an kToken contract"); require(_kTokenToToken[kTokenAddress] != address(0), "Invoker: kToken must been registered"); KToken kToken = KToken(kTokenAddress); /* Record starting and expected ending balance */ uint256 startingBalance = kToken.totalReserve(); _currentExpectedBalance = calculateExpectedBalance(startingBalance, tokenAmount); _currentKTokenAddress = kTokenAddress; _currentTokenAmount = tokenAmount; /* Transfer invocation amount of underlying token to caller's invocation address */ require(kToken.transferUnderlying(invokeAddress, tokenAmount), "Invoker: unable to transfer invocation amount"); /* Lock kToken to prevent interference */ kToken.lock(); /* Invoke caller's function */ (bool success, bytes memory returndata) = invokeAddress.call.value(msg.value)(invokeData); require(success, string(returndata)); emit Invocation(invokeAddress, keccak256(invokeData), kTokenAddress, tokenAmount); /* Unlock kToken to cleanup */ kToken.unlock(); /* Verify tokens were returned with correct reward */ uint256 endingBalance = kToken.totalReserve(); require(_currentExpectedBalance == endingBalance, "Invoker: incorrect ending balance"); /* Extract platform reward */ uint256 platformReward = calculatePlatformReward(tokenAmount); require(kToken.transferUnderlying(_platformVaultAddress, platformReward), "Invoker: unable to transfer platform reward"); emit Reward(calculatePoolReward(tokenAmount), platformReward, _kTokenToToken[kTokenAddress]); } function calculatePoolReward(uint256 tokenAmount) internal view returns (uint256) { return tokenAmount.mul(_poolRewardBips).div(10000); } function calculatePlatformReward(uint256 tokenAmount) internal view returns (uint256) { return tokenAmount.mul(_platformRewardBips).div(10000); } function calculateExpectedBalance(uint256 startingBalance, uint256 tokenAmount) internal view returns (uint256) { return startingBalance.add(calculatePoolReward(tokenAmount)).add(calculatePlatformReward(tokenAmount)); } function poolReward() external view returns (uint256) { return _poolRewardBips; } function platformReward() external view returns (uint256) { return _platformRewardBips; } /* Helper hook for invoked transaction */ function currentTokenAmount() external view returns (uint256) { return _currentTokenAmount; } function currentExpectedBalance() external view returns (uint256) { return _currentExpectedBalance; } function currentKTokenAddress() external view returns (address) { return _currentKTokenAddress; } function currentTokenAddress() external view returns (address) { return _kTokenToToken[_currentKTokenAddress]; } /* ADMIN FUNCTIONS */ function registerKToken(address kTokenAddress) external onlyOwner { KToken kToken = KToken(kTokenAddress); _kTokenToToken[kTokenAddress] = kToken.tokenAddress(); } function setPoolReward(uint256 poolRewardBips) external onlyOwner { _poolRewardBips = poolRewardBips; } function setPlatformReward(uint256 platformRewardBips) external onlyOwner { _platformRewardBips = platformRewardBips; } function setPlatformVaultAddress(address platformVaultAddress) external onlyOwner { _platformVaultAddress = platformVaultAddress; } }