Proxy Gas Refunder
A generic contract system for reliably refunding the gas costs of transactions. The purpose of the project is to:
- Enable Protocols to specify what contract calls they are willing to sponsor with a set of limitations (e.g gas price)
- Enable anyone to submit transactions that are eligible for refunding and get their transaction costs reimbursed.
Contracts
There are 3 major contracts:
- Refunder factory
- Refunder
- GatewayProxy
Refunder Factory
Factory contract used for the deployment of Refunder
contracts. Anyone is able to deploy a refunder contract and configure it for its own needs.
-
RefunderFactory
is aware of theGateWayProxy
and its interface. - On deployment:
-
msg.sender
is the initial owner of theRefunder
contract. - the deployed
Refunder
contract is added to the set of registeredrefunders
in theGatewayProxy
via theGatewayProxy.addRefunder
function.
-
Refunder
Refunder contract is a standalone, completely independent contract that represents the interest of a given protocol/entity that wants to sponsor a set of function calls. The contract:
- is
ownable
. Initially set to themsg.sender
that calls the factory - Holds
ETH
for gas cost reimbursements - has a
map(address, bool)
of whitelistedGatewayProxy
contracts - has a
map(address -> map(bytes4, uint256))
of whitelistedrefundableCalls
. The key of the outer map is a "whitelisted" contractaddress
. Calls are represented by the function's signature. The keys of the inner map (bytes4
) are calculated askeccak256('functionName(params)')
. For example, if a given refunder contract allows for refunding of ERC20approve
tx, the key of the inner map would bebytes4(keccak256(approve(address,uint256))
. The value (uint256
) of the inner map (map(bytes4, uint256)
) represents theestimated
gas costs of refunding users for that specific contract call. NOTE: this is not the gas costs that will be reimbursed to themsg.sender
, but theexpected
gas costs only for callingrefund
with those arguments.
Interface
Note: Function calls must be nonReentrant
// @param isEligible - Returns true/false whether the specified contract call is eligible for gas refund
function isEligible(address targetContract, bytes4 interfaceId, uint256 gasPrice) external returns (bool)
// @param getRefundCost - Returns the `expected` gas costs for executing the `refund` with the specified arguments
function getRefundCost(address targetContract, bytes4 interfaceId, uint256 gasPrice) external returns (uint256)
// @param refund - Refunds the sender, calling the target contract's function
function refund(address sender, address target, bytes4 interfaceId, uint256 amount) external returns (bool)
// @param withdraw - Withdraws ETH from the Refunder contract
function withdraw(uint256 amount)
Context
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}
GatewayProxy
The GatewayProxy contract is a singleton contract used to forward the provided contract call data (e.g raw msg + signature) to the target contract and request a refund for the msg.sender
afterwards from the responsible Refunder
contract.
Note v1 should implement the factory/registry pattern
The contract has a map(address, bool)
of the deployed refunder
contracts. Anyone is able to add addresses to the map
if they support the required Refunder
interface.
Interface
Note v1 should implement the factory/registry pattern
// Provides the `refunder` of the call, the target contract and the call data to be passed. Refunder reimburses the gas costs of the msg sender
function supplyAndRefund(address refunder, address target, bytes data)
// Adds new refunder in the `refunders` map. Internally this function calls the `refunder.refundGasCost` function to set the appropriate value in the `refunders` map
function addRefunder(address refunder)
Pseudo-code
Refund calculations
modifier netGasCost(targetContract, interfaceId) {
uint256 gasProvided = gasLeft();
uint256 refundCost = refunder.getRefundCost(targetContract, interfaceId, tx.gasprice) // FIXME - non-reentrance
_;
uint256 gasUsedSoFar = gasProvided - gasLeft();
refundAmount = (gasUsedSoFar + refundCost) * tx.gasprice;
refunder.refund(msg.sender, targetContract, interfaceId, refundAmount);
}
getRefundCost
information is required in order for the GatewayProxy
to know how much will be the additional cost for the actual refund call
NOTE: interfaceId
is the first 4 bytes of the provided bytes data
Supply and Refund
(bool success, bytes memory returnData) = target.call(data) // @dev forwarding value