Skip to content

Instantly share code, notes, and snippets.

@MihanixA
Created March 16, 2023 01:06
Show Gist options
  • Save MihanixA/e1ca8f478e77b4a968c04727ffa4e726 to your computer and use it in GitHub Desktop.
Save MihanixA/e1ca8f478e77b4a968c04727ffa4e726 to your computer and use it in GitHub Desktop.
Account Abstraction / Sample Use Case / ETHcc
// 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