@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.
Files changed (55) hide show
  1. package/README.md +55 -18
  2. package/abi/{ControlBlox.abi.json → AccountBlox.abi.json} +699 -2974
  3. package/abi/BareBlox.abi.json +127 -90
  4. package/abi/BaseStateMachine.abi.json +127 -90
  5. package/abi/EngineBlox.abi.json +11 -31
  6. package/abi/GuardController.abi.json +217 -895
  7. package/abi/GuardControllerDefinitions.abi.json +380 -0
  8. package/abi/IDefinition.abi.json +19 -0
  9. package/abi/RoleBlox.abi.json +818 -2404
  10. package/abi/RuntimeRBAC.abi.json +122 -328
  11. package/abi/RuntimeRBACDefinitions.abi.json +243 -0
  12. package/abi/SecureBlox.abi.json +620 -1952
  13. package/abi/SecureOwnable.abi.json +469 -1801
  14. package/abi/SecureOwnableDefinitions.abi.json +57 -0
  15. package/abi/SimpleRWA20.abi.json +486 -1999
  16. package/abi/SimpleRWA20Definitions.abi.json +19 -0
  17. package/abi/SimpleVault.abi.json +884 -2685
  18. package/abi/SimpleVaultDefinitions.abi.json +19 -0
  19. package/components/README.md +8 -0
  20. package/core/access/RuntimeRBAC.sol +184 -0
  21. package/core/access/interface/IRuntimeRBAC.sol +55 -0
  22. package/{contracts/core → core}/access/lib/definitions/RuntimeRBACDefinitions.sol +121 -1
  23. package/{contracts/core → core}/base/BaseStateMachine.sol +187 -54
  24. package/{contracts/core → core}/base/interface/IBaseStateMachine.sol +7 -0
  25. package/{contracts/core → core}/execution/GuardController.sol +89 -155
  26. package/{contracts/core → core}/execution/interface/IGuardController.sol +52 -12
  27. package/{contracts/core → core}/execution/lib/definitions/GuardControllerDefinitions.sol +91 -2
  28. package/{contracts/core → core}/lib/EngineBlox.sol +167 -64
  29. package/{contracts → core/lib}/interfaces/IDefinition.sol +15 -6
  30. package/{contracts → core/lib}/interfaces/IEventForwarder.sol +1 -1
  31. package/{contracts → core/lib}/utils/SharedValidation.sol +490 -486
  32. package/core/pattern/Account.sol +75 -0
  33. package/core/research/BloxchainWallet.sol +133 -0
  34. package/core/research/FactoryBlox/FactoryBlox.sol +344 -0
  35. package/core/research/FactoryBlox/FactoryBloxDefinitions.sol +144 -0
  36. package/core/research/erc1155-blox/ERC1155Blox.sol +170 -0
  37. package/core/research/erc1155-blox/lib/definitions/ERC1155BloxDefinitions.sol +203 -0
  38. package/core/research/erc20-blox/ERC20Blox.sol +135 -0
  39. package/core/research/erc20-blox/lib/definitions/ERC20BloxDefinitions.sol +185 -0
  40. package/core/research/erc721-blox/ERC721Blox.sol +131 -0
  41. package/core/research/erc721-blox/lib/definitions/ERC721BloxDefinitions.sol +172 -0
  42. package/core/research/lending-blox/.gitkeep +1 -0
  43. package/core/research/p2p-blox/P2PBlox.sol +266 -0
  44. package/core/research/p2p-blox/README.md +85 -0
  45. package/core/research/p2p-blox/lib/definitions/P2PBloxDefinitions.sol +19 -0
  46. package/{contracts/core → core}/security/SecureOwnable.sol +390 -419
  47. package/{contracts/core → core}/security/interface/ISecureOwnable.sol +27 -40
  48. package/{contracts/core → core}/security/lib/definitions/SecureOwnableDefinitions.sol +786 -757
  49. package/package.json +49 -47
  50. package/standards/README.md +12 -0
  51. package/standards/behavior/ICopyable.sol +36 -0
  52. package/standards/hooks/IOnActionHook.sol +21 -0
  53. package/contracts/core/access/RuntimeRBAC.sol +0 -344
  54. package/contracts/core/access/interface/IRuntimeRBAC.sol +0 -108
  55. package/contracts/interfaces/IOnActionHook.sol +0 -79
@@ -0,0 +1,266 @@
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
+ }
@@ -0,0 +1,85 @@
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.
@@ -0,0 +1,19 @@
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
+ }