@inco/lightning 0.7.10 → 0.8.0-devnet-1

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.
Files changed (34) hide show
  1. package/manifest.yaml +22 -0
  2. package/package.json +1 -1
  3. package/src/IncoLightning.sol +4 -0
  4. package/src/Lib.alphanet.sol +62 -6
  5. package/src/Lib.demonet.sol +62 -6
  6. package/src/Lib.devnet.sol +63 -7
  7. package/src/Lib.sol +63 -7
  8. package/src/Lib.template.sol +65 -9
  9. package/src/Lib.testnet.sol +62 -6
  10. package/src/interfaces/IIncoLightning.sol +2 -0
  11. package/src/libs/incoLightning_alphanet_v0_297966649.sol +62 -6
  12. package/src/libs/incoLightning_alphanet_v1_725458969.sol +62 -6
  13. package/src/libs/incoLightning_alphanet_v2_976644394.sol +62 -6
  14. package/src/libs/incoLightning_demonet_v0_863421733.sol +62 -6
  15. package/src/libs/incoLightning_demonet_v2_467437523.sol +62 -6
  16. package/src/libs/incoLightning_devnet_v0_340846814.sol +62 -6
  17. package/src/libs/incoLightning_devnet_v1_904635675.sol +62 -6
  18. package/src/libs/incoLightning_devnet_v2_295237520.sol +62 -6
  19. package/src/libs/incoLightning_devnet_v3_976859633.sol +534 -0
  20. package/src/libs/incoLightning_testnet_v0_183408998.sol +62 -6
  21. package/src/libs/incoLightning_testnet_v2_889158349.sol +62 -6
  22. package/src/lightning-parts/Fee.sol +10 -14
  23. package/src/lightning-parts/TEELifecycle.sol +11 -12
  24. package/src/lightning-parts/TEELifecycle.types.sol +1 -4
  25. package/src/periphery/IncoUtils.sol +42 -0
  26. package/src/test/AddTwo.sol +3 -3
  27. package/src/test/FakeIncoInfra/MockRemoteAttestation.sol +2 -3
  28. package/src/test/IncoTest.sol +2 -2
  29. package/src/test/TEELifecycle/TEELifecycleMockTest.t.sol +1 -1
  30. package/src/test/TestFakeInfra.t.sol +263 -0
  31. package/src/test/TestFeeWithdrawal.t.sol +60 -0
  32. package/src/test/TestIncoUtils.t.sol +242 -0
  33. package/src/version/IncoLightningConfig.sol +2 -2
  34. package/src/IIncoLightning.sol +0 -22
@@ -16,6 +16,11 @@ function typeOf(bytes32 handle) pure returns (ETypes) {
16
16
  }
17
17
 
18
18
  library e {
19
+ error CallFailedAfterFeeRefresh();
20
+
21
+ /// @dev slot to store the fee for inco operations
22
+ bytes32 private constant FEE_SLOT = keccak256("inco.fee");
23
+
19
24
  function sanitize(euint256 a) internal returns (euint256) {
20
25
  if (euint256.unwrap(a) == bytes32(0)) {
21
26
  return asEuint256(0);
@@ -355,17 +360,22 @@ library e {
355
360
 
356
361
  /// @dev costs the inco fee
357
362
  function rand() internal returns (euint256) {
358
- return euint256.wrap(inco.eRand{value: inco.getFee()}(ETypes.Uint256));
363
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.eRand.selector, ETypes.Uint256));
364
+ return euint256.wrap(result);
359
365
  }
360
366
 
361
367
  /// @dev costs the inco fee
362
368
  function randBounded(uint256 upperBound) internal returns (euint256) {
363
- return euint256.wrap(inco.eRandBounded{value: inco.getFee()}(euint256.unwrap(asEuint256(upperBound)), ETypes.Uint256));
369
+ bytes32 boundHandle = euint256.unwrap(asEuint256(upperBound));
370
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.eRandBounded.selector, boundHandle, ETypes.Uint256));
371
+ return euint256.wrap(result);
364
372
  }
365
373
 
366
374
  /// @dev costs the inco fee
367
375
  function randBounded(euint256 upperBound) internal returns (euint256) {
368
- return euint256.wrap(inco.eRandBounded{value: inco.getFee()}(euint256.unwrap(s(upperBound)), ETypes.Uint256));
376
+ bytes32 boundHandle = euint256.unwrap(s(upperBound));
377
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.eRandBounded.selector, boundHandle, ETypes.Uint256));
378
+ return euint256.wrap(result);
369
379
  }
370
380
 
371
381
  function asEuint256(uint256 a) internal returns (euint256) {
@@ -397,7 +407,8 @@ library e {
397
407
  /// @notice Creates a new encrypted uint256 for the given user.
398
408
  /// @dev costs the inco fee
399
409
  function newEuint256(bytes memory ciphertext, address user) internal returns (euint256) {
400
- return inco.newEuint256{value: inco.getFee()}(ciphertext, user);
410
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.newEuint256.selector, ciphertext, user));
411
+ return euint256.wrap(result);
401
412
  }
402
413
 
403
414
  /// @notice Creates a new encrypted bool assuming msg.sender is the user
@@ -409,7 +420,8 @@ library e {
409
420
  /// @notice Creates a new encrypted bool for the given user.
410
421
  /// @dev costs the inco fee
411
422
  function newEbool(bytes memory ciphertext, address user) internal returns (ebool) {
412
- return inco.newEbool{value: inco.getFee()}(ciphertext, user);
423
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.newEbool.selector, ciphertext, user));
424
+ return ebool.wrap(result);
413
425
  }
414
426
 
415
427
  /// @notice Creates a new encrypted address assuming msg.sender is the user
@@ -421,7 +433,8 @@ library e {
421
433
  /// @notice Creates a new encrypted address for the given user.
422
434
  /// @dev costs the inco fee
423
435
  function newEaddress(bytes memory ciphertext, address user) internal returns (eaddress) {
424
- return inco.newEaddress{value: inco.getFee()}(ciphertext, user);
436
+ bytes32 result = _callWithFeeRetry(abi.encodeWithSelector(inco.newEaddress.selector, ciphertext, user));
437
+ return eaddress.wrap(result);
425
438
  }
426
439
 
427
440
  function allow(euint256 a, address to) internal {
@@ -475,4 +488,47 @@ library e {
475
488
  function select(ebool control, eaddress ifTrue, eaddress ifFalse) internal returns (eaddress) {
476
489
  return eaddress.wrap(inco.eIfThenElse(s(control), eaddress.unwrap(s(ifTrue)), eaddress.unwrap(s(ifFalse))));
477
490
  }
491
+
492
+ /// @dev Store fee in the custom slot
493
+ /// @param _fee The fee to store
494
+ function _setFee(uint256 _fee) private {
495
+ bytes32 slot = FEE_SLOT;
496
+ assembly {
497
+ sstore(slot, _fee)
498
+ }
499
+ }
500
+
501
+ /// @dev Retrieve fee from the custom slot
502
+ /// @return fee The stored fee
503
+ function _getFee() private view returns (uint256 fee) {
504
+ bytes32 slot = FEE_SLOT;
505
+ assembly {
506
+ fee := sload(slot)
507
+ }
508
+ }
509
+
510
+ /// @dev Get current fee with fallback to inco.getFee() if not cached
511
+ function getCurrentFee() private returns (uint256) {
512
+ uint256 cachedFee = _getFee();
513
+ if (cachedFee == 0) {
514
+ cachedFee = inco.getFee();
515
+ _setFee(cachedFee);
516
+ }
517
+ return cachedFee;
518
+ }
519
+
520
+ /// @dev Execute a call to inco with fee, retrying with fresh fee if it fails
521
+ /// @param callData The encoded function call (use abi.encodeWithSelector)
522
+ /// @return result The bytes32 result from the call
523
+ function _callWithFeeRetry(bytes memory callData) private returns (bytes32) {
524
+ uint256 fee = getCurrentFee();
525
+ (bool success, bytes memory result) = address(inco).call{value: fee}(callData);
526
+ if (!success) {
527
+ fee = inco.getFee();
528
+ _setFee(fee);
529
+ (success, result) = address(inco).call{value: fee}(callData);
530
+ require(success, CallFailedAfterFeeRefresh());
531
+ }
532
+ return abi.decode(result, (bytes32));
533
+ }
478
534
  }
@@ -2,13 +2,15 @@
2
2
  pragma solidity ^0.8;
3
3
 
4
4
  // the fee should be modified through upgrades to limit gas cost
5
- uint256 constant FEE = 0.0001 ether;
5
+ uint256 constant FEE = 0.000001 ether;
6
6
 
7
7
  /// @notice Fee utils for lightning functions that require a fee
8
8
  /// @dev the fee may be changed through upgrades, develop your apps accordingly!
9
9
  abstract contract Fee {
10
10
 
11
11
  error FeeNotPaid();
12
+ error FeeWithdrawalFailed();
13
+ error NoFeesToWithdraw();
12
14
 
13
15
  /// @notice the fee to pay through msg.value for inputs and randomness IT MAY CHANGE
14
16
  function getFee() public pure returns (uint256) {
@@ -25,19 +27,13 @@ abstract contract Fee {
25
27
  _;
26
28
  }
27
29
 
28
- // Refund the difference between msg.value and what was actually spent
29
- // assumes that all outflows and inflows to the contract are due to the user
30
- // though the refund is capped at msg.value
31
- modifier refundUnspent() {
32
- uint256 balanceBefore = address(this).balance;
33
- _;
34
- uint256 balanceAfter = address(this).balance;
35
- uint256 spent = balanceBefore > balanceAfter ? balanceBefore - balanceAfter : 0;
36
- uint256 refund = msg.value > spent ? msg.value - spent : 0;
37
- if (refund > 0) {
38
- (bool success,) = msg.sender.call{value: refund}("");
39
- require(success, "Refund failed");
40
- }
30
+ /// @notice Internal helper to withdraw accumulated fees to a recipient
31
+ /// @param recipient The address to send the fees to
32
+ function _withdrawFeesTo(address recipient) internal {
33
+ uint256 fees = address(this).balance;
34
+ require(fees > 0, NoFeesToWithdraw());
35
+ (bool success,) = payable(recipient).call{value: fees}("");
36
+ require(success, FeeWithdrawalFailed());
41
37
  }
42
38
 
43
39
  }
@@ -29,8 +29,7 @@ contract TEELifecycleStorage {
29
29
  // @dev The index of the TEE version is the version number
30
30
  bytes32[] approvedTeeVersions;
31
31
  // @notice The Network key
32
- // @todo rename to NetworkPubkey https://github.com/Inco-fhevm/inco-monorepo/issues/983
33
- bytes eciesPubkey;
32
+ bytes networkPubkey;
34
33
  }
35
34
 
36
35
  bytes32 private constant TEE_LIFECYCLE_STORAGE_LOCATION = keccak256("inco.storage.TEELifecycle");
@@ -80,9 +79,9 @@ abstract contract TEELifecycle is
80
79
  event SignerHasUpdatedTDX(address indexed eoaSigner, bytes32 indexed mrAggregated);
81
80
 
82
81
  // Constants
83
- bytes32 public constant BOOTSTRAP_RESULT_STRUCT_HASH = keccak256(bytes("BootstrapResult(bytes ecies_pubkey)"));
84
- bytes32 public constant UPGRADE_RESULT_STRUCT_HASH = keccak256(bytes("UpgradeResult(bytes network_pubkey)"));
85
- bytes32 public constant ADD_NODE_RESULT_STRUCT_HASH = keccak256(bytes("AddNodeResult(bytes network_pubkey)"));
82
+ bytes32 public constant BOOTSTRAP_RESULT_STRUCT_HASH = keccak256(bytes("BootstrapResult(bytes networkPubkey)"));
83
+ bytes32 public constant UPGRADE_RESULT_STRUCT_HASH = keccak256(bytes("UpgradeResult(bytes networkPubkey)"));
84
+ bytes32 public constant ADD_NODE_RESULT_STRUCT_HASH = keccak256(bytes("AddNodeResult(bytes networkPubkey)"));
86
85
 
87
86
  uint16 public constant QUOTE_VERIFIER_VERSION = 4;
88
87
 
@@ -141,7 +140,7 @@ abstract contract TEELifecycle is
141
140
 
142
141
  // Update contract publicly viewable state
143
142
  addSigner(reportDataSigner);
144
- $.eciesPubkey = bootstrapResult.ecies_pubkey;
143
+ $.networkPubkey = bootstrapResult.networkPubkey;
145
144
 
146
145
  emit BootstrapStageComplete(reportDataSigner, bootstrapResult);
147
146
  }
@@ -163,7 +162,7 @@ abstract contract TEELifecycle is
163
162
 
164
163
  require(isBootstrapComplete(), BootstrapNotComplete());
165
164
  // Make sure the quote's network pubkey is the same as the one in the bootstrap result
166
- require(keccak256($.eciesPubkey) == keccak256(upgradeResult.networkPubkey), InvalidNetworkPubkey());
165
+ require(keccak256($.networkPubkey) == keccak256(upgradeResult.networkPubkey), InvalidNetworkPubkey());
167
166
  // Make sure the new MR_AGGREGATED has been pre-approved.
168
167
  bool isApproved = _isApprovedTeeVersion(newMrAggregated);
169
168
  require(isApproved, TEEVersionNotFound());
@@ -188,7 +187,7 @@ abstract contract TEELifecycle is
188
187
 
189
188
  require(isBootstrapComplete(), BootstrapNotComplete());
190
189
  // Make sure the quote's network pubkey is the same as the one in the bootstrap result
191
- require(keccak256($.eciesPubkey) == keccak256(addNodeResult.networkPubkey), InvalidNetworkPubkey());
190
+ require(keccak256($.networkPubkey) == keccak256(addNodeResult.networkPubkey), InvalidNetworkPubkey());
192
191
  // Make sure the new MR_AGGREGATED has been pre-approved.
193
192
  bool isApproved = _isApprovedTeeVersion(newMrAggregated);
194
193
  require(isApproved, TEEVersionNotFound());
@@ -257,7 +256,7 @@ abstract contract TEELifecycle is
257
256
  function isBootstrapComplete() public view returns (bool) {
258
257
  // The network pubkey is set once and once only, during the bootstrap process
259
258
  // So we can use the length of the network pubkey to check if the bootstrap is complete
260
- return getTeeLifecycleStorage().eciesPubkey.length > 0;
259
+ return getTeeLifecycleStorage().networkPubkey.length > 0;
261
260
  }
262
261
 
263
262
  /**
@@ -384,14 +383,14 @@ abstract contract TEELifecycle is
384
383
  return $.approvedTeeVersions[index];
385
384
  }
386
385
 
387
- function eciesPubkey() external view returns (bytes memory) {
388
- return getTeeLifecycleStorage().eciesPubkey;
386
+ function networkPubkey() external view returns (bytes memory) {
387
+ return getTeeLifecycleStorage().networkPubkey;
389
388
  }
390
389
 
391
390
  function bootstrapResultDigest(BootstrapResult memory bootstrapResult) public view returns (bytes32) {
392
391
  return
393
392
  _hashTypedDataV4(
394
- keccak256(abi.encode(BOOTSTRAP_RESULT_STRUCT_HASH, keccak256(bootstrapResult.ecies_pubkey)))
393
+ keccak256(abi.encode(BOOTSTRAP_RESULT_STRUCT_HASH, keccak256(bootstrapResult.networkPubkey)))
395
394
  );
396
395
  }
397
396
 
@@ -5,10 +5,7 @@ pragma solidity ^0.8.19;
5
5
  * @notice A struct representing what the TDX EOA signs during the bootstrap process.
6
6
  */
7
7
  struct BootstrapResult {
8
- // TODO: rename to networkPubkey
9
- // https://github.com/Inco-fhevm/inco-monorepo/issues/983
10
- // forge-lint: disable-next-line(mixed-case-variable)
11
- bytes ecies_pubkey;
8
+ bytes networkPubkey;
12
9
  }
13
10
 
14
11
  /**
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: No License
2
+ pragma solidity ^0.8;
3
+
4
+ import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol";
5
+
6
+ // Re-export FEE constant for convenience - consumers can import both IncoUtils and FEE from this file
7
+ import {FEE} from "../lightning-parts/Fee.sol";
8
+
9
+ contract IncoUtils {
10
+
11
+ error RefundFailed();
12
+ error ReentrantCall();
13
+
14
+ uint256 private constant NOT_ENTERED = 1;
15
+ uint256 private constant ENTERED = 2;
16
+
17
+ bytes32 private constant REENTRANCY_GUARD_SLOT = keccak256("inco.storage.IncoUtils.reentrancyGuard");
18
+
19
+ /// @notice Refund the difference between msg.value and what was actually spent
20
+ /// @dev Assumes all outflows and inflows to the contract are due to the user; refund is capped at msg.value
21
+ /// @dev Includes built-in reentrancy protection using OZ StorageSlot pattern (modifiers cannot use other modifiers)
22
+ modifier refundUnspent() {
23
+ StorageSlot.Uint256Slot storage status = StorageSlot.getUint256Slot(REENTRANCY_GUARD_SLOT);
24
+
25
+ require(status.value != ENTERED, ReentrantCall());
26
+ status.value = ENTERED;
27
+
28
+ uint256 balanceBefore = address(this).balance;
29
+ _;
30
+ uint256 balanceAfter = address(this).balance;
31
+ uint256 spent = balanceBefore > balanceAfter ? balanceBefore - balanceAfter : 0;
32
+ uint256 refund = msg.value > spent ? msg.value - spent : 0;
33
+
34
+ if (refund > 0) {
35
+ (bool success,) = msg.sender.call{value: refund}("");
36
+ require(success, RefundFailed());
37
+ }
38
+
39
+ status.value = NOT_ENTERED;
40
+ }
41
+
42
+ }
@@ -3,12 +3,12 @@ pragma solidity ^0.8;
3
3
 
4
4
  import {euint256, ebool} from "../Types.sol";
5
5
  import {IncoLightning} from "../IncoLightning.sol";
6
- import {Fee} from "../lightning-parts/Fee.sol";
6
+ import {IncoUtils, FEE} from "../periphery/IncoUtils.sol";
7
7
  import {DecryptionAttestation} from "../lightning-parts/DecryptionAttester.types.sol";
8
8
 
9
9
  // To implement such a contract, we would normally import e form Lib.sol. For test purposes, we take inco as
10
10
  // a constructor argument instead, so we can test it from other deployment addresses.
11
- contract AddTwo is Fee {
11
+ contract AddTwo is IncoUtils {
12
12
 
13
13
  IncoLightning inco;
14
14
 
@@ -36,7 +36,7 @@ contract AddTwo is Fee {
36
36
  refundUnspent
37
37
  returns (euint256 result, euint256 resultRevealed)
38
38
  {
39
- euint256 value = inco.newEuint256{value: getFee()}(uint256EInput, msg.sender);
39
+ euint256 value = inco.newEuint256{value: FEE}(uint256EInput, msg.sender);
40
40
  result = addTwo(value);
41
41
 
42
42
  inco.allow(euint256.unwrap(result), address(this));
@@ -43,7 +43,7 @@ contract MockRemoteAttestation is TestUtils {
43
43
  // Helper function to create a successful bootstrap result
44
44
  function successfulBootstrapResult(
45
45
  ITEELifecycle teeLifecycle,
46
- bytes memory eciesPublicKey,
46
+ bytes memory networkPubkey,
47
47
  address signer,
48
48
  uint256 signerPrivKey
49
49
  )
@@ -55,14 +55,13 @@ contract MockRemoteAttestation is TestUtils {
55
55
  bytes32 mrAggregated
56
56
  )
57
57
  {
58
- bytes memory eciesPubkey = eciesPublicKey;
59
58
  // See DEFAULT_MRTD in attestation/src/remote_attestation.rs
60
59
  bytes memory mrtd =
61
60
  hex"010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
62
61
  // See DEFAULT_MR_AGGREGATED in attestation/src/remote_attestation.rs to
63
62
  // see the calculation of the default value.
64
63
  mrAggregated = hex"c3a67bac251d4946d7b17481d39631676042fe3afab06e70c22105ad8383c19f";
65
- bootstrapResult = BootstrapResult({ecies_pubkey: eciesPubkey});
64
+ bootstrapResult = BootstrapResult({networkPubkey: networkPubkey});
66
65
 
67
66
  quote = createQuote(mrtd, signer);
68
67
  signature = signBootstrapResult(bootstrapResult, signerPrivKey, teeLifecycle);
@@ -39,12 +39,12 @@ contract IncoTest is MockOpHandler, DeployUtils, FakeDecryptionAttester, MockRem
39
39
  function setUp() public virtual {
40
40
  deployCreateX();
41
41
  vm.startPrank(testDeployer);
42
- vm.setEnv("USE_TDX_HW", "false"); // results in the test deployment using the FakeQuoteVerifier
42
+ vm.setEnv("SHOULD_SETUP_TEE_SIGNER", "true"); // results in a network pubkey and decrypt signer being populated in the TEE Lifecycle
43
43
  (IIncoLightning proxy,) = deployIncoLightningUsingConfig({
44
44
  deployer: testDeployer,
45
45
  // The highest precedent deployment
46
46
  // We select the pepper that will be used that will be generated in the lib.sol (usually "testnet"), but currently "alphanet" has higher major version
47
- pepper: "testnet",
47
+ pepper: "devnet",
48
48
  quoteVerifier: new FakeQuoteVerifier()
49
49
  });
50
50
  IOwnable(address(proxy)).transferOwnership(owner);
@@ -138,7 +138,7 @@ contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
138
138
  {
139
139
  (bootstrapPartyPrivkey, bootstrapPartyAddress) = getLabeledKeyPair("bootstrapParty");
140
140
  mrAggregated = testMrAggregated;
141
- bootstrapResult = BootstrapResult({ecies_pubkey: testNetworkPubkey});
141
+ bootstrapResult = BootstrapResult({networkPubkey: testNetworkPubkey});
142
142
 
143
143
  quote = createQuote(testMrtd, bootstrapPartyAddress);
144
144
  signature = signBootstrapResult(bootstrapResult, bootstrapPartyPrivkey);
@@ -14,6 +14,7 @@ contract TakesEInput is IncoTest {
14
14
 
15
15
  euint256 public a;
16
16
  ebool public b;
17
+ eaddress public c;
17
18
  uint256 public decryptedA;
18
19
 
19
20
  function setA(bytes memory uint256EInput) external {
@@ -25,6 +26,10 @@ contract TakesEInput is IncoTest {
25
26
  b = boolEInput.newEbool(msg.sender);
26
27
  }
27
28
 
29
+ function setC(bytes memory addressEInput) external {
30
+ c = addressEInput.newEaddress(msg.sender);
31
+ }
32
+
28
33
  }
29
34
 
30
35
  // its meta: this is testing correct behavior of our testing infrastructure
@@ -238,6 +243,264 @@ contract TestFakeInfra is IncoTest {
238
243
  assertEq(getUint256Value(a), 1);
239
244
  }
240
245
 
246
+ function testERandBoundedWithEuint256() public {
247
+ euint256 bound = e.asEuint256(100);
248
+ processAllOperations();
249
+ euint256 a = e.randBounded(bound);
250
+ processAllOperations();
251
+ assertEq(getUint256Value(a), 1);
252
+ }
253
+
254
+ // ============ FEE CACHING TESTS ============
255
+
256
+ // The fee slot used by the library
257
+ bytes32 constant FEE_SLOT = keccak256("inco.fee");
258
+
259
+ function testFeeCachingOnRand() public {
260
+ // Verify the slot is empty before any operation
261
+ bytes32 cachedFeeBefore = vm.load(address(this), FEE_SLOT);
262
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
263
+
264
+ // Call rand which should cache the fee
265
+ euint256 a = e.rand();
266
+ processAllOperations();
267
+ assertEq(getUint256Value(a), 1);
268
+
269
+ // Verify the fee is now cached
270
+ bytes32 cachedFeeAfter = vm.load(address(this), FEE_SLOT);
271
+ assertEq(uint256(cachedFeeAfter), inco.getFee(), "Cached fee should match inco.getFee()");
272
+ }
273
+
274
+ function testFeeCachingPersistsAcrossOperations() public {
275
+ // Verify the slot is empty before any operation
276
+ bytes32 cachedFeeBefore = vm.load(address(this), FEE_SLOT);
277
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
278
+
279
+ // First operation caches the fee
280
+ e.rand();
281
+ processAllOperations();
282
+
283
+ // Get the cached fee
284
+ bytes32 cachedFeeFirst = vm.load(address(this), FEE_SLOT);
285
+ assertEq(uint256(cachedFeeFirst), inco.getFee(), "Fee should be cached after first operation");
286
+
287
+ // Second operation should use cached fee
288
+ e.rand();
289
+ processAllOperations();
290
+
291
+ // Verify fee is still the same (wasn't re-fetched)
292
+ bytes32 cachedFeeSecond = vm.load(address(this), FEE_SLOT);
293
+ assertEq(uint256(cachedFeeFirst), uint256(cachedFeeSecond), "Cached fee should persist across operations");
294
+ }
295
+
296
+ function testFeeCachingOnRandBounded() public {
297
+ // Verify the slot is empty before any operation
298
+ bytes32 cachedFeeBefore = vm.load(address(this), FEE_SLOT);
299
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
300
+
301
+ // Call randBounded which should cache the fee
302
+ euint256 a = e.randBounded(100);
303
+ processAllOperations();
304
+ assertEq(getUint256Value(a), 1);
305
+
306
+ // Verify the fee is now cached
307
+ bytes32 cachedFeeAfter = vm.load(address(this), FEE_SLOT);
308
+ assertEq(uint256(cachedFeeAfter), inco.getFee(), "Cached fee should match inco.getFee()");
309
+ }
310
+
311
+ function testFeeCachingOnNewEuint256() public {
312
+ // Create a contract that will use e.newEuint256
313
+ TakesEInput inputContract = new TakesEInput();
314
+ vm.deal(address(inputContract), 1 ether);
315
+
316
+ // Verify the slot is empty in the input contract before operation
317
+ bytes32 cachedFeeBefore = vm.load(address(inputContract), FEE_SLOT);
318
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
319
+
320
+ // Call setA which uses e.newEuint256 internally
321
+ address self = address(this);
322
+ bytes memory ciphertext = fakePrepareEuint256Ciphertext(42, self, address(inputContract));
323
+ inputContract.setA(ciphertext);
324
+ processAllOperations();
325
+
326
+ // Verify the fee is now cached in the input contract
327
+ bytes32 cachedFeeAfter = vm.load(address(inputContract), FEE_SLOT);
328
+ assertEq(uint256(cachedFeeAfter), inco.getFee(), "Cached fee should match inco.getFee()");
329
+ }
330
+
331
+ function testFeeCachingOnNewEbool() public {
332
+ // Create a contract that will use e.newEbool
333
+ TakesEInput inputContract = new TakesEInput();
334
+ vm.deal(address(inputContract), 1 ether);
335
+
336
+ // Verify the slot is empty in the input contract before operation
337
+ bytes32 cachedFeeBefore = vm.load(address(inputContract), FEE_SLOT);
338
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
339
+
340
+ // Call setB which uses e.newEbool internally
341
+ address self = address(this);
342
+ bytes memory ciphertext = fakePrepareEboolCiphertext(true, self, address(inputContract));
343
+ inputContract.setB(ciphertext);
344
+ processAllOperations();
345
+
346
+ // Verify the fee is now cached in the input contract
347
+ bytes32 cachedFeeAfter = vm.load(address(inputContract), FEE_SLOT);
348
+ assertEq(uint256(cachedFeeAfter), inco.getFee(), "Cached fee should match inco.getFee()");
349
+ }
350
+
351
+ function testFeeCachingOnEAddress() public {
352
+ // Create a contract that will use e.newEaddress
353
+ TakesEInput inputContract = new TakesEInput();
354
+ vm.deal(address(inputContract), 1 ether);
355
+
356
+ // Verify the slot is empty in the input contract before operation
357
+ bytes32 cachedFeeBefore = vm.load(address(inputContract), FEE_SLOT);
358
+ assertEq(uint256(cachedFeeBefore), 0, "Fee should not be cached initially");
359
+
360
+ // Call setC which uses e.newEaddress internally
361
+ address self = address(this);
362
+ bytes memory ciphertext = fakePrepareEaddressCiphertext(alice, self, address(inputContract));
363
+ inputContract.setC(ciphertext);
364
+ processAllOperations();
365
+
366
+ // Verify the fee is now cached in the input contract
367
+ bytes32 cachedFeeAfter = vm.load(address(inputContract), FEE_SLOT);
368
+ assertEq(uint256(cachedFeeAfter), inco.getFee(), "Cached fee should match inco.getFee()");
369
+ }
370
+
371
+ function testFeeRetryWithStaleCachedFee() public {
372
+ // First, cache a valid fee by calling rand
373
+ e.rand();
374
+ processAllOperations();
375
+
376
+ // Now manually set the cached fee to a wrong value
377
+ // This simulates a fee increase after caching
378
+ vm.store(address(this), FEE_SLOT, bytes32(uint256(1)));
379
+
380
+ // Verify the stale fee is set
381
+ bytes32 staleFee = vm.load(address(this), FEE_SLOT);
382
+ assertEq(uint256(staleFee), 1, "Stale fee should be 1 wei");
383
+
384
+ // Call rand again - the first call with stale fee should fail,
385
+ // but retry with fresh fee should succeed (no revert = success)
386
+ euint256 a = e.rand();
387
+ processAllOperations();
388
+
389
+ // Verify we got a valid handle (non-zero)
390
+ assertTrue(euint256.unwrap(a) != bytes32(0), "Should return a valid handle");
391
+
392
+ // Verify the fee cache was updated to the correct value
393
+ bytes32 updatedFee = vm.load(address(this), FEE_SLOT);
394
+ assertEq(uint256(updatedFee), inco.getFee(), "Fee should be updated after retry");
395
+ }
396
+
397
+ function testFeeRetryOnRandBounded() public {
398
+ // Cache a valid fee first
399
+ e.rand();
400
+ processAllOperations();
401
+
402
+ // Set stale fee
403
+ vm.store(address(this), FEE_SLOT, bytes32(uint256(1)));
404
+
405
+ // randBounded should also retry successfully (no revert = success)
406
+ euint256 a = e.randBounded(100);
407
+ processAllOperations();
408
+
409
+ // Verify we got a valid handle (non-zero)
410
+ assertTrue(euint256.unwrap(a) != bytes32(0), "Should return a valid handle");
411
+
412
+ // Verify fee was updated
413
+ bytes32 updatedFee = vm.load(address(this), FEE_SLOT);
414
+ assertEq(uint256(updatedFee), inco.getFee(), "Fee should be updated after retry");
415
+ }
416
+
417
+ function testFeeRetryOnNewEuint256() public {
418
+ TakesEInput inputContract = new TakesEInput();
419
+ vm.deal(address(inputContract), 1 ether);
420
+
421
+ // First call to cache the fee
422
+ address self = address(this);
423
+ bytes memory ciphertext1 = fakePrepareEuint256Ciphertext(42, self, address(inputContract));
424
+ inputContract.setA(ciphertext1);
425
+ processAllOperations();
426
+
427
+ // Verify first value was stored correctly
428
+ assertEq(getUint256Value(inputContract.a()), 42, "First value should be 42");
429
+
430
+ // Set stale fee in the input contract
431
+ vm.store(address(inputContract), FEE_SLOT, bytes32(uint256(1)));
432
+
433
+ // Second call should retry and succeed
434
+ bytes memory ciphertext2 = fakePrepareEuint256Ciphertext(99, self, address(inputContract));
435
+ inputContract.setA(ciphertext2);
436
+ processAllOperations();
437
+
438
+ // Verify the retried value was stored correctly
439
+ assertEq(getUint256Value(inputContract.a()), 99, "Retried value should be 99");
440
+
441
+ // Verify fee was updated
442
+ bytes32 updatedFee = vm.load(address(inputContract), FEE_SLOT);
443
+ assertEq(uint256(updatedFee), inco.getFee(), "Fee should be updated after retry");
444
+ }
445
+
446
+ function testFeeRetryOnNewEbool() public {
447
+ TakesEInput inputContract = new TakesEInput();
448
+ vm.deal(address(inputContract), 1 ether);
449
+
450
+ // First call to cache the fee
451
+ address self = address(this);
452
+ bytes memory ciphertext1 = fakePrepareEboolCiphertext(true, self, address(inputContract));
453
+ inputContract.setB(ciphertext1);
454
+ processAllOperations();
455
+
456
+ // Verify first value was stored correctly
457
+ assertEq(getBoolValue(inputContract.b()), true, "First value should be true");
458
+
459
+ // Set stale fee
460
+ vm.store(address(inputContract), FEE_SLOT, bytes32(uint256(1)));
461
+
462
+ // Second call should retry and succeed
463
+ bytes memory ciphertext2 = fakePrepareEboolCiphertext(false, self, address(inputContract));
464
+ inputContract.setB(ciphertext2);
465
+ processAllOperations();
466
+
467
+ // Verify the retried value was stored correctly
468
+ assertEq(getBoolValue(inputContract.b()), false, "Retried value should be false");
469
+
470
+ // Verify fee was updated
471
+ bytes32 updatedFee = vm.load(address(inputContract), FEE_SLOT);
472
+ assertEq(uint256(updatedFee), inco.getFee(), "Fee should be updated after retry");
473
+ }
474
+
475
+ function testFeeRetryOnNewEaddress() public {
476
+ TakesEInput inputContract = new TakesEInput();
477
+ vm.deal(address(inputContract), 1 ether);
478
+
479
+ // First call to cache the fee
480
+ address self = address(this);
481
+ bytes memory ciphertext1 = fakePrepareEaddressCiphertext(alice, self, address(inputContract));
482
+ inputContract.setC(ciphertext1);
483
+ processAllOperations();
484
+
485
+ // Verify first value was stored correctly
486
+ assertEq(getAddressValue(inputContract.c()), alice, "First value should be alice");
487
+
488
+ // Set stale fee
489
+ vm.store(address(inputContract), FEE_SLOT, bytes32(uint256(1)));
490
+
491
+ // Second call should retry and succeed
492
+ bytes memory ciphertext2 = fakePrepareEaddressCiphertext(bob, self, address(inputContract));
493
+ inputContract.setC(ciphertext2);
494
+ processAllOperations();
495
+
496
+ // Verify the retried value was stored correctly
497
+ assertEq(getAddressValue(inputContract.c()), bob, "Retried value should be bob");
498
+
499
+ // Verify fee was updated
500
+ bytes32 updatedFee = vm.load(address(inputContract), FEE_SLOT);
501
+ assertEq(uint256(updatedFee), inco.getFee(), "Fee should be updated after retry");
502
+ }
503
+
241
504
  function testEIfThenElse() public {
242
505
  ebool controlA = e.asEbool(true);
243
506
  ebool controlB = e.asEbool(false);