Created
March 16, 2023 01:06
-
-
Save MihanixA/e1ca8f478e77b4a968c04727ffa4e726 to your computer and use it in GitHub Desktop.
Account Abstraction / Sample Use Case / ETHcc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.9; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; | |
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; | |
library Exceptions { | |
string constant SIGNATURE_VERIFICATION_ERROR = "SIGNVRF"; | |
string constant ONLY_PROTOCOL_GOVERNANCE = "GOV"; | |
string constant ONLY_PIPE_OWNER = "PIPEOWN"; | |
string constant PIPE_ALREADY_INITIALIZED = "PIPEINIT"; | |
string constant PIPE_NO_BURN_NO_TRANSFER = "PIPENOBURNNOTRNFS"; | |
} | |
interface IProtocolGovernance { | |
struct Params { | |
uint32 gasSupply; | |
uint32 duration; | |
uint32 cost; | |
IERC20 underlyingToken; | |
address factory; | |
address treasury; | |
} | |
event NewParamsSet(address indexed sender, Params newParams); | |
function params() external view returns (Params memory); | |
function setParams(Params calldata) external; | |
} | |
interface IMetaPipe { | |
struct PassThroughRequest { | |
address to; | |
uint256 value; | |
bytes data; | |
} | |
struct RelayMetaRequest { | |
address owner; | |
address to; | |
uint256 value; | |
uint256 gas; | |
uint256 deadline; | |
uint256 nonce; | |
bytes data; | |
} | |
event PipeCreated( | |
uint256 indexed tokenId, | |
address indexed owner, | |
address indexed backref | |
); | |
event PassThroughRequestExecuted(PassThroughRequest request); | |
event RelayMetaRequestExecuted(RelayMetaRequest request, bytes signature); | |
function nonce() external view returns (uint256); | |
function verify(RelayMetaRequest calldata request, bytes calldata signature) | |
external | |
view | |
returns (bool); | |
function executePassThrough(PassThroughRequest calldata request) | |
external | |
returns (bool, bytes memory); | |
function executeRelayMeta( | |
RelayMetaRequest calldata request, | |
bytes calldata signature | |
) external returns (bool, bytes memory); | |
} | |
interface IMetaPipeFactory { | |
function deploy(address backref, uint256 tokenId) | |
external | |
returns (address); | |
} | |
contract MetaPipeFactory is IMetaPipeFactory { | |
function deploy(address backref, uint256 tokenId) | |
external | |
returns (address) | |
{ | |
IMetaPipe mp = new MetaPipe(backref, tokenId); | |
return address(mp); | |
} | |
} | |
contract ProtocolGovernance is IProtocolGovernance, Ownable { | |
Params private _params; | |
constructor() Ownable() {} | |
function setParams(Params calldata newParams) external onlyOwner { | |
_params = newParams; | |
emit NewParamsSet(msg.sender, newParams); | |
} | |
function params() external view returns (Params memory) { | |
return _params; | |
} | |
} | |
interface IRegistry { | |
struct PipeRecord { | |
IMetaPipe metaPipeAddress; | |
uint256 deadlineAt; | |
uint256 gasLeft; | |
} | |
event PipeMinted(uint256 indexed tokenId, address indexed to); | |
event BaseURISet(string uri); | |
event CycleDepositMade( | |
address indexed sender, | |
uint256 indexed tokenId, | |
uint256 nextDeadlineAt, | |
uint256 gasLeft | |
); | |
event GasSupplyWithdrawn( | |
address indexed sender, | |
uint256 indexed tokenId, | |
uint256 gasAmount | |
); | |
function makeCycleDepositFor(uint256 tokenId) external; | |
function execute( | |
uint256 tokenId, | |
IMetaPipe.RelayMetaRequest calldata request, | |
bytes calldata signature | |
) external; | |
function setBaseURI(string calldata) external; | |
} | |
contract Registry is IRegistry, ERC721 { | |
using SafeERC20 for IERC20; | |
uint256 private constant _COLD_START_GAS_UNITS_OVERHEAD = 42_000; | |
IProtocolGovernance public immutable protocolGovernance; | |
string public baseURI; | |
uint256 public topMetaPipeId; | |
mapping(address => PipeRecord) public recordOf; | |
constructor(address protocolGovernance_) | |
ERC721("MetaPipe Registry", "MetaPipe-REG") | |
{ | |
protocolGovernance = IProtocolGovernance(protocolGovernance_); | |
} | |
function mint(address to) external { | |
_requireProtocolGovernance(); | |
uint256 newTokenId = ++topMetaPipeId; | |
_mint(to, newTokenId); | |
IProtocolGovernance.Params memory params = protocolGovernance.params(); | |
IMetaPipeFactory factory = IMetaPipeFactory(params.factory); | |
IMetaPipe metaPipe = IMetaPipe( | |
factory.deploy(address(this), newTokenId) | |
); | |
recordOf[to] = PipeRecord(metaPipe, 0, 0); | |
emit PipeMinted(newTokenId, to); | |
} | |
function makeCycleDepositFor(uint256 tokenId) external { | |
_requireMinted(tokenId); | |
address tokenOwner = ownerOf(tokenId); | |
IProtocolGovernance.Params memory params = protocolGovernance.params(); | |
PipeRecord memory record = recordOf[tokenOwner]; | |
params.underlyingToken.safeTransferFrom( | |
msg.sender, | |
params.treasury, | |
params.cost | |
); | |
record.gasLeft += params.gasSupply; | |
record.deadlineAt = | |
( | |
block.timestamp > record.deadlineAt | |
? block.timestamp | |
: record.deadlineAt | |
) + | |
uint256(params.duration); | |
recordOf[tokenOwner] = record; | |
emit CycleDepositMade( | |
msg.sender, | |
tokenId, | |
record.deadlineAt, | |
record.gasLeft | |
); | |
} | |
function execute( | |
uint256 tokenId, | |
IMetaPipe.RelayMetaRequest calldata request, | |
bytes calldata signature | |
) external { | |
_requireMinted(tokenId); | |
uint256 gas = gasleft(); | |
PipeRecord storage record = recordOf[ownerOf(tokenId)]; | |
(uint256 gasLeft, IMetaPipe pipe) = ( | |
record.gasLeft, | |
record.metaPipeAddress | |
); | |
require( | |
pipe.verify(request, signature), | |
Exceptions.SIGNATURE_VERIFICATION_ERROR | |
); | |
pipe.executeRelayMeta(request, signature); | |
// gas = gasleft() - gas + _COLD_START_GAS_UNITS_OVERHEAD; | |
gas = gas - gasleft() - _COLD_START_GAS_UNITS_OVERHEAD; | |
record.gasLeft = gasLeft >= gas ? gasLeft - gas : 0; | |
emit GasSupplyWithdrawn(msg.sender, tokenId, gas); | |
} | |
function multicall(bytes[] calldata data) | |
external | |
returns (bytes[] memory results) | |
{ | |
results = new bytes[](data.length); | |
for (uint i = 0; i < data.length; i++) { | |
(bool success, bytes memory result) = address(this).delegatecall( | |
data[i] | |
); | |
require(success); | |
results[i] = result; | |
} | |
return results; | |
} | |
function setBaseURI(string calldata baseURI_) external { | |
_requireProtocolGovernance(); | |
baseURI = baseURI_; | |
emit BaseURISet(baseURI_); | |
} | |
function _baseURI() internal view virtual override returns (string memory) { | |
return baseURI; | |
} | |
function _beforeTokenTransfer( | |
address from, | |
address, | |
uint256 | |
) internal view virtual override { | |
require(from == address(0), Exceptions.PIPE_NO_BURN_NO_TRANSFER); | |
} | |
function _afterTokenTransfer( | |
address, | |
address to, | |
uint256 | |
) internal view virtual override { | |
require(balanceOf(to) == 1, Exceptions.PIPE_ALREADY_INITIALIZED); | |
} | |
function _requireProtocolGovernance() internal view { | |
require( | |
msg.sender == Ownable(address(protocolGovernance)).owner(), | |
Exceptions.ONLY_PROTOCOL_GOVERNANCE | |
); | |
} | |
} | |
contract MetaPipe is IMetaPipe, EIP712 { | |
using ECDSA for bytes32; | |
bytes32 private constant _TYPEHASH = | |
keccak256( | |
"RelayMetaRequest(address owner,address to,uint256 value,uint256 gas,uint256 deadline,uint256 nonce,bytes data)" | |
); | |
uint256 public immutable tokenId; | |
address public immutable owner; | |
address public immutable backref; | |
uint256 public nonce; | |
constructor(address backref_, uint256 tokenId_) | |
EIP712("MetaPipe Instance", "V1") | |
{ | |
tokenId = tokenId_; | |
backref = backref_; | |
owner = IERC721(backref).ownerOf(tokenId); | |
emit PipeCreated(tokenId, owner, backref); | |
} | |
function executePassThrough(PassThroughRequest calldata request) | |
external | |
returns (bool success, bytes memory returndata) | |
{ | |
require(msg.sender == owner, Exceptions.ONLY_PIPE_OWNER); | |
(success, returndata) = request.to.call{value: request.value}( | |
request.data | |
); | |
if (!success) { | |
_revert(); | |
} | |
emit PassThroughRequestExecuted(request); | |
} | |
function executeRelayMeta( | |
RelayMetaRequest calldata request, | |
bytes calldata signature | |
) external returns (bool success, bytes memory returndata) { | |
require( | |
verify(request, signature), | |
Exceptions.SIGNATURE_VERIFICATION_ERROR | |
); | |
(success, returndata) = request.to.call{ | |
gas: request.gas, | |
value: request.value | |
}(request.data); | |
if (!success) { | |
_revert(); | |
} | |
nonce++; | |
assert(gasleft() > request.gas / 63); | |
emit RelayMetaRequestExecuted(request, signature); | |
} | |
function verify(RelayMetaRequest calldata request, bytes calldata signature) | |
public | |
view | |
returns (bool) | |
{ | |
address signer = _hashTypedDataV4( | |
keccak256( | |
abi.encode( | |
_TYPEHASH, | |
request.owner, | |
request.to, | |
request.value, | |
request.gas, | |
request.deadline, | |
request.nonce, | |
keccak256(request.data) | |
) | |
) | |
).recover(signature); | |
return ((signer == owner) && | |
(nonce == request.nonce) && | |
(block.timestamp < request.deadline)); | |
} | |
function _revert() internal pure { | |
assembly { | |
returndatacopy(0, 0, returndatasize()) | |
revert(0, returndatasize()) | |
} | |
} | |
} | |
// FIXME: deposit for cycle should not stack up with previous cycles gasSupply with next cycle | |
// IDEA: maintain & convert subscription gasSupply to underlying token => exclude ETH/gas price volatility correction | |
// add historical gas price oracle & univ3 twap oracle | |
// | |
// interface IGasPriceHistoryOracle { | |
// function ethGasPricePerBlock(uint256 blockNumber) external view returns (uint256); | |
// } | |
// | |
// interface IOracle { | |
// function underlyingForETH(address token, uint256 ethAmount) external view returns (uint256 tokenAmount); | |
// function ethForUnderlying(address token, uint256 tokenAmount) external view returns (uint256 ethAmount); | |
// } | |
// IDEA: permissionless Registry::execute with reward distribution => lower ETH collateral required | |
// gas fees should be compensated 1:1 with underlying token instantly | |
// most of the net profit is expected to be unused gasSupply burned at subscription deadline | |
// | |
// interface IVault { | |
// ... | |
// function depositDelayedCycleDeposit(uint256 amount, uint256 delay) external; | |
// function withdrawInstantGasFees(uint256 blockNumber, uint256 gasSpent) external; | |
// } | |
// IDEA: permissionless pipe creation & referral program with full LTV percentage | |
// validation - ? referral address signed by referree? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment