@inco/lightning 0.11.0-testnet-3 → 1.0.0-devnet-2

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.
@@ -1,7 +1,11 @@
1
1
  // SPDX-License-Identifier: No License
2
2
  pragma solidity ^0.8;
3
3
 
4
- import {AllowanceVoucher, AllowanceProof} from "./AdvancedAccessControl.types.sol";
4
+ import {
5
+ AllowanceVoucher,
6
+ AllowanceProof,
7
+ REQUIRED_ALLOWANCE_VOUCHER_WARNING_HASH
8
+ } from "./AdvancedAccessControl.types.sol";
5
9
  import {ALLOWANCE_GRANTED_MAGIC_VALUE} from "../../Types.sol";
6
10
  import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
7
11
  import {IAdvancedAccessControl, IVoucherEip712Checker} from "./interfaces/IAdvancedAccessControl.sol";
@@ -9,6 +13,8 @@ import {SharerNotAllowedForHandle} from "../../Types.sol";
9
13
  import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
10
14
  import {IBaseAccessControlList} from "./interfaces/IBaseAccessControlList.sol";
11
15
  import {LightningAddressGetter} from "../primitives/LightningAddressGetter.sol";
16
+ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
17
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
12
18
 
13
19
  /// @title AdvancedAccessControlStorage
14
20
  /// @notice Diamond storage pattern for advanced ACL session nonce tracking
@@ -47,7 +53,7 @@ abstract contract VoucherEip712Checker is IVoucherEip712Checker, EIP712Upgradeab
47
53
  /// @notice EIP-712 type hash for the AllowanceVoucher struct
48
54
  /// @dev Computed once at compile time for gas efficiency
49
55
  bytes32 constant ALLOWANCE_VOUCHER_STRUCT_HASH = keccak256(
50
- "AllowanceVoucher(bytes32 sessionNonce,address verifyingContract,bytes4 callFunction,bytes sharerArgData)"
56
+ "AllowanceVoucher(string warning,bytes32 sessionNonce,address verifyingContract,bytes4 callFunction,bytes sharerArgData)"
51
57
  );
52
58
 
53
59
  /// @notice Computes the EIP-712 digest for an allowance voucher
@@ -60,6 +66,7 @@ abstract contract VoucherEip712Checker is IVoucherEip712Checker, EIP712Upgradeab
60
66
  keccak256(
61
67
  abi.encode(
62
68
  ALLOWANCE_VOUCHER_STRUCT_HASH,
69
+ keccak256(bytes(voucher.warning)),
63
70
  voucher.sessionNonce,
64
71
  voucher.verifyingContract,
65
72
  voucher.callFunction,
@@ -86,15 +93,22 @@ abstract contract VoucherEip712Checker is IVoucherEip712Checker, EIP712Upgradeab
86
93
  /// - Session nonces allow sharers to invalidate all previous vouchers
87
94
  /// - EIP-712 typed data ensures vouchers are human-readable when signing
88
95
  /// - Supports both EOA and smart contract signatures (ERC-1271)
96
+ /// @dev `OwnableUpgradeable` is only here to align Context ordering with DecryptionAttester
97
+ /// for IncoVerifier's C3 linearization.
89
98
  abstract contract AdvancedAccessControl is
90
99
  IAdvancedAccessControl,
91
100
  AdvancedAccessControlStorage,
101
+ OwnableUpgradeable,
92
102
  VoucherEip712Checker,
93
- LightningAddressGetter
103
+ LightningAddressGetter,
104
+ PausableUpgradeable
94
105
  {
95
106
 
96
107
  using SignatureChecker for address;
97
108
 
109
+ /// @notice Thrown when a voucher's warning field does not match the required warning text
110
+ error InvalidVoucherWarning();
111
+
98
112
  /// @notice Thrown when a voucher signature is invalid for the claimed signer
99
113
  /// @param signer The address that allegedly signed the voucher
100
114
  /// @param digest The EIP-712 digest that should have been signed
@@ -111,6 +125,7 @@ abstract contract AdvancedAccessControl is
111
125
 
112
126
  /// @notice Checks if an account is allowed to access a handle using a signed voucher proof
113
127
  /// @dev Intended for simulation/off-chain calls. Not a view function as it calls external contracts.
128
+ /// Returns false unconditionally when this contract is paused, before any input validation.
114
129
  /// Verification steps:
115
130
  /// 1. Verify the sharer has access to the handle
116
131
  /// 2. Verify the voucher signature is valid
@@ -120,7 +135,17 @@ abstract contract AdvancedAccessControl is
120
135
  /// @param account The account requesting access
121
136
  /// @param proof The allowance proof containing voucher, signature, and requester data
122
137
  /// @return True if access is allowed, false or reverts otherwise
123
- function isAllowedWithProof(bytes32 handle, address account, AllowanceProof memory proof) public returns (bool) {
138
+ function isAllowedWithProof(bytes32 handle, address account, AllowanceProof memory proof)
139
+ public
140
+ virtual
141
+ returns (bool)
142
+ {
143
+ if (paused()) {
144
+ return false;
145
+ }
146
+ require(
147
+ keccak256(bytes(proof.voucher.warning)) == REQUIRED_ALLOWANCE_VOUCHER_WARNING_HASH, InvalidVoucherWarning()
148
+ );
124
149
  require(proof.voucher.verifyingContract != address(0), InvalidVerifyingContract());
125
150
  require(
126
151
  IBaseAccessControlList(incoLightningAddress).isAllowed(handle, proof.sharer),
@@ -1,9 +1,21 @@
1
1
  // SPDX-License-Identifier: No License
2
2
  pragma solidity ^0.8;
3
3
 
4
+ // Required warning that must be present in every AllowanceVoucher.
5
+ // Must match SESSION_KEY_WARNING in js/src/advancedacl/session-key.ts.
6
+ string constant REQUIRED_ALLOWANCE_VOUCHER_WARNING =
7
+ "Inco Warning: signing this message may leak your private data, including from unrelated apps. Sign only if you fully trust this app.";
8
+
9
+ // Precomputed hash of REQUIRED_ALLOWANCE_VOUCHER_WARNING for cheap runtime comparison.
10
+ // Evaluated at compile time — no storage slot used, no runtime keccak256 of the full string.
11
+ bytes32 constant REQUIRED_ALLOWANCE_VOUCHER_WARNING_HASH = keccak256(bytes(REQUIRED_ALLOWANCE_VOUCHER_WARNING));
12
+
4
13
  // can be for arbitrary handles to arbitrary accounts
5
14
  // signed by the account sharing its read access
6
15
  struct AllowanceVoucher {
16
+ // Human-readable warning displayed by wallets at signing time.
17
+ // Must be first so wallets show it before the opaque byte fields below.
18
+ string warning;
7
19
  bytes32 sessionNonce;
8
20
  address verifyingContract;
9
21
  bytes4 callFunction;
@@ -162,17 +162,25 @@ abstract contract BaseAccessControlList is
162
162
 
163
163
  /// @notice Checks if an account has access to an encrypted handle.
164
164
  /// @dev Returns true if any of: transient access, persistent access, or handle is revealed.
165
+ /// Returns false unconditionally when the contract is paused.
165
166
  /// @param handle The encrypted handle to check.
166
167
  /// @param account The account to check access for.
167
168
  /// @return True if the account has access to the handle.
168
169
  function isAllowed(bytes32 handle, address account) public view returns (bool) {
170
+ if (paused()) {
171
+ return false;
172
+ }
169
173
  return allowedTransient(handle, account) || persistAllowed(handle, account) || isRevealed(handle);
170
174
  }
171
175
 
172
176
  /// @notice Checks if a handle has been revealed for public access.
177
+ /// @dev Returns false unconditionally when the contract is paused.
173
178
  /// @param handle The encrypted handle to check.
174
179
  /// @return True if the handle has been revealed.
175
180
  function isRevealed(bytes32 handle) public view returns (bool) {
181
+ if (paused()) {
182
+ return false;
183
+ }
176
184
  AclStorage storage $ = getAclStorage();
177
185
  return $.persistedAllowedForDecryption[handle];
178
186
  }
@@ -4,6 +4,7 @@ pragma solidity ^0.8;
4
4
  import {IncoTest} from "../../../test/IncoTest.sol";
5
5
  import {SessionVerifier, Session} from "../../../periphery/SessionVerifier.sol";
6
6
  import {AllowanceVoucher, AllowanceProof} from "../AdvancedAccessControl.sol";
7
+ import {REQUIRED_ALLOWANCE_VOUCHER_WARNING} from "../AdvancedAccessControl.types.sol";
7
8
  import {euint256, SharerNotAllowedForHandle} from "../../../Types.sol";
8
9
  import {e, inco} from "../../../Lib.sol";
9
10
  import {AdvancedAccessControl} from "../AdvancedAccessControl.sol";
@@ -93,7 +94,8 @@ contract TestAdvancedAccessControl is IncoTest {
93
94
  sessionNonce: bytes32(0),
94
95
  verifyingContract: address(0),
95
96
  callFunction: SessionVerifier.canUseSession.selector,
96
- sharerArgData: abi.encode(Session({decrypter: bob, expiresAt: block.timestamp + 1 days}))
97
+ sharerArgData: abi.encode(Session({decrypter: bob, expiresAt: block.timestamp + 1 days})),
98
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
97
99
  });
98
100
  AllowanceProof memory bobsProof = getBobsProof(invalidSessionVoucher);
99
101
  vm.expectRevert(abi.encodeWithSelector(AdvancedAccessControl.InvalidVerifyingContract.selector));
@@ -111,7 +113,8 @@ contract TestAdvancedAccessControl is IncoTest {
111
113
  sessionNonce: bytes32(0),
112
114
  verifyingContract: address(sessionVerifier),
113
115
  callFunction: SessionVerifier.canUseSession.selector,
114
- sharerArgData: abi.encode(Session({decrypter: bob, expiresAt: block.timestamp + 1 days}))
116
+ sharerArgData: abi.encode(Session({decrypter: bob, expiresAt: block.timestamp + 1 days})),
117
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
115
118
  });
116
119
  AllowanceProof memory bobsProof = getBobsProof(aliceSessionVoucherForBob);
117
120
  assertTrue(
@@ -128,7 +131,8 @@ contract TestAdvancedAccessControl is IncoTest {
128
131
  sessionNonce: bytes32(0),
129
132
  verifyingContract: address(verifier),
130
133
  callFunction: verifier.someCheck.selector,
131
- sharerArgData: ""
134
+ sharerArgData: "",
135
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
132
136
  });
133
137
  AllowanceProof memory bobsFirstProof = getBobsProof(voucher);
134
138
  assertTrue(
@@ -140,7 +144,8 @@ contract TestAdvancedAccessControl is IncoTest {
140
144
  sessionNonce: madeUpNonce,
141
145
  verifyingContract: address(verifier),
142
146
  callFunction: verifier.someCheck.selector,
143
- sharerArgData: ""
147
+ sharerArgData: "",
148
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
144
149
  });
145
150
  AllowanceProof memory invalidBobProof = getBobsProof(voucher);
146
151
  // the session nonce should be checked by inco
@@ -164,7 +169,8 @@ contract TestAdvancedAccessControl is IncoTest {
164
169
  sessionNonce: alicesNewNonce,
165
170
  verifyingContract: address(verifier),
166
171
  callFunction: verifier.someCheck.selector,
167
- sharerArgData: ""
172
+ sharerArgData: "",
173
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
168
174
  });
169
175
  AllowanceProof memory bobsSecondProof = getBobsProof(voucher);
170
176
  assertTrue(
@@ -179,7 +185,8 @@ contract TestAdvancedAccessControl is IncoTest {
179
185
  sessionNonce: bytes32(0),
180
186
  verifyingContract: address(verifier),
181
187
  callFunction: verifier.someCheck.selector,
182
- sharerArgData: abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob}))
188
+ sharerArgData: abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob})),
189
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
183
190
  });
184
191
  AllowanceProof memory bobsProof = AllowanceProof({
185
192
  sharer: alice,
@@ -217,7 +224,8 @@ contract TestAdvancedAccessControl is IncoTest {
217
224
  sessionNonce: bytes32(0),
218
225
  verifyingContract: address(verifier),
219
226
  callFunction: verifier.someCheck.selector,
220
- sharerArgData: ""
227
+ sharerArgData: "",
228
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
221
229
  });
222
230
  // Use bob as sharer, but bob is NOT allowed on the secret (only alice is)
223
231
  AllowanceProof memory proof = AllowanceProof({
@@ -237,7 +245,8 @@ contract TestAdvancedAccessControl is IncoTest {
237
245
  sessionNonce: bytes32(0),
238
246
  verifyingContract: address(verifier),
239
247
  callFunction: verifier.someCheck.selector,
240
- sharerArgData: ""
248
+ sharerArgData: "",
249
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
241
250
  });
242
251
  // Alice is the sharer (and is allowed), but we sign with Bob's key
243
252
  bytes memory wrongSignature = getSignatureForDigest(incoVerifier.allowanceVoucherDigest(voucher), bobPrivKey);
@@ -252,6 +261,23 @@ contract TestAdvancedAccessControl is IncoTest {
252
261
  incoVerifier.isAllowedWithProof(secretHandle, bob, proof);
253
262
  }
254
263
 
264
+ /// @notice Test InvalidVoucherWarning error when voucher warning does not match required text
265
+ function testIsAllowedWithProofInvalidVoucherWarning() public {
266
+ DoesNotVerifyAnything verifier = new DoesNotVerifyAnything();
267
+ AllowanceVoucher memory voucher = AllowanceVoucher({
268
+ sessionNonce: bytes32(0),
269
+ verifyingContract: address(verifier),
270
+ callFunction: verifier.someCheck.selector,
271
+ sharerArgData: "",
272
+ warning: "wrong warning"
273
+ });
274
+ AllowanceProof memory proof = AllowanceProof({
275
+ sharer: alice, voucher: voucher, voucherSignature: getAliceSig(voucher), requesterArgData: ""
276
+ });
277
+ vm.expectRevert(abi.encodeWithSelector(AdvancedAccessControl.InvalidVoucherWarning.selector));
278
+ incoVerifier.isAllowedWithProof(secretHandle, bob, proof);
279
+ }
280
+
255
281
  /// @notice Test claimHandle fails when proof verification fails (line 107)
256
282
  function testClaimHandleProofVerificationFailed() public {
257
283
  SomeVerifier verifier = new SomeVerifier();
@@ -261,7 +287,8 @@ contract TestAdvancedAccessControl is IncoTest {
261
287
  sessionNonce: bytes32(0),
262
288
  verifyingContract: address(verifier),
263
289
  callFunction: verifier.someCheck.selector,
264
- sharerArgData: abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob}))
290
+ sharerArgData: abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob})),
291
+ warning: REQUIRED_ALLOWANCE_VOUCHER_WARNING
265
292
  });
266
293
  AllowanceProof memory proof = AllowanceProof({
267
294
  sharer: alice, // alice IS allowed on the secret
@@ -10,6 +10,7 @@ import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/crypt
10
10
  import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
11
11
  import {SignatureVerifier} from "./primitives/SignatureVerifier.sol";
12
12
  import {IDecryptionAttester} from "./interfaces/IDecryptionAttester.sol";
13
+ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
13
14
 
14
15
  /// @title DecryptionAttester
15
16
  /// @notice Verifies decryption attestations signed by covalidators.
@@ -17,7 +18,7 @@ import {IDecryptionAttester} from "./interfaces/IDecryptionAttester.sol";
17
18
  /// the value and signs an attestation. This contract verifies that attestation using EIP-712
18
19
  /// typed data signatures. The attestation binds a handle to its decrypted value, allowing
19
20
  /// on-chain verification that a decryption was performed correctly by authorized covalidators.
20
- abstract contract DecryptionAttester is IDecryptionAttester, SignatureVerifier, EIP712Upgradeable {
21
+ abstract contract DecryptionAttester is IDecryptionAttester, SignatureVerifier, EIP712Upgradeable, PausableUpgradeable {
21
22
 
22
23
  using ECDSA for bytes32;
23
24
 
@@ -61,14 +62,19 @@ abstract contract DecryptionAttester is IDecryptionAttester, SignatureVerifier,
61
62
 
62
63
  /// @notice Validates a decryption attestation against covalidator signatures.
63
64
  /// @dev Verifies that the attestation was signed by the required threshold of covalidators.
65
+ /// Returns false unconditionally when this contract is paused.
64
66
  /// @param decryption The decryption attestation to validate.
65
67
  /// @param signatures Array of signatures from covalidators.
66
68
  /// @return True if the attestation has valid signatures from the required covalidators.
67
69
  function isValidDecryptionAttestation(DecryptionAttestation memory decryption, bytes[] calldata signatures)
68
70
  public
69
71
  view
72
+ virtual
70
73
  returns (bool)
71
74
  {
75
+ if (paused()) {
76
+ return false;
77
+ }
72
78
  return isValidSignature(decryptionAttestationDigest(decryption), signatures);
73
79
  }
74
80
 
@@ -85,7 +91,10 @@ abstract contract DecryptionAttester is IDecryptionAttester, SignatureVerifier,
85
91
  ElementAttestationWithProof[] calldata proofElements,
86
92
  bytes32 proof,
87
93
  bytes[] calldata signatures
88
- ) public view override returns (bool) {
94
+ ) public view virtual override returns (bool) {
95
+ if (paused()) {
96
+ return false;
97
+ }
89
98
  DecryptionAttestation memory attestation = DecryptionAttestation({handle: elistHandle, value: proof});
90
99
 
91
100
  // Verify the provided proof by reconstructing it from the provided value-commitment pairs and/or hashes.
@@ -118,7 +127,10 @@ abstract contract DecryptionAttester is IDecryptionAttester, SignatureVerifier,
118
127
  function isValidReencryptionAttestation(
119
128
  ReencryptionAttestation[] calldata attestations,
120
129
  bytes[] calldata signatures
121
- ) public view returns (bool) {
130
+ ) public view virtual returns (bool) {
131
+ if (paused()) {
132
+ return false;
133
+ }
122
134
  if (attestations.length != signatures.length) {
123
135
  revert AttestationsSignaturesLengthMismatch(attestations.length, signatures.length);
124
136
  }
@@ -2,6 +2,7 @@
2
2
  pragma solidity ^0.8;
3
3
 
4
4
  import {IEventCounter} from "./interfaces/IEventCounter.sol";
5
+ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
5
6
 
6
7
  /// @title EventCounterStorage
7
8
  /// @notice Diamond storage pattern for the event counter state
@@ -40,7 +41,7 @@ contract EventCounterStorage {
40
41
  ///
41
42
  /// The event ID is incorporated into handle generation to ensure uniqueness even when
42
43
  /// the same operation is performed multiple times in the same transaction.
43
- contract EventCounter is IEventCounter, EventCounterStorage {
44
+ contract EventCounter is IEventCounter, EventCounterStorage, PausableUpgradeable {
44
45
 
45
46
  /// @notice Generates and returns a new unique event ID
46
47
  /// @dev Post-increments the counter, so the returned value is the ID before incrementing.
@@ -52,9 +53,12 @@ contract EventCounter is IEventCounter, EventCounterStorage {
52
53
 
53
54
  /// @notice Sets the event counter to a digest value for batch operation tracking
54
55
  /// @dev Used when processing batched operations where a serialization hash
55
- /// provides better uniqueness than sequential IDs
56
+ /// provides better uniqueness than sequential IDs. Reverts when the contract
57
+ /// is paused, which transitively blocks every event-emitting operation that
58
+ /// finalizes by calling this function (encrypted ops, list ops, trivial encryption,
59
+ /// input registration, allow/reveal).
56
60
  /// @param serialization The serialized batch data to hash
57
- function setDigest(bytes memory serialization) internal {
61
+ function setDigest(bytes memory serialization) internal whenNotPaused {
58
62
  getEventCounterStorage().eventCounter = uint256(keccak256(serialization));
59
63
  }
60
64
 
@@ -26,18 +26,21 @@ struct Session {
26
26
 
27
27
  /// @title SessionVerifier
28
28
  /// @notice Inco access sharing verifier for browser dApp sessions
29
- /// @dev Grants access to all encrypted data held by the sharer to one decrypter for a limited time.
30
- /// This is the recommended pattern for dApps that need to decrypt user data during a browsing session.
29
+ /// @dev Grants a single decrypter address temporary access to all of the sharer's
30
+ /// encrypted handles. The session is valid as long as block.timestamp < expiresAt
31
+ /// and the requesting account matches the authorized decrypter.
31
32
  ///
32
33
  /// Usage:
33
- /// 1. User signs a voucher containing a Session struct with their chosen decrypter and expiration
34
- /// 2. The voucher specifies canUseSession.selector as the callFunction
35
- /// 3. When the decrypter requests access, this contract verifies the session is still valid
34
+ /// 1. User signs a voucher containing a Session struct with their chosen decrypter
35
+ /// and expiration time.
36
+ /// 2. The voucher specifies canUseSession.selector as the callFunction.
37
+ /// 3. When the decrypter requests access, this contract verifies the session is still
38
+ /// valid and the caller matches the authorized decrypter.
36
39
  ///
37
40
  /// To use this verifier, set the voucher's callFunction to SessionVerifier.canUseSession.selector
38
41
  contract SessionVerifier is UUPSUpgradeable, OwnableUpgradeable, Version {
39
42
 
40
- /// @notice Initializes the SessionVerifier with version information
43
+ /// @notice Initializes the SessionVerifier with version information.
41
44
  /// @param _salt Unique salt used for deterministic deployment via CreateX
42
45
  constructor(bytes32 _salt)
43
46
  Version(
@@ -49,29 +52,28 @@ contract SessionVerifier is UUPSUpgradeable, OwnableUpgradeable, Version {
49
52
  )
50
53
  {}
51
54
 
52
- /// @notice Verifies if an account can use a session to access encrypted data
55
+ /// @notice Verifies if an account can use a session to access an encrypted handle.
53
56
  /// @dev This function is called by the ACL system when validating access permissions.
54
- /// The session grants blanket access to all handles owned by the sharer - the handle
55
- /// parameter is intentionally ignored.
57
+ /// Access is granted if the session has not expired and the caller is the authorized decrypter.
56
58
  /// @param account The address requesting access (must match session.decrypter)
57
- /// @param sharerArgData ABI-encoded Session struct containing decrypter address and expiration
58
- /// @return ALLOWANCE_GRANTED_MAGIC_VALUE if session is valid, bytes32(0) otherwise
59
+ /// @param sharerArgData ABI-encoded Session struct containing decrypter and expiration
60
+ /// @return ALLOWANCE_GRANTED_MAGIC_VALUE if access is granted, bytes32(0) otherwise
59
61
  function canUseSession(
60
62
  bytes32, /* handle */
61
63
  address account,
62
64
  bytes memory sharerArgData,
63
- bytes memory requesterArgData
65
+ bytes memory /* requesterArgData */
64
66
  )
65
67
  external
66
68
  view
67
69
  returns (bytes32)
68
70
  {
69
- // unused variable just here to bypass linter
70
- (requesterArgData);
71
71
  Session memory session = abi.decode(sharerArgData, (Session));
72
+
72
73
  if (session.expiresAt >= block.timestamp && session.decrypter == account) {
73
74
  return ALLOWANCE_GRANTED_MAGIC_VALUE;
74
75
  }
76
+
75
77
  return bytes32(0);
76
78
  }
77
79
 
@@ -12,6 +12,7 @@ import {FakeQuoteVerifier} from "./FakeIncoInfra/FakeQuoteVerifier.sol";
12
12
  import {IOwnable} from "../../src/shared/IOwnable.sol";
13
13
  import {MockRemoteAttestation} from "./FakeIncoInfra/MockRemoteAttestation.sol";
14
14
  import {BootstrapResult} from "../lightning-parts/TEELifecycle.types.sol";
15
+ import {AllowanceProof, AllowanceVoucher} from "../lightning-parts/AccessControl/AdvancedAccessControl.sol";
15
16
  import {Safe} from "safe-smart-account/Safe.sol";
16
17
  import {SafeProxyFactory} from "safe-smart-account/proxies/SafeProxyFactory.sol";
17
18
 
@@ -61,7 +62,7 @@ contract IncoTest is MockOpHandler, DeployUtils, FakeDecryptionAttester, MockRem
61
62
  deployer: testDeployer,
62
63
  owner: owner,
63
64
  // The highest precedent deployment pepper
64
- pepper: "testnet",
65
+ pepper: "devnet",
65
66
  quoteVerifier: new FakeQuoteVerifier()
66
67
  });
67
68
  vm.stopPrank();
@@ -82,4 +83,20 @@ contract IncoTest is MockOpHandler, DeployUtils, FakeDecryptionAttester, MockRem
82
83
  vm.recordLogs();
83
84
  }
84
85
 
86
+ /// @dev Creates an empty AllowanceProof (no proof required when sharer is address(0))
87
+ function _emptyAllowanceProof() internal pure returns (AllowanceProof memory) {
88
+ return AllowanceProof({
89
+ sharer: address(0),
90
+ voucher: AllowanceVoucher({
91
+ sessionNonce: bytes32(0),
92
+ verifyingContract: address(0),
93
+ callFunction: bytes4(0),
94
+ sharerArgData: "",
95
+ warning: ""
96
+ }),
97
+ voucherSignature: "",
98
+ requesterArgData: ""
99
+ });
100
+ }
101
+
85
102
  }
@@ -18,7 +18,6 @@ import {
18
18
  } from "../Types.sol";
19
19
  import {EncryptedOperations} from "../lightning-parts/EncryptedOperations.sol";
20
20
  import {DecryptionAttestation, ElementAttestationWithProof} from "../lightning-parts/DecryptionAttester.types.sol";
21
- import {AllowanceProof, AllowanceVoucher} from "../lightning-parts/AccessControl/AdvancedAccessControl.sol";
22
21
 
23
22
  /// @notice Wrapper to expose internal requireEqual as an external call so vm.expectRevert works
24
23
  contract RequireEqualCaller {
@@ -958,18 +957,6 @@ contract TestLib is IncoTest {
958
957
  assertFalse(isValid, "Invalid bool value should return false");
959
958
  }
960
959
 
961
- /// @dev Creates an empty AllowanceProof (no proof required when sharer is address(0))
962
- function _emptyAllowanceProof() internal pure returns (AllowanceProof memory) {
963
- return AllowanceProof({
964
- sharer: address(0),
965
- voucher: AllowanceVoucher({
966
- sessionNonce: bytes32(0), verifyingContract: address(0), callFunction: bytes4(0), sharerArgData: ""
967
- }),
968
- voucherSignature: "",
969
- requesterArgData: ""
970
- });
971
- }
972
-
973
960
  // ============ REQUIRE EQUAL TESTS ============
974
961
 
975
962
  function testRequireEqual_Ebool_ValidAttestation_True() public {
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: No License
2
+ pragma solidity ^0.8;
3
+
4
+ import {inco} from "../Lib.sol";
5
+ import {euint256, ebool, ETypes, elist, typeBitSize, SenderNotAllowedForHandle} from "../Types.sol";
6
+ import {IncoTest} from "./IncoTest.sol";
7
+ import {IEncryptedOperations} from "../lightning-parts/interfaces/IEncryptedOperations.sol";
8
+ import {ITrivialEncryption} from "../lightning-parts/interfaces/ITrivialEncryption.sol";
9
+ import {IBaseAccessControlList} from "../lightning-parts/AccessControl/interfaces/IBaseAccessControlList.sol";
10
+ import {IEList} from "../lightning-parts/interfaces/IEList.sol";
11
+ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
12
+ import {BIT_FEE, FEE} from "../lightning-parts/Fee.sol";
13
+
14
+ contract TestPause is IncoTest {
15
+
16
+ /// @dev A single op spec: calldata, msg.value, and the exact selector the op must
17
+ /// revert with when the contract is paused. Annotating each op keeps payable and
18
+ /// non-payable ops in one loop while still asserting the specific expected error.
19
+ struct Op {
20
+ bytes data;
21
+ uint256 value;
22
+ bytes4 expectedPauseError;
23
+ }
24
+
25
+ function testPausedOpsRevertAndUnpausedOpsSucceed() public {
26
+ // Fund this contract for the payable ops below.
27
+ vm.deal(address(this), 100 ether);
28
+
29
+ // Create encrypted operands. Trivial-encryption grants transient ACL access to
30
+ // this contract for the lifetime of this transaction, so subsequent ops can
31
+ // call back into `inco` as msg.sender.
32
+ euint256 a = inco.asEuint256(10);
33
+ euint256 b = inco.asEuint256(5);
34
+ ebool t = inco.asEbool(true);
35
+ bytes32 aH = euint256.unwrap(a);
36
+ bytes32 bH = euint256.unwrap(b);
37
+
38
+ // Build a 3-element euint256 elist to feed into list ops.
39
+ uint256 listFee = uint256(3) * typeBitSize(ETypes.Uint256) * BIT_FEE;
40
+ elist list = inco.listRange{value: listFee}(0, 3, ETypes.Uint256);
41
+ // Pull a valid encrypted index/value out of the list (transient ACL access).
42
+ bytes32 idxH = inco.listGet(list, 0);
43
+ uint256 listBits = uint256(3) * typeBitSize(ETypes.Uint256);
44
+ uint256 listAppendFee = listBits + typeBitSize(ETypes.Uint256);
45
+
46
+ // Selector each op is expected to revert with under pause:
47
+ // - aclErr: ops that take input handles. `isAllowed(...)` returns false when
48
+ // paused, so `checkInput` reverts before the op reaches `setDigest`.
49
+ // - pauseErr: ops without input handle checks. They reach `setDigest`, which
50
+ // has the `whenNotPaused` modifier.
51
+ bytes4 aclErr = SenderNotAllowedForHandle.selector;
52
+ bytes4 pauseErr = PausableUpgradeable.EnforcedPause.selector;
53
+
54
+ // Build the op list. Each op MUST revert with `expectedPauseError` when paused
55
+ // and succeed when unpaused.
56
+ Op[] memory ops = new Op[](41);
57
+ uint256 i;
58
+ // ----- EncryptedOperations: all check input handles -----
59
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eAdd, (a, b)), 0, aclErr);
60
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eSub, (a, b)), 0, aclErr);
61
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eMul, (a, b)), 0, aclErr);
62
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eDiv, (a, b)), 0, aclErr);
63
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eRem, (a, b)), 0, aclErr);
64
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eShl, (a, b)), 0, aclErr);
65
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eShr, (a, b)), 0, aclErr);
66
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eRotl, (a, b)), 0, aclErr);
67
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eRotr, (a, b)), 0, aclErr);
68
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eBitAnd, (aH, bH)), 0, aclErr);
69
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eBitOr, (aH, bH)), 0, aclErr);
70
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eBitXor, (aH, bH)), 0, aclErr);
71
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eEq, (aH, bH)), 0, aclErr);
72
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eNe, (aH, bH)), 0, aclErr);
73
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eGe, (a, b)), 0, aclErr);
74
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eGt, (a, b)), 0, aclErr);
75
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eLe, (a, b)), 0, aclErr);
76
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eLt, (a, b)), 0, aclErr);
77
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eMin, (a, b)), 0, aclErr);
78
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eMax, (a, b)), 0, aclErr);
79
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eNot, (t)), 0, aclErr);
80
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eCast, (aH, ETypes.AddressOrUint160OrBytes20)), 0, aclErr);
81
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eIfThenElse, (t, aH, bH)), 0, aclErr);
82
+ ops[i++] = Op(abi.encodeCall(IEncryptedOperations.eRandBounded, (aH, ETypes.Uint256)), FEE, aclErr);
83
+ // ----- TrivialEncryption: plaintext inputs, no ACL check -----
84
+ ops[i++] = Op(abi.encodeCall(ITrivialEncryption.asEuint256, (uint256(7))), 0, pauseErr);
85
+ ops[i++] = Op(abi.encodeCall(ITrivialEncryption.asEbool, (false)), 0, pauseErr);
86
+ ops[i++] = Op(abi.encodeCall(ITrivialEncryption.asEaddress, (bob)), 0, pauseErr);
87
+ // ----- ACL grant: gated on `isAllowed(handle, msg.sender)` -----
88
+ ops[i++] = Op(abi.encodeCall(IBaseAccessControlList.allow, (aH, bob)), 0, aclErr);
89
+ ops[i++] = Op(abi.encodeCall(IBaseAccessControlList.reveal, (bH)), 0, aclErr);
90
+ // ----- EList -----
91
+ // listRange takes only plain integers; reaches setDigest directly.
92
+ ops[i++] = Op(
93
+ abi.encodeCall(IEList.listRange, (0, 2, ETypes.Uint256)),
94
+ 2 * typeBitSize(ETypes.Uint256) * BIT_FEE,
95
+ pauseErr
96
+ );
97
+ // The rest take a list handle (and often value/index handles).
98
+ ops[i++] = Op(abi.encodeCall(IEList.listAppend, (list, idxH)), listAppendFee * BIT_FEE, aclErr);
99
+ ops[i++] = Op(abi.encodeCall(IEList.listGet, (list, uint16(0))), 0, aclErr);
100
+ ops[i++] = Op(abi.encodeCall(IEList.listGetOr, (list, idxH, idxH)), 0, aclErr);
101
+ ops[i++] = Op(abi.encodeCall(IEList.listSet, (list, idxH, idxH)), listBits * BIT_FEE, aclErr);
102
+ ops[i++] = Op(abi.encodeCall(IEList.listInsert, (list, idxH, idxH)), listAppendFee * BIT_FEE, aclErr);
103
+ ops[i++] = Op(abi.encodeCall(IEList.listConcat, (list, list)), (listBits + listBits) * BIT_FEE, aclErr);
104
+ ops[i++] = Op(
105
+ abi.encodeCall(IEList.listSlice, (list, idxH, uint16(2), idxH)),
106
+ 2 * typeBitSize(ETypes.Uint256) * BIT_FEE,
107
+ aclErr
108
+ );
109
+ ops[i++] = Op(abi.encodeCall(IEList.listShuffle, (list)), listBits * BIT_FEE, aclErr);
110
+ ops[i++] = Op(abi.encodeCall(IEList.listReverse, (list)), listBits * BIT_FEE, aclErr);
111
+ // newEList overloads — overloaded, so resolve by signature.
112
+ // Empty arrays are valid inputs and skip the inner loops, isolating the pause
113
+ // gate (the `setDigest` call) as the only thing that can revert.
114
+ bytes32[] memory emptyHandles = new bytes32[](0);
115
+ bytes[] memory emptyInputs = new bytes[](0);
116
+ ops[i++] = Op(abi.encodeWithSignature("newEList(bytes32[],uint8)", emptyHandles, ETypes.Uint256), 0, pauseErr);
117
+ ops[i++] = Op(
118
+ abi.encodeWithSignature("newEList(bytes[],uint8,address)", emptyInputs, ETypes.Uint256, address(this)),
119
+ 0,
120
+ pauseErr
121
+ );
122
+ assertEq(i, ops.length, "ops list length mismatch");
123
+
124
+ // ---- Pause: every op must revert with its expected selector ----
125
+ vm.prank(owner);
126
+ inco.pause();
127
+
128
+ for (uint256 j = 0; j < ops.length; j++) {
129
+ (bool ok, bytes memory ret) = address(inco).call{value: ops[j].value}(ops[j].data);
130
+ assertFalse(ok, string.concat("expected revert when paused, op #", vm.toString(j)));
131
+ assertGe(ret.length, 4, "revert payload too short");
132
+ // Read the first 4 bytes of revert data (the error selector).
133
+ bytes4 sel;
134
+ assembly {
135
+ sel := mload(add(ret, 0x20))
136
+ }
137
+ assertTrue(
138
+ sel == ops[j].expectedPauseError, string.concat("unexpected revert selector, op #", vm.toString(j))
139
+ );
140
+ }
141
+
142
+ // ---- Unpause: every op must succeed ----
143
+ vm.prank(owner);
144
+ inco.unpause();
145
+
146
+ for (uint256 j = 0; j < ops.length; j++) {
147
+ (bool ok,) = address(inco).call{value: ops[j].value}(ops[j].data);
148
+ assertTrue(ok, string.concat("expected success when unpaused, op #", vm.toString(j)));
149
+ }
150
+ }
151
+
152
+ }
@@ -277,7 +277,7 @@ contract TestUpgrade is IncoTest {
277
277
 
278
278
  // Helpers
279
279
  function _deploySessionVerifierProxy(address proxyOwner) private returns (SessionVerifier) {
280
- SessionVerifier impl = new SessionVerifier("");
280
+ SessionVerifier impl = new SessionVerifier(bytes32(0));
281
281
  ERC1967Proxy proxy = new ERC1967Proxy(address(impl), abi.encodeCall(SessionVerifier.initialize, (proxyOwner)));
282
282
  return SessionVerifier(address(proxy));
283
283
  }