Skip to content

Commit

Permalink
Merge pull request #32 from forta-network/caner/trusted-attesters
Browse files Browse the repository at this point in the history
Add ability to trust an externally managed set of attesters
  • Loading branch information
canercidam authored Nov 4, 2024
2 parents abd6189 + 92b03b5 commit ee4fa2f
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 2 deletions.
11 changes: 11 additions & 0 deletions src/Firewall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ abstract contract Firewall is IFirewall, IAttesterInfo, FirewallPermissions {
return ($.validator, $.checkpointHook, $.attesterControllerId, firewallAccess);
}

/**
* Updates the trusted attesters contract. This contract is used for checking current attester
* against the trusted ones only if set. If it is set as zero address (or is unset) then the check
* defaults to the firewall access contract.
* @param _trustedAttesters The contract which knows the set of trusted attesters.
*/
function updateTrustedAttesters(ITrustedAttesters _trustedAttesters) public virtual onlyFirewallAdmin {
_updateTrustedAttesters(_trustedAttesters);
emit TrustedAttestersUpdated(_trustedAttesters);
}

/**
* @notice Returns the attester controller id from the configuration.
*/
Expand Down
16 changes: 15 additions & 1 deletion src/FirewallPermissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
pragma solidity ^0.8.25;

import "./interfaces/IFirewallAccess.sol";
import "./interfaces/ITrustedAttesters.sol";

/**
* @notice Simplifies interactions with a firewall access contract.
*/
abstract contract FirewallPermissions {
struct FirewallPermissionsStorage {
IFirewallAccess firewallAccess;
ITrustedAttesters trustedAttesters;
}

/// @custom:storage-location erc7201:forta.FirewallPermissions.storage
Expand Down Expand Up @@ -55,13 +57,25 @@ abstract contract FirewallPermissions {
return _getFirewallPermissionsStorage().firewallAccess;
}

function _updateTrustedAttesters(ITrustedAttesters trustedAttesters) internal {
_getFirewallPermissionsStorage().trustedAttesters = trustedAttesters;
}

function _getTrustedAttesters() internal view returns (ITrustedAttesters) {
return _getFirewallPermissionsStorage().trustedAttesters;
}

function _getFirewallPermissionsStorage() private pure returns (FirewallPermissionsStorage storage $) {
assembly {
$.slot := STORAGE_SLOT
}
}

function _isTrustedAttester(address attester) internal view returns (bool) {
return _getFirewallPermissionsStorage().firewallAccess.isTrustedAttester(attester);
FirewallPermissionsStorage storage $ = _getFirewallPermissionsStorage();
if (address($.trustedAttesters) != address(0)) {
return $.trustedAttesters.isTrustedAttester(attester);
}
return $.firewallAccess.isTrustedAttester(attester);
}
}
30 changes: 30 additions & 0 deletions src/TrustedAttesters.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: UNLICENSED
// See Forta Network License: https://github.com/forta-network/forta-firewall-contracts/blob/master/LICENSE.md

pragma solidity ^0.8.25;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import "./interfaces/ITrustedAttesters.sol";

/// @dev All role ids are keccak256() of their names.
bytes32 constant ATTESTER_MANAGER_ROLE = 0xa6104eeb16757cf1b916694e5bc99107eaf38064b4948290b9f96447e33d6396;
bytes32 constant TRUSTED_ATTESTER_ROLE = 0x725a15d5fb1f1294f13d7272d4441134b951367ff5aebd74853471ce1cfb9cc4;

/**
* @notice Keeps the set of accounts which are trusted attesters.
*/
contract TrustedAttesters is AccessControl, ITrustedAttesters {
constructor(address _defaultAdmin) {
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
_setRoleAdmin(ATTESTER_MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(TRUSTED_ATTESTER_ROLE, ATTESTER_MANAGER_ROLE);
}

/**
* @notice Checks if the given address is a trusted attester.
* @param caller Caller address.
*/
function isTrustedAttester(address caller) public view returns (bool) {
return hasRole(TRUSTED_ATTESTER_ROLE, caller);
}
}
1 change: 1 addition & 0 deletions src/interfaces/FirewallDependencies.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pragma solidity ^0.8.25;
import "./ISecurityValidator.sol";
import "./ICheckpointHook.sol";
import "./IFirewallAccess.sol";
import "./ITrustedAttesters.sol";
4 changes: 4 additions & 0 deletions src/interfaces/IFirewall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import "./ISecurityValidator.sol";
import "./IFirewallAccess.sol";
import "./Checkpoint.sol";
import "./ICheckpointHook.sol";
import "./ITrustedAttesters.sol";

interface IFirewall {
event SecurityConfigUpdated(ISecurityValidator indexed validator, IFirewallAccess indexed firewallAccess);
event TrustedAttestersUpdated(ITrustedAttesters indexed trustedAttesters);
event SupportsTrustedOrigin(address indexed firewall);
event CheckpointUpdated(bytes4 selector, Checkpoint checkpoint);

Expand All @@ -30,6 +32,8 @@ interface IFirewall {
IFirewallAccess _firewallAccess
);

function updateTrustedAttesters(ITrustedAttesters _trustedAttesters) external;

function setCheckpoint(bytes4 selector, Checkpoint memory checkpoint) external;

function setCheckpointActivation(bytes4 selector, Activation activation) external;
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/ITrustedAttesters.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
// See Forta Network License: https://github.com/forta-network/forta-firewall-contracts/blob/master/LICENSE.md

pragma solidity ^0.8.25;

interface ITrustedAttesters {
function isTrustedAttester(address caller) external view returns (bool);
}
88 changes: 87 additions & 1 deletion test/Firewall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ contract FirewallTest is Test {
address constant mockAccess = address(uint160(345));
address constant testAttester = address(uint160(456));
address constant mockHook = address(uint160(567));
address constant mockTrustedAttesters = address(uint160(678));

FirewallImpl firewall;

Checkpoint checkpoint =
Checkpoint({threshold: 1, refStart: 2, refEnd: 3, activation: Activation.AlwaysActive, trustedOrigin: true});
Checkpoint({threshold: 1, refStart: 0, refEnd: 0, activation: Activation.AlwaysActive, trustedOrigin: true});

function setUp() public {
firewall = new FirewallImpl(
Expand Down Expand Up @@ -417,4 +418,89 @@ contract FirewallTest is Test {
/// No validator call!
firewall.secureExecution(arg);
}

function testFirewall_trustedAttesters() public {
vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isCheckpointManager.selector, address(this)),
abi.encode(true)
);
Checkpoint memory chk = Checkpoint({
threshold: 2,
refStart: 4,
refEnd: 36,
activation: Activation.ConstantThreshold,
trustedOrigin: false
});
firewall.setCheckpoint(FirewallImpl.secureExecution.selector, chk);

uint256 arg = 3;
bytes32 checkpointHash = keccak256(
abi.encode(
address(this), address(firewall), FirewallImpl.secureExecution.selector, Quantization.quantize(arg)
)
);
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(ISecurityValidator.getCurrentAttester.selector),
abi.encode(testAttester)
);
vm.mockCall(
address(mockHook),
abi.encodeWithSelector(
ICheckpointHook.handleCheckpoint.selector, address(this), FirewallImpl.secureExecution.selector, arg
),
abi.encode(HookResult.Inconclusive)
);
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(ISecurityValidator.executeCheckpoint.selector, checkpointHash),
abi.encode(keccak256(abi.encode(checkpointHash, address(firewall), bytes32(0))))
);

/// Expect the call on the access control contract if trusted attesters is not set.
vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isTrustedAttester.selector, address(testAttester)),
abi.encode(true)
);

firewall.secureExecution(arg);

/// Set the trusted attesters.
vm.mockCall(
address(mockAccess),
abi.encodeWithSelector(IFirewallAccess.isFirewallAdmin.selector, address(this)),
abi.encode(true)
);
firewall.updateTrustedAttesters(ITrustedAttesters(mockTrustedAttesters));

/// Put the rest of the mock calls in place.
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(ISecurityValidator.getCurrentAttester.selector),
abi.encode(testAttester)
);
vm.mockCall(
address(mockHook),
abi.encodeWithSelector(
ICheckpointHook.handleCheckpoint.selector, address(this), FirewallImpl.secureExecution.selector, arg
),
abi.encode(HookResult.Inconclusive)
);
vm.mockCall(
address(mockValidator),
abi.encodeWithSelector(ISecurityValidator.executeCheckpoint.selector, checkpointHash),
abi.encode(keccak256(abi.encode(checkpointHash, address(firewall), bytes32(0))))
);

/// Expect the call on the trusted attesters contract when it is set.
vm.mockCall(
address(mockTrustedAttesters),
abi.encodeWithSelector(IFirewallAccess.isTrustedAttester.selector, address(testAttester)),
abi.encode(true)
);

firewall.secureExecution(arg);
}
}

0 comments on commit ee4fa2f

Please sign in to comment.