Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion contracts/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {Ownable, Ownable2Step} from '@openzeppelin/contracts/access/Ownable2Step
import {EfficientCall} from '@matterlabs/zksync-contracts/contracts/system-contracts/libraries/EfficientCall.sol';
import {Errors} from './libraries/Errors.sol';
import {IAGWRegistry} from './interfaces/IAGWRegistry.sol';
import {IAccountFactory} from './interfaces/IAccountFactory.sol';

/**
* @title Factory contract to create AGW accounts
* @dev Forked from Clave for Abstract
* @author https://abs.xyz
* @author https://getclave.io
*/
contract AccountFactory is Ownable2Step {
contract AccountFactory is Ownable2Step, IAccountFactory {

/**
* @notice Address of the account implementation
Expand Down
11 changes: 11 additions & 0 deletions contracts/interfaces/IAccountFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IAccountFactory {
function deployAccount(bytes32 salt, bytes calldata initializer)
external
payable
returns (address accountAddress);

function authorizedDeployers(address) external view returns (bool);
}
124 changes: 124 additions & 0 deletions contracts/paymasters/ChainOpsPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Transaction} from "@matterlabs/zksync-contracts/contracts/system-contracts/libraries/TransactionHelper.sol";
import {
IPaymaster,
ExecutionResult,
PAYMASTER_VALIDATION_SUCCESS_MAGIC
} from "@matterlabs/zksync-contracts/contracts/system-contracts/interfaces/IPaymaster.sol";
import {IAccountFactory} from "../interfaces/IAccountFactory.sol";
import {BOOTLOADER_FORMAL_ADDRESS} from "@matterlabs/zksync-contracts/contracts/system-contracts/Constants.sol";
import {OwnableRoles} from "solady/src/auth/OwnableRoles.sol";
import {SafeTransferLib} from "solady/src/utils/ext/zksync/SafeTransferLib.sol";

contract ChainOpsPaymaster is OwnableRoles, IPaymaster {
using SafeTransferLib for address;
using SafeTransferLib for address payable;

error OnlyBootloader();
error WithdrawalFailed();
error SponsorshipRefused();

uint256 public constant MANAGER_ROLE = _ROLE_0;

IAccountFactory public immutable AA_FACTORY;
address private immutable _deployer;

mapping(address from => bool sponsored) public sponsoredAccounts;
mapping(address from => mapping(address to => mapping(bytes4 selector => bool sponsored))) public sponsoredCalls;

constructor(address owner, address aaFactory) {
AA_FACTORY = IAccountFactory(aaFactory);
_initializeOwner(owner);
_grantRoles(owner, MANAGER_ROLE);
}

function validateAndPayForPaymasterTransaction(bytes32, bytes32, Transaction calldata _transaction)
external
payable
returns (bytes4 magic, bytes memory context)
{
if (msg.sender != BOOTLOADER_FORMAL_ADDRESS) {
revert OnlyBootloader();
}

bool shouldSponsor = false;

address from = address(uint160(_transaction.from));
address to = address(uint160(_transaction.to));

bytes4 selector;
if (_transaction.data.length > 4) {
selector = bytes4(_transaction.data[0:4]);
}

if (to == address(AA_FACTORY)) {
if (selector == IAccountFactory.deployAccount.selector) {
if (AA_FACTORY.authorizedDeployers(from)) {
shouldSponsor = true;
}
}
}

if (!shouldSponsor) {
if (sponsoredAccounts[from]) {
shouldSponsor = true;
} else if (sponsoredCalls[from][to][selector]) {
shouldSponsor = true;
}
}

if (!shouldSponsor) {
revert SponsorshipRefused();
}

context = "";
magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;

uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas;

BOOTLOADER_FORMAL_ADDRESS.safeTransferETH(requiredETH);
}

function postTransaction(
bytes calldata _context,
Transaction calldata _transaction,
bytes32 _txHash,
bytes32 _suggestedSignedHash,
ExecutionResult _txResult,
uint256 _maxRefundedGas
) external payable {}

function withdraw(address payable to, uint256 amount) external onlyOwner {
uint256 balance = address(this).balance;
if (amount > balance) {
amount = balance;
}
(bool success,) = to.call{value: amount}("");
if (!success) {
revert WithdrawalFailed();
}
}

function rescueERC20(address token, address to, uint256 amount) external onlyOwner {
token.safeTransfer(to, amount);
}

function rescueERC721(address token, address to, uint256 tokenId) external onlyOwner {
token.safeTransferFrom(address(this), to, tokenId);
}

function setSponsoredAccount(address from, bool sponsored) external onlyRolesOrOwner(MANAGER_ROLE) {
sponsoredAccounts[from] = sponsored;
}

function setSponsoredCall(address from, address to, bytes4 selector, bool sponsored)
external
onlyRolesOrOwner(MANAGER_ROLE)
{
sponsoredCalls[from][to][selector] = sponsored;
}

receive() external payable {}
}
29 changes: 29 additions & 0 deletions deploy/deploy-chain-ops-paymaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright Clave - All Rights Reserved
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
*/
import * as hre from 'hardhat';
import { Wallet } from 'zksync-ethers';
import { create2IfNotExists, getProvider, getWallet } from '../deploy/utils';
let fundingWallet: Wallet;

export default async function (): Promise<void> {
fundingWallet = getWallet(hre);

const provider = getProvider(hre);

const network = await provider.getNetwork();

const implementation = await create2IfNotExists(hre, "ChainOpsPaymaster", [
"0x6f6426a9b93a7567fCCcBfE5d0d6F26c1085999b",
"0x9B947df68D35281C972511B3E7BC875926f26C1A"
]);

await implementation.setSponsoredAccount("0x6f6426a9b93a7567fCCcBfE5d0d6F26c1085999b", true);
if (network.chainId === 2741n) {
await implementation.setSponsoredAccount("0x7f60048318AD245B29A2cE9E87733C22d1f8335E", true);
} else if (network.chainId === 11124n) {
await implementation.setSponsoredAccount("0x84ffdFA5737012752AA9bfc89C6865E22a40c446", true);
}
}
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ src = "contracts"
test = "test"
script = "script"
out = "out"
libs = ["node_modules"]
libs = ["node_modules", "lib"]
remappings = [
"@chainlink/=node_modules/@chainlink/",
"@eth-optimism/=node_modules/@chainlink/contracts/node_modules/@eth-optimism/",
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@nomad-xyz/excessively-safe-call": "github:nomad-xyz/ExcessivelySafeCall",
"@openzeppelin/contracts": "5.1.0",
"@openzeppelin/contracts-upgradeable": "5.1.0",
"solady": "^0.1.21",
"ts-morph": "^19.0.0"
},
"devDependencies": {
Expand Down
Loading