create-aztec-privacy-template 0.1.1 → 0.2.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/dist/constants.js CHANGED
@@ -4,6 +4,7 @@ export const OVERLAY_EXAMPLES_DIR = 'overlays/examples';
4
4
  export const EXAMPLE_OVERLAY_ORDER = ['aave', 'lido', 'uniswap'];
5
5
  export const SUPPORTED_EXAMPLE_SELECTIONS = ['none', ...EXAMPLE_OVERLAY_ORDER, 'all'];
6
6
  export const TEMPLATE_COPY_ENTRIES = [
7
+ '.env.deploy.example',
7
8
  '.solhint.json',
8
9
  'gitignore',
9
10
  'Makefile',
@@ -2,7 +2,7 @@ import { access, chmod } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  const POST_INIT_HOOKS = [
4
4
  verifyRequiredLayoutHook,
5
- ensureCompileScriptExecutableHook,
5
+ ensureScriptExecutablesHook,
6
6
  ];
7
7
  export async function runPostInitHooks(context) {
8
8
  for (const hook of POST_INIT_HOOKS) {
@@ -16,6 +16,9 @@ async function verifyRequiredLayoutHook(context) {
16
16
  join('contracts', 'l1'),
17
17
  join('contracts', 'aztec'),
18
18
  join('scripts', 'compile-aztec-contract.sh'),
19
+ join('scripts', 'deploy.sh'),
20
+ join('scripts', 'integration-test-deployment.sh'),
21
+ join('scripts', 'verify-deployment.sh'),
19
22
  ];
20
23
  if (context.exampleSelection === 'all') {
21
24
  requiredPaths.push(join('examples', 'aave'), join('examples', 'lido'), join('examples', 'uniswap'));
@@ -27,7 +30,13 @@ async function verifyRequiredLayoutHook(context) {
27
30
  await access(join(context.absoluteTargetPath, relativePath));
28
31
  }
29
32
  }
30
- async function ensureCompileScriptExecutableHook(context) {
33
+ async function ensureScriptExecutablesHook(context) {
31
34
  const compileScript = join(context.absoluteTargetPath, 'scripts', 'compile-aztec-contract.sh');
35
+ const deployScript = join(context.absoluteTargetPath, 'scripts', 'deploy.sh');
36
+ const integrationDeploymentScript = join(context.absoluteTargetPath, 'scripts', 'integration-test-deployment.sh');
37
+ const verifyDeploymentScript = join(context.absoluteTargetPath, 'scripts', 'verify-deployment.sh');
32
38
  await chmod(compileScript, 0o755);
39
+ await chmod(deployScript, 0o755);
40
+ await chmod(integrationDeploymentScript, 0o755);
41
+ await chmod(verifyDeploymentScript, 0o755);
33
42
  }
@@ -1,5 +1,5 @@
1
1
  import { cp, mkdir } from 'node:fs/promises';
2
- import { join } from 'node:path';
2
+ import { join, relative, sep } from 'node:path';
3
3
  import { EXAMPLE_OVERLAY_ORDER, OVERLAY_EXAMPLES_DIR, SCAFFOLD_DIR, TEMPLATE_COPY_ENTRIES, } from '../constants.js';
4
4
  export function resolveTemplateInstallPlan(generatorRoot, exampleSelection) {
5
5
  return {
@@ -18,6 +18,7 @@ export async function installTemplatePlan(absoluteTargetPath, plan) {
18
18
  errorOnExist: true,
19
19
  force: false,
20
20
  preserveTimestamps: true,
21
+ filter: createTemplateCopyFilter(plan.base.sourceDir),
21
22
  });
22
23
  }
23
24
  for (const overlay of plan.overlays) {
@@ -26,9 +27,25 @@ export async function installTemplatePlan(absoluteTargetPath, plan) {
26
27
  errorOnExist: false,
27
28
  force: true,
28
29
  preserveTimestamps: true,
30
+ filter: createTemplateCopyFilter(overlay.sourceDir),
29
31
  });
30
32
  }
31
33
  }
34
+ const GENERATED_ARTIFACT_PREFIXES = [
35
+ 'contracts/l1/cache',
36
+ 'contracts/l1/out',
37
+ 'contracts/aztec/target',
38
+ ];
39
+ function createTemplateCopyFilter(sourceRoot) {
40
+ return (sourcePath) => {
41
+ const relPath = relative(sourceRoot, sourcePath);
42
+ if (relPath === '') {
43
+ return true;
44
+ }
45
+ const normalizedRelPath = relPath.split(sep).join('/');
46
+ return !GENERATED_ARTIFACT_PREFIXES.some((prefix) => normalizedRelPath === prefix || normalizedRelPath.startsWith(`${prefix}/`));
47
+ };
48
+ }
32
49
  function getBaseTemplate(generatorRoot) {
33
50
  return {
34
51
  mode: 'base',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-aztec-privacy-template",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "CLI to scaffold protocol-agnostic Aztec privacy starter projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,28 @@
1
+ # Copy this file to .env.deploy and edit values for your environment.
2
+ # Usage example:
3
+ # set -a && source .env.deploy && set +a && bash scripts/deploy.sh
4
+
5
+ # Common
6
+ PROTOCOL_ID=0xe9b9beae393069af7271ea16e89d289254d64491636d5137dcc231679d9eb042
7
+ RELAYER_ADDRESS=
8
+ AZTEC_ACCOUNT_ALIAS=
9
+ AZTEC_CONTRACT_ALIAS=generic-privacy-adapter
10
+ WALLET_DATA_DIR=.aztec-wallet
11
+ # Integration test helpers (used by make test-deployment)
12
+ RELAYER_PRIVATE_KEY=
13
+ OPERATOR_PRIVATE_KEY=
14
+ INTEGRATION_AMOUNT_WEI=1000000000000000
15
+ FAILURE_TIMEOUT_BLOCKS=1
16
+ # FAILURE_TARGET=0x000000000000000000000000000000000000dEaD
17
+
18
+ # Local devnet defaults (aztec start --local-network)
19
+ L1_RPC_URL=http://127.0.0.1:8545
20
+ AZTEC_NODE_URL=http://127.0.0.1:8080
21
+ DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
22
+ SKIP_BUILD=0
23
+
24
+ # Sepolia / Aztec testnet example values
25
+ # L1_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
26
+ # AZTEC_NODE_URL=https://devnet-6.aztec-labs.com
27
+ # DEPLOYER_PRIVATE_KEY=0x...
28
+ # SPONSORED_FPC_ADDRESS=0x1586f476995be97f07ebd415340a14be48dc28c6c661cc6bdddb80ae790caa4e
package/scaffold/Makefile CHANGED
@@ -10,7 +10,7 @@ SOLIDITY_FMT_FILES := $(shell find contracts/l1 -type f -name '*.sol' -not -path
10
10
 
11
11
  AZTEC_CONTRACT_DIRS := contracts/aztec
12
12
 
13
- .PHONY: help install verify-toolchain fmt fmt-check lint test _test-core _test-flow build build-aztec check clean
13
+ .PHONY: help install verify-toolchain fmt fmt-check lint test _test-core _test-flow build build-aztec deploy test-deployment verify-deployment integration-deployment check clean
14
14
 
15
15
  help:
16
16
  @printf "Aztec Privacy Starter Make Targets\n\n"
@@ -28,6 +28,11 @@ help:
28
28
  @printf "Build\n"
29
29
  @printf " make build Build Solidity + Aztec artifacts\n"
30
30
  @printf " make build-aztec Compile Aztec Noir contract\n\n"
31
+ @printf "Deploy\n"
32
+ @printf " make deploy Deploy scaffold contracts (local or sepolia/testnet)\n"
33
+ @printf " make test-deployment Run live post-deploy integration checks\n"
34
+ @printf " make verify-deployment Run deployment invariant checks only\n"
35
+ @printf " make integration-deployment Run state-changing integration flow only\n\n"
31
36
  @printf "Cleanup\n"
32
37
  @printf " make clean Remove build artifacts\n"
33
38
 
@@ -147,6 +152,32 @@ build:
147
152
  @mkdir -p target
148
153
  @echo "Build complete."
149
154
 
155
+ deploy:
156
+ @bash scripts/deploy.sh
157
+
158
+ test-deployment:
159
+ @if [ -n "$(DEPLOYMENT_FILE)" ]; then \
160
+ bash scripts/verify-deployment.sh "$(DEPLOYMENT_FILE)"; \
161
+ bash scripts/integration-test-deployment.sh "$(DEPLOYMENT_FILE)"; \
162
+ else \
163
+ bash scripts/verify-deployment.sh; \
164
+ bash scripts/integration-test-deployment.sh; \
165
+ fi
166
+
167
+ verify-deployment:
168
+ @if [ -n "$(DEPLOYMENT_FILE)" ]; then \
169
+ bash scripts/verify-deployment.sh "$(DEPLOYMENT_FILE)"; \
170
+ else \
171
+ bash scripts/verify-deployment.sh; \
172
+ fi
173
+
174
+ integration-deployment:
175
+ @if [ -n "$(DEPLOYMENT_FILE)" ]; then \
176
+ bash scripts/integration-test-deployment.sh "$(DEPLOYMENT_FILE)"; \
177
+ else \
178
+ bash scripts/integration-test-deployment.sh; \
179
+ fi
180
+
150
181
  check:
151
182
  @$(MAKE) fmt-check
152
183
  @$(MAKE) lint
@@ -157,4 +188,5 @@ clean:
157
188
  @rm -rf target
158
189
  @rm -rf cache out
159
190
  @rm -rf contracts/l1/cache contracts/l1/out
191
+ @rm -rf contracts/aztec/target
160
192
  @rm -rf coverage .turbo .nyc_output
@@ -42,13 +42,16 @@ Important integration boundary:
42
42
  ```text
43
43
  .
44
44
  |-- contracts/
45
- | |-- l1/ # BasePortal, EscapeHatch, GenericPortal + tests
45
+ | |-- l1/ # BasePortal, EscapeHatch, GenericPortal, GenericActionExecutor + tests
46
46
  | `-- aztec/ # Generic Noir adapter skeleton
47
47
  |-- docs/
48
- | |-- DEPLOYMENT.md # deployment/configuration runbook (manual until script is added)
48
+ | |-- DEPLOYMENT.md # deployment/configuration runbook + script usage
49
49
  | `-- RELAYER_SPEC.md # minimal relayer operational spec
50
50
  |-- scripts/
51
- | `-- compile-aztec-contract.sh
51
+ | |-- compile-aztec-contract.sh
52
+ | |-- deploy.sh
53
+ | |-- integration-test-deployment.sh
54
+ | `-- verify-deployment.sh
52
55
  |-- Makefile
53
56
  `-- package.json
54
57
  ```
@@ -85,10 +88,16 @@ Compile Solidity and Aztec contract artifacts.
85
88
  2. `make test`
86
89
  Run all starter Solidity tests (`BasePortal`, `EscapeHatch`, `GenericPortal` flow).
87
90
 
91
+ 3. `bash scripts/deploy.sh`
92
+ Deploy scaffold contracts to local Aztec/Anvil or Aztec testnet + Sepolia.
93
+
94
+ 4. `make test-deployment`
95
+ Run live post-deploy checks (invariants + state-changing integration flow).
96
+
88
97
  ## Adaptation workflow
89
98
 
90
99
  1. Start from `contracts/l1/GenericPortal.sol`.
91
- 2. Replace `IGenericActionExecutor` wiring with your protocol integration call(s).
100
+ 2. Adapt `contracts/l1/GenericActionExecutor.sol` target allowlist + forwarding payload to your protocol integration.
92
101
  3. Extend `contracts/aztec/src/main.nr` intent fields to match your private flow.
93
102
  4. Define a single completion payload schema and keep it identical in `GenericPortal` success emission, relayer transport,
94
103
  and Noir `finalize_action` hashing.
@@ -119,7 +128,7 @@ Use `docs/DEPLOYMENT.md` as the source of truth for:
119
128
  3. address dependency planning between L1 and L2 contracts
120
129
  4. deployment validation checks
121
130
 
122
- This template will include a deployment script in a future iteration. Until then, use the runbook for manual or custom automation.
131
+ Use `bash scripts/deploy.sh` for scaffold deployment automation, and keep `docs/DEPLOYMENT.md` as the source of truth for environment values.
123
132
 
124
133
  ## Relayer Operational Spec
125
134
 
@@ -20,10 +20,12 @@ abstract contract BasePortal {
20
20
 
21
21
  /// @notice protocol identifier used in message derivation.
22
22
  bytes32 public immutable PROTOCOL_ID;
23
- /// @notice bound L2 portal contract address.
24
- address public immutable L2_CONTRACT;
23
+ /// @notice bound L2 portal contract identifier (Aztec contract address bytes32).
24
+ bytes32 public L2_CONTRACT;
25
25
  /// @notice authorized relayer address.
26
26
  address public immutable RELAYER;
27
+ /// @notice one-time configuration admin for deferred L2 binding.
28
+ address public immutable CONFIG_ADMIN;
27
29
  /// @notice monotonic message nonce.
28
30
  uint64 public messageNonce;
29
31
 
@@ -33,35 +35,40 @@ abstract contract BasePortal {
33
35
  error EmptyRecipient();
34
36
  error InvalidL2Contract();
35
37
  error InvalidRelayer();
38
+ error L2ContractAlreadyConfigured();
39
+ error L2ContractNotConfigured();
36
40
  error MessageAlreadyIssued();
37
41
  error MessageAlreadyConsumed();
38
42
  error MessageNotIssued();
43
+ error UnauthorizedConfigAdmin();
39
44
  error UnauthorizedCaller();
40
45
 
46
+ event L2ContractConfigured(bytes32 indexed l2Contract);
47
+
41
48
  /// @notice Initializes the portal with immutable protocol metadata.
42
49
  /// @param protocolId_ protocol identifier used for hashing messages.
43
- /// @param l2Contract_ bound L2 contract address (Aztec adapter for this flow).
50
+ /// @param l2Contract_ bound L2 contract identifier (`bytes32` Aztec address for this flow).
51
+ /// `bytes32(0)` is allowed for deferred one-time initialization via `setL2Contract`.
44
52
  /// @param relayer_ authorized relayer/service address.
45
53
  /// @dev Value source guidance is documented in scaffold docs/DEPLOYMENT.md.
46
- constructor(bytes32 protocolId_, address l2Contract_, address relayer_) {
47
- if (l2Contract_ == address(0)) {
48
- revert InvalidL2Contract();
49
- }
50
-
54
+ constructor(bytes32 protocolId_, bytes32 l2Contract_, address relayer_) {
51
55
  if (relayer_ == address(0)) {
52
56
  revert InvalidRelayer();
53
57
  }
54
58
 
55
59
  PROTOCOL_ID = protocolId_;
56
- L2_CONTRACT = l2Contract_;
57
60
  RELAYER = relayer_;
61
+ CONFIG_ADMIN = msg.sender;
58
62
  messageNonce = 0;
63
+
64
+ if (l2Contract_ != bytes32(0)) {
65
+ L2_CONTRACT = l2Contract_;
66
+ emit L2ContractConfigured(l2Contract_);
67
+ }
59
68
  }
60
69
 
61
70
  modifier onlyRelayer() {
62
- if (msg.sender != RELAYER) {
63
- revert UnauthorizedCaller();
64
- }
71
+ _onlyRelayer();
65
72
  _;
66
73
  }
67
74
 
@@ -70,6 +77,8 @@ abstract contract BasePortal {
70
77
  /// @param sender Message sender address.
71
78
  /// @return messageHash Derived message hash.
72
79
  function _sendL1ToL2Message(bytes32 content, address sender) internal returns (bytes32 messageHash) {
80
+ _assertL2ContractConfigured();
81
+
73
82
  if (sender == address(0)) {
74
83
  revert EmptyRecipient();
75
84
  }
@@ -85,6 +94,7 @@ abstract contract BasePortal {
85
94
  }
86
95
 
87
96
  function _consumeL2ToL1Message(bytes32 content, address sender, uint64 nonce) internal onlyRelayer {
97
+ _assertL2ContractConfigured();
88
98
  bytes32 messageHash = _buildMessageHash(content, sender, nonce);
89
99
 
90
100
  if (!issuedMessages[messageHash]) {
@@ -105,9 +115,43 @@ abstract contract BasePortal {
105
115
  /// @param nonce Monotonic nonce.
106
116
  /// @return Hashed message payload.
107
117
  function _buildMessageHash(bytes32 content, address sender, uint64 nonce) internal view returns (bytes32) {
118
+ _assertL2ContractConfigured();
119
+ // forge-lint: disable-next-line(asm-keccak256)
108
120
  return keccak256(abi.encodePacked(PROTOCOL_ID, L2_CONTRACT, content, sender, nonce));
109
121
  }
110
122
 
123
+ function _onlyRelayer() private view {
124
+ if (msg.sender != RELAYER) {
125
+ revert UnauthorizedCaller();
126
+ }
127
+ }
128
+
129
+ function _assertL2ContractConfigured() private view {
130
+ if (L2_CONTRACT == bytes32(0)) {
131
+ revert L2ContractNotConfigured();
132
+ }
133
+ }
134
+
135
+ /// @notice Configures the L2 contract binding exactly once.
136
+ /// @param l2Contract_ Aztec contract address (`bytes32`) bound to this portal.
137
+ function setL2Contract(bytes32 l2Contract_) external {
138
+ if (msg.sender != CONFIG_ADMIN) {
139
+ revert UnauthorizedConfigAdmin();
140
+ }
141
+ if (l2Contract_ == bytes32(0)) {
142
+ revert InvalidL2Contract();
143
+ }
144
+ if (L2_CONTRACT != bytes32(0)) {
145
+ revert L2ContractAlreadyConfigured();
146
+ }
147
+ if (messageNonce != 0) {
148
+ revert MessageAlreadyIssued();
149
+ }
150
+
151
+ L2_CONTRACT = l2Contract_;
152
+ emit L2ContractConfigured(l2Contract_);
153
+ }
154
+
111
155
  /// @notice Checks if a message hash was already issued.
112
156
  /// @param messageHash Message hash.
113
157
  /// @return Whether the message has been issued.
@@ -0,0 +1,97 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.33;
3
+
4
+ import {IGenericActionExecutor} from "./GenericPortal.sol";
5
+
6
+ /// @title GenericActionExecutor
7
+ /// @author aztec-privacy-template
8
+ /// @notice Generic L1 executor that forwards validated actions to allowlisted targets.
9
+ /// @dev `actionData` schema: `abi.encode(address target, uint256 value, bytes callData)`.
10
+ contract GenericActionExecutor is IGenericActionExecutor {
11
+ /// @notice Current operator allowed to configure this executor.
12
+ address public operator;
13
+ /// @notice Generic portal authorized to invoke `executeAction`.
14
+ address public portal;
15
+ /// @notice Allowlist of protocol targets callable by this executor.
16
+ mapping(address target => bool allowed) public allowedTargets;
17
+
18
+ error InvalidAddress();
19
+ error UnauthorizedCaller();
20
+ error PortalAlreadyConfigured();
21
+ error TargetNotAllowed(address target);
22
+
23
+ event OperatorUpdated(address indexed previousOperator, address indexed newOperator);
24
+ event PortalConfigured(address indexed portal);
25
+ event TargetPermissionSet(address indexed target, bool allowed);
26
+ event ActionForwarded(address indexed actor, uint256 amount, address indexed target, uint256 value, bool success);
27
+
28
+ constructor(address operator_) {
29
+ if (operator_ == address(0)) {
30
+ revert InvalidAddress();
31
+ }
32
+ operator = operator_;
33
+ }
34
+
35
+ modifier onlyOperator() {
36
+ if (msg.sender != operator) {
37
+ revert UnauthorizedCaller();
38
+ }
39
+ _;
40
+ }
41
+
42
+ modifier onlyPortal() {
43
+ if (msg.sender != portal) {
44
+ revert UnauthorizedCaller();
45
+ }
46
+ _;
47
+ }
48
+
49
+ /// @notice Sets the portal once after deployment.
50
+ function setPortal(address portal_) external onlyOperator {
51
+ if (portal_ == address(0)) {
52
+ revert InvalidAddress();
53
+ }
54
+ if (portal != address(0)) {
55
+ revert PortalAlreadyConfigured();
56
+ }
57
+ portal = portal_;
58
+ emit PortalConfigured(portal_);
59
+ }
60
+
61
+ /// @notice Transfers executor configuration rights.
62
+ function setOperator(address nextOperator) external onlyOperator {
63
+ if (nextOperator == address(0)) {
64
+ revert InvalidAddress();
65
+ }
66
+ address previous = operator;
67
+ operator = nextOperator;
68
+ emit OperatorUpdated(previous, nextOperator);
69
+ }
70
+
71
+ /// @notice Sets target allowlist status for forwarded calls.
72
+ function setTargetPermission(address target, bool allowed) external onlyOperator {
73
+ if (target == address(0)) {
74
+ revert InvalidAddress();
75
+ }
76
+ allowedTargets[target] = allowed;
77
+ emit TargetPermissionSet(target, allowed);
78
+ }
79
+
80
+ /// @inheritdoc IGenericActionExecutor
81
+ function executeAction(address actor, uint256 amount, bytes calldata actionData)
82
+ external
83
+ onlyPortal
84
+ returns (bool)
85
+ {
86
+ (address target, uint256 value, bytes memory callData) = abi.decode(actionData, (address, uint256, bytes));
87
+ if (!allowedTargets[target]) {
88
+ revert TargetNotAllowed(target);
89
+ }
90
+
91
+ (bool success,) = target.call{value: value}(callData);
92
+ emit ActionForwarded(actor, amount, target, value, success);
93
+ return success;
94
+ }
95
+
96
+ receive() external payable {}
97
+ }
@@ -73,11 +73,12 @@ contract GenericPortal is BasePortal, EscapeHatch {
73
73
 
74
74
  /// @notice Creates a new generic portal.
75
75
  /// @param protocolId_ Protocol identifier used in deterministic message hashing.
76
- /// @param l2Contract_ L2 adapter contract address for this flow.
76
+ /// @param l2Contract_ L2 adapter contract identifier (`bytes32` Aztec contract address) for this flow.
77
+ /// `bytes32(0)` enables deferred one-time binding via `setL2Contract`.
77
78
  /// @param relayer_ Relayer/service address authorized to execute inbound messages.
78
79
  /// @param executor_ Protocol execution hook for concrete integration.
79
80
  /// @dev See scaffold docs/DEPLOYMENT.md for value sources and address planning guidance.
80
- constructor(bytes32 protocolId_, address l2Contract_, address relayer_, address executor_)
81
+ constructor(bytes32 protocolId_, bytes32 l2Contract_, address relayer_, address executor_)
81
82
  BasePortal(protocolId_, l2Contract_, relayer_)
82
83
  {
83
84
  if (executor_ == address(0)) {
@@ -140,6 +141,7 @@ contract GenericPortal is BasePortal, EscapeHatch {
140
141
  }
141
142
 
142
143
  bytes32 messageHash = _buildMessageHash(content, sender, nonce);
144
+ // forge-lint: disable-next-line(asm-keccak256)
143
145
  bytes32 actionHash = keccak256(actionData);
144
146
 
145
147
  _assertFlowRequest(messageHash, sender, amount, actionHash);
@@ -5,6 +5,7 @@ This folder contains all Solidity contracts for the starter:
5
5
  1. `BasePortal.sol`
6
6
  2. `EscapeHatch.sol`
7
7
  3. `GenericPortal.sol`
8
+ 4. `GenericActionExecutor.sol`
8
9
 
9
10
  It also includes tests under `test/`.
10
11
 
@@ -20,6 +21,8 @@ It also includes tests under `test/`.
20
21
  These contracts define deterministic message content and local request state. For real cross-chain delivery, integrate
21
22
  your relayer with Aztec canonical Inbox/Outbox contracts (or their current equivalent on your target network).
22
23
 
24
+ `BasePortal` supports deferred one-time L2 binding via `setL2Contract(bytes32)` for deployment sequencing.
25
+
23
26
  ## Deployment parameters
24
27
 
25
28
  See `../../docs/DEPLOYMENT.md` for constructor input requirements and value sources for:
@@ -37,10 +40,18 @@ See `../../docs/RELAYER_SPEC.md` for minimum relayer responsibilities, nonce/ide
37
40
 
38
41
  1. Keep hash construction consistent between Aztec and L1.
39
42
  2. Keep `onlyRelayer` on execution/finalization entrypoints.
40
- 3. Wire your real protocol integration in `IGenericActionExecutor`.
43
+ 3. Adapt `GenericActionExecutor` target allowlist + payload schema to your protocol integration.
41
44
  4. Preserve escape registration behavior on execution failure.
42
45
  5. Extend tests in `test/GenericPortal.t.sol` for protocol-specific invariants.
43
46
 
47
+ ## Default executor payload
48
+
49
+ `GenericActionExecutor` expects:
50
+
51
+ 1. `actionData = abi.encode(address target, uint256 value, bytes callData)`
52
+ 2. `target` must be allowlisted via `setTargetPermission(target, true)`
53
+ 3. `executeAction` forwards `callData` to `target` and returns call success
54
+
44
55
  ## Personalization examples
45
56
 
46
57
  Example A: lend/repay style flow
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import "../BasePortal.sol";
4
+ import {BasePortal} from "../BasePortal.sol";
5
5
 
6
6
  contract BasePortalHarness is BasePortal {
7
- constructor(bytes32 protocolId, address l2Contract, address relayer) BasePortal(protocolId, l2Contract, relayer) {}
7
+ constructor(bytes32 protocolId, bytes32 l2Contract, address relayer) BasePortal(protocolId, l2Contract, relayer) {}
8
8
 
9
9
  function sendMessage(bytes32 content, address sender) external returns (bytes32) {
10
10
  return _sendL1ToL2Message(content, sender);
@@ -20,23 +20,22 @@ contract BasePortalHarness is BasePortal {
20
20
  }
21
21
 
22
22
  contract UnauthorizedPortalCaller {
23
+ function configure(BasePortalHarness portal, bytes32 l2Contract) external {
24
+ portal.setL2Contract(l2Contract);
25
+ }
26
+
23
27
  function consume(BasePortalHarness portal, bytes32 content, address sender, uint64 nonce) external {
24
28
  portal.consumeMessage(content, sender, nonce);
25
29
  }
26
30
  }
27
31
 
28
32
  contract BasePortalTest {
29
- function testBasePortalConstructorRejectsInvalidAddresses() external {
30
- bool invalidL2 = false;
31
- try new BasePortalHarness(bytes32("BASE_PORTAL"), address(0), address(this)) {
32
- invalidL2 = false;
33
- } catch {
34
- invalidL2 = true;
35
- }
36
- assert(invalidL2);
33
+ bytes32 private constant PROTOCOL_ID = keccak256("BASE_PORTAL");
34
+ bytes32 private constant L2_CONTRACT_ID = keccak256("L2_CONTRACT");
37
35
 
36
+ function testBasePortalConstructorRejectsInvalidRelayer() external {
38
37
  bool invalidRelayer = false;
39
- try new BasePortalHarness(bytes32("BASE_PORTAL"), address(0xA11CE), address(0)) {
38
+ try new BasePortalHarness(PROTOCOL_ID, L2_CONTRACT_ID, address(0)) {
40
39
  invalidRelayer = false;
41
40
  } catch {
42
41
  invalidRelayer = true;
@@ -44,8 +43,34 @@ contract BasePortalTest {
44
43
  assert(invalidRelayer);
45
44
  }
46
45
 
46
+ function testBasePortalDeferredL2Configuration() external {
47
+ BasePortalHarness portal = new BasePortalHarness(PROTOCOL_ID, bytes32(0), address(this));
48
+ UnauthorizedPortalCaller unauthorized = new UnauthorizedPortalCaller();
49
+
50
+ assert(portal.L2_CONTRACT() == bytes32(0));
51
+
52
+ bool unauthorizedReverted = false;
53
+ try unauthorized.configure(portal, L2_CONTRACT_ID) {
54
+ unauthorizedReverted = false;
55
+ } catch {
56
+ unauthorizedReverted = true;
57
+ }
58
+ assert(unauthorizedReverted);
59
+
60
+ portal.setL2Contract(L2_CONTRACT_ID);
61
+ assert(portal.L2_CONTRACT() == L2_CONTRACT_ID);
62
+
63
+ bool cannotReconfigure = false;
64
+ try portal.setL2Contract(keccak256("other")) {
65
+ cannotReconfigure = false;
66
+ } catch {
67
+ cannotReconfigure = true;
68
+ }
69
+ assert(cannotReconfigure);
70
+ }
71
+
47
72
  function testBasePortalSendGeneratesDeterministicHashesAndMonotonicNonce() external {
48
- BasePortalHarness portal = new BasePortalHarness(bytes32("BASE_PORTAL"), address(0xA11CE), address(this));
73
+ BasePortalHarness portal = new BasePortalHarness(PROTOCOL_ID, L2_CONTRACT_ID, address(this));
49
74
 
50
75
  bytes32 content = keccak256("content-key");
51
76
  address sender = address(0xB0B);
@@ -66,7 +91,7 @@ contract BasePortalTest {
66
91
  }
67
92
 
68
93
  function testBasePortalRejectsEmptyRecipientMessageSend() external {
69
- BasePortalHarness portal = new BasePortalHarness(bytes32("BASE_PORTAL"), address(0xA11CE), address(this));
94
+ BasePortalHarness portal = new BasePortalHarness(PROTOCOL_ID, L2_CONTRACT_ID, address(this));
70
95
 
71
96
  bool reverted = false;
72
97
 
@@ -80,7 +105,7 @@ contract BasePortalTest {
80
105
  }
81
106
 
82
107
  function testBasePortalConsumptionFlow() external {
83
- BasePortalHarness portal = new BasePortalHarness(bytes32("BASE_PORTAL"), address(0xA11CE), address(this));
108
+ BasePortalHarness portal = new BasePortalHarness(PROTOCOL_ID, L2_CONTRACT_ID, address(this));
84
109
  UnauthorizedPortalCaller unauthorized = new UnauthorizedPortalCaller();
85
110
 
86
111
  bytes32 content = keccak256("consume-key");
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.33;
3
3
 
4
- import "../EscapeHatch.sol";
4
+ import {EscapeHatch} from "../EscapeHatch.sol";
5
5
 
6
6
  interface IHevm {
7
7
  function roll(uint256) external;