Skip to content

Commit ab85233

Browse files
committed
Paymaster refactoring
1 parent 9a1538d commit ab85233

File tree

6 files changed

+289
-42
lines changed

6 files changed

+289
-42
lines changed

script/deployChainAbstractionSetup.s.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
pragma solidity ^0.8.0;
33

44
import {Script, console} from "forge-std/Script.sol";
5-
import {IEntryPoint} from "account-abstraction/core/EntryPoint.sol";
65
import {IInvoiceManager} from "../src/interfaces/IInvoiceManager.sol";
76
import {IVaultManager} from "../src/interfaces/IVaultManager.sol";
7+
8+
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
89
import {CheckOrDeployEntryPoint} from "./auxiliary/checkOrDeployEntrypoint.sol";
910
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1011
import {UpgradeableOpenfortProxy} from "../src/proxy/UpgradeableOpenfortProxy.sol";
@@ -84,9 +85,9 @@ contract DeployChainAbstractionSetup is Script, CheckOrDeployEntryPoint {
8485
IEntryPoint entryPoint = checkOrDeployEntryPoint();
8586

8687
CABPaymaster paymaster = new CABPaymaster{salt: versionSalt}(
87-
entryPoint, IInvoiceManager(address(invoiceManager)), ICrossL2Prover(crossL2Prover), verifyingSigner, owner
88+
ICrossL2Prover(crossL2Prover), IInvoiceManager(address(invoiceManager)), verifyingSigner, owner
8889
);
89-
90+
paymaster.initialize(tokens);
9091
console.log("Paymaster Address", address(paymaster));
9192
vm.stopBroadcast();
9293
}

src/interfaces/IPaymasterVerifier.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,14 @@ interface IPaymasterVerifier {
3131
* @notice Withdraw the token.
3232
*/
3333
function withdraw(address token, uint256 amount) external;
34+
35+
/**
36+
* @notice Emitted when a CABPaymaster is initialized.
37+
* @param cabPaymaster The address of the CABPaymaster.
38+
* @param supportedTokensLength The length of the supported tokens.
39+
* @param supportedTokensHash The hash of the supported tokens.
40+
*/
41+
event CABPaymasterInitialized(
42+
address indexed cabPaymaster, uint256 indexed supportedTokensLength, bytes32 indexed supportedTokensHash
43+
);
3444
}

src/paymasters/CABPaymaster.sol

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,23 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
99
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
1010
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
1111
import {IInvoiceManager} from "../interfaces/IInvoiceManager.sol";
12+
import {LibTokens} from "./LibTokens.sol";
1213
import {IVault} from "../interfaces/IVault.sol";
1314
import {IPaymasterVerifier} from "../interfaces/IPaymasterVerifier.sol";
1415
import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/ICrossL2Prover.sol";
1516
import {LibBytes} from "@solady/utils/LibBytes.sol";
17+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
1618

1719
/**
1820
* @title CABPaymaster
1921
* @dev A paymaster used in chain abstracted balance to sponsor the gas fee and tokens cross-chain.
2022
*/
21-
contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
23+
contract CABPaymaster is IPaymasterVerifier, BasePaymaster, Initializable {
2224
using SafeERC20 for IERC20;
2325
using UserOperationLib for PackedUserOperation;
26+
using LibTokens for LibTokens.TokensStore;
27+
28+
LibTokens.TokensStore private tokensStore;
2429

2530
IInvoiceManager public immutable invoiceManager;
2631
ICrossL2Prover public immutable crossL2Prover;
@@ -30,20 +35,25 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
3035
uint256 private constant VALID_TIMESTAMP_OFFSET = PAYMASTER_DATA_OFFSET;
3136
uint256 private constant SIGNATURE_OFFSET = VALID_TIMESTAMP_OFFSET + 12;
3237

33-
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
38+
address public constant ENTRY_POINT_V7 = 0x0000000071727De22E5E9d8BAf0edAc6f37da032;
3439

3540
constructor(
36-
IEntryPoint _entryPoint,
37-
IInvoiceManager _invoiceManager,
3841
ICrossL2Prover _crossL2Prover,
42+
IInvoiceManager _invoiceManager,
3943
address _verifyingSigner,
4044
address _owner
41-
) BasePaymaster(_entryPoint, _owner) {
42-
invoiceManager = _invoiceManager;
45+
) BasePaymaster(IEntryPoint(ENTRY_POINT_V7), _owner) {
4346
crossL2Prover = _crossL2Prover;
47+
invoiceManager = _invoiceManager;
4448
verifyingSigner = _verifyingSigner;
4549
}
4650

51+
function initialize(address[] memory _supportedTokens) public initializer {
52+
for (uint256 i = 0; i < _supportedTokens.length; i++) {
53+
tokensStore.addSupportedToken(_supportedTokens[i]);
54+
}
55+
}
56+
4757
/// @inheritdoc IPaymasterVerifier
4858
function verifyInvoice(
4959
bytes32 _invoiceId,
@@ -67,7 +77,7 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
6777
}
6878

6979
function withdraw(address token, uint256 amount) external override onlyOwner {
70-
if (token == NATIVE_TOKEN) {
80+
if (token == LibTokens.NATIVE_TOKEN) {
7181
(bool success,) = payable(owner()).call{value: amount}("");
7282
require(success, "Native token transfer failed");
7383
} else {
@@ -104,18 +114,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
104114
);
105115
}
106116

107-
function getInvoiceHash(IInvoiceManager.InvoiceWithRepayTokens calldata invoice) public pure returns (bytes32) {
108-
return keccak256(
109-
abi.encode(
110-
invoice.account,
111-
invoice.nonce,
112-
invoice.paymaster,
113-
invoice.sponsorChainId,
114-
keccak256(abi.encode(invoice.repayTokenInfos))
115-
)
116-
);
117-
}
118-
119117
function _validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund)
120118
internal
121119
override
@@ -134,10 +132,12 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
134132
// revoke the approval at the end of userOp
135133
for (uint256 i = 0; i < sponsorTokenLength;) {
136134
address token = sponsorTokens[i].token;
135+
require(tokensStore.supported[token], "Unsupported token");
136+
137137
address spender = sponsorTokens[i].spender;
138138
uint256 amount = sponsorTokens[i].amount;
139139

140-
if (token == NATIVE_TOKEN) {
140+
if (token == LibTokens.NATIVE_TOKEN) {
141141
(bool success,) = payable(spender).call{value: amount}("");
142142
require(success, "Native token transfer failed");
143143
} else {
@@ -161,7 +161,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
161161
_packValidationData(true, validUntil, validAfter)
162162
);
163163
}
164-
165164
return (
166165
abi.encodePacked(invoiceId, sender, userOp.nonce, sponsorTokenData[0:1 + sponsorTokenLength * 72]),
167166
_packValidationData(false, validUntil, validAfter)
@@ -179,8 +178,8 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
179178
for (uint8 i = 0; i < sponsorTokenLength;) {
180179
address token = sponsorTokens[i].token;
181180
address spender = sponsorTokens[i].spender;
182-
if (token != NATIVE_TOKEN) {
183-
require(IERC20(token).approve(spender, 0), "Reset approval failed");
181+
if (token != LibTokens.NATIVE_TOKEN) {
182+
require(IERC20(token).approve(spender, 0), "CABPaymaster: Reset approval failed");
184183
}
185184
unchecked {
186185
i++;
@@ -189,7 +188,6 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
189188
// TODO: Batch Proving Optimistation -> write in settlement contract on `opSucceeded`
190189
if (mode == PostOpMode.opSucceeded) {
191190
//emit IInvoiceManager.InvoiceCreated(bytes32(context[:32]), address(bytes20(context[32:52])), address(this));
192-
193191
// This add ~= 100k gas compared to only emitting the InvoiceCreated event
194192
// Question: is storing the invoices onchain truly necessary?
195193
bytes32 invoiceId = bytes32(context[:32]);
@@ -281,5 +279,19 @@ contract CABPaymaster is IPaymasterVerifier, BasePaymaster {
281279
return abi.encodePacked(uint8(repayTokens.length), encodedRepayToken);
282280
}
283281

282+
function addSupportedToken(address token) public onlyOwner {
283+
tokensStore.addSupportedToken(token);
284+
emit LibTokens.SupportedTokenAdded(token);
285+
}
286+
287+
function removeSupportedToken(address token) public onlyOwner {
288+
tokensStore.removeSupportedToken(token);
289+
emit LibTokens.SupportedTokenRemoved(token);
290+
}
291+
292+
function getSupportedTokens() public view returns (address[] memory) {
293+
return tokensStore.getSupportedTokens();
294+
}
295+
284296
receive() external payable {}
285297
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import "account-abstraction/core/BasePaymaster.sol";
2+
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
3+
import {CABPaymaster} from "./CABPaymaster.sol";
4+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
5+
import {IInvoiceManager} from "../interfaces/IInvoiceManager.sol";
6+
import {ICrossL2Prover} from "@vibc-core-smart-contracts/contracts/interfaces/ICrossL2Prover.sol";
7+
8+
contract CABPaymasterFactory is Ownable {
9+
address public invoiceManager;
10+
address public crossL2Prover;
11+
address public verifyingSigner;
12+
13+
event CABPaymasterCreated(address indexed owner, address indexed cabPaymaster);
14+
15+
event InvoiceManagerUpdated(address indexed newInvoiceManager);
16+
event CrossL2ProverUpdated(address indexed newCrossL2Prover);
17+
event VerifyingSignerUpdated(address indexed newVerifyingSigner);
18+
19+
constructor(address _owner, address _crossL2Prover, address _invoiceManager, address _verifyingSigner)
20+
Ownable(_owner)
21+
{
22+
crossL2Prover = _crossL2Prover;
23+
invoiceManager = _invoiceManager;
24+
verifyingSigner = _verifyingSigner;
25+
}
26+
27+
/*
28+
* @notice Create a CABPaymaster with the given _owner and _salt.
29+
* @param _owner The owner of the CABPaymaster.
30+
* @param _nonce The nonce for the CABPaymaster.
31+
* @return cabPaymaster The address of the CABPaymaster.
32+
*/
33+
function createCABPaymaster(address _owner, bytes32 _nonce, address[] memory _supportedTokens)
34+
external
35+
returns (address cabPaymaster)
36+
{
37+
bytes32 salt = keccak256(abi.encode(_owner, _nonce));
38+
require(_owner != owner(), "CABPaymasterFactory: Wrong owner");
39+
cabPaymaster = getAddressWithNonce(_owner, _nonce);
40+
if (cabPaymaster.code.length > 0) return cabPaymaster;
41+
cabPaymaster = address(
42+
new CABPaymaster{salt: salt}(
43+
ICrossL2Prover(crossL2Prover), IInvoiceManager(invoiceManager), verifyingSigner, _owner
44+
)
45+
);
46+
47+
CABPaymaster(payable(cabPaymaster)).initialize(_supportedTokens);
48+
emit CABPaymasterCreated(_owner, cabPaymaster);
49+
}
50+
51+
/*
52+
* @notice Return the address of a CABPaymaster that would be deployed with the given _salt.
53+
*/
54+
function getAddressWithNonce(address _owner, bytes32 _nonce) public view returns (address) {
55+
bytes32 salt = keccak256(abi.encode(_owner, _nonce));
56+
return Create2.computeAddress(
57+
salt,
58+
keccak256(
59+
abi.encodePacked(
60+
type(CABPaymaster).creationCode, abi.encode(crossL2Prover, invoiceManager, verifyingSigner, _owner)
61+
)
62+
)
63+
);
64+
}
65+
66+
/*
67+
* @notice Update the invoice manager.
68+
* @param _invoiceManager The new invoice manager.
69+
*/
70+
function updateInvoiceManager(address _invoiceManager) public onlyOwner {
71+
require(_invoiceManager != address(0), "Invoice manager cannot be the zero address");
72+
invoiceManager = _invoiceManager;
73+
emit InvoiceManagerUpdated(_invoiceManager);
74+
}
75+
76+
/*
77+
* @notice Update the cross-chain prover.
78+
* @param _crossL2Prover The new cross-chain prover.
79+
*/
80+
function updateCrossL2Prover(address _crossL2Prover) public onlyOwner {
81+
require(_crossL2Prover != address(0), "Cross-chain prover cannot be the zero address");
82+
crossL2Prover = _crossL2Prover;
83+
emit CrossL2ProverUpdated(_crossL2Prover);
84+
}
85+
86+
/*
87+
* @notice Update the verifying signer.
88+
* @param _verifyingSigner The new verifying signer.
89+
*/
90+
function updateVerifyingSigner(address _verifyingSigner) public onlyOwner {
91+
require(_verifyingSigner != address(0), "Verifying signer cannot be the zero address");
92+
verifyingSigner = _verifyingSigner;
93+
emit VerifyingSignerUpdated(_verifyingSigner);
94+
}
95+
}

src/paymasters/LibTokens.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/access/Ownable.sol";
5+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6+
7+
library LibTokens {
8+
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
9+
10+
event SupportedTokenAdded(address token);
11+
event SupportedTokenRemoved(address token);
12+
13+
struct TokensStore {
14+
address[] tokens;
15+
mapping(address => bool) supported;
16+
}
17+
18+
function addSupportedToken(TokensStore storage store, address token) public {
19+
require(!store.supported[token], "TokenManager: token already supported");
20+
store.supported[token] = true;
21+
store.tokens.push(token);
22+
}
23+
24+
function removeSupportedToken(TokensStore storage store, address token) public {
25+
if (token == NATIVE_TOKEN) {
26+
require(address(this).balance == 0, "TokenManager: native token has balance");
27+
} else {
28+
uint256 balance = IERC20(token).balanceOf(address(this));
29+
require(balance == 0, "TokenManager: token has balance");
30+
}
31+
32+
uint256 length = store.tokens.length;
33+
for (uint256 i = 0; i < length;) {
34+
if (store.tokens[i] == token) {
35+
store.supported[token] = false;
36+
store.tokens[i] = store.tokens[length - 1];
37+
store.tokens.pop();
38+
break;
39+
}
40+
unchecked {
41+
i++;
42+
}
43+
}
44+
}
45+
46+
function getSupportedTokens(TokensStore storage store) public view returns (address[] memory) {
47+
return store.tokens;
48+
}
49+
}

0 commit comments

Comments
 (0)