Skip to content

Commit 72308dc

Browse files
committed
feat(excubiae): add basic EASExcubia extension
1 parent 20e2c67 commit 72308dc

File tree

10 files changed

+405
-3
lines changed

10 files changed

+405
-3
lines changed

packages/excubiae/.solcover.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module.exports = {
2-
istanbulFolder: "../../coverage/gatekeepers"
2+
istanbulFolder: "../../coverage/excubiae",
3+
skipFiles: ["test"]
34
}

packages/excubiae/contracts/IExcubia.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ interface IExcubia {
99
/// @param gate The address of the excubia-protected contract address.
1010
event GatePassed(address indexed passerby, address indexed gate);
1111

12-
/// @notice Error thrown when an address is zero.
12+
/// @notice Error thrown when an address equal to zero is given.
1313
error ZeroAddress();
1414

1515
/// @notice Error thrown when the gate address is not set.

packages/excubiae/contracts/extensions/.gitkeep

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
import {Excubia} from "../Excubia.sol";
5+
import {IEAS} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol";
6+
import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol";
7+
8+
/// @title EAS Excubia Contract.
9+
/// @notice This contract extends the Excubia contract to integrate with the Ethereum Attestation Service (EAS).
10+
/// This contract checks an EAS attestation to permit access through the gate.
11+
/// @dev The contract uses a specific attestation schema & attester to admit the recipient of the attestation.
12+
contract EASExcubia is Excubia {
13+
/// @notice The Ethereum Attestation Service contract interface.
14+
IEAS public immutable EAS;
15+
/// @notice The specific schema ID that attestations must match to pass the gate.
16+
bytes32 public immutable SCHEMA;
17+
/// @notice The trusted attester address whose attestations are considered
18+
/// the only ones valid to pass the gate.
19+
address public immutable ATTESTER;
20+
21+
/// @notice Mapping to track which attestations have been registered by the contract to
22+
/// avoid double checks with the same attestation.
23+
mapping(bytes32 => bool) public registeredAttestations;
24+
25+
/// @notice Error thrown when the attestation has been already used to pass the gate.
26+
error AlreadyRegistered();
27+
28+
/// @notice Error thrown when the attestation does not match the designed schema.
29+
error UnexpectedSchema();
30+
31+
/// @notice Error thrown when the attestation does not match the designed trusted attester.
32+
error UnexpectedAttester();
33+
34+
/// @notice Error thrown when the attestation does not match the passerby as recipient.
35+
error UnexpectedRecipient();
36+
37+
/// @notice Error thrown when the attestation has been revoked.
38+
error RevokedAttestation();
39+
40+
/// @notice Constructor to initialize with target EAS contract with specific attester and schema.
41+
/// @param _eas The address of the EAS contract.
42+
/// @param _attester The address of the trusted attester.
43+
/// @param _schema The schema ID that attestations must match.
44+
constructor(address _eas, address _attester, bytes32 _schema) {
45+
if (_eas == address(0) || _attester == address(0)) revert ZeroAddress();
46+
47+
EAS = IEAS(_eas);
48+
ATTESTER = _attester;
49+
SCHEMA = _schema;
50+
}
51+
52+
/// @notice Overrides the `_pass` function to register a correct attestation.
53+
/// @param passerby The address of the entity attempting to pass the gate.
54+
/// @param data Encoded attestation ID.
55+
function _pass(address passerby, bytes calldata data) internal override {
56+
super._pass(passerby, data);
57+
58+
registeredAttestations[decodeAttestationId(data)] = true;
59+
}
60+
61+
/// @notice Overrides the `_check` function to validate the attestation against specific criteria.
62+
/// @param passerby The address of the entity attempting to pass the gate.
63+
/// @param data Encoded attestation ID.
64+
/// @return True if the attestation meets all criteria, revert otherwise.
65+
function _check(address passerby, bytes calldata data) internal view override returns (bool) {
66+
bytes32 attestationId = decodeAttestationId(data);
67+
68+
if (registeredAttestations[attestationId]) revert AlreadyRegistered();
69+
70+
Attestation memory attestation = EAS.getAttestation(attestationId);
71+
72+
if (attestation.schema != SCHEMA) revert UnexpectedSchema();
73+
if (attestation.attester != ATTESTER) revert UnexpectedAttester();
74+
if (attestation.recipient != passerby) revert UnexpectedRecipient();
75+
if (attestation.revocationTime != 0) revert RevokedAttestation();
76+
77+
return true;
78+
}
79+
80+
/// @notice Decodes an EAS attestation identifier from the encoded form.
81+
/// @param data Encoded attestation ID.
82+
/// @return Decoded attestation ID.
83+
function decodeAttestationId(bytes calldata data) private pure returns (bytes32) {
84+
return abi.decode(data, (bytes32));
85+
}
86+
}

packages/excubiae/contracts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"access": "public"
2626
},
2727
"dependencies": {
28+
"@ethereum-attestation-service/eas-contracts": "^1.7.1",
2829
"@openzeppelin/contracts": "^5.0.2"
2930
}
3031
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
/* solhint-disable max-line-length */
5+
import {IEAS, ISchemaRegistry, AttestationRequest, MultiAttestationRequest, DelegatedAttestationRequest, MultiDelegatedAttestationRequest, DelegatedRevocationRequest, RevocationRequest, MultiRevocationRequest, MultiDelegatedRevocationRequest} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol";
6+
import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol";
7+
8+
/// @title Mock Ethereum Attestation Service (EAS) Contract.
9+
/// @notice This contract is a mock implementation of the IEAS interface for testing purposes.
10+
/// @dev It simulates the behavior of a real EAS contract by providing predefined mocked attestations.
11+
contract MockEAS is IEAS {
12+
/// @notice A mock schema registry, represented simply as an address.
13+
ISchemaRegistry public override getSchemaRegistry;
14+
15+
/// @notice A mapping to store mocked attestations by their unique identifiers.
16+
mapping(bytes32 => Attestation) private mockedAttestations;
17+
18+
/// MOCKS ///
19+
20+
/// @notice Constructor to initialize the mock contract with predefined attestations.
21+
/// @param _recipient The recipient address used in mocked attestations.
22+
/// @param _attester The attester address used in mocked attestations.
23+
/// @param _schema The schema identifier used in mocked attestations.
24+
constructor(address _recipient, address _attester, bytes32 _schema) {
25+
getSchemaRegistry = ISchemaRegistry(address(1));
26+
27+
Attestation memory valid = Attestation({
28+
uid: bytes32("0x01"),
29+
schema: _schema,
30+
time: 0,
31+
expirationTime: 0,
32+
revocationTime: 0,
33+
refUID: bytes32("0x01"),
34+
recipient: _recipient,
35+
attester: _attester,
36+
revocable: true,
37+
data: bytes("")
38+
});
39+
40+
Attestation memory revoked = Attestation({
41+
uid: bytes32("0x02"),
42+
schema: _schema,
43+
time: 0,
44+
expirationTime: 0,
45+
revocationTime: 1,
46+
refUID: bytes32("0x01"),
47+
recipient: _recipient,
48+
attester: _attester,
49+
revocable: true,
50+
data: bytes("")
51+
});
52+
53+
Attestation memory invalidSchema = Attestation({
54+
uid: bytes32("0x03"),
55+
schema: bytes32("0x01"),
56+
time: 0,
57+
expirationTime: 0,
58+
revocationTime: 0,
59+
refUID: bytes32("0x01"),
60+
recipient: _recipient,
61+
attester: _attester,
62+
revocable: true,
63+
data: bytes("")
64+
});
65+
66+
Attestation memory invalidRecipient = Attestation({
67+
uid: bytes32("0x04"),
68+
schema: _schema,
69+
time: 0,
70+
expirationTime: 0,
71+
revocationTime: 0,
72+
refUID: bytes32("0x01"),
73+
recipient: address(1),
74+
attester: _attester,
75+
revocable: true,
76+
data: bytes("")
77+
});
78+
79+
Attestation memory invalidAttester = Attestation({
80+
uid: bytes32("0x05"),
81+
schema: _schema,
82+
time: 0,
83+
expirationTime: 0,
84+
revocationTime: 0,
85+
refUID: bytes32("0x000000000000000000000000000001"),
86+
recipient: _recipient,
87+
attester: address(1),
88+
revocable: true,
89+
data: bytes("")
90+
});
91+
92+
mockedAttestations[bytes32("0x01")] = valid;
93+
mockedAttestations[bytes32("0x02")] = revoked;
94+
mockedAttestations[bytes32("0x03")] = invalidSchema;
95+
mockedAttestations[bytes32("0x04")] = invalidRecipient;
96+
mockedAttestations[bytes32("0x05")] = invalidAttester;
97+
}
98+
99+
/// @notice Retrieves a mocked attestation by its unique identifier.
100+
/// @param uid The unique identifier of the attestation.
101+
/// @return The mocked attestation associated with the given identifier.
102+
function getAttestation(bytes32 uid) external view override returns (Attestation memory) {
103+
return mockedAttestations[uid];
104+
}
105+
106+
/// STUBS ///
107+
// The following functions are stubs and do not perform any meaningful operations.
108+
// They are placeholders to comply with the IEAS interface.
109+
function attest(AttestationRequest calldata /*request*/) external payable override returns (bytes32) {
110+
return bytes32(0);
111+
}
112+
113+
function attestByDelegation(
114+
DelegatedAttestationRequest calldata /*delegatedRequest*/
115+
) external payable override returns (bytes32) {
116+
return bytes32(0);
117+
}
118+
119+
function multiAttest(
120+
MultiAttestationRequest[] calldata multiRequests
121+
) external payable override returns (bytes32[] memory) {
122+
return new bytes32[](multiRequests.length);
123+
}
124+
125+
function multiAttestByDelegation(
126+
MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests
127+
) external payable override returns (bytes32[] memory) {
128+
return new bytes32[](multiDelegatedRequests.length);
129+
}
130+
131+
function revoke(RevocationRequest calldata request) external payable override {}
132+
133+
function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable override {}
134+
135+
function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable override {}
136+
137+
function multiRevokeByDelegation(
138+
MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests
139+
) external payable override {}
140+
141+
function timestamp(bytes32 /*data*/) external view override returns (uint64) {
142+
return uint64(block.timestamp);
143+
}
144+
145+
function multiTimestamp(bytes32[] calldata /*data*/) external view override returns (uint64) {
146+
return uint64(block.timestamp);
147+
}
148+
149+
function revokeOffchain(bytes32 /*data*/) external view override returns (uint64) {
150+
return uint64(block.timestamp);
151+
}
152+
153+
function multiRevokeOffchain(bytes32[] calldata /*data*/) external view override returns (uint64) {
154+
return uint64(block.timestamp);
155+
}
156+
157+
function isAttestationValid(bytes32 uid) external view override returns (bool) {
158+
return mockedAttestations[uid].uid != bytes32(0);
159+
}
160+
161+
function getTimestamp(bytes32 /*data*/) external view override returns (uint64) {
162+
return uint64(block.timestamp);
163+
}
164+
165+
function getRevokeOffchain(address /*revoker*/, bytes32 /*data*/) external view override returns (uint64) {
166+
return uint64(block.timestamp);
167+
}
168+
169+
function version() external pure returns (string memory) {
170+
return string("");
171+
}
172+
}

packages/excubiae/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"typescript": "^5.3.3"
3636
},
3737
"dependencies": {
38+
"@ethereum-attestation-service/eas-contracts": "^1.7.1",
3839
"@openzeppelin/contracts": "^5.0.2"
3940
}
4041
}

packages/excubiae/test/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)