@bloxchain/contracts 1.0.0-alpha.7 → 1.0.0
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/CHANGELOG.md +19 -0
- package/README.md +8 -9
- package/abi/BaseStateMachine.abi.json +773 -822
- package/abi/EngineBlox.abi.json +562 -552
- package/abi/GuardController.abi.json +1597 -1609
- package/abi/GuardControllerDefinitions.abi.json +235 -199
- package/abi/IDefinition.abi.json +57 -47
- package/abi/RuntimeRBAC.abi.json +841 -842
- package/abi/RuntimeRBACDefinitions.abi.json +212 -202
- package/abi/SecureOwnable.abi.json +1365 -1349
- package/abi/SecureOwnableDefinitions.abi.json +174 -164
- package/core/AUDIT.md +45 -0
- package/core/access/RuntimeRBAC.sol +130 -61
- package/core/access/interface/IRuntimeRBAC.sol +3 -3
- package/core/access/lib/definitions/RuntimeRBACDefinitions.sol +7 -3
- package/core/base/BaseStateMachine.sol +971 -967
- package/core/base/interface/IBaseStateMachine.sol +153 -160
- package/core/execution/GuardController.sol +89 -75
- package/core/execution/interface/IGuardController.sol +146 -160
- package/core/execution/lib/definitions/GuardControllerDefinitions.sol +136 -25
- package/core/lib/EngineBlox.sol +577 -327
- package/core/lib/interfaces/IDefinition.sol +49 -49
- package/core/lib/interfaces/IEventForwarder.sol +4 -2
- package/core/lib/utils/SharedValidation.sol +534 -490
- package/core/pattern/Account.sol +84 -75
- package/core/security/SecureOwnable.sol +446 -390
- package/core/security/interface/ISecureOwnable.sol +105 -105
- package/core/security/lib/definitions/SecureOwnableDefinitions.sol +49 -17
- package/package.json +51 -49
- package/standards/behavior/ICopyable.sol +3 -11
- package/standards/hooks/IOnActionHook.sol +1 -1
- package/abi/AccountBlox.abi.json +0 -3935
- package/abi/BareBlox.abi.json +0 -1378
- package/abi/RoleBlox.abi.json +0 -2983
- package/abi/SecureBlox.abi.json +0 -2753
- package/abi/SimpleRWA20.abi.json +0 -4032
- package/abi/SimpleRWA20Definitions.abi.json +0 -191
- package/abi/SimpleVault.abi.json +0 -3407
- package/abi/SimpleVaultDefinitions.abi.json +0 -269
- package/core/research/BloxchainWallet.sol +0 -133
- package/core/research/FactoryBlox/FactoryBlox.sol +0 -343
- package/core/research/FactoryBlox/FactoryBloxDefinitions.sol +0 -143
- package/core/research/erc1155-blox/ERC1155Blox.sol +0 -169
- package/core/research/erc1155-blox/lib/definitions/ERC1155BloxDefinitions.sol +0 -203
- package/core/research/erc20-blox/ERC20Blox.sol +0 -167
- package/core/research/erc20-blox/lib/definitions/ERC20BloxDefinitions.sol +0 -185
- package/core/research/erc721-blox/ERC721Blox.sol +0 -131
- package/core/research/erc721-blox/lib/definitions/ERC721BloxDefinitions.sol +0 -172
- package/core/research/lending-blox/.gitkeep +0 -1
- package/core/research/p2p-blox/P2PBlox.sol +0 -266
- package/core/research/p2p-blox/README.md +0 -85
- package/core/research/p2p-blox/lib/definitions/P2PBloxDefinitions.sol +0 -19
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.33;
|
|
3
|
-
|
|
4
|
-
// OpenZeppelin
|
|
5
|
-
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
|
|
6
|
-
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
|
|
7
|
-
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
8
|
-
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
9
|
-
|
|
10
|
-
// Core
|
|
11
|
-
import "../../security/SecureOwnable.sol";
|
|
12
|
-
import "../../access/RuntimeRBAC.sol";
|
|
13
|
-
import "../../execution/GuardController.sol";
|
|
14
|
-
import "../../base/BaseStateMachine.sol";
|
|
15
|
-
import "../../lib/interfaces/IDefinition.sol";
|
|
16
|
-
import "./lib/definitions/ERC721BloxDefinitions.sol";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @title ERC721Blox
|
|
20
|
-
* @dev ERC721 NFT (IERC721) with SecureOwnable, RuntimeRBAC, and GuardController.
|
|
21
|
-
* Exposes transferFrom, safeTransferFrom (callable only via GuardController), mint (owner-only via controller), and burn (ERC721Burnable via controller).
|
|
22
|
-
* @custom:security-contact security@particlecrypto.com
|
|
23
|
-
*/
|
|
24
|
-
contract ERC721Blox is
|
|
25
|
-
IERC721,
|
|
26
|
-
ERC721Upgradeable,
|
|
27
|
-
ERC721BurnableUpgradeable,
|
|
28
|
-
SecureOwnable,
|
|
29
|
-
RuntimeRBAC,
|
|
30
|
-
GuardController
|
|
31
|
-
{
|
|
32
|
-
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
33
|
-
constructor() {
|
|
34
|
-
_disableInitializers();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @dev Override to resolve diamond: SecureOwnable, RuntimeRBAC, GuardController all define initialize(5 params).
|
|
39
|
-
* Call this first, then initializeToken(name, symbol) to set ERC721 metadata.
|
|
40
|
-
*/
|
|
41
|
-
function initialize(
|
|
42
|
-
address initialOwner,
|
|
43
|
-
address broadcaster,
|
|
44
|
-
address recovery,
|
|
45
|
-
uint256 timeLockPeriodSec,
|
|
46
|
-
address eventForwarder
|
|
47
|
-
) public virtual override(GuardController, RuntimeRBAC, SecureOwnable) initializer {
|
|
48
|
-
GuardController.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
49
|
-
RuntimeRBAC.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
50
|
-
SecureOwnable.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
51
|
-
|
|
52
|
-
IDefinition.RolePermission memory erc721Permissions = ERC721BloxDefinitions.getRolePermissions();
|
|
53
|
-
_loadDefinitions(
|
|
54
|
-
ERC721BloxDefinitions.getFunctionSchemas(),
|
|
55
|
-
erc721Permissions.roleHashes,
|
|
56
|
-
erc721Permissions.functionPermissions,
|
|
57
|
-
true
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
_addMacroSelector(ERC721BloxDefinitions.TRANSFER_FROM_SELECTOR);
|
|
61
|
-
_addMacroSelector(ERC721BloxDefinitions.SAFE_TRANSFER_FROM_SELECTOR);
|
|
62
|
-
_addMacroSelector(ERC721BloxDefinitions.MINT_SELECTOR);
|
|
63
|
-
_addMacroSelector(ERC721BloxDefinitions.BURN_SELECTOR);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @notice Initialize the ERC721 token name and symbol. Call after initialize(5 params).
|
|
68
|
-
* @param name Token name
|
|
69
|
-
* @param symbol Token symbol
|
|
70
|
-
*/
|
|
71
|
-
function initializeToken(string memory name, string memory symbol) public reinitializer(2) {
|
|
72
|
-
__ERC721_init(name, symbol);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @notice Transfer NFT from one account to another; callable only by this contract via GuardController
|
|
77
|
-
* @param from Sender address
|
|
78
|
-
* @param to Recipient address
|
|
79
|
-
* @param tokenId Token ID to transfer
|
|
80
|
-
*/
|
|
81
|
-
function transferFrom(address from, address to, uint256 tokenId) public virtual override(ERC721Upgradeable, IERC721) {
|
|
82
|
-
_validateExecuteBySelf();
|
|
83
|
-
super.transferFrom(from, to, tokenId);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @notice Safe transfer NFT from one account to another (with data); callable only by this contract via GuardController.
|
|
88
|
-
* The 3-arg overload (no data) in the base contract calls this with empty data, so it is also guarded.
|
|
89
|
-
* @param from Sender address
|
|
90
|
-
* @param to Recipient address
|
|
91
|
-
* @param tokenId Token ID to transfer
|
|
92
|
-
* @param data Additional data for recipient hook
|
|
93
|
-
*/
|
|
94
|
-
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
|
|
95
|
-
public
|
|
96
|
-
virtual
|
|
97
|
-
override(ERC721Upgradeable, IERC721)
|
|
98
|
-
{
|
|
99
|
-
_validateExecuteBySelf();
|
|
100
|
-
super.safeTransferFrom(from, to, tokenId, data);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* @notice Mint an NFT to an account (callable only by this contract via GuardController)
|
|
105
|
-
* @param to Recipient address
|
|
106
|
-
* @param tokenId Token ID to mint
|
|
107
|
-
*/
|
|
108
|
-
function mint(address to, uint256 tokenId) external virtual {
|
|
109
|
-
_validateExecuteBySelf();
|
|
110
|
-
_mint(to, tokenId);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* @notice Burn an NFT (callable only by this contract via GuardController)
|
|
115
|
-
* @param tokenId Token ID to burn
|
|
116
|
-
*/
|
|
117
|
-
function burn(uint256 tokenId) public virtual override(ERC721BurnableUpgradeable) {
|
|
118
|
-
_validateExecuteBySelf();
|
|
119
|
-
super.burn(tokenId);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function supportsInterface(bytes4 interfaceId)
|
|
123
|
-
public
|
|
124
|
-
view
|
|
125
|
-
virtual
|
|
126
|
-
override(ERC721Upgradeable, SecureOwnable, RuntimeRBAC, GuardController, IERC165)
|
|
127
|
-
returns (bool)
|
|
128
|
-
{
|
|
129
|
-
return interfaceId == type(IERC721).interfaceId || super.supportsInterface(interfaceId) || GuardController.supportsInterface(interfaceId);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.33;
|
|
3
|
-
|
|
4
|
-
import "../../../../lib/EngineBlox.sol";
|
|
5
|
-
import "../../../../lib/interfaces/IDefinition.sol";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @title ERC721BloxDefinitions
|
|
9
|
-
* @dev Definition library for ERC721Blox execution selectors (transferFrom, safeTransferFrom, mint, burn).
|
|
10
|
-
* Registers function schemas and role permissions so the GuardController can execute these functions
|
|
11
|
-
* via time-lock and meta-transaction workflows.
|
|
12
|
-
* @custom:security-contact security@particlecrypto.com
|
|
13
|
-
*/
|
|
14
|
-
library ERC721BloxDefinitions {
|
|
15
|
-
|
|
16
|
-
// System macro selectors (allowed to target address(this) for GuardController execution)
|
|
17
|
-
bytes4 public constant TRANSFER_FROM_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
|
|
18
|
-
bytes4 public constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)"));
|
|
19
|
-
bytes4 public constant MINT_SELECTOR = bytes4(keccak256("mint(address,uint256)"));
|
|
20
|
-
bytes4 public constant BURN_SELECTOR = bytes4(keccak256("burn(uint256)"));
|
|
21
|
-
|
|
22
|
-
bytes32 public constant ERC721_OPERATION = keccak256("ERC721_OPERATION");
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @dev Returns function schemas for ERC721Blox execution selectors (used by controller).
|
|
26
|
-
*/
|
|
27
|
-
function getFunctionSchemas() public pure returns (EngineBlox.FunctionSchema[] memory) {
|
|
28
|
-
EngineBlox.FunctionSchema[] memory schemas = new EngineBlox.FunctionSchema[](4);
|
|
29
|
-
|
|
30
|
-
EngineBlox.TxAction[] memory timeDelayRequestActions = new EngineBlox.TxAction[](1);
|
|
31
|
-
timeDelayRequestActions[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_REQUEST;
|
|
32
|
-
EngineBlox.TxAction[] memory timeDelayApproveActions = new EngineBlox.TxAction[](1);
|
|
33
|
-
timeDelayApproveActions[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_APPROVE;
|
|
34
|
-
EngineBlox.TxAction[] memory timeDelayCancelActions = new EngineBlox.TxAction[](1);
|
|
35
|
-
timeDelayCancelActions[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_CANCEL;
|
|
36
|
-
EngineBlox.TxAction[] memory metaTxRequestApproveActions = new EngineBlox.TxAction[](2);
|
|
37
|
-
metaTxRequestApproveActions[0] = EngineBlox.TxAction.SIGN_META_REQUEST_AND_APPROVE;
|
|
38
|
-
metaTxRequestApproveActions[1] = EngineBlox.TxAction.EXECUTE_META_REQUEST_AND_APPROVE;
|
|
39
|
-
EngineBlox.TxAction[] memory metaTxApproveActions = new EngineBlox.TxAction[](2);
|
|
40
|
-
metaTxApproveActions[0] = EngineBlox.TxAction.SIGN_META_APPROVE;
|
|
41
|
-
metaTxApproveActions[1] = EngineBlox.TxAction.EXECUTE_META_APPROVE;
|
|
42
|
-
EngineBlox.TxAction[] memory metaTxCancelActions = new EngineBlox.TxAction[](2);
|
|
43
|
-
metaTxCancelActions[0] = EngineBlox.TxAction.SIGN_META_CANCEL;
|
|
44
|
-
metaTxCancelActions[1] = EngineBlox.TxAction.EXECUTE_META_CANCEL;
|
|
45
|
-
|
|
46
|
-
uint16 actionsBitmap = EngineBlox.createBitmapFromActions(timeDelayRequestActions)
|
|
47
|
-
| EngineBlox.createBitmapFromActions(timeDelayApproveActions)
|
|
48
|
-
| EngineBlox.createBitmapFromActions(timeDelayCancelActions)
|
|
49
|
-
| EngineBlox.createBitmapFromActions(metaTxRequestApproveActions)
|
|
50
|
-
| EngineBlox.createBitmapFromActions(metaTxApproveActions)
|
|
51
|
-
| EngineBlox.createBitmapFromActions(metaTxCancelActions);
|
|
52
|
-
|
|
53
|
-
bytes4[] memory transferFromHandlers = new bytes4[](1);
|
|
54
|
-
transferFromHandlers[0] = TRANSFER_FROM_SELECTOR;
|
|
55
|
-
bytes4[] memory safeTransferFromHandlers = new bytes4[](1);
|
|
56
|
-
safeTransferFromHandlers[0] = SAFE_TRANSFER_FROM_SELECTOR;
|
|
57
|
-
bytes4[] memory mintHandlers = new bytes4[](1);
|
|
58
|
-
mintHandlers[0] = MINT_SELECTOR;
|
|
59
|
-
bytes4[] memory burnHandlers = new bytes4[](1);
|
|
60
|
-
burnHandlers[0] = BURN_SELECTOR;
|
|
61
|
-
|
|
62
|
-
schemas[0] = EngineBlox.FunctionSchema({
|
|
63
|
-
functionSignature: "transferFrom(address,address,uint256)",
|
|
64
|
-
functionSelector: TRANSFER_FROM_SELECTOR,
|
|
65
|
-
operationType: ERC721_OPERATION,
|
|
66
|
-
operationName: "ERC721_TRANSFER_FROM",
|
|
67
|
-
supportedActionsBitmap: actionsBitmap,
|
|
68
|
-
isProtected: true,
|
|
69
|
-
handlerForSelectors: transferFromHandlers
|
|
70
|
-
});
|
|
71
|
-
schemas[1] = EngineBlox.FunctionSchema({
|
|
72
|
-
functionSignature: "safeTransferFrom(address,address,uint256,bytes)",
|
|
73
|
-
functionSelector: SAFE_TRANSFER_FROM_SELECTOR,
|
|
74
|
-
operationType: ERC721_OPERATION,
|
|
75
|
-
operationName: "ERC721_SAFE_TRANSFER_FROM",
|
|
76
|
-
supportedActionsBitmap: actionsBitmap,
|
|
77
|
-
isProtected: true,
|
|
78
|
-
handlerForSelectors: safeTransferFromHandlers
|
|
79
|
-
});
|
|
80
|
-
schemas[2] = EngineBlox.FunctionSchema({
|
|
81
|
-
functionSignature: "mint(address,uint256)",
|
|
82
|
-
functionSelector: MINT_SELECTOR,
|
|
83
|
-
operationType: ERC721_OPERATION,
|
|
84
|
-
operationName: "ERC721_MINT",
|
|
85
|
-
supportedActionsBitmap: actionsBitmap,
|
|
86
|
-
isProtected: true,
|
|
87
|
-
handlerForSelectors: mintHandlers
|
|
88
|
-
});
|
|
89
|
-
schemas[3] = EngineBlox.FunctionSchema({
|
|
90
|
-
functionSignature: "burn(uint256)",
|
|
91
|
-
functionSelector: BURN_SELECTOR,
|
|
92
|
-
operationType: ERC721_OPERATION,
|
|
93
|
-
operationName: "ERC721_BURN",
|
|
94
|
-
supportedActionsBitmap: actionsBitmap,
|
|
95
|
-
isProtected: true,
|
|
96
|
-
handlerForSelectors: burnHandlers
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return schemas;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* @dev Returns role permissions for ERC721Blox execution selectors (OWNER and BROADCASTER).
|
|
104
|
-
*/
|
|
105
|
-
function getRolePermissions() public pure returns (IDefinition.RolePermission memory) {
|
|
106
|
-
bytes32[] memory roleHashes = new bytes32[](8);
|
|
107
|
-
EngineBlox.FunctionPermission[] memory functionPermissions = new EngineBlox.FunctionPermission[](8);
|
|
108
|
-
|
|
109
|
-
EngineBlox.TxAction[] memory ownerTimeLockRequest = new EngineBlox.TxAction[](1);
|
|
110
|
-
ownerTimeLockRequest[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_REQUEST;
|
|
111
|
-
EngineBlox.TxAction[] memory ownerTimeLockApprove = new EngineBlox.TxAction[](1);
|
|
112
|
-
ownerTimeLockApprove[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_APPROVE;
|
|
113
|
-
EngineBlox.TxAction[] memory ownerTimeLockCancel = new EngineBlox.TxAction[](1);
|
|
114
|
-
ownerTimeLockCancel[0] = EngineBlox.TxAction.EXECUTE_TIME_DELAY_CANCEL;
|
|
115
|
-
EngineBlox.TxAction[] memory ownerMetaSign = new EngineBlox.TxAction[](1);
|
|
116
|
-
ownerMetaSign[0] = EngineBlox.TxAction.SIGN_META_REQUEST_AND_APPROVE;
|
|
117
|
-
EngineBlox.TxAction[] memory ownerMetaApprove = new EngineBlox.TxAction[](1);
|
|
118
|
-
ownerMetaApprove[0] = EngineBlox.TxAction.SIGN_META_APPROVE;
|
|
119
|
-
EngineBlox.TxAction[] memory ownerMetaCancel = new EngineBlox.TxAction[](1);
|
|
120
|
-
ownerMetaCancel[0] = EngineBlox.TxAction.SIGN_META_CANCEL;
|
|
121
|
-
EngineBlox.TxAction[] memory broadcasterMetaExec = new EngineBlox.TxAction[](1);
|
|
122
|
-
broadcasterMetaExec[0] = EngineBlox.TxAction.EXECUTE_META_REQUEST_AND_APPROVE;
|
|
123
|
-
EngineBlox.TxAction[] memory broadcasterMetaApprove = new EngineBlox.TxAction[](1);
|
|
124
|
-
broadcasterMetaApprove[0] = EngineBlox.TxAction.EXECUTE_META_APPROVE;
|
|
125
|
-
EngineBlox.TxAction[] memory broadcasterMetaCancel = new EngineBlox.TxAction[](1);
|
|
126
|
-
broadcasterMetaCancel[0] = EngineBlox.TxAction.EXECUTE_META_CANCEL;
|
|
127
|
-
|
|
128
|
-
uint16 ownerBitmap = EngineBlox.createBitmapFromActions(ownerTimeLockRequest)
|
|
129
|
-
| EngineBlox.createBitmapFromActions(ownerTimeLockApprove)
|
|
130
|
-
| EngineBlox.createBitmapFromActions(ownerTimeLockCancel)
|
|
131
|
-
| EngineBlox.createBitmapFromActions(ownerMetaSign)
|
|
132
|
-
| EngineBlox.createBitmapFromActions(ownerMetaApprove)
|
|
133
|
-
| EngineBlox.createBitmapFromActions(ownerMetaCancel);
|
|
134
|
-
uint16 broadcasterBitmap = EngineBlox.createBitmapFromActions(broadcasterMetaExec)
|
|
135
|
-
| EngineBlox.createBitmapFromActions(broadcasterMetaApprove)
|
|
136
|
-
| EngineBlox.createBitmapFromActions(broadcasterMetaCancel);
|
|
137
|
-
|
|
138
|
-
bytes4[4] memory selectors = [TRANSFER_FROM_SELECTOR, SAFE_TRANSFER_FROM_SELECTOR, MINT_SELECTOR, BURN_SELECTOR];
|
|
139
|
-
for (uint256 i = 0; i < 4; i++) {
|
|
140
|
-
bytes4[] memory selfRef = new bytes4[](1);
|
|
141
|
-
selfRef[0] = selectors[i];
|
|
142
|
-
roleHashes[i] = EngineBlox.OWNER_ROLE;
|
|
143
|
-
functionPermissions[i] = EngineBlox.FunctionPermission({
|
|
144
|
-
functionSelector: selectors[i],
|
|
145
|
-
grantedActionsBitmap: ownerBitmap,
|
|
146
|
-
handlerForSelectors: selfRef
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
for (uint256 i = 0; i < 4; i++) {
|
|
150
|
-
bytes4[] memory selfRef = new bytes4[](1);
|
|
151
|
-
selfRef[0] = selectors[i];
|
|
152
|
-
roleHashes[4 + i] = EngineBlox.BROADCASTER_ROLE;
|
|
153
|
-
functionPermissions[4 + i] = EngineBlox.FunctionPermission({
|
|
154
|
-
functionSelector: selectors[i],
|
|
155
|
-
grantedActionsBitmap: broadcasterBitmap,
|
|
156
|
-
handlerForSelectors: selfRef
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return IDefinition.RolePermission({
|
|
161
|
-
roleHashes: roleHashes,
|
|
162
|
-
functionPermissions: functionPermissions
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @dev ERC165: report support for IDefinition when this library is used at an address
|
|
168
|
-
*/
|
|
169
|
-
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
|
|
170
|
-
return interfaceId == type(IDefinition).interfaceId;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Placeholder to keep lending-blox directory in version control
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.33;
|
|
3
|
-
|
|
4
|
-
// OpenZeppelin
|
|
5
|
-
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
|
|
6
|
-
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
|
-
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
8
|
-
|
|
9
|
-
// Core
|
|
10
|
-
import "../../security/SecureOwnable.sol";
|
|
11
|
-
import "../../access/RuntimeRBAC.sol";
|
|
12
|
-
import "../../execution/GuardController.sol";
|
|
13
|
-
import "../../base/BaseStateMachine.sol";
|
|
14
|
-
import "../../lib/utils/SharedValidation.sol";
|
|
15
|
-
import "./lib/definitions/P2PBloxDefinitions.sol";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @title P2PBlox
|
|
19
|
-
* @dev Peer-to-peer token swap blox: two parties agree on a direct transfer where
|
|
20
|
-
* the first entity sends token A and the second entity sends token B.
|
|
21
|
-
* Each side submits a request (offer / counter-offer); a dedicated P2P_APPROVER_ROLE
|
|
22
|
-
* then approves or rejects the swap before execution.
|
|
23
|
-
*
|
|
24
|
-
* Flow:
|
|
25
|
-
* 1. Party A calls requestOffer(tokenA, amountA, tokenB, amountB) — "I send token A, I want token B".
|
|
26
|
-
* 2. Party B calls requestCounterOffer(offerId, tokenB, amountB, tokenA, amountA) — must match the offer.
|
|
27
|
-
* 3. A wallet with P2P_APPROVER_ROLE calls approveSwap(offerId) to execute both transfers, or
|
|
28
|
-
* rejectSwap(offerId) to reject the swap.
|
|
29
|
-
*
|
|
30
|
-
* Both parties must have approved this contract for their respective tokens before approveSwap.
|
|
31
|
-
* Uses SafeERC20 and CEI pattern; protected by ReentrancyGuard where state changes and external calls occur.
|
|
32
|
-
*
|
|
33
|
-
* @custom:security-contact security@particlecrypto.com
|
|
34
|
-
*/
|
|
35
|
-
contract P2PBlox is GuardController, RuntimeRBAC, SecureOwnable {
|
|
36
|
-
using SafeERC20 for IERC20;
|
|
37
|
-
|
|
38
|
-
/// @dev Swap lifecycle status
|
|
39
|
-
enum SwapStatus {
|
|
40
|
-
PENDING, // Offer created, no counter yet
|
|
41
|
-
MATCHED, // Counter-offer submitted, awaiting approver
|
|
42
|
-
EXECUTING, // Transfers in progress (internal; prevents reentrancy)
|
|
43
|
-
EXECUTED, // Swap approved and transfers completed
|
|
44
|
-
REJECTED, // Swap rejected by approver
|
|
45
|
-
CANCELLED // Offer cancelled (reserved for future use)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// @dev Single offer/counter pair: party A gives (tokenA, amountA), party B gives (tokenB, amountB)
|
|
49
|
-
struct P2PSwap {
|
|
50
|
-
address partyA;
|
|
51
|
-
address tokenA;
|
|
52
|
-
uint256 amountA;
|
|
53
|
-
address tokenB;
|
|
54
|
-
uint256 amountB;
|
|
55
|
-
address partyB; // Set when counter-offer is submitted
|
|
56
|
-
SwapStatus status;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/// @dev Next offer ID (1-based)
|
|
60
|
-
uint256 private _nextOfferId;
|
|
61
|
-
|
|
62
|
-
/// @dev offerId => P2PSwap
|
|
63
|
-
mapping(uint256 => P2PSwap) private _swaps;
|
|
64
|
-
|
|
65
|
-
/// @dev Emitted when party A creates an offer
|
|
66
|
-
event OfferCreated(
|
|
67
|
-
uint256 indexed offerId,
|
|
68
|
-
address indexed partyA,
|
|
69
|
-
address tokenA,
|
|
70
|
-
uint256 amountA,
|
|
71
|
-
address tokenB,
|
|
72
|
-
uint256 amountB
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
/// @dev Emitted when party B submits a counter-offer matching the offer
|
|
76
|
-
event CounterOfferCreated(
|
|
77
|
-
uint256 indexed offerId,
|
|
78
|
-
address indexed partyB
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
/// @dev Emitted when the approver executes the swap
|
|
82
|
-
event SwapApproved(uint256 indexed offerId);
|
|
83
|
-
|
|
84
|
-
/// @dev Emitted when the approver rejects the swap
|
|
85
|
-
event SwapRejected(uint256 indexed offerId);
|
|
86
|
-
|
|
87
|
-
error InvalidOffer(uint256 offerId);
|
|
88
|
-
error OfferNotPending(uint256 offerId);
|
|
89
|
-
error OfferNotMatched(uint256 offerId);
|
|
90
|
-
error CounterOfferMismatch();
|
|
91
|
-
error InvalidParties();
|
|
92
|
-
error OnlyApprover();
|
|
93
|
-
error ZeroAmount();
|
|
94
|
-
|
|
95
|
-
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
96
|
-
constructor() {
|
|
97
|
-
_disableInitializers();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @notice Initializes P2PBlox and creates the P2P_APPROVER_ROLE.
|
|
102
|
-
* Assign approver wallets via RuntimeRBAC roleConfigBatch (ADD_WALLET for P2P_APPROVER_ROLE) after deployment.
|
|
103
|
-
* @param initialOwner Initial owner
|
|
104
|
-
* @param broadcaster Broadcaster address
|
|
105
|
-
* @param recovery Recovery address
|
|
106
|
-
* @param timeLockPeriodSec Time-lock period in seconds
|
|
107
|
-
* @param eventForwarder Event forwarder address
|
|
108
|
-
*/
|
|
109
|
-
function initialize(
|
|
110
|
-
address initialOwner,
|
|
111
|
-
address broadcaster,
|
|
112
|
-
address recovery,
|
|
113
|
-
uint256 timeLockPeriodSec,
|
|
114
|
-
address eventForwarder
|
|
115
|
-
) public virtual override(GuardController, RuntimeRBAC, SecureOwnable) initializer {
|
|
116
|
-
GuardController.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
117
|
-
RuntimeRBAC.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
118
|
-
SecureOwnable.initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder);
|
|
119
|
-
|
|
120
|
-
// Create dedicated approver role (non-protected; assign wallets via RuntimeRBAC roleConfigBatch)
|
|
121
|
-
_createRole("P2P_APPROVER_ROLE", P2PBloxDefinitions.P2P_APPROVER_MAX_WALLETS, false);
|
|
122
|
-
|
|
123
|
-
_nextOfferId = 1;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @notice Party A creates an offer: "I send token A (amount A), I want token B (amount B)".
|
|
128
|
-
* @param tokenA Token address party A will send
|
|
129
|
-
* @param amountA Amount of token A
|
|
130
|
-
* @param tokenB Token address party A wants to receive
|
|
131
|
-
* @param amountB Amount of token B
|
|
132
|
-
* @return offerId The created offer ID
|
|
133
|
-
*/
|
|
134
|
-
function requestOffer(
|
|
135
|
-
address tokenA,
|
|
136
|
-
uint256 amountA,
|
|
137
|
-
address tokenB,
|
|
138
|
-
uint256 amountB
|
|
139
|
-
) external nonReentrant returns (uint256 offerId) {
|
|
140
|
-
SharedValidation.validateNotZeroAddress(tokenA);
|
|
141
|
-
SharedValidation.validateNotZeroAddress(tokenB);
|
|
142
|
-
if (amountA == 0 || amountB == 0) revert ZeroAmount();
|
|
143
|
-
|
|
144
|
-
offerId = _nextOfferId++;
|
|
145
|
-
unchecked {
|
|
146
|
-
_nextOfferId = _nextOfferId;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
_swaps[offerId] = P2PSwap({
|
|
150
|
-
partyA: msg.sender,
|
|
151
|
-
tokenA: tokenA,
|
|
152
|
-
amountA: amountA,
|
|
153
|
-
tokenB: tokenB,
|
|
154
|
-
amountB: amountB,
|
|
155
|
-
partyB: address(0),
|
|
156
|
-
status: SwapStatus.PENDING
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
emit OfferCreated(offerId, msg.sender, tokenA, amountA, tokenB, amountB);
|
|
160
|
-
return offerId;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @notice Party B submits a counter-offer that must match the existing offer (token/amounts).
|
|
165
|
-
* @param offerId The offer to match
|
|
166
|
-
* @param tokenB Token address party B will send (must equal offer.tokenB)
|
|
167
|
-
* @param amountB Amount party B will send (must equal offer.amountB)
|
|
168
|
-
* @param tokenA Token address party B wants to receive (must equal offer.tokenA)
|
|
169
|
-
* @param amountA Amount party B wants (must equal offer.amountA)
|
|
170
|
-
*/
|
|
171
|
-
function requestCounterOffer(
|
|
172
|
-
uint256 offerId,
|
|
173
|
-
address tokenB,
|
|
174
|
-
uint256 amountB,
|
|
175
|
-
address tokenA,
|
|
176
|
-
uint256 amountA
|
|
177
|
-
) external nonReentrant {
|
|
178
|
-
P2PSwap storage swap = _swaps[offerId];
|
|
179
|
-
if (swap.partyA == address(0)) revert InvalidOffer(offerId);
|
|
180
|
-
if (swap.status != SwapStatus.PENDING) revert OfferNotPending(offerId);
|
|
181
|
-
if (msg.sender == swap.partyA) revert InvalidParties();
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
tokenA != swap.tokenA || amountA != swap.amountA ||
|
|
185
|
-
tokenB != swap.tokenB || amountB != swap.amountB
|
|
186
|
-
) revert CounterOfferMismatch();
|
|
187
|
-
|
|
188
|
-
swap.partyB = msg.sender;
|
|
189
|
-
swap.status = SwapStatus.MATCHED;
|
|
190
|
-
|
|
191
|
-
emit CounterOfferCreated(offerId, msg.sender);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* @notice Executes the swap: transfers token A from party A to party B and token B from party B to party A.
|
|
196
|
-
* Callable only by P2P_APPROVER_ROLE. Both parties must have approved this contract for their tokens.
|
|
197
|
-
* @param offerId The matched offer to execute
|
|
198
|
-
*/
|
|
199
|
-
function approveSwap(uint256 offerId) external nonReentrant {
|
|
200
|
-
if (!hasRole(P2PBloxDefinitions.P2P_APPROVER_ROLE, msg.sender)) revert OnlyApprover();
|
|
201
|
-
|
|
202
|
-
P2PSwap storage swap = _swaps[offerId];
|
|
203
|
-
if (swap.partyA == address(0)) revert InvalidOffer(offerId);
|
|
204
|
-
if (swap.status != SwapStatus.MATCHED) revert OfferNotMatched(offerId);
|
|
205
|
-
|
|
206
|
-
// CEI: capture and update state before external calls
|
|
207
|
-
address partyA = swap.partyA;
|
|
208
|
-
address partyB = swap.partyB;
|
|
209
|
-
address tokenA = swap.tokenA;
|
|
210
|
-
address tokenB = swap.tokenB;
|
|
211
|
-
uint256 amountA = swap.amountA;
|
|
212
|
-
uint256 amountB = swap.amountB;
|
|
213
|
-
|
|
214
|
-
swap.status = SwapStatus.EXECUTING;
|
|
215
|
-
|
|
216
|
-
IERC20(tokenA).safeTransferFrom(partyA, partyB, amountA);
|
|
217
|
-
IERC20(tokenB).safeTransferFrom(partyB, partyA, amountB);
|
|
218
|
-
|
|
219
|
-
swap.status = SwapStatus.EXECUTED;
|
|
220
|
-
emit SwapApproved(offerId);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* @notice Rejects a matched swap. Callable only by P2P_APPROVER_ROLE.
|
|
225
|
-
* @param offerId The matched offer to reject
|
|
226
|
-
*/
|
|
227
|
-
function rejectSwap(uint256 offerId) external {
|
|
228
|
-
if (!hasRole(P2PBloxDefinitions.P2P_APPROVER_ROLE, msg.sender)) revert OnlyApprover();
|
|
229
|
-
|
|
230
|
-
P2PSwap storage swap = _swaps[offerId];
|
|
231
|
-
if (swap.partyA == address(0)) revert InvalidOffer(offerId);
|
|
232
|
-
if (swap.status != SwapStatus.MATCHED) revert OfferNotMatched(offerId);
|
|
233
|
-
|
|
234
|
-
swap.status = SwapStatus.REJECTED;
|
|
235
|
-
emit SwapRejected(offerId);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* @notice Returns the full swap record for an offer ID.
|
|
240
|
-
* @param offerId The offer ID
|
|
241
|
-
*/
|
|
242
|
-
function getSwap(uint256 offerId) external view returns (P2PSwap memory) {
|
|
243
|
-
return _swaps[offerId];
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* @notice Returns the next offer ID (for frontends).
|
|
248
|
-
*/
|
|
249
|
-
function getNextOfferId() external view returns (uint256) {
|
|
250
|
-
return _nextOfferId;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function supportsInterface(bytes4 interfaceId)
|
|
254
|
-
public
|
|
255
|
-
view
|
|
256
|
-
virtual
|
|
257
|
-
override(GuardController, RuntimeRBAC, SecureOwnable)
|
|
258
|
-
returns (bool)
|
|
259
|
-
{
|
|
260
|
-
return GuardController.supportsInterface(interfaceId)
|
|
261
|
-
|| RuntimeRBAC.supportsInterface(interfaceId)
|
|
262
|
-
|| SecureOwnable.supportsInterface(interfaceId);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
uint256[50] private __gap;
|
|
266
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# P2P Blox — Peer-to-Peer Token Swap
|
|
2
|
-
|
|
3
|
-
P2PBlox is a research blox that lets two parties agree on a **direct token swap**: the first entity sends **token A** and the second entity sends **token B**. Each side submits a request (offer and counter-offer); a **dedicated approver role** then approves or rejects the swap before execution.
|
|
4
|
-
|
|
5
|
-
## Design
|
|
6
|
-
|
|
7
|
-
- **Dual request**: Party A creates an offer; Party B submits a counter-offer that must match (same tokens and amounts).
|
|
8
|
-
- **Approver role**: Only wallets with `P2P_APPROVER_ROLE` can call `approveSwap` or `rejectSwap`.
|
|
9
|
-
- **Execution**: On approval, the contract performs `transferFrom` for both legs (A→B for token A, B→A for token B). Both parties must have approved the P2PBlox contract for their respective tokens before approval.
|
|
10
|
-
- **Stack**: P2PBlox extends GuardController, RuntimeRBAC, and SecureOwnable (same pattern as FactoryBlox). Swap lifecycle (offer / counter / approve / reject) is implemented in the blox; approver membership is managed via RuntimeRBAC.
|
|
11
|
-
|
|
12
|
-
## Flow
|
|
13
|
-
|
|
14
|
-
1. **Party A** — `requestOffer(tokenA, amountA, tokenB, amountB)`
|
|
15
|
-
“I send `tokenA` amount `amountA`, I want `tokenB` amount `amountB`.”
|
|
16
|
-
|
|
17
|
-
2. **Party B** — `requestCounterOffer(offerId, tokenB, amountB, tokenA, amountA)`
|
|
18
|
-
Must match the offer: same `tokenA`/`amountA` and `tokenB`/`amountB`. Party B cannot be the same as Party A.
|
|
19
|
-
|
|
20
|
-
3. **Approver** — either:
|
|
21
|
-
- `approveSwap(offerId)`
|
|
22
|
-
Executes: `tokenA.transferFrom(partyA, partyB, amountA)` and `tokenB.transferFrom(partyB, partyA, amountB)`.
|
|
23
|
-
- `rejectSwap(offerId)`
|
|
24
|
-
Marks the swap as rejected (no transfers).
|
|
25
|
-
|
|
26
|
-
## State and Status
|
|
27
|
-
|
|
28
|
-
- **PENDING** — Offer created, no counter-offer yet.
|
|
29
|
-
- **MATCHED** — Counter-offer submitted; waiting for approver.
|
|
30
|
-
- **EXECUTING** — Internal state during transfers (reentrancy guard).
|
|
31
|
-
- **EXECUTED** — Swap completed.
|
|
32
|
-
- **REJECTED** — Approver rejected the swap.
|
|
33
|
-
- **CANCELLED** — Reserved for future use.
|
|
34
|
-
|
|
35
|
-
## Role: P2P_APPROVER_ROLE
|
|
36
|
-
|
|
37
|
-
- Created at initialization (non-protected).
|
|
38
|
-
- Max wallets defined in `P2PBloxDefinitions.P2P_APPROVER_MAX_WALLETS` (e.g. 5).
|
|
39
|
-
- No wallets are assigned in `initialize`; assign approvers after deployment via RuntimeRBAC `roleConfigBatch` (e.g. `ADD_WALLET` for `P2P_APPROVER_ROLE`).
|
|
40
|
-
|
|
41
|
-
## Security
|
|
42
|
-
|
|
43
|
-
- **CEI**: State (including status) is updated before external `transferFrom` calls.
|
|
44
|
-
- **Reentrancy**: `requestOffer`, `requestCounterOffer`, and `approveSwap` use `nonReentrant`.
|
|
45
|
-
- **Inputs**: Zero address and zero amount checks; counter-offer must match offer; party B ≠ party A.
|
|
46
|
-
- **Transfers**: SafeERC20 for both tokens.
|
|
47
|
-
|
|
48
|
-
## API Summary
|
|
49
|
-
|
|
50
|
-
| Function | Caller | Description |
|
|
51
|
-
|----------|--------|-------------|
|
|
52
|
-
| `requestOffer(tokenA, amountA, tokenB, amountB)` | Any | Create offer; returns `offerId`. |
|
|
53
|
-
| `requestCounterOffer(offerId, tokenB, amountB, tokenA, amountA)` | Any (≠ party A) | Match offer; must match tokens/amounts. |
|
|
54
|
-
| `approveSwap(offerId)` | P2P_APPROVER_ROLE | Execute both transfers. |
|
|
55
|
-
| `rejectSwap(offerId)` | P2P_APPROVER_ROLE | Reject swap (no transfers). |
|
|
56
|
-
| `getSwap(offerId)` | Any | Return full swap record. |
|
|
57
|
-
| `getNextOfferId()` | Any | Next offer ID (for UIs). |
|
|
58
|
-
|
|
59
|
-
## Events
|
|
60
|
-
|
|
61
|
-
- `OfferCreated(offerId, partyA, tokenA, amountA, tokenB, amountB)`
|
|
62
|
-
- `CounterOfferCreated(offerId, partyB)`
|
|
63
|
-
- `SwapApproved(offerId)`
|
|
64
|
-
- `SwapRejected(offerId)`
|
|
65
|
-
|
|
66
|
-
## Files
|
|
67
|
-
|
|
68
|
-
- `P2PBlox.sol` — Main contract (swap logic, role checks, SafeERC20, CEI).
|
|
69
|
-
- `lib/definitions/P2PBloxDefinitions.sol` — Constants: `P2P_APPROVER_ROLE`, `P2P_APPROVER_MAX_WALLETS`, `P2P_SWAP_OPERATION`.
|
|
70
|
-
|
|
71
|
-
## Deployment and Setup
|
|
72
|
-
|
|
73
|
-
1. Deploy P2PBlox (e.g. via proxy/clone pattern used elsewhere in the protocol).
|
|
74
|
-
2. Call `initialize(initialOwner, broadcaster, recovery, timeLockPeriodSec, eventForwarder)`.
|
|
75
|
-
3. Assign at least one wallet to `P2P_APPROVER_ROLE` using RuntimeRBAC `roleConfigBatch` (CREATE_ROLE already done in `initialize`; use ADD_WALLET for the approver role hash from `P2PBloxDefinitions.P2P_APPROVER_ROLE`).
|
|
76
|
-
|
|
77
|
-
## Custom Errors
|
|
78
|
-
|
|
79
|
-
- `InvalidOffer(offerId)` — Offer does not exist.
|
|
80
|
-
- `OfferNotPending(offerId)` — Offer not in PENDING (e.g. already matched).
|
|
81
|
-
- `OfferNotMatched(offerId)` — Offer not in MATCHED (cannot approve/reject).
|
|
82
|
-
- `CounterOfferMismatch()` — Counter-offer tokens/amounts do not match offer.
|
|
83
|
-
- `InvalidParties()` — Counter-offer from same address as party A.
|
|
84
|
-
- `OnlyApprover()` — Caller does not have `P2P_APPROVER_ROLE`.
|
|
85
|
-
- `ZeroAmount()` — Either amount in an offer is zero.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
-
pragma solidity 0.8.33;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @title P2PBloxDefinitions
|
|
6
|
-
* @dev Constants and definitions for the P2P (peer-to-peer) swap blox.
|
|
7
|
-
* Defines the dedicated approver role and operation types used by P2PBlox.
|
|
8
|
-
* @custom:security-contact security@particlecrypto.com
|
|
9
|
-
*/
|
|
10
|
-
library P2PBloxDefinitions {
|
|
11
|
-
/// @dev Role hash for the dedicated swap approver (can approve or reject matched swaps)
|
|
12
|
-
bytes32 public constant P2P_APPROVER_ROLE = keccak256(bytes("P2P_APPROVER_ROLE"));
|
|
13
|
-
|
|
14
|
-
/// @dev Maximum number of wallets that can hold the P2P_APPROVER_ROLE
|
|
15
|
-
uint256 public constant P2P_APPROVER_MAX_WALLETS = 5;
|
|
16
|
-
|
|
17
|
-
/// @dev Operation type for P2P swap lifecycle (offer, counter, approve, reject)
|
|
18
|
-
bytes32 public constant P2P_SWAP_OPERATION = keccak256("P2P_SWAP_OPERATION");
|
|
19
|
-
}
|