Update GSN interface (#628)

This commit is contained in:
Leonid Tyurin 2021-08-20 12:48:10 +03:00 committed by GitHub
parent 7579b5249e
commit 93b1afbe67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1260 additions and 507 deletions

View File

@ -4,7 +4,6 @@ pragma experimental ABIEncoderV2;
import "../upgradeable_contracts/Ownable.sol";
import "./interfaces/GsnTypes.sol";
import "./interfaces/IPaymaster.sol";
import "./interfaces/IRelayHub.sol";
import "./utils/GsnEip712Library.sol";
@ -18,7 +17,7 @@ import "./forwarder/IForwarder.sol";
*/
contract BasePaymaster is IPaymaster, Ownable {
IRelayHub internal relayHub;
IForwarder public trustedForwarder;
address private _trustedForwarder;
function getHubAddr() public view returns (address) {
return address(relayHub);
@ -27,20 +26,26 @@ contract BasePaymaster is IPaymaster, Ownable {
//overhead of forwarder verify+signature, plus hub overhead.
uint256 public constant FORWARDER_HUB_OVERHEAD = 50000;
//These parameters are documented in IPaymaster.GasLimits
//These parameters are documented in IPaymaster.GasAndDataLimits
uint256 public constant PRE_RELAYED_CALL_GAS_LIMIT = 100000;
uint256 public constant POST_RELAYED_CALL_GAS_LIMIT = 110000;
uint256 public constant PAYMASTER_ACCEPTANCE_BUDGET = PRE_RELAYED_CALL_GAS_LIMIT + FORWARDER_HUB_OVERHEAD;
uint256 public constant CALLDATA_SIZE_LIMIT = 10500;
function getGasLimits() external view returns (IPaymaster.GasLimits) {
function getGasAndDataLimits() external view returns (IPaymaster.GasAndDataLimits limits) {
return
IPaymaster.GasLimits(PAYMASTER_ACCEPTANCE_BUDGET, PRE_RELAYED_CALL_GAS_LIMIT, POST_RELAYED_CALL_GAS_LIMIT);
IPaymaster.GasAndDataLimits(
PAYMASTER_ACCEPTANCE_BUDGET,
PRE_RELAYED_CALL_GAS_LIMIT,
POST_RELAYED_CALL_GAS_LIMIT,
CALLDATA_SIZE_LIMIT
);
}
// this method must be called from preRelayedCall to validate that the forwarder
// is approved by the paymaster as well as by the recipient contract.
function _verifyForwarder(GsnTypes.RelayRequest relayRequest) public view {
require(address(trustedForwarder) == relayRequest.relayData.forwarder, "Forwarder is not trusted");
require(address(_trustedForwarder) == relayRequest.relayData.forwarder, "Forwarder is not trusted");
GsnEip712Library.verifyForwarderTrusted(relayRequest);
}
@ -48,7 +53,7 @@ contract BasePaymaster is IPaymaster, Ownable {
* modifier to be used by recipients as access control protection for preRelayedCall & postRelayedCall
*/
modifier relayHubOnly() {
require(msg.sender == getHubAddr(), "Function can only be called by RelayHub");
require(msg.sender == getHubAddr(), "can only be called by RelayHub");
_;
}
@ -56,16 +61,28 @@ contract BasePaymaster is IPaymaster, Ownable {
relayHub = hub;
}
function setTrustedForwarder(IForwarder forwarder) public onlyOwner {
trustedForwarder = forwarder;
function setTrustedForwarder(address forwarder) public onlyOwner {
_trustedForwarder = forwarder;
}
// check current deposit on relay hub.
function getRelayHubDeposit() public view returns (uint256) {
function trustedForwarder() external view returns (address) {
return _trustedForwarder;
}
/// check current deposit on relay hub.
function getRelayHubDeposit() external view returns (uint256) {
return relayHub.balanceOf(address(this));
}
// withdraw deposit from relayHub
// any eth moved into the paymaster is transferred as a deposit.
// This way, we don't need to understand the RelayHub API in order to replenish
// the paymaster.
function() external payable {
require(address(relayHub) != address(0), "relay hub address not set");
relayHub.depositFor.value(msg.value)(address(this));
}
/// withdraw deposit from relayHub
function withdrawRelayHubDepositTo(uint256 amount, address target) public onlyOwner {
relayHub.withdraw(amount, target);
}

View File

@ -10,14 +10,20 @@ contract IForwarder {
uint256 gas;
uint256 nonce;
bytes data;
uint256 validUntil;
}
event DomainRegistered(bytes32 indexed domainSeparator, bytes domainValue);
event RequestTypeRegistered(bytes32 indexed typeHash, string typeStr);
function getNonce(address from) external view returns (uint256);
/**
* verify the transaction would execute.
* validate the signature and the nonce of the request.
* revert if either signature or nonce are incorrect.
* also revert if domainSeparator or requestTypeHash are not registered.
*/
function verify(
ForwardRequest forwardRequest,
@ -51,8 +57,8 @@ contract IForwarder {
/**
* Register a new Request typehash.
* @param typeName - the name of the request type.
* @param typeSuffix - anything after the generic params can be empty string (if no extra fields are needed)
* if it does contain a value, then a comma is added first.
* @param typeSuffix - any extra data after the generic params.
* (must add at least one param. The generic ForwardRequest type is always registered by the constructor)
*/
function registerRequestType(string typeName, string typeSuffix) external;

View File

@ -4,15 +4,16 @@ pragma solidity 0.4.24;
import "../forwarder/IForwarder.sol";
contract GsnTypes {
/// @notice gasPrice, pctRelayFee and baseRelayFee must be validated inside of the paymaster's preRelayedCall in order not to overpay
struct RelayData {
uint256 gasPrice;
uint256 pctRelayFee;
uint256 baseRelayFee;
address relayWorker;
address paymaster;
address forwarder;
bytes paymasterData;
uint256 clientId;
address forwarder;
}
//note: must start with the ForwardRequest to be an extension of the generic forwarder

View File

@ -29,16 +29,19 @@ contract IPaymaster {
* note that an OOG will revert the transaction, but the paymaster already committed to pay,
* so the relay will get compensated, at the expense of the paymaster
*/
struct GasLimits {
struct GasAndDataLimits {
uint256 acceptanceBudget;
uint256 preRelayedCallGasLimit;
uint256 postRelayedCallGasLimit;
uint256 calldataSizeLimit;
}
/**
* Return the GasLimits constants used by the Paymaster.
* Return the Gas Limits and msg.data max size constants used by the Paymaster.
*/
function getGasLimits() external view returns (GasLimits memory limits);
function getGasAndDataLimits() external view returns (GasAndDataLimits memory limits);
function trustedForwarder() external view returns (address);
/**
* return the relayHub of this contract.
@ -49,7 +52,7 @@ contract IPaymaster {
* Can be used to determine if the contract can pay for incoming calls before making any.
* @return the paymaster's deposit in the RelayHub.
*/
function getRelayHubDeposit() public view returns (uint256);
function getRelayHubDeposit() external view returns (uint256);
/**
* Called by Relay (and RelayHub), to validate if the paymaster agrees to pay for this call.
@ -74,7 +77,7 @@ contract IPaymaster {
* Note that in most cases the paymaster shouldn't try use it at all. It is always checked
* by the forwarder immediately after preRelayedCall returns.
* @param approvalData - extra dapp-specific data (e.g. signature from trusted party)
* @param maxPossibleGas - based on values returned from {@link getGasLimits},
* @param maxPossibleGas - based on values returned from {@link getGasAndDataLimits},
* the RelayHub will calculate the maximum possible amount of gas the user may be charged for.
* In order to convert this value to wei, the Paymaster has to call "relayHub.calculateCharge()"
* return:

View File

@ -6,21 +6,25 @@ import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "../BasePaymaster.sol";
import "./IUniswapV2Router02.sol";
import "../../upgradeable_contracts/GSNForeignERC20Bridge.sol";
import "../../upgradeable_contracts/Claimable.sol";
contract TokenPaymaster is BasePaymaster {
address private token;
contract TokenPaymaster is BasePaymaster, Claimable {
ERC20 private token;
IUniswapV2Router02 private router;
address private bridge;
address[] private tokenWethPair = new address[](2);
uint256 public postGasUsage = 300000;
// Default value from BasePaymaster, may be changed later
// if we need to increase number of signatures for bridge contract
uint256 public calldataSizeLimit = 10500;
constructor(address _relayHub, address _forwarder, address _token, address _router, address _bridge) public {
_setOwner(msg.sender);
relayHub = IRelayHub(_relayHub);
trustedForwarder = IForwarder(_forwarder);
setRelayHub(IRelayHub(_relayHub));
setTrustedForwarder(_forwarder);
token = _token;
token = ERC20(_token);
router = IUniswapV2Router02(_router);
bridge = _bridge;
@ -29,7 +33,7 @@ contract TokenPaymaster is BasePaymaster {
}
function setToken(address t) external onlyOwner {
token = t;
token = ERC20(t);
}
function setRouter(IUniswapV2Router02 r) external onlyOwner {
@ -40,13 +44,16 @@ contract TokenPaymaster is BasePaymaster {
bridge = b;
}
function versionPaymaster() external view returns (string memory) {
return "2.0.0+opengsn.tokengsn.ipaymaster";
function setPostGasUsage(uint256 gasUsage) external onlyOwner {
postGasUsage = gasUsage;
}
function() external payable {
require(address(relayHub) != address(0), "relay hub address not set");
relayHub.depositFor.value(msg.value)(address(this));
function setCalldataSizeLimit(uint256 sizeLimit) external onlyOwner {
calldataSizeLimit = sizeLimit;
}
function versionPaymaster() external view returns (string memory) {
return "2.2.0+opengsn.bridgetokengsn.ipaymaster";
}
function deposit() external payable {
@ -54,19 +61,16 @@ contract TokenPaymaster is BasePaymaster {
relayHub.depositFor.value(msg.value)(address(this));
}
function getGasLimits() external view returns (IPaymaster.GasLimits memory limits) {
function getGasAndDataLimits() external view returns (IPaymaster.GasAndDataLimits memory limits) {
return
IPaymaster.GasLimits(
IPaymaster.GasAndDataLimits(
PAYMASTER_ACCEPTANCE_BUDGET,
PRE_RELAYED_CALL_GAS_LIMIT,
postGasUsage // maximum postRelayedCall gasLimit
postGasUsage, // maximum postRelayedCall gasLimit
calldataSizeLimit
);
}
function erc20() internal view returns (ERC20) {
return ERC20(token);
}
function readBytes32(bytes memory b, uint256 index) internal pure returns (bytes32 res) {
require(b.length >= index + 32, "data too short");
assembly {
@ -79,7 +83,7 @@ contract TokenPaymaster is BasePaymaster {
bytes signature,
bytes approvalData,
uint256 maxPossibleGas
) public returns (bytes memory context, bool revertOnRecipientRevert) {
) public relayHubOnly returns (bytes memory context, bool revertOnRecipientRevert) {
(signature, approvalData);
_verifyForwarder(relayRequest);
bytes memory reqData = relayRequest.request.data;
@ -108,12 +112,9 @@ contract TokenPaymaster is BasePaymaster {
return a < b ? a : b;
}
function setPostGasUsage(uint256 gasUsage) external onlyOwner {
postGasUsage = gasUsage;
}
function postRelayedCall(bytes context, bool success, uint256 gasUseWithoutPost, GsnTypes.RelayData relayData)
public
relayHubOnly
{
(success);
// Extract data from context
@ -137,7 +138,7 @@ contract TokenPaymaster is BasePaymaster {
uint256 chargeWei = relayHub.calculateCharge(min(gasUseWithoutPost + postGasUsage, maxPossibleGas), relayData);
// Uniswap
require(erc20().approve(address(router), maxTokensFee), "approve failed");
require(token.approve(address(router), maxTokensFee), "approve failed");
// NOTE: Received eth automatically converts to relayhub deposit
uint256 spentTokens = router.swapTokensForExactETH(
chargeWei,
@ -149,7 +150,11 @@ contract TokenPaymaster is BasePaymaster {
// Send rest of tokens to user
if (spentTokens < maxTokensFee) {
require(erc20().transfer(to, maxTokensFee - spentTokens));
require(token.transfer(to, maxTokensFee - spentTokens));
}
}
function claimTokens(address _token, address _to) external onlyOwner {
claimValues(_token, _to);
}
}

View File

@ -36,7 +36,8 @@ contract GSNForeignERC20Bridge is BasicForeignBridge, ERC20Bridge, BaseRelayReci
* as a commission
*/
function executeSignaturesGSN(bytes message, bytes signatures, uint256 maxTokensFee) external {
require(msg.sender == addressStorage[TRUSTED_FORWARDER], "invalid forwarder");
// Allow only forwarder calls
require(isTrustedForwarder(msg.sender), "invalid forwarder");
Message.hasEnoughValidSignatures(message, signatures, validatorContract(), false);
address recipient;

1602
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,12 +20,15 @@
"author": "POA network",
"license": "GPLv3",
"dependencies": {
"@opengsn/gsn": "^2.1.0",
"@opengsn/cli": "^2.2.2",
"@opengsn/contracts": "^2.2.2",
"@opengsn/provider": "^2.2.2",
"array-flat-polyfill": "^1.0.1",
"openzeppelin-solidity": "1.12.0",
"truffle": "^5.3.2",
"truffle-flattener": "^1.4.2",
"web3-provider-engine": "^14.0.6"
"web3-provider-engine": "^14.0.6",
"web3-utils": "^1.2.6"
},
"devDependencies": {
"@codechecks/client": "^0.1.9",

View File

@ -3,13 +3,14 @@ require('array-flat-polyfill')
const XDaiForeignBridgeMock = artifacts.require('XDaiForeignBridgeMock.sol')
const BridgeValidators = artifacts.require('BridgeValidators.sol')
const ERC677BridgeToken = artifacts.require('ERC677BridgeToken.sol')
const UniswapRouterMock = artifacts.require('UniswapRouterMock.sol')
const TokenPaymaster = artifacts.require('TokenPaymaster.sol')
// GSN
const { RelayProvider } = require('@opengsn/gsn')
const { GsnTestEnvironment } = require('@opengsn/gsn/dist/GsnTestEnvironment')
const { RelayProvider } = require('@opengsn/provider')
const { GsnTestEnvironment } = require('@opengsn/cli/dist/GsnTestEnvironment')
const { toBN, ERROR_MSG, ZERO_ADDRESS } = require('../setup')
const {
@ -32,6 +33,7 @@ const minPerTx = ether('0.01')
const dailyLimit = homeDailyLimit
const ZERO = toBN(0)
const decimalShiftZero = 0
const HALF_ETHER = ether('0.5')
const FIVE_ETHER = ether('5')
const GSNGasLimit = 500000
@ -75,7 +77,7 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
await GsnTestEnvironment.stopGsn()
})
describe('#initialize', async () => {
it('should initialize', async () => {
it('should initialize paymaster', async () => {
router = await UniswapRouterMock.new()
foreignBridge = await XDaiForeignBridgeMock.new()
@ -284,4 +286,29 @@ contract('ForeignBridge_ERC20_to_Native_GSN', async accounts => {
.should.be.rejectedWith(`${ERROR_MSG} invalid forwarder`)
})
})
describe('#claimTokens', async () => {
it('can send erc20', async () => {
router = await UniswapRouterMock.new()
paymaster = await TokenPaymaster.new(
RelayHubAddress,
ForwarderAddress,
ZERO_ADDRESS,
router.address,
ZERO_ADDRESS,
{ from: owner }
)
const token = await ERC677BridgeToken.new('Some ERC20', 'RSZT', 18)
await token.mint(accounts[1], HALF_ETHER).should.be.fulfilled
await token.transfer(paymaster.address, HALF_ETHER, { from: accounts[1] })
await paymaster.claimTokens(token.address, accounts[3], { from: accounts[3] }).should.be.rejectedWith(ERROR_MSG)
await paymaster.claimTokens(token.address, accounts[3], { from: owner })
const pmBalance = await token.balanceOf(paymaster.address)
const accBalance = await token.balanceOf(accounts[3])
pmBalance.should.be.bignumber.equal(ZERO)
accBalance.should.be.bignumber.equal(HALF_ETHER)
})
})
})