@bloxchain/contracts 1.0.0-alpha → 1.0.0-alpha.10
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/README.md +55 -18
- package/abi/{ControlBlox.abi.json → AccountBlox.abi.json} +699 -2974
- package/abi/BareBlox.abi.json +127 -90
- package/abi/BaseStateMachine.abi.json +127 -90
- package/abi/EngineBlox.abi.json +11 -31
- package/abi/GuardController.abi.json +217 -895
- package/abi/GuardControllerDefinitions.abi.json +380 -0
- package/abi/IDefinition.abi.json +19 -0
- package/abi/RoleBlox.abi.json +818 -2404
- package/abi/RuntimeRBAC.abi.json +122 -328
- package/abi/RuntimeRBACDefinitions.abi.json +243 -0
- package/abi/SecureBlox.abi.json +620 -1952
- package/abi/SecureOwnable.abi.json +469 -1801
- package/abi/SecureOwnableDefinitions.abi.json +57 -0
- package/abi/SimpleRWA20.abi.json +486 -1999
- package/abi/SimpleRWA20Definitions.abi.json +19 -0
- package/abi/SimpleVault.abi.json +884 -2685
- package/abi/SimpleVaultDefinitions.abi.json +19 -0
- package/components/README.md +8 -0
- package/core/access/RuntimeRBAC.sol +184 -0
- package/core/access/interface/IRuntimeRBAC.sol +55 -0
- package/{contracts/core → core}/access/lib/definitions/RuntimeRBACDefinitions.sol +121 -1
- package/{contracts/core → core}/base/BaseStateMachine.sol +187 -54
- package/{contracts/core → core}/base/interface/IBaseStateMachine.sol +7 -0
- package/{contracts/core → core}/execution/GuardController.sol +89 -155
- package/{contracts/core → core}/execution/interface/IGuardController.sol +52 -12
- package/{contracts/core → core}/execution/lib/definitions/GuardControllerDefinitions.sol +91 -2
- package/{contracts/core → core}/lib/EngineBlox.sol +167 -64
- package/{contracts → core/lib}/interfaces/IDefinition.sol +15 -6
- package/{contracts → core/lib}/interfaces/IEventForwarder.sol +1 -1
- package/{contracts → core/lib}/utils/SharedValidation.sol +490 -486
- package/core/pattern/Account.sol +75 -0
- package/core/research/BloxchainWallet.sol +133 -0
- package/core/research/FactoryBlox/FactoryBlox.sol +344 -0
- package/core/research/FactoryBlox/FactoryBloxDefinitions.sol +144 -0
- package/core/research/erc1155-blox/ERC1155Blox.sol +170 -0
- package/core/research/erc1155-blox/lib/definitions/ERC1155BloxDefinitions.sol +203 -0
- package/core/research/erc20-blox/ERC20Blox.sol +135 -0
- package/core/research/erc20-blox/lib/definitions/ERC20BloxDefinitions.sol +185 -0
- package/core/research/erc721-blox/ERC721Blox.sol +131 -0
- package/core/research/erc721-blox/lib/definitions/ERC721BloxDefinitions.sol +172 -0
- package/core/research/lending-blox/.gitkeep +1 -0
- package/core/research/p2p-blox/P2PBlox.sol +266 -0
- package/core/research/p2p-blox/README.md +85 -0
- package/core/research/p2p-blox/lib/definitions/P2PBloxDefinitions.sol +19 -0
- package/{contracts/core → core}/security/SecureOwnable.sol +390 -419
- package/{contracts/core → core}/security/interface/ISecureOwnable.sol +27 -40
- package/{contracts/core → core}/security/lib/definitions/SecureOwnableDefinitions.sol +786 -757
- package/package.json +49 -47
- package/standards/README.md +12 -0
- package/standards/behavior/ICopyable.sol +36 -0
- package/standards/hooks/IOnActionHook.sol +21 -0
- package/contracts/core/access/RuntimeRBAC.sol +0 -344
- package/contracts/core/access/interface/IRuntimeRBAC.sol +0 -108
- package/contracts/interfaces/IOnActionHook.sol +0 -79
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
pragma solidity 0.8.33;
|
|
3
|
+
|
|
4
|
+
import "../execution/GuardController.sol";
|
|
5
|
+
import "../access/RuntimeRBAC.sol";
|
|
6
|
+
import "../security/SecureOwnable.sol";
|
|
7
|
+
import "../lib/utils/SharedValidation.sol";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @title Account
|
|
11
|
+
* @dev Abstract account pattern combining GuardController, RuntimeRBAC, and SecureOwnable.
|
|
12
|
+
*
|
|
13
|
+
* Use this as the base for account-style contracts (e.g. AccountBlox) to avoid duplicating
|
|
14
|
+
* initialization, interface support, and receive/fallback boilerplate.
|
|
15
|
+
*
|
|
16
|
+
* Combines:
|
|
17
|
+
* - GuardController: Execution workflows and time-locked transactions
|
|
18
|
+
* - RuntimeRBAC: Runtime role creation and management
|
|
19
|
+
* - SecureOwnable: Secure ownership transfer and management
|
|
20
|
+
*
|
|
21
|
+
* @custom:security-contact security@particlecs.com
|
|
22
|
+
*/
|
|
23
|
+
abstract contract Account is GuardController, RuntimeRBAC, SecureOwnable {
|
|
24
|
+
/**
|
|
25
|
+
* @dev Emitted when plain ETH is received (receive()).
|
|
26
|
+
* @param sender Address that sent the ETH
|
|
27
|
+
* @param value Amount of wei received
|
|
28
|
+
* @custom:security Gas-efficient so receive() stays within 2,300 gas stipend (transfer/send compatible).
|
|
29
|
+
*/
|
|
30
|
+
event EthReceived(address indexed sender, uint256 value);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @notice Initializer for the Account pattern (GuardController + RuntimeRBAC + SecureOwnable).
|
|
34
|
+
* @param initialOwner The initial owner address
|
|
35
|
+
* @param broadcaster The broadcaster address
|
|
36
|
+
* @param recovery The recovery address
|
|
37
|
+
* @param timeLockPeriodSec The timelock period in seconds
|
|
38
|
+
* @param eventForwarder The event forwarder address (optional)
|
|
39
|
+
*/
|
|
40
|
+
function initialize(
|
|
41
|
+
address initialOwner,
|
|
42
|
+
address broadcaster,
|
|
43
|
+
address recovery,
|
|
44
|
+
uint256 timeLockPeriodSec,
|
|
45
|
+
address eventForwarder
|
|
46
|
+
) public virtual override(GuardController, RuntimeRBAC, SecureOwnable) onlyInitializing {
|
|
47
|
+
GuardController.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
48
|
+
RuntimeRBAC.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
49
|
+
SecureOwnable.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @dev See {IERC165-supportsInterface}.
|
|
54
|
+
*/
|
|
55
|
+
function supportsInterface(bytes4 interfaceId) public view virtual override(GuardController, RuntimeRBAC, SecureOwnable) returns (bool) {
|
|
56
|
+
return GuardController.supportsInterface(interfaceId) || RuntimeRBAC.supportsInterface(interfaceId) || SecureOwnable.supportsInterface(interfaceId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @dev Accepts plain ETH transfers (no calldata).
|
|
61
|
+
* @notice General-use wallet: ETH can be sent naturally; balance is credited.
|
|
62
|
+
* @custom:security No external calls—reentrancy-safe; outgoing ETH only via GuardController execution. Uses simple emit to stay within 2,300 gas stipend (transfer/send compatible).
|
|
63
|
+
*/
|
|
64
|
+
receive() external payable virtual {
|
|
65
|
+
emit EthReceived(msg.sender, msg.value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @dev Rejects calls with unknown selector (with or without value).
|
|
70
|
+
* @notice Only plain transfers hit receive(); all other calls revert.
|
|
71
|
+
*/
|
|
72
|
+
fallback() external payable virtual {
|
|
73
|
+
revert SharedValidation.NotSupported();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2025 Particle Crypto Security
|
|
3
|
+
pragma solidity 0.8.33;
|
|
4
|
+
|
|
5
|
+
import "../pattern/Account.sol";
|
|
6
|
+
import "../base/BaseStateMachine.sol";
|
|
7
|
+
import "../lib/utils/SharedValidation.sol";
|
|
8
|
+
import "../lib/interfaces/IDefinition.sol";
|
|
9
|
+
import "../../standards/behavior/ICopyable.sol";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @title BloxchainWallet
|
|
13
|
+
* @dev Official ParticleCS wallet controller built on Bloxchain Protocol.
|
|
14
|
+
*
|
|
15
|
+
* Extends the Account pattern (GuardController + RuntimeRBAC + SecureOwnable) with
|
|
16
|
+
* timelock bounds, optional roles/definitions init, and ICopyable for factory cloning.
|
|
17
|
+
*/
|
|
18
|
+
contract BloxchainWallet is Account, ICopyable {
|
|
19
|
+
uint256 public constant MIN_TIME_LOCK_PERIOD = 1 days;
|
|
20
|
+
uint256 public constant MAX_TIME_LOCK_PERIOD = 90 days;
|
|
21
|
+
uint256 public constant MAX_DEFINITION_CONTRACTS = 50;
|
|
22
|
+
uint256 public constant MAX_INITIAL_ROLES = 50;
|
|
23
|
+
uint256 public constant MAX_SCHEMAS_PER_DEFINITION = 100;
|
|
24
|
+
uint256 public constant MAX_PERMISSIONS_PER_DEFINITION = 200;
|
|
25
|
+
|
|
26
|
+
bool private _cloneDataSet;
|
|
27
|
+
|
|
28
|
+
struct RoleConfig {
|
|
29
|
+
string roleName;
|
|
30
|
+
uint256 maxWallets;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function initialize(
|
|
34
|
+
address initialOwner,
|
|
35
|
+
address broadcaster,
|
|
36
|
+
address recovery,
|
|
37
|
+
uint256 timeLockPeriodSec,
|
|
38
|
+
address eventForwarder
|
|
39
|
+
) public virtual override(Account) initializer {
|
|
40
|
+
_initializeBase(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function initializeWithData(
|
|
44
|
+
address initialOwner,
|
|
45
|
+
address broadcaster,
|
|
46
|
+
address recovery,
|
|
47
|
+
uint256 timeLockPeriodSec,
|
|
48
|
+
address eventForwarder,
|
|
49
|
+
bytes calldata initData
|
|
50
|
+
) external initializer {
|
|
51
|
+
_initializeBase(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
52
|
+
if (initData.length > 0) {
|
|
53
|
+
(RoleConfig[] memory roles, IDefinition[] memory definitionContracts) =
|
|
54
|
+
abi.decode(initData, (RoleConfig[], IDefinition[]));
|
|
55
|
+
_applyRolesAndDefinitions(roles, definitionContracts);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _initializeBase(
|
|
60
|
+
address initialOwner,
|
|
61
|
+
address broadcaster,
|
|
62
|
+
address recovery,
|
|
63
|
+
uint256 timeLockPeriodSec,
|
|
64
|
+
address eventForwarder
|
|
65
|
+
) internal {
|
|
66
|
+
_validateTimeLockPeriod(timeLockPeriodSec);
|
|
67
|
+
super.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function setCloneData(bytes calldata initData) external {
|
|
71
|
+
if (!initialized()) revert SharedValidation.NotInitialized();
|
|
72
|
+
if (_cloneDataSet) revert SharedValidation.AlreadyInitialized();
|
|
73
|
+
_cloneDataSet = true;
|
|
74
|
+
(RoleConfig[] memory roles, IDefinition[] memory definitionContracts) =
|
|
75
|
+
abi.decode(initData, (RoleConfig[], IDefinition[]));
|
|
76
|
+
_applyRolesAndDefinitions(roles, definitionContracts);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function supportsInterface(bytes4 interfaceId)
|
|
80
|
+
public
|
|
81
|
+
view
|
|
82
|
+
virtual
|
|
83
|
+
override(Account)
|
|
84
|
+
returns (bool)
|
|
85
|
+
{
|
|
86
|
+
return interfaceId == type(ICopyable).interfaceId || super.supportsInterface(interfaceId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function _updateTimeLockPeriod(uint256 newTimeLockPeriodSec)
|
|
90
|
+
internal
|
|
91
|
+
virtual
|
|
92
|
+
override(BaseStateMachine)
|
|
93
|
+
{
|
|
94
|
+
_validateTimeLockPeriod(newTimeLockPeriodSec);
|
|
95
|
+
super._updateTimeLockPeriod(newTimeLockPeriodSec);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _validateTimeLockPeriod(uint256 timeLockPeriodSec) internal pure {
|
|
99
|
+
if (timeLockPeriodSec < MIN_TIME_LOCK_PERIOD || timeLockPeriodSec > MAX_TIME_LOCK_PERIOD) {
|
|
100
|
+
revert SharedValidation.InvalidTimeLockPeriod(timeLockPeriodSec);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function _applyRolesAndDefinitions(
|
|
105
|
+
RoleConfig[] memory roles,
|
|
106
|
+
IDefinition[] memory definitionContracts
|
|
107
|
+
) internal {
|
|
108
|
+
if (roles.length > MAX_INITIAL_ROLES) {
|
|
109
|
+
revert SharedValidation.BatchSizeExceeded(roles.length, MAX_INITIAL_ROLES);
|
|
110
|
+
}
|
|
111
|
+
for (uint256 i = 0; i < roles.length; i++) {
|
|
112
|
+
_createRole(roles[i].roleName, roles[i].maxWallets, false);
|
|
113
|
+
}
|
|
114
|
+
if (definitionContracts.length > MAX_DEFINITION_CONTRACTS) {
|
|
115
|
+
revert SharedValidation.BatchSizeExceeded(definitionContracts.length, MAX_DEFINITION_CONTRACTS);
|
|
116
|
+
}
|
|
117
|
+
for (uint256 i = 0; i < definitionContracts.length; i++) {
|
|
118
|
+
address def = address(definitionContracts[i]);
|
|
119
|
+
if (!definitionContracts[i].supportsInterface(type(IDefinition).interfaceId)) {
|
|
120
|
+
revert SharedValidation.DefinitionNotIDefinition(def);
|
|
121
|
+
}
|
|
122
|
+
EngineBlox.FunctionSchema[] memory schemas = definitionContracts[i].getFunctionSchemas();
|
|
123
|
+
IDefinition.RolePermission memory permissions = definitionContracts[i].getRolePermissions();
|
|
124
|
+
if (schemas.length > MAX_SCHEMAS_PER_DEFINITION) {
|
|
125
|
+
revert SharedValidation.BatchSizeExceeded(schemas.length, MAX_SCHEMAS_PER_DEFINITION);
|
|
126
|
+
}
|
|
127
|
+
if (permissions.roleHashes.length > MAX_PERMISSIONS_PER_DEFINITION) {
|
|
128
|
+
revert SharedValidation.BatchSizeExceeded(permissions.roleHashes.length, MAX_PERMISSIONS_PER_DEFINITION);
|
|
129
|
+
}
|
|
130
|
+
_loadDefinitions(schemas, permissions.roleHashes, permissions.functionPermissions, false);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2025 Particle Crypto Security
|
|
3
|
+
pragma solidity 0.8.33;
|
|
4
|
+
|
|
5
|
+
import "@openzeppelin/contracts/proxy/Clones.sol";
|
|
6
|
+
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
|
|
7
|
+
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
8
|
+
import "../../execution/GuardController.sol";
|
|
9
|
+
import "../../access/RuntimeRBAC.sol";
|
|
10
|
+
import "../../security/SecureOwnable.sol";
|
|
11
|
+
import "../../base/interface/IBaseStateMachine.sol";
|
|
12
|
+
import "../../lib/EngineBlox.sol";
|
|
13
|
+
import "../../../standards/behavior/ICopyable.sol";
|
|
14
|
+
import "../../lib/interfaces/IDefinition.sol";
|
|
15
|
+
import "../../lib/interfaces/IEventForwarder.sol";
|
|
16
|
+
import "../../lib/utils/SharedValidation.sol";
|
|
17
|
+
import "./FactoryBloxDefinitions.sol";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @title FactoryBlox
|
|
21
|
+
* @dev CopyBlox-style blox factory with RBAC and GuardController like AccountBlox
|
|
22
|
+
*
|
|
23
|
+
* Combines:
|
|
24
|
+
* - GuardController: Execution workflows and time-locked transactions
|
|
25
|
+
* - RuntimeRBAC: Runtime role creation and management
|
|
26
|
+
* - SecureOwnable: Secure ownership transfer and management
|
|
27
|
+
* - Clone factory: Clone blox contracts (EIP-1167) with RBAC-protected cloneBlox
|
|
28
|
+
* - IEventForwarder: Centralize events from clones
|
|
29
|
+
*
|
|
30
|
+
* Only wallets with clone permission (OWNER_ROLE by default; custom roles via RuntimeRBAC)
|
|
31
|
+
* can call cloneBlox. Clones are initialized with eventForwarder set to this contract.
|
|
32
|
+
* When initData is non-empty, the implementation must support ICopyable and is initialized
|
|
33
|
+
* via initializeWithData; otherwise initialize(address,address,address,uint256,address) is used.
|
|
34
|
+
*
|
|
35
|
+
* clonesWhitelist: lightweight set of trusted blox implementation addresses. Only addresses in the
|
|
36
|
+
* whitelist can be cloned via cloneBlox, giving certainty about deploying the same contract.
|
|
37
|
+
* addToWhitelist and removeFromWhitelist are controller-only (macro + _validateExecuteBySelf).
|
|
38
|
+
*/
|
|
39
|
+
contract FactoryBlox is GuardController, RuntimeRBAC, SecureOwnable, IEventForwarder {
|
|
40
|
+
using Clones for address;
|
|
41
|
+
using ERC165Checker for address;
|
|
42
|
+
using EnumerableSet for EnumerableSet.AddressSet;
|
|
43
|
+
|
|
44
|
+
EnumerableSet.AddressSet private _clones;
|
|
45
|
+
/// @dev Trusted blox implementation addresses; only these can be cloned via cloneBlox
|
|
46
|
+
EnumerableSet.AddressSet private _clonesWhitelist;
|
|
47
|
+
/// @notice Whitelist entry containing implementation address and its configured clone price
|
|
48
|
+
struct CloneWhitelistEntry {
|
|
49
|
+
address bloxImplementation;
|
|
50
|
+
EngineBlox.PaymentDetails price;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// @dev Per-implementation clone pricing configuration
|
|
54
|
+
mapping(address => EngineBlox.PaymentDetails) private _clonePrices;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @notice Initializer for FactoryBlox
|
|
58
|
+
*/
|
|
59
|
+
function initialize(
|
|
60
|
+
address initialOwner,
|
|
61
|
+
address broadcaster,
|
|
62
|
+
address recovery,
|
|
63
|
+
uint256 timeLockPeriodSec,
|
|
64
|
+
address eventForwarder
|
|
65
|
+
) public virtual override(GuardController, RuntimeRBAC, SecureOwnable) initializer {
|
|
66
|
+
GuardController.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
67
|
+
RuntimeRBAC.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
68
|
+
SecureOwnable.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
69
|
+
|
|
70
|
+
IDefinition.RolePermission memory factoryPermissions = FactoryBloxDefinitions.getRolePermissions();
|
|
71
|
+
_loadDefinitions(
|
|
72
|
+
FactoryBloxDefinitions.getFunctionSchemas(),
|
|
73
|
+
factoryPermissions.roleHashes,
|
|
74
|
+
factoryPermissions.functionPermissions,
|
|
75
|
+
true
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Register macro selectors so controller can target address(this) for time-lock/meta-tx execution
|
|
79
|
+
_addMacroSelector(FactoryBloxDefinitions.CLONE_BLOX_SELECTOR);
|
|
80
|
+
_addMacroSelector(FactoryBloxDefinitions.ADD_TO_WHITELIST_SELECTOR);
|
|
81
|
+
_addMacroSelector(FactoryBloxDefinitions.REMOVE_FROM_WHITELIST_SELECTOR);
|
|
82
|
+
|
|
83
|
+
// Allow cloning this factory's implementation (or proxy) by default
|
|
84
|
+
_clonesWhitelist.add(address(this));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function supportsInterface(bytes4 interfaceId)
|
|
88
|
+
public
|
|
89
|
+
view
|
|
90
|
+
virtual
|
|
91
|
+
override(GuardController, RuntimeRBAC, SecureOwnable)
|
|
92
|
+
returns (bool)
|
|
93
|
+
{
|
|
94
|
+
return interfaceId == type(IEventForwarder).interfaceId
|
|
95
|
+
|| GuardController.supportsInterface(interfaceId)
|
|
96
|
+
|| RuntimeRBAC.supportsInterface(interfaceId)
|
|
97
|
+
|| SecureOwnable.supportsInterface(interfaceId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @notice Clone a blox contract (RBAC-protected). Caller must have clone permission (e.g. OWNER_ROLE).
|
|
102
|
+
* @param initData When non-empty, target must implement ICopyable and is initialized via initializeWithData; otherwise standard initialize(...) is used.
|
|
103
|
+
*/
|
|
104
|
+
function cloneBlox(
|
|
105
|
+
address bloxAddress,
|
|
106
|
+
address initialOwner,
|
|
107
|
+
address broadcaster,
|
|
108
|
+
address recovery,
|
|
109
|
+
uint256 timeLockPeriodSec,
|
|
110
|
+
bytes calldata initData
|
|
111
|
+
) external nonReentrant returns (address cloneAddress) {
|
|
112
|
+
_validateExecuteBySelf();
|
|
113
|
+
_validateBloxImplementation(bloxAddress);
|
|
114
|
+
|
|
115
|
+
if (initData.length > 0 && !bloxAddress.supportsInterface(type(ICopyable).interfaceId)) {
|
|
116
|
+
revert SharedValidation.InvalidOperation(bloxAddress);
|
|
117
|
+
}
|
|
118
|
+
if (!_clonesWhitelist.contains(bloxAddress)) {
|
|
119
|
+
revert SharedValidation.InvalidOperation(bloxAddress);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
cloneAddress = Clones.clone(bloxAddress);
|
|
123
|
+
address eventForwarder = address(this);
|
|
124
|
+
bool success;
|
|
125
|
+
|
|
126
|
+
if (initData.length > 0) {
|
|
127
|
+
success = _initializeCloneWithData(
|
|
128
|
+
cloneAddress,
|
|
129
|
+
initialOwner,
|
|
130
|
+
broadcaster,
|
|
131
|
+
recovery,
|
|
132
|
+
timeLockPeriodSec,
|
|
133
|
+
eventForwarder,
|
|
134
|
+
initData
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
success = _initializeClone(
|
|
138
|
+
cloneAddress,
|
|
139
|
+
initialOwner,
|
|
140
|
+
broadcaster,
|
|
141
|
+
recovery,
|
|
142
|
+
timeLockPeriodSec,
|
|
143
|
+
eventForwarder
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!success) revert SharedValidation.OperationFailed();
|
|
148
|
+
|
|
149
|
+
_clones.add(cloneAddress);
|
|
150
|
+
_logComponentEvent(abi.encode(bloxAddress, cloneAddress, initialOwner, _clones.length()));
|
|
151
|
+
return cloneAddress;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @notice Add a trusted blox implementation to the whitelist with an associated clone price.
|
|
156
|
+
* Only callable by the controller (macro + _validateExecuteBySelf).
|
|
157
|
+
* @param bloxImplementation Address of the blox implementation contract (must be IBaseStateMachine, not zero, not this).
|
|
158
|
+
* @param price Per-clone payment configuration for this implementation.
|
|
159
|
+
*/
|
|
160
|
+
function addToWhitelist(address bloxImplementation, EngineBlox.PaymentDetails calldata price) external nonReentrant {
|
|
161
|
+
_validateExecuteBySelf();
|
|
162
|
+
_validateBloxImplementation(bloxImplementation);
|
|
163
|
+
if (_clonesWhitelist.add(bloxImplementation)) {
|
|
164
|
+
_clonePrices[bloxImplementation] = price;
|
|
165
|
+
_logComponentEvent(
|
|
166
|
+
abi.encode(
|
|
167
|
+
bloxImplementation,
|
|
168
|
+
price.recipient,
|
|
169
|
+
price.nativeTokenAmount,
|
|
170
|
+
price.erc20TokenAddress,
|
|
171
|
+
price.erc20TokenAmount
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @notice Remove a blox implementation from the whitelist. Only callable by the controller (macro + _validateExecuteBySelf).
|
|
179
|
+
* @param bloxImplementation Address to remove from the whitelist.
|
|
180
|
+
*/
|
|
181
|
+
function removeFromWhitelist(address bloxImplementation) external nonReentrant {
|
|
182
|
+
_validateExecuteBySelf();
|
|
183
|
+
if (_clonesWhitelist.remove(bloxImplementation)) {
|
|
184
|
+
// Clear any configured price for the removed implementation
|
|
185
|
+
delete _clonePrices[bloxImplementation];
|
|
186
|
+
_logComponentEvent(abi.encode(bloxImplementation));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @dev Validates that an address is a deployed contract implementing IBaseStateMachine.
|
|
192
|
+
*/
|
|
193
|
+
function _validateBloxImplementation(address bloxAddress) internal view {
|
|
194
|
+
if (!bloxAddress.supportsInterface(type(IBaseStateMachine).interfaceId)) {
|
|
195
|
+
revert SharedValidation.InvalidOperation(bloxAddress);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _initializeClone(
|
|
200
|
+
address cloneAddress,
|
|
201
|
+
address initialOwner,
|
|
202
|
+
address broadcaster,
|
|
203
|
+
address recovery,
|
|
204
|
+
uint256 timeLockPeriodSec,
|
|
205
|
+
address eventForwarder
|
|
206
|
+
) internal returns (bool) {
|
|
207
|
+
(bool success, ) = cloneAddress.call(
|
|
208
|
+
abi.encodeWithSignature(
|
|
209
|
+
"initialize(address,address,address,uint256,address)",
|
|
210
|
+
initialOwner,
|
|
211
|
+
broadcaster,
|
|
212
|
+
recovery,
|
|
213
|
+
timeLockPeriodSec,
|
|
214
|
+
eventForwarder
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
return success;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function _initializeCloneWithData(
|
|
221
|
+
address cloneAddress,
|
|
222
|
+
address initialOwner,
|
|
223
|
+
address broadcaster,
|
|
224
|
+
address recovery,
|
|
225
|
+
uint256 timeLockPeriodSec,
|
|
226
|
+
address eventForwarder,
|
|
227
|
+
bytes calldata initData
|
|
228
|
+
) internal returns (bool) {
|
|
229
|
+
(bool success, ) = cloneAddress.call(
|
|
230
|
+
abi.encodeWithSignature(
|
|
231
|
+
"initializeWithData(address,address,address,uint256,address,bytes)",
|
|
232
|
+
initialOwner,
|
|
233
|
+
broadcaster,
|
|
234
|
+
recovery,
|
|
235
|
+
timeLockPeriodSec,
|
|
236
|
+
eventForwarder,
|
|
237
|
+
initData
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
return success;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function getCloneAtIndex(uint256 index) external view returns (address) {
|
|
244
|
+
return _clones.at(index);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isClone(address cloneAddress) external view returns (bool) {
|
|
248
|
+
return _clones.contains(cloneAddress);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function getClonesCount() external view returns (uint256) {
|
|
252
|
+
return _clones.length();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getWhitelistCount() external view returns (uint256) {
|
|
256
|
+
return _clonesWhitelist.length();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// function getWhitelistAtIndex(uint256 index) external view returns (address) {
|
|
260
|
+
// return _clonesWhitelist.at(index);
|
|
261
|
+
// }
|
|
262
|
+
|
|
263
|
+
function isWhitelisted(address bloxImplementation) external view returns (bool) {
|
|
264
|
+
return _clonesWhitelist.contains(bloxImplementation);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// /**
|
|
268
|
+
// * @notice Returns the configured clone price for a given blox implementation.
|
|
269
|
+
// * @dev Zero-valued fields indicate no explicit pricing (free clone) for this implementation.
|
|
270
|
+
// */
|
|
271
|
+
// function getClonePrice(address bloxImplementation) external view returns (EngineBlox.PaymentDetails memory) {
|
|
272
|
+
// return _clonePrices[bloxImplementation];
|
|
273
|
+
// }
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @notice Returns whitelist entry (implementation + price) at a given index.
|
|
277
|
+
*/
|
|
278
|
+
function getWhitelistEntryAtIndex(uint256 index) external view returns (CloneWhitelistEntry memory) {
|
|
279
|
+
address bloxImplementation = _clonesWhitelist.at(index);
|
|
280
|
+
return CloneWhitelistEntry({ bloxImplementation: bloxImplementation, price: _clonePrices[bloxImplementation] });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @dev Post-action hook: when cloneBlox was requested with payment, verify the tx payment matches
|
|
285
|
+
* the configured per-implementation clone price. Ensures the requestor did not alter the payment
|
|
286
|
+
* (e.g. recipient or amounts). Only runs when TxStatus is COMPLETED (e.g. not on cancellation
|
|
287
|
+
* or failed execution).
|
|
288
|
+
*/
|
|
289
|
+
function _postActionHook(EngineBlox.TxRecord memory txRecord) internal virtual override {
|
|
290
|
+
if (txRecord.params.executionSelector != FactoryBloxDefinitions.CLONE_BLOX_SELECTOR) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (txRecord.status != EngineBlox.TxStatus.COMPLETED) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Decode the blox implementation address from execution params:
|
|
298
|
+
// cloneBlox(address bloxAddress, address initialOwner, address broadcaster, address recovery, uint256 timeLockPeriodSec, bytes initData)
|
|
299
|
+
(address bloxImplementation, , , , , ) = abi.decode(
|
|
300
|
+
txRecord.params.executionParams,
|
|
301
|
+
(address, address, address, address, uint256, bytes)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
EngineBlox.PaymentDetails memory expectedPrice = _clonePrices[bloxImplementation];
|
|
305
|
+
EngineBlox.PaymentDetails memory payment = txRecord.payment;
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
payment.recipient != expectedPrice.recipient
|
|
309
|
+
|| payment.nativeTokenAmount != expectedPrice.nativeTokenAmount
|
|
310
|
+
|| payment.erc20TokenAddress != expectedPrice.erc20TokenAddress
|
|
311
|
+
|| payment.erc20TokenAmount != expectedPrice.erc20TokenAmount
|
|
312
|
+
) {
|
|
313
|
+
revert SharedValidation.InvalidPayment();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function forwardTxEvent(
|
|
318
|
+
uint256 txId,
|
|
319
|
+
bytes4 functionSelector,
|
|
320
|
+
EngineBlox.TxStatus status,
|
|
321
|
+
address requester,
|
|
322
|
+
address target,
|
|
323
|
+
bytes32 operationType
|
|
324
|
+
) external override {
|
|
325
|
+
if (!_clones.contains(msg.sender)) revert SharedValidation.NoPermission(msg.sender);
|
|
326
|
+
|
|
327
|
+
_logComponentEvent(abi.encode(msg.sender, txId, functionSelector, status, requester, target, operationType));
|
|
328
|
+
|
|
329
|
+
address eventForwarder = _secureState.eventForwarder;
|
|
330
|
+
if (eventForwarder != address(0) && eventForwarder != address(this)) {
|
|
331
|
+
try IEventForwarder(eventForwarder).forwardTxEvent(
|
|
332
|
+
txId, functionSelector, status, requester, target, operationType
|
|
333
|
+
) {} catch {}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
receive() external payable {}
|
|
338
|
+
|
|
339
|
+
fallback() external payable {
|
|
340
|
+
revert SharedValidation.NotSupported();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
uint256[50] private __gap;
|
|
344
|
+
}
|