feat: added calldata executor and bridge receiver#7
Conversation
|
Caution Review failedFailed to post review comments WalkthroughAdds BungeeReceiver and CalldataExecutor plus interfaces and a safe-call library; enforces signed DestPayload execution with nonce bitmap, transfers funds, dispatches IBungeeExecutor calls via CalldataExecutor, includes CREATE3 deterministic deployment helpers/scripts and end-to-end Foundry tests. ChangesDestination-Side Execution & Deployment
Sequence DiagramsequenceDiagram
participant Sender
participant BungeeReceiver
participant CalldataExecutor
participant IBungeeExecutor
Sender->>BungeeReceiver: executeDestPayload(payload, executionCallData, signature)
BungeeReceiver->>BungeeReceiver: verify signature & consume nonce
BungeeReceiver->>CalldataExecutor: executeCalldata(executor, encoded executeData, gasLimit)
CalldataExecutor->>IBungeeExecutor: executeData(quoteId, amount, token, callData) (low-level)
CalldataExecutor-->>BungeeReceiver: return success
BungeeReceiver-->>Sender: emit DestPayloadExecuted(quoteId, success)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/deploy/checkReceiverDeployment.ts`:
- Around line 69-72: The check currently only logs deployment info for
CalldataExecutor; add a strict equality check between
executorStatus.bungeeReceiver and the computed receiverAddress and, if they
differ, set hasError = true and emit a clear error/log indicating the mismatch
(include executorAddress, receiverAddress and executorStatus.bungeeReceiver for
context). Locate the block that prints the console.log for "CalldataExecutor
deployed ..." and insert the comparison and error handling there so the script
fails the check when the BUNGEE_RECEIVER wiring is not deterministic.
In `@scripts/deploy/create3.ts`:
- Around line 124-146: Update getBungeeReceiverDeploymentStatus to verify the
contract is actually a BungeeReceiver by calling one or more
BungeeReceiver-specific view methods (e.g., CALLDATA_EXECUTOR() and/or
SOLVER_SIGNER()) in addition to owner(); after confirming bytecode exists,
instantiate the Contract as you do now, call owner() and then call
CALLDATA_EXECUTOR() and/or SOLVER_SIGNER(), treat the deployment as valid only
if those Bungee-specific calls succeed and return plausible values
(addresses/non-zero), and propagate or handle errors so that exceptions from
unrelated Ownable contracts do not yield a false positive; keep the existing
return shape { address, deployed: boolean, owner?: string } and only set
deployed=true when the Bungee-specific probes succeed.
In `@scripts/deploy/deployReceiverAndExecutor.ts`:
- Around line 74-78: When executorStatus.deployed is true, assert that the
deployed executor is wired to the expected receiver by checking
executorStatus.bungeeReceiver === receiverAddress before skipping deployment; if
the check fails log an error (including executorAddress, expected
receiverAddress and actual executorStatus.bungeeReceiver) and either throw or
force redeploy. Update the branch around executorStatus.deployed (the block that
currently logs "CalldataExecutor already deployed...") to perform this
validation using executorStatus.bungeeReceiver and receiverAddress and handle
the mismatch deterministically (error/throw or redeploy) instead of silently
accepting it.
In `@src/BungeeReceiver.sol`:
- Around line 152-156: Reject zero addresses when configuring SOLVER_SIGNER and
CALLDATA_EXECUTOR: in the constructor (where SOLVER_SIGNER and CALLDATA_EXECUTOR
are assigned) add require(_solverSigner != address(0) && _calldataExecutor !=
address(0), "invalid-zero-address") so both must be non-zero, and apply the same
check to any setter/initializer that assigns these state vars (e.g., functions
that set SOLVER_SIGNER or CALLDATA_EXECUTOR such as
setSolverSigner/setCalldataExecutor or initialize) to prevent assigning
address(0).
- Around line 282-285: The unchecked subtraction of feeData.amount from
outputAmount in the POST_SWAP branch can wrap; update the block in the function
handling fee deduction (the code referencing feeData, FeeType.POST_SWAP,
CurrencyLib.transfer, and outputAmount) to first require(feeData.amount <=
outputAmount) with a clear revert/error message, then perform the transfer and
subtraction (remove or keep unchecked after the guard). This ensures any
misconfigured fee causes an immediate, explicit revert rather than a silent
wrap.
In `@src/CalldataExecutor.sol`:
- Around line 25-27: The constructor currently assigns BUNGEE_RECEIVER without
validation, allowing address(0) which would break executeCalldata access
control; update the constructor to require(_bungeeReceiver != address(0)) (or
revert with a clear error) before setting BUNGEE_RECEIVER so any zero address
passed causes deployment to revert, referencing the constructor and the
BUNGEE_RECEIVER state used by executeCalldata.
- Around line 30-36: In executeCalldata, reject EOAs/no-code targets before
calling to.excessivelySafeCall by checking to.code.length > 0 and reverting if
zero; add a guard (e.g., revert TargetNotContract() or require(to.code.length >
0, "target not contract")) immediately after the OnlyBungeeReceiver check and
before calling excessivelySafeCall so the call cannot return success for an
address with no contract code (referencing executeCalldata, BUNGEE_RECEIVER,
OnlyBungeeReceiver, excessivelySafeCall, and MAX_COPY_BYTES).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5063be1d-6f67-4eb6-8da0-42f34351ae3e
📒 Files selected for processing (8)
scripts/deploy/checkReceiverDeployment.tsscripts/deploy/create3.tsscripts/deploy/deployReceiverAndExecutor.tssrc/BungeeReceiver.solsrc/CalldataExecutor.solsrc/interfaces/IBungeeExecutor.solsrc/interfaces/ICalldataExecutor.solsrc/lib/ExcessivelySafeCall.sol
| console.log( | ||
| `CalldataExecutor deployed on ${networkName} (chainId=${chainId}) at ${executorAddress}, BUNGEE_RECEIVER=${executorStatus.bungeeReceiver}`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Fail check when CalldataExecutor.BUNGEE_RECEIVER mismatches computed receiver.
This checker should enforce deterministic wiring, not just deployment presence. Add a strict comparison against receiverAddress and mark hasError = true on mismatch.
Suggested fix
} else {
+ if (
+ !executorStatus.bungeeReceiver ||
+ executorStatus.bungeeReceiver.toLowerCase() !== receiverAddress.toLowerCase()
+ ) {
+ console.error(
+ `CalldataExecutor wiring mismatch on ${networkName} (chainId=${chainId}): expected BUNGEE_RECEIVER=${receiverAddress}, got ${executorStatus.bungeeReceiver}`,
+ );
+ hasError = true;
+ }
console.log(
`CalldataExecutor deployed on ${networkName} (chainId=${chainId}) at ${executorAddress}, BUNGEE_RECEIVER=${executorStatus.bungeeReceiver}`,
);
}As per coding guidelines, “Check for resource leaks, race conditions, and unhandled edge cases. Flag over-engineering and premature abstractions.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy/checkReceiverDeployment.ts` around lines 69 - 72, The check
currently only logs deployment info for CalldataExecutor; add a strict equality
check between executorStatus.bungeeReceiver and the computed receiverAddress
and, if they differ, set hasError = true and emit a clear error/log indicating
the mismatch (include executorAddress, receiverAddress and
executorStatus.bungeeReceiver for context). Locate the block that prints the
console.log for "CalldataExecutor deployed ..." and insert the comparison and
error handling there so the script fails the check when the BUNGEE_RECEIVER
wiring is not deterministic.
| export async function getBungeeReceiverDeploymentStatus(params: { | ||
| provider: Provider; | ||
| address: string; | ||
| }): Promise<{ address: string; deployed: boolean; owner?: string }> { | ||
| const { provider, address } = params; | ||
| const bytecode = await provider.getCode(address); | ||
|
|
||
| if (!hasContractBytecode(bytecode)) { | ||
| return { address, deployed: false }; | ||
| } | ||
|
|
||
| try { | ||
| const contract = new Contract( | ||
| address, | ||
| ['function owner() view returns (address)'], | ||
| provider, | ||
| ); | ||
| const owner = (await contract.owner()) as string; | ||
| return { address, deployed: true, owner }; | ||
| } catch { | ||
| return { address, deployed: false }; | ||
| } | ||
| } |
There was a problem hiding this comment.
Strengthen getBungeeReceiverDeploymentStatus contract identity check.
Current logic marks deployment as valid if owner() responds. That can false-positive on unrelated Ownable contracts. This should also probe BungeeReceiver-specific view(s), e.g. CALLDATA_EXECUTOR() and/or SOLVER_SIGNER().
Suggested direction
export async function getBungeeReceiverDeploymentStatus(params: {
provider: Provider;
address: string;
-}): Promise<{ address: string; deployed: boolean; owner?: string }> {
+}): Promise<{ address: string; deployed: boolean; owner?: string; calldataExecutor?: string }> {
@@
try {
const contract = new Contract(
address,
- ['function owner() view returns (address)'],
+ [
+ 'function owner() view returns (address)',
+ 'function CALLDATA_EXECUTOR() view returns (address)',
+ ],
provider,
);
const owner = (await contract.owner()) as string;
- return { address, deployed: true, owner };
+ const calldataExecutor = (await contract.CALLDATA_EXECUTOR()) as string;
+ return { address, deployed: true, owner, calldataExecutor };
} catch {
return { address, deployed: false };
}
}As per coding guidelines, “Check for resource leaks, race conditions, and unhandled edge cases. Flag over-engineering and premature abstractions.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function getBungeeReceiverDeploymentStatus(params: { | |
| provider: Provider; | |
| address: string; | |
| }): Promise<{ address: string; deployed: boolean; owner?: string }> { | |
| const { provider, address } = params; | |
| const bytecode = await provider.getCode(address); | |
| if (!hasContractBytecode(bytecode)) { | |
| return { address, deployed: false }; | |
| } | |
| try { | |
| const contract = new Contract( | |
| address, | |
| ['function owner() view returns (address)'], | |
| provider, | |
| ); | |
| const owner = (await contract.owner()) as string; | |
| return { address, deployed: true, owner }; | |
| } catch { | |
| return { address, deployed: false }; | |
| } | |
| } | |
| export async function getBungeeReceiverDeploymentStatus(params: { | |
| provider: Provider; | |
| address: string; | |
| }): Promise<{ address: string; deployed: boolean; owner?: string; calldataExecutor?: string }> { | |
| const { provider, address } = params; | |
| const bytecode = await provider.getCode(address); | |
| if (!hasContractBytecode(bytecode)) { | |
| return { address, deployed: false }; | |
| } | |
| try { | |
| const contract = new Contract( | |
| address, | |
| [ | |
| 'function owner() view returns (address)', | |
| 'function CALLDATA_EXECUTOR() view returns (address)', | |
| ], | |
| provider, | |
| ); | |
| const owner = (await contract.owner()) as string; | |
| const calldataExecutor = (await contract.CALLDATA_EXECUTOR()) as string; | |
| return { address, deployed: true, owner, calldataExecutor }; | |
| } catch { | |
| return { address, deployed: false }; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy/create3.ts` around lines 124 - 146, Update
getBungeeReceiverDeploymentStatus to verify the contract is actually a
BungeeReceiver by calling one or more BungeeReceiver-specific view methods
(e.g., CALLDATA_EXECUTOR() and/or SOLVER_SIGNER()) in addition to owner(); after
confirming bytecode exists, instantiate the Contract as you do now, call owner()
and then call CALLDATA_EXECUTOR() and/or SOLVER_SIGNER(), treat the deployment
as valid only if those Bungee-specific calls succeed and return plausible values
(addresses/non-zero), and propagate or handle errors so that exceptions from
unrelated Ownable contracts do not yield a false positive; keep the existing
return shape { address, deployed: boolean, owner?: string } and only set
deployed=true when the Bungee-specific probes succeed.
| if (executorStatus.deployed) { | ||
| console.log( | ||
| `CalldataExecutor already deployed at ${executorAddress}, BUNGEE_RECEIVER=${executorStatus.bungeeReceiver}`, | ||
| ); | ||
| } else { |
There was a problem hiding this comment.
Assert executor wiring before skipping deployment.
When executorStatus.deployed is true, this should verify executorStatus.bungeeReceiver === receiverAddress. Without that invariant, a miswired executor is silently accepted.
Suggested fix
if (executorStatus.deployed) {
+ if (
+ !executorStatus.bungeeReceiver ||
+ executorStatus.bungeeReceiver.toLowerCase() !== receiverAddress.toLowerCase()
+ ) {
+ throw new Error(
+ `CalldataExecutor wiring mismatch: expected BUNGEE_RECEIVER=${receiverAddress}, got ${executorStatus.bungeeReceiver}`,
+ );
+ }
console.log(
`CalldataExecutor already deployed at ${executorAddress}, BUNGEE_RECEIVER=${executorStatus.bungeeReceiver}`,
);
} else {As per coding guidelines, “Check for resource leaks, race conditions, and unhandled edge cases. Flag over-engineering and premature abstractions.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/deploy/deployReceiverAndExecutor.ts` around lines 74 - 78, When
executorStatus.deployed is true, assert that the deployed executor is wired to
the expected receiver by checking executorStatus.bungeeReceiver ===
receiverAddress before skipping deployment; if the check fails log an error
(including executorAddress, expected receiverAddress and actual
executorStatus.bungeeReceiver) and either throw or force redeploy. Update the
branch around executorStatus.deployed (the block that currently logs
"CalldataExecutor already deployed...") to perform this validation using
executorStatus.bungeeReceiver and receiverAddress and handle the mismatch
deterministically (error/throw or redeploy) instead of silently accepting it.
| constructor(address _owner, address _solverSigner, address _calldataExecutor) AccessControl(_owner) { | ||
| _grantRole(RESCUE_ROLE, _owner); | ||
| SOLVER_SIGNER = _solverSigner; | ||
| CALLDATA_EXECUTOR = _calldataExecutor; | ||
| } |
There was a problem hiding this comment.
Block zero-address configuration for signer/executor.
Line 154, Line 155, and Line 170 accept zero addresses. This should be rejected to avoid accidental bricking and signer-auth ambiguity.
Suggested fix
error InvalidSigner();
+ error InvalidAddress();
@@
constructor(address _owner, address _solverSigner, address _calldataExecutor) AccessControl(_owner) {
+ if (_solverSigner == address(0) || _calldataExecutor == address(0)) revert InvalidAddress();
_grantRole(RESCUE_ROLE, _owner);
SOLVER_SIGNER = _solverSigner;
CALLDATA_EXECUTOR = _calldataExecutor;
}
@@
function setSolverSigner(address _solverSigner) external onlyOwner {
+ if (_solverSigner == address(0)) revert InvalidAddress();
SOLVER_SIGNER = _solverSigner;
emit SolverSignerUpdated(_solverSigner);
}Also applies to: 169-172
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/BungeeReceiver.sol` around lines 152 - 156, Reject zero addresses when
configuring SOLVER_SIGNER and CALLDATA_EXECUTOR: in the constructor (where
SOLVER_SIGNER and CALLDATA_EXECUTOR are assigned) add require(_solverSigner !=
address(0) && _calldataExecutor != address(0), "invalid-zero-address") so both
must be non-zero, and apply the same check to any setter/initializer that
assigns these state vars (e.g., functions that set SOLVER_SIGNER or
CALLDATA_EXECUTOR such as setSolverSigner/setCalldataExecutor or initialize) to
prevent assigning address(0).
| constructor(address _bungeeReceiver) { | ||
| BUNGEE_RECEIVER = _bungeeReceiver; | ||
| } |
There was a problem hiding this comment.
Validate _bungeeReceiver is non-zero in constructor.
Line 26 currently allows address(0), which permanently bricks executeCalldata access control (no caller can ever be zero address). This should revert on invalid config.
Suggested fix
contract CalldataExecutor is ICalldataExecutor {
using ExcessivelySafeCall for address;
error OnlyBungeeReceiver();
+ error InvalidBungeeReceiver();
@@
constructor(address _bungeeReceiver) {
+ if (_bungeeReceiver == address(0)) revert InvalidBungeeReceiver();
BUNGEE_RECEIVER = _bungeeReceiver;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| constructor(address _bungeeReceiver) { | |
| BUNGEE_RECEIVER = _bungeeReceiver; | |
| } | |
| constructor(address _bungeeReceiver) { | |
| if (_bungeeReceiver == address(0)) revert InvalidBungeeReceiver(); | |
| BUNGEE_RECEIVER = _bungeeReceiver; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/CalldataExecutor.sol` around lines 25 - 27, The constructor currently
assigns BUNGEE_RECEIVER without validation, allowing address(0) which would
break executeCalldata access control; update the constructor to
require(_bungeeReceiver != address(0)) (or revert with a clear error) before
setting BUNGEE_RECEIVER so any zero address passed causes deployment to revert,
referencing the constructor and the BUNGEE_RECEIVER state used by
executeCalldata.
| function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool success) { | ||
| if (msg.sender != BUNGEE_RECEIVER) { | ||
| revert OnlyBungeeReceiver(); | ||
| } | ||
|
|
||
| (success,) = to.excessivelySafeCall(msgGasLimit, MAX_COPY_BYTES, encodedData); | ||
| } |
There was a problem hiding this comment.
Reject non-contract targets before low-level execution.
Line 35 can report success=true when to is an EOA/no-code address, which silently skips protocol execution while appearing successful. This should enforce to.code.length > 0.
Suggested fix
error OnlyBungeeReceiver();
+ error InvalidExecutorTarget();
@@
function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool success) {
if (msg.sender != BUNGEE_RECEIVER) {
revert OnlyBungeeReceiver();
}
+ if (to.code.length == 0) revert InvalidExecutorTarget();
(success,) = to.excessivelySafeCall(msgGasLimit, MAX_COPY_BYTES, encodedData);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool success) { | |
| if (msg.sender != BUNGEE_RECEIVER) { | |
| revert OnlyBungeeReceiver(); | |
| } | |
| (success,) = to.excessivelySafeCall(msgGasLimit, MAX_COPY_BYTES, encodedData); | |
| } | |
| function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool success) { | |
| if (msg.sender != BUNGEE_RECEIVER) { | |
| revert OnlyBungeeReceiver(); | |
| } | |
| if (to.code.length == 0) revert InvalidExecutorTarget(); | |
| (success,) = to.excessivelySafeCall(msgGasLimit, MAX_COPY_BYTES, encodedData); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/CalldataExecutor.sol` around lines 30 - 36, In executeCalldata, reject
EOAs/no-code targets before calling to.excessivelySafeCall by checking
to.code.length > 0 and reverting if zero; add a guard (e.g., revert
TargetNotContract() or require(to.code.length > 0, "target not contract"))
immediately after the OnlyBungeeReceiver check and before calling
excessivelySafeCall so the call cannot return success for an address with no
contract code (referencing executeCalldata, BUNGEE_RECEIVER, OnlyBungeeReceiver,
excessivelySafeCall, and MAX_COPY_BYTES).
Summary by CodeRabbit
New Features
Tests
Chores