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.
-
RefunderFactoryis aware of theGateWayProxyand its interface. - On deployment:
-
msg.senderis the initial owner of theRefundercontract. - the deployed
Refundercontract is added to the set of registeredrefundersin theGatewayProxyvia theGatewayProxy.addRefunderfunction.
-
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.senderthat calls the factory - Holds
ETHfor gas cost reimbursements - has a
map(address, bool)of whitelistedGatewayProxycontracts - 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 ERC20approvetx, the key of the inner map would bebytes4(keccak256(approve(address,uint256)). The value (uint256) of the inner map (map(bytes4, uint256)) represents theestimatedgas costs of refunding users for that specific contract call. NOTE: this is not the gas costs that will be reimbursed to themsg.sender, but theexpectedgas costs only for callingrefundwith 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