Skip to content

Commit

Permalink
attestedCall support in checkpoint executor (external firewall case) (#…
Browse files Browse the repository at this point in the history
…34)

attestedCall support in checkpoint executor (external firewall case)
  • Loading branch information
canercidam authored Oct 28, 2024
1 parent a5e331e commit 57aa264
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 14 deletions.
36 changes: 23 additions & 13 deletions src/CheckpointExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

pragma solidity ^0.8.25;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
import "./interfaces/IExternallFirewall.sol";
import "./interfaces/IExternalFirewall.sol";

/**
* @notice A helper contract to call an external firewall.
Expand All @@ -25,11 +26,7 @@ abstract contract CheckpointExecutor {
* @param ref The reference number to compare with the threshold
*/
function _executeCheckpoint(bytes4 selector, uint256 ref) internal virtual {
CheckpointExecutorStorage storage $;
assembly {
$.slot := STORAGE_SLOT
}
IExternalFirewall($.externalFirewall).executeCheckpoint(msg.sender, selector, ref);
_getCheckpointExecutorStorage().externalFirewall.executeCheckpoint(msg.sender, selector, ref);
}

/**
Expand All @@ -38,22 +35,35 @@ abstract contract CheckpointExecutor {
* @param input The input value to use in checkpoint hash computation
*/
function _executeCheckpoint(bytes4 selector, bytes32 input) internal virtual {
CheckpointExecutorStorage storage $;
assembly {
$.slot := STORAGE_SLOT
}
IExternalFirewall($.externalFirewall).executeCheckpoint(msg.sender, selector, input);
_getCheckpointExecutorStorage().externalFirewall.executeCheckpoint(msg.sender, selector, input);
}

/**
* @notice Sets the external firewall in the namespaced storage.
* @param externalFirewall New external firewall
*/
function _setExternalFirewall(IExternalFirewall externalFirewall) internal virtual {
CheckpointExecutorStorage storage $;
_getCheckpointExecutorStorage().externalFirewall = externalFirewall;
}

/**
* @notice Helps write an attestation through the external firewall and call any function
* of this contract.
* @param attestation The set of fields that correspond to and enable the execution of call(s)
* @param attestationSignature Signature of EIP-712 message
* @param data Call data which contains the function selector and the encoded arguments
*/
function attestedCall(Attestation calldata attestation, bytes calldata attestationSignature, bytes calldata data)
public
returns (bytes memory)
{
_getCheckpointExecutorStorage().externalFirewall.saveAttestation(attestation, attestationSignature);
return Address.functionDelegateCall(address(this), data);
}

function _getCheckpointExecutorStorage() internal pure virtual returns (CheckpointExecutorStorage storage $) {
assembly {
$.slot := STORAGE_SLOT
}
$.externalFirewall = externalFirewall;
}
}
15 changes: 14 additions & 1 deletion src/ExternalFirewall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pragma solidity ^0.8.25;

import {Firewall} from "./Firewall.sol";
import "./interfaces/IExternalFirewall.sol";
import "./interfaces/ISecurityValidator.sol";
import "./interfaces/ICheckpointHook.sol";
import "./interfaces/IFirewallAccess.sol";
Expand All @@ -14,7 +15,7 @@ import "./interfaces/IFirewallAccess.sol";
* function to call this contract. The checkpoints must be adjusted by calling the
* setCheckpoint() function.
*/
contract ExternalFirewall is Firewall {
contract ExternalFirewall is IExternalFirewall, Firewall {
constructor(
ISecurityValidator _validator,
ICheckpointHook _checkpointHook,
Expand Down Expand Up @@ -43,4 +44,16 @@ contract ExternalFirewall is Firewall {
function executeCheckpoint(address caller, bytes4 selector, bytes32 input) public onlyCheckpointExecutor {
_secureExecution(caller, selector, input);
}

/**
* @notice Accepts and stores an attestation to the transient storage introduced
* with EIP-1153. Multiple contracts that operate in the same transaction can call
* a singleton of this contract. The stored values are later used during checkpoint
* execution.
* @param attestation The set of fields that correspond to and enable the execution of call(s)
* @param attestationSignature Signature of EIP-712 message
*/
function saveAttestation(Attestation calldata attestation, bytes calldata attestationSignature) public {
_getFirewallStorage().validator.saveAttestation(attestation, attestationSignature);
}
}
42 changes: 42 additions & 0 deletions src/examples/ExternalFirewallIntegration.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {CheckpointExecutor} from "../CheckpointExecutor.sol";
import {ExternalFirewall} from "../ExternalFirewall.sol";
import {ISecurityValidator} from "../interfaces/ISecurityValidator.sol";
import {FirewallAccess} from "../FirewallAccess.sol";
import {IExternalFirewall} from "../interfaces/IExternalFirewall.sol";
import {ICheckpointHook} from "../interfaces/ICheckpointHook.sol";

contract ProtectedContract is CheckpointExecutor {
constructor(IExternalFirewall externalFirewall) {
_setExternalFirewall(externalFirewall);
}

modifier safeExecution() {
_executeCheckpoint(msg.sig, keccak256(msg.data));
_;
}

function foo(uint256 num) public {}
}

contract Deployer {
event DeployedFirewall(ExternalFirewall firewall);
event DeployedProtectedContract(ProtectedContract protectedContract);

constructor(ISecurityValidator validator, address firewallAdmin, bytes32 attesterControllerId) {
FirewallAccess firewallAccess = new FirewallAccess(firewallAdmin);

ExternalFirewall externalFirewall =
new ExternalFirewall(validator, ICheckpointHook(address(0)), attesterControllerId, firewallAccess);
emit DeployedFirewall(externalFirewall);

ProtectedContract protectedContract = new ProtectedContract(externalFirewall);
emit DeployedProtectedContract(protectedContract);

/// Next steps:
/// - Grant ProtectedContact the CHECKPOINT_EXECUTOR_ROLE.
/// - Set a checkpoint for "foo" func in the firewall by using the firewall admin account.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

pragma solidity ^0.8.25;

import "./Attestation.sol";

interface IExternalFirewall {
function saveAttestation(Attestation calldata attestation, bytes calldata attestationSignature) external;
function executeCheckpoint(address caller, bytes4 selector, uint256 ref) external;
function executeCheckpoint(address caller, bytes4 selector, bytes32 input) external;
}
88 changes: 88 additions & 0 deletions test/ExternalFirewall.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {Test, console, Vm} from "forge-std/Test.sol";
import {CheckpointExecutor} from "../src/CheckpointExecutor.sol";
import {ExternalFirewall} from "../src/ExternalFirewall.sol";
import {ISecurityValidator} from "../src/interfaces/ISecurityValidator.sol";
import {IFirewallAccess} from "../src/interfaces/IFirewallAccess.sol";
import {IExternalFirewall} from "../src/interfaces/IExternalFirewall.sol";
import {ICheckpointHook} from "../src/interfaces/ICheckpointHook.sol";
import {Checkpoint} from "../src/interfaces/Checkpoint.sol";
import {Activation} from "../src/interfaces/Activation.sol";

contract ProtectedContract is CheckpointExecutor {
constructor(IExternalFirewall externalFirewall) {
_setExternalFirewall(externalFirewall);
}

modifier safeExecution() {
_executeCheckpoint(msg.sig, keccak256(msg.data));
_;
}

function foo(uint256 num) public safeExecution {}
}

contract ExternalFirewallTest is Test {
ISecurityValidator mockValidator;
IFirewallAccess mockAccess;

ExternalFirewall externalFirewall;

ProtectedContract protectedContract;

address constant testAttester = address(uint160(456));

function setUp() public {
mockValidator = ISecurityValidator(address(0));
mockAccess = IFirewallAccess(address(this));

externalFirewall =
new ExternalFirewall(mockValidator, ICheckpointHook(address(0)), bytes32(uint256(123)), mockAccess);
protectedContract = new ProtectedContract(IExternalFirewall(externalFirewall));

vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isCheckpointManager.selector, address(this)),
abi.encode(true)
);
externalFirewall.setCheckpoint(
ProtectedContract.foo.selector,
Checkpoint({
threshold: 0,
refStart: 4,
refEnd: 65_535,
activation: Activation.AlwaysActive,
trustedOrigin: false
})
);
}

function testExternalFirewallExecuteCheckpoint() public {
vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isCheckpointExecutor.selector, address(protectedContract)),
abi.encode(true)
);
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(
ISecurityValidator.executeCheckpoint.selector,
0x798fc659bb170634b1299a9687fc4a0970fd3fad348333887ca54763dd19cbb4
),
abi.encode(bytes32(uint256(1)))
);
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(ISecurityValidator.getCurrentAttester.selector),
abi.encode(testAttester)
);
vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isTrustedAttester.selector, address(testAttester)),
abi.encode(true)
);
protectedContract.foo(123);
}
}

0 comments on commit 57aa264

Please sign in to comment.