Skip to content

Instantly share code, notes, and snippets.

@k06a
Last active September 8, 2019 10:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save k06a/4c7da3296e6594c6481c7dbbd133838c to your computer and use it in GitHub Desktop.
Save k06a/4c7da3296e6594c6481c7dbbd133838c to your computer and use it in GitHub Desktop.
gDAI
pragma solidity ^0.5.0;
// TODO: Use latest git version with GSN support => "@openzeppelin/contracts@next"
import "github.com/openzeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/SafeERC20.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20Detailed.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/ownership/Ownable.sol";
import "github.com/openzeppelin/openzeppelin-contracts/contracts/GSN/GSNRecipient.sol";
contract IFulcrum is IERC20 {
function tokenPrice() external view returns (uint256 price);
function mint(address receiver, uint256 amount) external returns (uint256 minted);
function burn(address receiver, uint256 amount) external returns (uint256 burned);
}
contract IUniswap {
function getEthToTokenInputPrice(uint256 eth_sold) external view returns (uint256 tokens_bought);
}
interface IKyber {
function getExpectedRate(IERC20 src, IERC20 dest, uint srcQty) external view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(
IERC20 src,
uint srcAmount,
IERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId,
bytes calldata hint
) external payable returns (uint);
}
contract IGasToken is IERC20 {
function freeUpTo(uint256 value) external returns (uint256 freed);
}
contract GasDiscounter {
IGasToken public constant gasToken = IGasToken(0x0000000000b3F879cb30FE243b4Dfee438691c04);
modifier gasDiscount() {
uint256 initialGasLeft = gasleft();
_;
_makeGasDiscount(initialGasLeft - gasleft());
}
function _makeGasDiscount(uint256 gasSpent) internal {
uint256 tokens = (gasSpent + 14154) / 41130;
gasToken.freeUpTo(tokens);
}
}
contract EarnedInterestERC20 is ERC20 {
using SafeMath for uint256;
IFulcrum private fulcrum = IFulcrum(0x14094949152EDDBFcd073717200DA82fEd8dC960);
mapping(address => int256) public priceOf;
function earnedInterest(address user) public view returns (uint256) {
if (balanceOf(user) == 0) {
return uint256(priceOf[user]);
}
return balanceOf(user) * uint256(int256(fulcrum.tokenPrice()) - priceOf[user]) / 1e18;
}
function _setEarnedInteres(address user, uint256 earned) internal {
if (balanceOf(user) == 0) {
priceOf[user] = int256(earned);
return;
}
priceOf[user] = int256(fulcrum.tokenPrice()) - int256(earned * 1e18 / balanceOf(user));
}
function _transferEarnedInterestFirst(address from, address to, uint256 amount) internal {
uint256 earned = earnedInterest(from);
if (amount < earned) {
_setEarnedInteres(from, earned.sub(amount));
} else {
_setEarnedInteres(from, 0);
_burn(from, amount.sub(earned));
_mint(to, amount);
}
}
}
contract gDAI is Ownable, EarnedInterestERC20, ERC20Detailed, GasDiscounter, GSNRecipient {
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for IGasToken;
address public feeReceiver;
IERC20 public eth = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
IERC20 public dai = IERC20(0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359);
IFulcrum public fulcrum = IFulcrum(0x14094949152EDDBFcd073717200DA82fEd8dC960);
IKyber public kyber = IKyber(0x818E6FECD516Ecc3849DAf6845e3EC868087B755);
IUniswap public uniswap = IUniswap(0x09cabEC1eAd1c0Ba254B09efb3EE13841712bE14);
modifier compensateGas {
uint256 gasProvided = gasleft();
_;
if (_msgSender() == msg.sender) {
if (tx.origin == msg.sender) {
_makeGasDiscount(gasProvided.sub(gasleft()));
}
} else {
_compensateGas(gasProvided.sub(gasleft()));
}
}
constructor(
address _feeReceiver
) public ERC20Detailed("Gasless DAI", "gDAI", 18) {
feeReceiver = _feeReceiver;
dai.approve(address(fulcrum), uint256(- 1));
}
function setFeeReceiver(address _feeReceiver) public onlyOwner {
feeReceiver = _feeReceiver;
}
function() external payable {
if (msg.sender == owner()) {
_withdrawDeposits(IRelayHub(getHubAddr()).balanceOf(address(this)), msg.sender);
gasToken.safeTransfer(msg.sender, gasToken.balanceOf(address(this)));
}
// Allow get eth from subcalls only
require(msg.sender != tx.origin);
}
function preRelayedCall(bytes calldata /*context*/) external returns (bytes32) {
}
function postRelayedCall(bytes calldata /*context*/, bool /*success*/, uint /*actualCharge*/, bytes32 /*preRetVal*/) external {
}
function deposit(uint256 amount) public compensateGas {
uint256 earned = earnedInterest(_msgSender());
_mint(_msgSender(), amount);
dai.safeTransferFrom(_msgSender(), address(this), amount);
_putToFulcrum();
_setEarnedInteres(_msgSender(), earned);
}
function withdraw(uint256 amount) public compensateGas {
uint256 earned = earnedInterest(_msgSender());
_burn(_msgSender(), amount);
_getFromFulcrum(amount); // Get with extra
dai.safeTransfer(_msgSender(), amount);
// _putToFulcrum(); // Return some extra
_setEarnedInteres(_msgSender(), earned);
}
function transfer(address to, uint256 amount) public compensateGas returns (bool) {
return super.transfer(to, amount);
}
function transferFrom(address from, address to, uint256 amount) public compensateGas returns (bool) {
return super.transferFrom(from, to, amount);
}
function approve(address to, uint256 amount) public compensateGas returns (bool) {
return super.approve(to, amount);
}
function _transfer(address from, address to, uint256 amount) internal {
uint256 earnedFrom = earnedInterest(from);
uint256 earnedTo = earnedInterest(to);
super._transfer(from, to, amount);
_setEarnedInteres(from, earnedFrom);
_setEarnedInteres(to, earnedTo);
}
// Check user have enough balance for gas compensation and method is allowed (have modifiers)
function acceptRelayedCall(
address relay,
address from,
bytes memory encodedFunction,
uint256 transactionFee,
uint256 gasPrice,
uint256 gasLimit,
uint256 nonce,
bytes memory approvalData,
uint256 maxPossibleCharge
)
public
view
returns (uint256, bytes memory)
{
// Avoid "Stack too deep" error
address sender = from;
// Kyber prica discovery costs too many gas, so use Uniswap
uint256 ethPrice = uniswap.getEthToTokenInputPrice(maxPossibleCharge);
uint256 subAmount;
if (compareBytesWithSelector(encodedFunction, this.transfer.selector)) {
assembly {
subAmount := sload(add(encodedFunction, 68))
}
}
if (compareBytesWithSelector(encodedFunction, this.withdraw.selector)) {
assembly {
subAmount := sload(add(encodedFunction, 36))
}
}
if (balanceOf(sender).sub(subAmount).add(earnedInterest(sender)) < maxPossibleCharge.mul(1e18).div(ethPrice)) {
return (1, "Not enough gDAI to pay for Tx");
}
if (!compareBytesWithSelector(encodedFunction, this.transfer.selector) &&
!compareBytesWithSelector(encodedFunction, this.transferFrom.selector) &&
!compareBytesWithSelector(encodedFunction, this.approve.selector) &&
!compareBytesWithSelector(encodedFunction, this.deposit.selector) &&
!compareBytesWithSelector(encodedFunction, this.withdraw.selector))
{
return (2, "This gDAI function can't ba called via GSN");
}
return (0, "");
}
function compareBytesWithSelector(bytes memory data, bytes4 sel) internal pure returns (bool) {
return data[0] == sel[0]
&& data[1] == sel[1]
&& data[2] == sel[2]
&& data[3] == sel[3];
}
function _putToFulcrum() internal {
fulcrum.mint(address(this), dai.balanceOf(address(this)));
}
function _getFromFulcrum(uint256 amount) internal returns (uint256 actualAmount) {
uint256 iDAIAmount = amount.add(1e16).mul(fulcrum.tokenPrice()).div(1e18);
return fulcrum.burn(address(this), iDAIAmount);
}
function _compensateGas(uint256 gasSpent) internal {
(uint256 ethPrice,) = kyber.getExpectedRate(eth, dai, 10e18); // 10 DAI
uint256 actualCharge = tx.gasprice.mul(gasSpent.add(50000)); // +50K for _transfer
uint256 daiNeeded = actualCharge.mul(1e18).div(ethPrice);
if (balanceOf(address(this)).add(daiNeeded) < 10e18) { // less than 10 DAI
_transferEarnedInterestFirst(_msgSender(), address(this), daiNeeded);
return;
}
actualCharge = actualCharge.add(tx.gasprice.mul(500000)); // +500K for Kyber
daiNeeded = actualCharge.mul(1e18).div(ethPrice);
_transferEarnedInterestFirst(_msgSender(), address(this), daiNeeded);
uint256 daiExtracted = _getFromFulcrum(daiNeeded);
kyber.tradeWithHint(
dai,
daiExtracted,
eth,
address(this),
1 << 255,
1,
feeReceiver,
""
);
IRelayHub(getHubAddr()).depositFor.value(address(this).balance)(address(this));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment