@towns-protocol/contracts 0.0.383 → 0.0.385
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/scripts/deployments/diamonds/DeployAppRegistry.s.sol +22 -15
- package/scripts/deployments/diamonds/DeploySimpleAppBeacon.s.sol +149 -0
- package/scripts/deployments/diamonds/DeploySpaceFactory.s.sol +0 -1
- package/scripts/deployments/facets/DeployAppFactoryFacet.s.sol +14 -3
- package/scripts/interactions/InteractRegisterApp.s.sol +0 -1
- package/scripts/interactions/diamonds/InteractAppRegistry.s.sol +75 -0
- package/src/apps/BaseApp.sol +9 -6
- package/src/apps/ITownsApp.sol +4 -0
- package/src/apps/facets/factory/AppFactoryBase.sol +108 -0
- package/src/apps/facets/factory/AppFactoryFacet.sol +55 -19
- package/src/apps/facets/factory/AppFactoryStorage.sol +29 -0
- package/src/apps/facets/factory/IAppFactory.sol +46 -0
- package/src/apps/facets/registry/AppRegistryBase.sol +7 -20
- package/src/apps/facets/registry/IAppRegistry.sol +3 -2
- package/src/apps/facets/registry/LibAppRegistry.sol +33 -0
- package/src/apps/simple/account/ISimpleAccount.sol +40 -0
- package/src/apps/simple/account/SimpleAccountBase.sol +58 -0
- package/src/apps/simple/account/SimpleAccountFacet.sol +151 -0
- package/src/apps/simple/account/SimpleAccountStorage.sol +25 -0
- package/src/apps/{helpers → simple/app}/ISimpleApp.sol +20 -27
- package/src/apps/simple/app/SimpleAppFacet.sol +202 -0
- package/src/apps/{helpers → simple/app}/SimpleAppStorage.sol +1 -1
- package/scripts/deployments/facets/DeploySimpleApp.s.sol +0 -10
- package/src/apps/helpers/SimpleApp.sol +0 -123
|
@@ -3,26 +3,50 @@ pragma solidity ^0.8.29;
|
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
5
|
import {IAppFactory} from "./IAppFactory.sol";
|
|
6
|
-
import {ISimpleApp} from "../../helpers/ISimpleApp.sol";
|
|
7
6
|
import {ITownsApp} from "../../ITownsApp.sol";
|
|
8
7
|
|
|
9
8
|
// libraries
|
|
10
|
-
import {LibClone} from "solady/utils/LibClone.sol";
|
|
11
9
|
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
10
|
+
import {AppFactoryStorage} from "./AppFactoryStorage.sol";
|
|
12
11
|
|
|
13
12
|
// contracts
|
|
14
13
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
15
14
|
import {AppRegistryBase} from "../registry/AppRegistryBase.sol";
|
|
15
|
+
import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
|
|
16
16
|
import {ReentrancyGuardTransient} from "solady/utils/ReentrancyGuardTransient.sol";
|
|
17
|
+
import {AppFactoryBase} from "./AppFactoryBase.sol";
|
|
17
18
|
|
|
18
19
|
/// @title AppInstallerFacet
|
|
19
20
|
/// @author Towns Protocol
|
|
20
21
|
/// @notice Facet for installing apps to spaces
|
|
21
|
-
contract AppFactoryFacet is
|
|
22
|
+
contract AppFactoryFacet is
|
|
23
|
+
IAppFactory,
|
|
24
|
+
AppRegistryBase,
|
|
25
|
+
OwnableBase,
|
|
26
|
+
ReentrancyGuardTransient,
|
|
27
|
+
Facet,
|
|
28
|
+
AppFactoryBase
|
|
29
|
+
{
|
|
22
30
|
using CustomRevert for bytes4;
|
|
23
31
|
|
|
24
|
-
function __AppFactory_init(
|
|
32
|
+
function __AppFactory_init(
|
|
33
|
+
Beacon[] calldata beacons,
|
|
34
|
+
address entryPoint
|
|
35
|
+
) external onlyInitializing {
|
|
36
|
+
_addBeacons(beacons);
|
|
25
37
|
_addInterface(type(IAppFactory).interfaceId);
|
|
38
|
+
_setEntryPoint(entryPoint);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createAppByBeacon(
|
|
42
|
+
bytes32 beaconId,
|
|
43
|
+
AppParams calldata params
|
|
44
|
+
) external payable nonReentrant returns (address app, bytes32 appId) {
|
|
45
|
+
_validateParams(params);
|
|
46
|
+
|
|
47
|
+
app = _createApp(beaconId, params);
|
|
48
|
+
appId = _registerApp(ITownsApp(app), params.client);
|
|
49
|
+
emit AppCreated(app, appId);
|
|
26
50
|
}
|
|
27
51
|
|
|
28
52
|
/// @notice Create an upgradeable simple app contract
|
|
@@ -30,23 +54,35 @@ contract AppFactoryFacet is IAppFactory, AppRegistryBase, ReentrancyGuardTransie
|
|
|
30
54
|
function createApp(
|
|
31
55
|
AppParams calldata params
|
|
32
56
|
) external payable nonReentrant returns (address app, bytes32 appId) {
|
|
33
|
-
|
|
34
|
-
if (params.permissions.length == 0) AppFactory__InvalidArrayInput.selector.revertWith();
|
|
35
|
-
if (params.client == address(0)) AppFactory__InvalidAddressInput.selector.revertWith();
|
|
36
|
-
|
|
37
|
-
uint48 duration = _validateDuration(params.accessDuration);
|
|
38
|
-
|
|
39
|
-
app = LibClone.deployERC1967BeaconProxy(address(this));
|
|
40
|
-
ISimpleApp(app).initialize(
|
|
41
|
-
msg.sender,
|
|
42
|
-
params.name,
|
|
43
|
-
params.permissions,
|
|
44
|
-
params.installPrice,
|
|
45
|
-
duration,
|
|
46
|
-
params.client
|
|
47
|
-
);
|
|
57
|
+
_validateParams(params);
|
|
48
58
|
|
|
59
|
+
bytes32 beaconId = _getDefaultBeaconId();
|
|
60
|
+
app = _createApp(beaconId, params);
|
|
49
61
|
appId = _registerApp(ITownsApp(app), params.client);
|
|
50
62
|
emit AppCreated(app, appId);
|
|
51
63
|
}
|
|
64
|
+
|
|
65
|
+
function addBeacons(Beacon[] calldata beacons) external onlyOwner {
|
|
66
|
+
_addBeacons(beacons);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function removeBeacons(bytes32[] calldata beaconIds) external onlyOwner {
|
|
70
|
+
_removeBeacons(beaconIds);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setEntryPoint(address entryPoint) external onlyOwner {
|
|
74
|
+
_setEntryPoint(entryPoint);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getBeacon(bytes32 beaconId) external view returns (address beacon) {
|
|
78
|
+
return _getBeacon(beaconId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getBeacons() external view returns (bytes32[] memory beaconIds) {
|
|
82
|
+
return _getBeacons();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getEntryPoint() external view returns (address entryPoint) {
|
|
86
|
+
entryPoint = AppFactoryStorage.getLayout().entryPoint;
|
|
87
|
+
}
|
|
52
88
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
|
|
6
|
+
// libraries
|
|
7
|
+
import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
|
|
8
|
+
|
|
9
|
+
// contracts
|
|
10
|
+
|
|
11
|
+
library AppFactoryStorage {
|
|
12
|
+
using EnumerableSetLib for EnumerableSetLib.Bytes32Set;
|
|
13
|
+
|
|
14
|
+
// keccak256(abi.encode(uint256(keccak256("app.factory.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
15
|
+
bytes32 internal constant STORAGE_SLOT =
|
|
16
|
+
0xca64e2f4a307cd87bb6e650a2b64b61bb8e05eb19e155e37ac786fdc5c4f0500;
|
|
17
|
+
|
|
18
|
+
struct Layout {
|
|
19
|
+
address entryPoint;
|
|
20
|
+
EnumerableSetLib.Bytes32Set beaconIds;
|
|
21
|
+
mapping(bytes32 beaconId => address beacon) beacons;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getLayout() internal pure returns (Layout storage $) {
|
|
25
|
+
assembly {
|
|
26
|
+
$.slot := STORAGE_SLOT
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -16,12 +16,20 @@ interface IAppFactoryBase {
|
|
|
16
16
|
uint48 accessDuration;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
struct Beacon {
|
|
20
|
+
bytes32 beaconId;
|
|
21
|
+
address beacon;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
20
25
|
/* ERRORS */
|
|
21
26
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
22
27
|
error AppFactory__InvalidAppName();
|
|
23
28
|
error AppFactory__InvalidArrayInput();
|
|
24
29
|
error AppFactory__InvalidAddressInput();
|
|
30
|
+
error AppFactory__BeaconNotFound();
|
|
31
|
+
error AppFactory__InvalidBeaconId();
|
|
32
|
+
error AppFactory__BeaconAlreadyExists();
|
|
25
33
|
|
|
26
34
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
27
35
|
|
|
@@ -29,6 +37,9 @@ interface IAppFactoryBase {
|
|
|
29
37
|
/* EVENTS */
|
|
30
38
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
31
39
|
event AppCreated(address indexed app, bytes32 indexed uid);
|
|
40
|
+
event BeaconAdded(bytes32 indexed beaconId, address indexed beacon);
|
|
41
|
+
event BeaconRemoved(bytes32 indexed beaconId, address indexed beacon);
|
|
42
|
+
event EntryPointSet(address indexed oldEntryPoint, address indexed newEntryPoint);
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
interface IAppFactory is IAppFactoryBase {
|
|
@@ -39,4 +50,39 @@ interface IAppFactory is IAppFactoryBase {
|
|
|
39
50
|
function createApp(
|
|
40
51
|
AppParams calldata params
|
|
41
52
|
) external payable returns (address app, bytes32 appId);
|
|
53
|
+
|
|
54
|
+
/// @notice Create an app by beacon ID
|
|
55
|
+
/// @param beaconId The ID of the beacon to use for app deployment
|
|
56
|
+
/// @param params The parameters of the app
|
|
57
|
+
/// @return app The app address
|
|
58
|
+
/// @return appId The attestation UID of the registered app
|
|
59
|
+
function createAppByBeacon(
|
|
60
|
+
bytes32 beaconId,
|
|
61
|
+
AppParams calldata params
|
|
62
|
+
) external payable returns (address app, bytes32 appId);
|
|
63
|
+
|
|
64
|
+
/// @notice Add new beacon contracts for app deployment
|
|
65
|
+
/// @param beacons Array of beacon contracts to add with their IDs
|
|
66
|
+
function addBeacons(Beacon[] calldata beacons) external;
|
|
67
|
+
|
|
68
|
+
/// @notice Remove existing beacon contracts
|
|
69
|
+
/// @param beaconIds Array of beacon IDs to remove
|
|
70
|
+
function removeBeacons(bytes32[] calldata beaconIds) external;
|
|
71
|
+
|
|
72
|
+
/// @notice Get the beacon contract address for a given beacon ID
|
|
73
|
+
/// @param beaconId The ID of the beacon to look up
|
|
74
|
+
/// @return beacon The address of the beacon contract
|
|
75
|
+
function getBeacon(bytes32 beaconId) external view returns (address beacon);
|
|
76
|
+
|
|
77
|
+
/// @notice Get all registered beacon IDs
|
|
78
|
+
/// @return beaconIds Array of all registered beacon IDs
|
|
79
|
+
function getBeacons() external view returns (bytes32[] memory beaconIds);
|
|
80
|
+
|
|
81
|
+
/// @notice Set the entry point contract address for account abstraction
|
|
82
|
+
/// @param entryPoint The address of the entry point contract
|
|
83
|
+
function setEntryPoint(address entryPoint) external;
|
|
84
|
+
|
|
85
|
+
/// @notice Get the current entry point contract address
|
|
86
|
+
/// @return entryPoint The address of the entry point contract
|
|
87
|
+
function getEntryPoint() external view returns (address entryPoint);
|
|
42
88
|
}
|
|
@@ -8,7 +8,6 @@ import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
|
|
|
8
8
|
import {ITownsApp} from "../../ITownsApp.sol";
|
|
9
9
|
import {IAppRegistryBase} from "./IAppRegistry.sol";
|
|
10
10
|
import {ISchemaResolver} from "@ethereum-attestation-service/eas-contracts/resolver/ISchemaResolver.sol";
|
|
11
|
-
import {ISimpleApp} from "../../helpers/ISimpleApp.sol";
|
|
12
11
|
import {IPlatformRequirements} from "../../../factory/facets/platform/requirements/IPlatformRequirements.sol";
|
|
13
12
|
import {IERC173} from "@towns-protocol/diamond/src/facets/ownable/IERC173.sol";
|
|
14
13
|
import {IAppAccount} from "../../../spaces/facets/account/IAppAccount.sol";
|
|
@@ -20,6 +19,7 @@ import {AppRegistryStorage, ClientInfo, AppInfo} from "./AppRegistryStorage.sol"
|
|
|
20
19
|
import {LibClone} from "solady/utils/LibClone.sol";
|
|
21
20
|
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
|
|
22
21
|
import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
|
|
22
|
+
import {LibAppRegistry} from "./LibAppRegistry.sol";
|
|
23
23
|
|
|
24
24
|
// types
|
|
25
25
|
import {ExecutionManifest} from "@erc6900/reference-implementation/interfaces/IExecutionModule.sol";
|
|
@@ -33,8 +33,6 @@ import {AttestationBase} from "../attest/AttestationBase.sol";
|
|
|
33
33
|
abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBase {
|
|
34
34
|
using CustomRevert for bytes4;
|
|
35
35
|
|
|
36
|
-
uint48 private constant MAX_DURATION = 365 days;
|
|
37
|
-
|
|
38
36
|
modifier onlyAllowed(IAppAccount account) {
|
|
39
37
|
if (IERC173(address(account)).owner() != msg.sender) NotAllowed.selector.revertWith();
|
|
40
38
|
_;
|
|
@@ -99,10 +97,11 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
|
|
|
99
97
|
|
|
100
98
|
function _getAppDuration(address app) internal view returns (uint48 duration) {
|
|
101
99
|
try ITownsApp(app).accessDuration() returns (uint48 accessDuration) {
|
|
102
|
-
|
|
100
|
+
duration = LibAppRegistry.validateDuration(accessDuration);
|
|
103
101
|
} catch {
|
|
104
|
-
|
|
102
|
+
duration = LibAppRegistry.validateDuration(0);
|
|
105
103
|
}
|
|
104
|
+
return duration;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
/// @notice Retrieves detailed information about an app by its ID
|
|
@@ -389,18 +388,6 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
|
|
|
389
388
|
newVersionId = _attest(msg.sender, msg.value, request).uid;
|
|
390
389
|
}
|
|
391
390
|
|
|
392
|
-
function _validatePricing(uint256 price) internal view returns (uint256) {
|
|
393
|
-
uint256 minPlatformFee = _getPlatformRequirements().getMembershipFee();
|
|
394
|
-
if (price > 0 && price < minPlatformFee) InvalidPrice.selector.revertWith();
|
|
395
|
-
return price;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function _validateDuration(uint48 duration) internal pure returns (uint48) {
|
|
399
|
-
if (duration > MAX_DURATION) InvalidDuration.selector.revertWith();
|
|
400
|
-
if (duration == 0) duration = MAX_DURATION;
|
|
401
|
-
return duration;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
391
|
/// @notice Validates inputs for adding a new app
|
|
405
392
|
/// @param appContract The app contract to verify
|
|
406
393
|
/// @param client The client address to verify
|
|
@@ -434,8 +421,8 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
|
|
|
434
421
|
if (permissions.length == 0) InvalidArrayInput.selector.revertWith();
|
|
435
422
|
if (owner == address(0)) InvalidAddressInput.selector.revertWith();
|
|
436
423
|
|
|
437
|
-
|
|
438
|
-
|
|
424
|
+
installPrice = LibAppRegistry.validatePricing(_getPlatformRequirements(), installPrice);
|
|
425
|
+
accessDuration = LibAppRegistry.validateDuration(accessDuration);
|
|
439
426
|
|
|
440
427
|
if (
|
|
441
428
|
!IERC165(appAddress).supportsInterface(type(IModule).interfaceId) ||
|
|
@@ -445,7 +432,7 @@ abstract contract AppRegistryBase is IAppRegistryBase, SchemaBase, AttestationBa
|
|
|
445
432
|
AppDoesNotImplementInterface.selector.revertWith();
|
|
446
433
|
}
|
|
447
434
|
|
|
448
|
-
return (owner, permissions, manifest,
|
|
435
|
+
return (owner, permissions, manifest, accessDuration);
|
|
449
436
|
}
|
|
450
437
|
|
|
451
438
|
function _getPlatformRequirements() internal view returns (IPlatformRequirements) {
|
|
@@ -35,13 +35,14 @@ interface IAppRegistryBase {
|
|
|
35
35
|
error InvalidArrayInput();
|
|
36
36
|
error BannedApp();
|
|
37
37
|
error InvalidAppId();
|
|
38
|
-
error InvalidPrice();
|
|
39
|
-
error InvalidDuration();
|
|
40
38
|
error InsufficientPayment();
|
|
41
39
|
error NotAllowed();
|
|
42
40
|
error ClientAlreadyRegistered();
|
|
43
41
|
error ClientNotRegistered();
|
|
44
42
|
|
|
43
|
+
error AppRegistry__InvalidDuration();
|
|
44
|
+
error AppRegistry__InvalidPrice();
|
|
45
|
+
|
|
45
46
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
46
47
|
/* EVENTS */
|
|
47
48
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IPlatformRequirements} from "../../../factory/facets/platform/requirements/IPlatformRequirements.sol";
|
|
6
|
+
import {IAppRegistryBase} from "./IAppRegistry.sol";
|
|
7
|
+
|
|
8
|
+
// libraries
|
|
9
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
10
|
+
|
|
11
|
+
// contracts
|
|
12
|
+
|
|
13
|
+
library LibAppRegistry {
|
|
14
|
+
using CustomRevert for bytes4;
|
|
15
|
+
|
|
16
|
+
uint48 internal constant MAX_DURATION = 365 days;
|
|
17
|
+
|
|
18
|
+
function validateDuration(uint48 accessDuration) internal pure returns (uint48 duration) {
|
|
19
|
+
if (accessDuration > MAX_DURATION)
|
|
20
|
+
IAppRegistryBase.AppRegistry__InvalidDuration.selector.revertWith();
|
|
21
|
+
duration = accessDuration == 0 ? MAX_DURATION : accessDuration;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validatePricing(
|
|
25
|
+
IPlatformRequirements platformRequirements,
|
|
26
|
+
uint256 price
|
|
27
|
+
) internal view returns (uint256) {
|
|
28
|
+
uint256 minPlatformFee = platformRequirements.getMembershipFee();
|
|
29
|
+
if (price > 0 && price < minPlatformFee)
|
|
30
|
+
IAppRegistryBase.AppRegistry__InvalidPrice.selector.revertWith();
|
|
31
|
+
return price;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
|
|
6
|
+
|
|
7
|
+
// libraries
|
|
8
|
+
|
|
9
|
+
// contracts
|
|
10
|
+
|
|
11
|
+
interface ISimpleAccountBase {
|
|
12
|
+
error SimpleAccount__NotFromTrustedCaller();
|
|
13
|
+
error SimpleAccount__OpDataNotSupported();
|
|
14
|
+
|
|
15
|
+
/// @notice Emitted when the entry point is updated
|
|
16
|
+
/// @param oldEntryPoint The old entry point
|
|
17
|
+
/// @param newEntryPoint The new entry point
|
|
18
|
+
event EntryPointUpdated(address indexed oldEntryPoint, address indexed newEntryPoint);
|
|
19
|
+
|
|
20
|
+
/// @notice Emitted when the coordinator is updated
|
|
21
|
+
/// @param oldCoordinator The old coordinator
|
|
22
|
+
/// @param newCoordinator The new coordinator
|
|
23
|
+
event CoordinatorUpdated(address indexed oldCoordinator, address indexed newCoordinator);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ISimpleAccount is ISimpleAccountBase {
|
|
27
|
+
/// @notice Return the account nonce.
|
|
28
|
+
/// @dev This method returns the next sequential nonce.
|
|
29
|
+
/// @dev For a nonce of a specific key, use `entrypoint.getNonce(account, key)`
|
|
30
|
+
/// @return The next sequential nonce.
|
|
31
|
+
function getNonce() external view returns (uint256);
|
|
32
|
+
|
|
33
|
+
/// @notice Updates the entry point of the account
|
|
34
|
+
/// @param newEntryPoint The new entry point
|
|
35
|
+
function updateEntryPoint(address newEntryPoint) external;
|
|
36
|
+
|
|
37
|
+
/// @notice Updates the coordinator of the account
|
|
38
|
+
/// @param newCoordinator The new coordinator
|
|
39
|
+
function updateCoordinator(address newCoordinator) external;
|
|
40
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol";
|
|
6
|
+
import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
|
|
7
|
+
import {ISimpleAccountBase} from "./ISimpleAccount.sol";
|
|
8
|
+
|
|
9
|
+
// libraries
|
|
10
|
+
import {UserOperationLib, PackedUserOperation} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol";
|
|
11
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
12
|
+
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
|
|
13
|
+
|
|
14
|
+
/// @title SimpleAccountBase
|
|
15
|
+
/// @notice Base contract for simple accounts implementing core ERC-6900 account functionality
|
|
16
|
+
abstract contract SimpleAccountBase is IAccount, ISimpleAccountBase {
|
|
17
|
+
using UserOperationLib for PackedUserOperation;
|
|
18
|
+
using CustomRevert for bytes4;
|
|
19
|
+
|
|
20
|
+
/// @notice Return the entryPoint used by this account.
|
|
21
|
+
/// @dev Subclass should return the current entryPoint used by this account.
|
|
22
|
+
function entryPoint() public view virtual returns (IEntryPoint);
|
|
23
|
+
|
|
24
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
25
|
+
/* Hooks */
|
|
26
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
27
|
+
|
|
28
|
+
/// @dev Internal function to require the sender to be the entry point.
|
|
29
|
+
function _requireFromEntryPoint() internal view virtual {
|
|
30
|
+
if (msg.sender != address(entryPoint()))
|
|
31
|
+
SimpleAccount__NotFromTrustedCaller.selector.revertWith();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// @dev Internal function to require the sender to be the entry point.
|
|
35
|
+
function _requireForExecute() internal view virtual {
|
|
36
|
+
_requireFromEntryPoint();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// @dev Internal function to validate the signature of the user operation.
|
|
40
|
+
/// @param userOp The user operation to validate.
|
|
41
|
+
/// @param userOpHash The hash of the user operation.
|
|
42
|
+
/// @return validationData The validation data.
|
|
43
|
+
function _validateSignature(
|
|
44
|
+
PackedUserOperation calldata userOp,
|
|
45
|
+
bytes32 userOpHash
|
|
46
|
+
) internal virtual returns (uint256 validationData);
|
|
47
|
+
|
|
48
|
+
/// @dev Internal function to validate the nonce of the user operation.
|
|
49
|
+
/// @param nonce The nonce of the user operation.
|
|
50
|
+
function _validateNonce(uint256 nonce) internal view virtual {}
|
|
51
|
+
|
|
52
|
+
/// @dev Internal function to pay the prefund.
|
|
53
|
+
/// @param missingAccountFunds The missing account funds.
|
|
54
|
+
function _payPrefund(uint256 missingAccountFunds) internal virtual {
|
|
55
|
+
if (missingAccountFunds == 0) return;
|
|
56
|
+
SafeTransferLib.safeTransferETH(msg.sender, missingAccountFunds);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
import {IEntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol";
|
|
6
|
+
import {IAccount} from "@eth-infinitism/account-abstraction/interfaces/IAccount.sol";
|
|
7
|
+
import {ISimpleAccount} from "./ISimpleAccount.sol";
|
|
8
|
+
import {IERC7821} from "@openzeppelin/contracts/interfaces/draft-IERC7821.sol";
|
|
9
|
+
|
|
10
|
+
// contracts
|
|
11
|
+
import {SimpleAccountBase} from "./SimpleAccountBase.sol";
|
|
12
|
+
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
13
|
+
import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
|
|
14
|
+
import {ERC7821} from "solady/accounts/ERC7821.sol";
|
|
15
|
+
|
|
16
|
+
// libraries
|
|
17
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
18
|
+
import {UserOperationLib, PackedUserOperation} from "@eth-infinitism/account-abstraction/core/UserOperationLib.sol";
|
|
19
|
+
import {ECDSA} from "solady/utils/ECDSA.sol";
|
|
20
|
+
import {LibBit} from "solady/utils/LibBit.sol";
|
|
21
|
+
import {SIG_VALIDATION_FAILED, SIG_VALIDATION_SUCCESS} from "@eth-infinitism/account-abstraction/core/Helpers.sol";
|
|
22
|
+
import {SimpleAppStorage} from "../app/SimpleAppStorage.sol";
|
|
23
|
+
import {SimpleAccountStorage} from "./SimpleAccountStorage.sol";
|
|
24
|
+
import {Validator} from "../../../utils/libraries/Validator.sol";
|
|
25
|
+
|
|
26
|
+
contract SimpleAccountFacet is ISimpleAccount, SimpleAccountBase, OwnableBase, ERC7821, Facet {
|
|
27
|
+
using CustomRevert for bytes4;
|
|
28
|
+
using UserOperationLib for PackedUserOperation;
|
|
29
|
+
|
|
30
|
+
function __SimpleAccountFacet_init(
|
|
31
|
+
address entrypoint,
|
|
32
|
+
address coordinator
|
|
33
|
+
) external onlyInitializing {
|
|
34
|
+
Validator.checkAddress(entrypoint);
|
|
35
|
+
Validator.checkAddress(coordinator);
|
|
36
|
+
__SimpleAccountFacet_init_unchained(entrypoint, coordinator);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function __SimpleAccountFacet_init_unchained(address entrypoint, address coordinator) internal {
|
|
40
|
+
SimpleAccountStorage.Layout storage $ = SimpleAccountStorage.getLayout();
|
|
41
|
+
$.entryPoint = entrypoint;
|
|
42
|
+
$.coordinator = coordinator;
|
|
43
|
+
_addInterface(type(IAccount).interfaceId);
|
|
44
|
+
_addInterface(type(ISimpleAccount).interfaceId);
|
|
45
|
+
_addInterface(type(IERC7821).interfaceId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// @inheritdoc ISimpleAccount
|
|
49
|
+
function updateEntryPoint(address newEntryPoint) external onlyOwner {
|
|
50
|
+
Validator.checkAddress(newEntryPoint);
|
|
51
|
+
SimpleAccountStorage.Layout storage $ = SimpleAccountStorage.getLayout();
|
|
52
|
+
address oldEntryPoint = $.entryPoint;
|
|
53
|
+
$.entryPoint = newEntryPoint;
|
|
54
|
+
emit EntryPointUpdated(oldEntryPoint, newEntryPoint);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// @inheritdoc ISimpleAccount
|
|
58
|
+
function updateCoordinator(address newCoordinator) external onlyOwner {
|
|
59
|
+
Validator.checkAddress(newCoordinator);
|
|
60
|
+
SimpleAccountStorage.Layout storage $ = SimpleAccountStorage.getLayout();
|
|
61
|
+
address oldCoordinator = $.coordinator;
|
|
62
|
+
$.coordinator = newCoordinator;
|
|
63
|
+
emit CoordinatorUpdated(oldCoordinator, newCoordinator);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// @notice Return the account nonce.
|
|
67
|
+
/// @dev This method returns the next sequential nonce.
|
|
68
|
+
/// @dev For a nonce of a specific key, use `entrypoint.getNonce(account, key)`
|
|
69
|
+
/// @return The next sequential nonce.
|
|
70
|
+
function getNonce() public view virtual returns (uint256) {
|
|
71
|
+
return entryPoint().getNonce(address(this), 0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// @notice Return the entryPoint used by this account.
|
|
75
|
+
function entryPoint() public view override(SimpleAccountBase) returns (IEntryPoint) {
|
|
76
|
+
return IEntryPoint(SimpleAccountStorage.getLayout().entryPoint);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// @inheritdoc IAccount
|
|
80
|
+
function validateUserOp(
|
|
81
|
+
PackedUserOperation calldata userOp,
|
|
82
|
+
bytes32 userOpHash,
|
|
83
|
+
uint256 missingAccountFunds
|
|
84
|
+
) external virtual override returns (uint256 validationData) {
|
|
85
|
+
_requireFromEntryPoint();
|
|
86
|
+
validationData = _validateSignature(userOp, userOpHash);
|
|
87
|
+
_validateNonce(userOp.nonce);
|
|
88
|
+
_payPrefund(missingAccountFunds);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
92
|
+
/* INTERNAL FUNCTIONS */
|
|
93
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
94
|
+
function _getCoordinator() internal view returns (address) {
|
|
95
|
+
return SimpleAccountStorage.getLayout().coordinator;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
99
|
+
/* Overrides */
|
|
100
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
101
|
+
/// @dev Override the 4-parameter execution to add custom authorization before the standard checks
|
|
102
|
+
function _execute(
|
|
103
|
+
bytes32,
|
|
104
|
+
bytes calldata,
|
|
105
|
+
Call[] calldata calls,
|
|
106
|
+
bytes calldata opData
|
|
107
|
+
) internal virtual override {
|
|
108
|
+
if (opData.length == 0) {
|
|
109
|
+
_requireForExecute();
|
|
110
|
+
return _execute(calls, bytes32(0));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
SimpleAccount__OpDataNotSupported.selector.revertWith();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// @notice Override the _requireForExecute function to allow the owner, client, coordinator, entry point, and self-calls.
|
|
117
|
+
function _requireForExecute() internal view override {
|
|
118
|
+
// Early return for owner
|
|
119
|
+
if (msg.sender == _owner()) return;
|
|
120
|
+
|
|
121
|
+
SimpleAppStorage.Layout storage $ = SimpleAppStorage.getLayout();
|
|
122
|
+
SimpleAccountStorage.Layout storage $$ = SimpleAccountStorage.getLayout();
|
|
123
|
+
|
|
124
|
+
// Use LibBit.or for efficient multi-condition check
|
|
125
|
+
if (
|
|
126
|
+
LibBit.or(
|
|
127
|
+
LibBit.or(msg.sender == $.client, msg.sender == $$.coordinator),
|
|
128
|
+
LibBit.or(msg.sender == $$.entryPoint, msg.sender == address(this))
|
|
129
|
+
)
|
|
130
|
+
) return;
|
|
131
|
+
|
|
132
|
+
SimpleAccount__NotFromTrustedCaller.selector.revertWith();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function _validateSignature(
|
|
136
|
+
PackedUserOperation calldata userOp,
|
|
137
|
+
bytes32 userOpHash
|
|
138
|
+
) internal virtual override returns (uint256 validationData) {
|
|
139
|
+
address signer = ECDSA.recoverCalldata(userOpHash, userOp.signature);
|
|
140
|
+
address owner = _owner();
|
|
141
|
+
|
|
142
|
+
// Early return for owner
|
|
143
|
+
if (signer == owner) return SIG_VALIDATION_SUCCESS;
|
|
144
|
+
|
|
145
|
+
// Check client using LibBit for consistency
|
|
146
|
+
SimpleAppStorage.Layout storage $ = SimpleAppStorage.getLayout();
|
|
147
|
+
if (signer == $.client) return SIG_VALIDATION_SUCCESS;
|
|
148
|
+
|
|
149
|
+
return SIG_VALIDATION_FAILED;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.29;
|
|
3
|
+
|
|
4
|
+
// interfaces
|
|
5
|
+
|
|
6
|
+
// libraries
|
|
7
|
+
|
|
8
|
+
// contracts
|
|
9
|
+
|
|
10
|
+
library SimpleAccountStorage {
|
|
11
|
+
// keccak256(abi.encode(uint256(keccak256("simple.account.storage")) - 1)) & ~bytes32(uint256(0xff))
|
|
12
|
+
bytes32 internal constant STORAGE_SLOT =
|
|
13
|
+
0x50b1c084b04d93141a198030532d2108cf8654a43d025af0de681aa06dac5f00;
|
|
14
|
+
|
|
15
|
+
struct Layout {
|
|
16
|
+
address entryPoint;
|
|
17
|
+
address coordinator;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getLayout() internal pure returns (Layout storage $) {
|
|
21
|
+
assembly ("memory-safe") {
|
|
22
|
+
$.slot := STORAGE_SLOT
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|