Files
openzeppelin-contracts/contracts/GSN/bouncers/GSNBouncerERC20Fee.sol
Nicolás Venturo 0ec1d761aa Initial GSN support (beta) (#1844)
* Add base Context contract

* Add GSNContext and tests

* Add RelayHub deployment to tests

* Add RelayProvider integration, complete GSNContext tests

* Switch dependency to openzeppelin-gsn-provider

* Add default txfee to provider

* Add basic signing recipient

* Sign more values

* Add comment clarifying RelayHub's msg.data

* Make context constructors internal

* Rename SigningRecipient to GSNRecipientSignedData

* Add ERC20Charge recipients

* Harcode RelayHub address into GSNContext

* Fix Solidity linter errors

* Run server from binary, use gsn-helpers to fund it

* Migrate to published @openzeppelin/gsn-helpers

* Silence false-positive compiler warning

* Use GSN helper assertions

* Rename meta-tx to gsn, take out of drafts

* Merge ERC20 charge recipients into a single one

* Rename GSNRecipients to Bouncers

* Add GSNBouncerUtils to decouple the bouncers from GSNRecipient

* Add _upgradeRelayHub

* Store RelayHub address using unstructored storage

* Add IRelayHub

* Add _withdrawDeposits to GSNRecipient

* Add relayHub version to recipient

* Make _acceptRelayedCall and _declineRelayedCall easier to use

* Rename GSNBouncerUtils to GSNBouncerBase, make it IRelayRecipient

* Improve GSNBouncerBase, make pre and post sender-protected and optional

* Fix GSNBouncerERC20Fee, add tests

* Add missing GSNBouncerSignature test

* Override transferFrom in __unstable__ERC20PrimaryAdmin

* Fix gsn dependencies in package.json

* Rhub address slot reduced by 1

* Rename relay hub changed event

* Use released gsn-provider

* Run relayer with short sleep of 1s instead of 100ms

* update package-lock.json

* clear circle cache

* use optimized gsn-provider

* update to latest @openzeppelin/gsn-provider

* replace with gsn dev provider

* remove relay server

* rename arguments in approveFunction

* fix GSNBouncerSignature test

* change gsn txfee

* initialize development provider only once

* update RelayHub interface

* adapt to new IRelayHub.withdraw

* update @openzeppelin/gsn-helpers

* update relayhub singleton address

* fix helper name

* set up gsn provider for coverage too

* lint

* Revert "set up gsn provider for coverage too"

This reverts commit 8a7b5be5f9.

* remove unused code

* add gsn provider to coverage

* move truffle contract options back out

* increase gas limit for coverage

* remove unreachable code

* add more gas for GSNContext test

* fix test suite name

* rename GSNBouncerBase internal API

* remove onlyRelayHub modifier

* add explicit inheritance

* remove redundant event

* update name of bouncers error codes enums

* add basic docs page for gsn contracts

* make gsn directory all caps

* add changelog entry

* lint

* enable test run to fail in coverage
2019-08-12 13:30:03 -03:00

122 lines
4.4 KiB
Solidity

pragma solidity ^0.5.0;
import "./GSNBouncerBase.sol";
import "../../math/SafeMath.sol";
import "../../ownership/Secondary.sol";
import "../../token/ERC20/SafeERC20.sol";
import "../../token/ERC20/ERC20.sol";
import "../../token/ERC20/ERC20Detailed.sol";
contract GSNBouncerERC20Fee is GSNBouncerBase {
using SafeERC20 for __unstable__ERC20PrimaryAdmin;
using SafeMath for uint256;
enum GSNBouncerERC20FeeErrorCodes {
INSUFFICIENT_BALANCE
}
__unstable__ERC20PrimaryAdmin private _token;
constructor(string memory name, string memory symbol, uint8 decimals) public {
_token = new __unstable__ERC20PrimaryAdmin(name, symbol, decimals);
}
function token() public view returns (IERC20) {
return IERC20(_token);
}
function _mint(address account, uint256 amount) internal {
_token.mint(account, amount);
}
function acceptRelayedCall(
address,
address from,
bytes calldata,
uint256 transactionFee,
uint256 gasPrice,
uint256,
uint256,
bytes calldata,
uint256 maxPossibleCharge
)
external
view
returns (uint256, bytes memory)
{
if (_token.balanceOf(from) < maxPossibleCharge) {
return _rejectRelayedCall(uint256(GSNBouncerERC20FeeErrorCodes.INSUFFICIENT_BALANCE));
}
return _approveRelayedCall(abi.encode(from, maxPossibleCharge, transactionFee, gasPrice));
}
function _preRelayedCall(bytes memory context) internal returns (bytes32) {
(address from, uint256 maxPossibleCharge) = abi.decode(context, (address, uint256));
// The maximum token charge is pre-charged from the user
_token.safeTransferFrom(from, address(this), maxPossibleCharge);
}
function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal {
(address from, uint256 maxPossibleCharge, uint256 transactionFee, uint256 gasPrice) =
abi.decode(context, (address, uint256, uint256, uint256));
// actualCharge is an _estimated_ charge, which assumes postRelayedCall will use all available gas.
// This implementation's gas cost can be roughly estimated as 10k gas, for the two SSTORE operations in an
// ERC20 transfer.
uint256 overestimation = _computeCharge(POST_RELAYED_CALL_MAX_GAS.sub(10000), gasPrice, transactionFee);
actualCharge = actualCharge.sub(overestimation);
// After the relayed call has been executed and the actual charge estimated, the excess pre-charge is returned
_token.safeTransfer(from, maxPossibleCharge.sub(actualCharge));
}
}
/**
* @title __unstable__ERC20PrimaryAdmin
* @dev An ERC20 token owned by another contract, which has minting permissions and can use transferFrom to receive
* anyone's tokens. This contract is an internal helper for GSNRecipientERC20Fee, and should not be used
* outside of this context.
*/
// solhint-disable-next-line contract-name-camelcase
contract __unstable__ERC20PrimaryAdmin is ERC20, ERC20Detailed, Secondary {
uint256 private constant UINT256_MAX = 2**256 - 1;
constructor(string memory name, string memory symbol, uint8 decimals) public ERC20Detailed(name, symbol, decimals) {
// solhint-disable-previous-line no-empty-blocks
}
// The primary account (GSNRecipientERC20Fee) can mint tokens
function mint(address account, uint256 amount) public onlyPrimary {
_mint(account, amount);
}
// The primary account has 'infinite' allowance for all token holders
function allowance(address owner, address spender) public view returns (uint256) {
if (spender == primary()) {
return UINT256_MAX;
} else {
return super.allowance(owner, spender);
}
}
// Allowance for the primary account cannot be changed (it is always 'infinite')
function _approve(address owner, address spender, uint256 value) internal {
if (spender == primary()) {
return;
} else {
super._approve(owner, spender, value);
}
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
if (recipient == primary()) {
_transfer(sender, recipient, amount);
return true;
} else {
return super.transferFrom(sender, recipient, amount);
}
}
}