GSNProxy.sol 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. pragma solidity ^0.5.8;
  2. // contract we {}
  3. import "./IUniswapExchange.sol";
  4. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  5. import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol";
  6. import "@openzeppelin/contracts-ethereum-package/contracts/GSN/IRelayHub.sol";
  7. import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol";
  8. contract IMixer {
  9. function withdraw(uint256[8] calldata proof, uint256[6] calldata input) external payable;
  10. function checkWithdrawalValidity(uint256[8] calldata proof, uint256[6] calldata input) external view;
  11. function denomination() external view returns(uint256);
  12. function token() external view returns(address); // only for ERC20 version
  13. }
  14. contract GSNProxy is GSNRecipient, Ownable {
  15. IMixer public mixer;
  16. IUniswapExchange public uniswap;
  17. IERC20 public token;
  18. constructor(address _mixer, address _uniswap) public {
  19. mixer = IMixer(_mixer);
  20. if (_uniswap != address(0)) {
  21. uniswap = IUniswapExchange(_uniswap);
  22. require(mixer.token() == uniswap.tokenAddress(), "mixer and uniswap have different tokens");
  23. token = IERC20(uniswap.tokenAddress());
  24. } else {
  25. // todo: require that mixer is ETH version?
  26. }
  27. }
  28. // Allow to refill mixer balance
  29. function () external payable {}
  30. modifier onlyHub() {
  31. require(msg.sender == getHubAddr(), "only relay hub");
  32. _;
  33. }
  34. /**
  35. @dev Checks fee and calls mixer withdraw
  36. */
  37. function withdraw(uint256[8] calldata proof, uint256[6] calldata input) external {
  38. mixer.withdraw.value(refund)(proof, input);
  39. // todo: check that we received expected fee?
  40. }
  41. // gsn related stuff
  42. // this func is called by a Relayer via the RelayerHub before sending a tx
  43. function acceptRelayedCall(
  44. address /*relay*/,
  45. address /*from*/,
  46. bytes memory encodedFunction,
  47. uint256 /*transactionFee*/,
  48. uint256 /*gasPrice*/,
  49. uint256 /*gasLimit*/,
  50. uint256 /*nonce*/,
  51. bytes memory /*approvalData*/,
  52. uint256 maxPossibleCharge
  53. ) public view returns (uint256, bytes memory) {
  54. // think of a withdraw dry-run
  55. if (!compareBytesWithSelector(encodedFunction, this.withdraw.selector)) {
  56. return (1, "Only withdrawViaRelayer can be called");
  57. }
  58. bytes memory proof;
  59. bytes memory root;
  60. uint256 fee;
  61. uint256 refund;
  62. assembly {
  63. let dataPointer := add(encodedFunction, 32)
  64. let nullifierPointer := mload(add(dataPointer, 4)) // 4 + (8 * 32) + (32) == selector + proof + root
  65. let recipientPointer := mload(add(dataPointer, 324)) // 4 + (8 * 32) + (32) + (32) == selector + proof + root + nullifier
  66. mstore(recipient, 64) // save array length
  67. mstore(add(recipient, 32), recipientPointer) // save recipient address
  68. mstore(add(recipient, 64), nullifierPointer) // save nullifier address
  69. }
  70. //mixer.checkWithdrawalValidity(proof, inputs)
  71. // todo: duplicate withdraw checks?
  72. if (token != IERC20(0)) {
  73. // todo maybe static exchange rate?
  74. if (uniswap.getTokenToEthInputPrice(fee) < maxPossibleCharge + refund) {
  75. return (11, "Fee is too low");
  76. }
  77. } else {
  78. // refund is expected to be 0, checked by mixer contract
  79. if (fee < maxPossibleCharge + refund) {
  80. return (11, "Fee is too low");
  81. }
  82. }
  83. if (mixer.checkWithdrawalValidity()) {
  84. }
  85. return _approveRelayedCall();
  86. }
  87. // this func is called by RelayerHub right before calling a target func
  88. function preRelayedCall(bytes calldata /*context*/) onlyHub external returns (bytes32) {}
  89. function postRelayedCall(bytes memory /*context*/, bool /*success*/, uint actualCharge, bytes32 /*preRetVal*/) onlyHub public {
  90. IRelayHub(getHubAddr()).depositFor.value(actualCharge)(address(this));
  91. }
  92. function compareBytesWithSelector(bytes memory data, bytes4 sel) internal pure returns (bool) {
  93. return data[0] == sel[0]
  94. && data[1] == sel[1]
  95. && data[2] == sel[2]
  96. && data[3] == sel[3];
  97. }
  98. // Admin functions
  99. function withdrawFundsFromHub(uint256 amount, address payable dest) onlyOwner external {
  100. IRelayHub(getHubAddr()).withdraw(amount, dest);
  101. }
  102. function upgradeRelayHub(address newRelayHub) onlyOwner external {
  103. _upgradeRelayHub(newRelayHub);
  104. }
  105. function withdrawEther(uint256 amount) onlyOwner external {
  106. msg.sender.transfer(amount);
  107. }
  108. function withdrawTokens(uint256 amount) onlyOwner external {
  109. safeErc20Transfer(msg.sender, amount);
  110. }
  111. function sellTokens(uint256 amount, uint256 min_eth) onlyOwner external {
  112. token.approve(address(uniswap), amount);
  113. uniswap.tokenToEthSwapInput(amount, min_eth, now);
  114. }
  115. function safeErc20Transfer(address to, uint256 amount) internal {
  116. bool success;
  117. bytes memory data;
  118. bytes4 transferSelector = 0xa9059cbb;
  119. (success, data) = address(token).call(
  120. abi.encodeWithSelector(
  121. transferSelector,
  122. to, amount
  123. )
  124. );
  125. require(success, "not enough tokens");
  126. // if contract returns some data let's make sure that is `true` according to standard
  127. if (data.length > 0) {
  128. assembly {
  129. success := mload(add(data, 0x20))
  130. }
  131. require(success, "not enough tokens. Token returns false.");
  132. }
  133. }
  134. }