@coti-io/coti-contracts 1.1.0 → 1.3.0

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 (433) hide show
  1. package/.github/workflows/npm-publish.yml +19 -15
  2. package/README.md +3 -1
  3. package/contracts/disperse/coinByRatio/FixedRatioCoinDisperser.sol +452 -0
  4. package/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1.sol +490 -0
  5. package/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow.sol +432 -0
  6. package/contracts/disperse/disperseToken/MintDisperser.sol +120 -0
  7. package/contracts/disperse/disperseToken/TokenDisperser.sol +139 -0
  8. package/contracts/messaging/PrivateMessaging.sol +456 -0
  9. package/contracts/mocks/messaging/PrivateMessagingHarness.sol +54 -0
  10. package/contracts/mocks/oracle/CotiPriceConsumerMock.sol +31 -0
  11. package/contracts/mocks/token/ERC20DecimalsMock.sol +21 -0
  12. package/contracts/mocks/token/ERC20Mock.sol +19 -0
  13. package/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock.sol +18 -0
  14. package/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock.sol +18 -0
  15. package/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock.sol +44 -0
  16. package/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock.sol +23 -0
  17. package/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock.sol +29 -0
  18. package/contracts/mocks/token/PrivateERC20/PrivateERC20Mock.sol +9 -4
  19. package/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol +39 -0
  20. package/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock.sol +29 -0
  21. package/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock.sol +18 -0
  22. package/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock.sol +12 -0
  23. package/contracts/mocks/utils/mpc/Arithmetic128TestsContract.sol +123 -0
  24. package/contracts/mocks/utils/mpc/Arithmetic256TestsContract.sol +122 -0
  25. package/contracts/mocks/utils/mpc/Bitwise128TestsContract.sol +65 -0
  26. package/contracts/mocks/utils/mpc/Bitwise256TestsContract.sol +65 -0
  27. package/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract.sol +64 -0
  28. package/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract.sol +64 -0
  29. package/contracts/mocks/utils/mpc/Comparison128TestsContract.sol +98 -0
  30. package/contracts/mocks/utils/mpc/Comparison256TestsContract.sol +98 -0
  31. package/contracts/mocks/utils/mpc/MinMax128TestsContract.sol +44 -0
  32. package/contracts/mocks/utils/mpc/MinMax256TestsContract.sol +44 -0
  33. package/contracts/mocks/utils/mpc/MinimalImplementation.sol +55 -0
  34. package/contracts/mocks/utils/mpc/MinimalProxy.sol +27 -0
  35. package/contracts/mocks/utils/mpc/MpcOperations128TestContract.sol +267 -0
  36. package/contracts/mocks/utils/mpc/MpcOperationsTestContract.sol +334 -0
  37. package/contracts/mocks/utils/mpc/Mux128TestsContract.sol +24 -0
  38. package/contracts/mocks/utils/mpc/Mux256TestsContract.sol +24 -0
  39. package/contracts/mocks/utils/mpc/OnBoard128TestsContract.sol +93 -0
  40. package/contracts/mocks/utils/mpc/OnBoard256TestsContract.sol +116 -0
  41. package/contracts/mocks/utils/mpc/PrivacyImplementationV1.sol +157 -0
  42. package/contracts/mocks/utils/mpc/PrivacyImplementationV2.sol +133 -0
  43. package/contracts/mocks/utils/mpc/PrivacyProxy.sol +137 -0
  44. package/contracts/mocks/utils/mpc/Random128TestsContract.sol +32 -0
  45. package/contracts/mocks/utils/mpc/Random256TestsContract.sol +32 -0
  46. package/contracts/mocks/utils/mpc/Shift128TestsContract.sol +36 -0
  47. package/contracts/mocks/utils/mpc/Shift256TestsContract.sol +36 -0
  48. package/contracts/mocks/utils/mpc/Transfer128TestsContract.sol +29 -0
  49. package/contracts/mocks/utils/mpc/Transfer256TestsContract.sol +29 -0
  50. package/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract.sol +32 -0
  51. package/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract.sol +32 -0
  52. package/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract.sol +59 -0
  53. package/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract.sol +63 -0
  54. package/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract.sol +99 -0
  55. package/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock.sol +7 -6
  56. package/contracts/node/CotiNodeRewards.sol +199 -0
  57. package/contracts/node/SoulboundNodeNFT.sol +519 -0
  58. package/contracts/oracle/CotiPriceConsumer.sol +103 -0
  59. package/contracts/oracle/ICotiPriceConsumer.sol +15 -0
  60. package/contracts/oracle/IStdReference.sol +26 -0
  61. package/contracts/privacyBridge/PrivacyBridge.sol +600 -0
  62. package/contracts/privacyBridge/PrivacyBridgeCotiNative.sol +316 -0
  63. package/contracts/privacyBridge/PrivacyBridgeERC20.sol +333 -0
  64. package/contracts/privacyBridge/PrivacyBridgeUSDCe.sol +22 -0
  65. package/contracts/privacyBridge/PrivacyBridgeUSDT.sol +23 -0
  66. package/contracts/privacyBridge/PrivacyBridgeWADA.sol +23 -0
  67. package/contracts/privacyBridge/PrivacyBridgeWBTC.sol +23 -0
  68. package/contracts/privacyBridge/PrivacyBridgeWETH.sol +23 -0
  69. package/contracts/privacyBridge/PrivacyBridgegCoti.sol +23 -0
  70. package/contracts/token/PrivateERC20/IPrivateERC20.sol +273 -40
  71. package/contracts/token/PrivateERC20/ITokenReceiver.sol +17 -0
  72. package/contracts/token/PrivateERC20/ITokenReceiverEncrypted.sol +20 -0
  73. package/contracts/token/PrivateERC20/PrivateERC20.sol +861 -152
  74. package/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC.sol +17 -0
  75. package/contracts/token/PrivateERC20/tokens/PrivateCOTI.sol +13 -0
  76. package/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken.sol +17 -0
  77. package/contracts/token/PrivateERC20/tokens/PrivateTetherUSD.sol +17 -0
  78. package/contracts/token/PrivateERC20/tokens/PrivateWrappedADA.sol +17 -0
  79. package/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC.sol +17 -0
  80. package/contracts/token/PrivateERC20/tokens/PrivateWrappedEther.sol +13 -0
  81. package/contracts/utils/mpc/MpcCore.sol +10780 -4962
  82. package/contracts/utils/mpc/MpcInterface.sol +7 -2
  83. package/hardhat/gasPriceBump.ts +60 -0
  84. package/hardhat.config.ts +63 -12
  85. package/package.json +22 -4
  86. package/scripts/add-bridge-operators.cjs +87 -0
  87. package/scripts/deploy-coti-price-consumer-mainnet.cjs +49 -0
  88. package/scripts/deployCotiNodeRewards.ts +51 -0
  89. package/scripts/deploySoulboundNodeNFT.ts +57 -0
  90. package/scripts/redeploy-private-and-bridges.cjs +201 -0
  91. package/scripts/set-bridge-limits-mainnet.cjs +177 -0
  92. package/scripts/verify-mainnet-private-bridges.cjs +289 -0
  93. package/sonar-project.properties +11 -0
  94. package/test/messaging/PrivateMessaging.test.ts +158 -0
  95. package/test/privacyBridge/UnifiedPrivacyBridges.test.cjs +1663 -0
  96. package/test/token/PrivateERC20/PrivacyBridge.fees.test.ts +250 -0
  97. package/test/token/PrivateERC20/PrivateERC20.test.ts +1109 -268
  98. package/test/utils/mpc/Bitwise128.test.ts +92 -0
  99. package/test/utils/mpc/Bitwise256.test.ts +101 -0
  100. package/test/utils/mpc/BuildInputText128.test.ts +398 -0
  101. package/test/utils/mpc/CheckedArithmetic128.test.ts +45 -0
  102. package/test/utils/mpc/CheckedArithmetic256.test.ts +45 -0
  103. package/test/utils/mpc/Comparison128.test.ts +48 -0
  104. package/test/utils/mpc/Comparison256.test.ts +48 -0
  105. package/test/utils/mpc/InvalidCiphertext.test.ts +179 -0
  106. package/test/utils/mpc/InvalidCiphertext128.test.ts +179 -0
  107. package/test/utils/mpc/InvalidCiphertext256.test.ts +201 -0
  108. package/test/utils/mpc/InvalidTransfer128.test.ts +166 -0
  109. package/test/utils/mpc/InvalidTransfer256.test.ts +158 -0
  110. package/test/utils/mpc/MinMax128.test.ts +44 -0
  111. package/test/utils/mpc/MinMax256.test.ts +44 -0
  112. package/test/utils/mpc/MinimalProxyDemo.test.ts +280 -0
  113. package/test/utils/mpc/MpcOperations.test.ts +589 -0
  114. package/test/utils/mpc/MpcOperations128.test.ts +632 -0
  115. package/test/utils/mpc/Mux128.test.ts +41 -0
  116. package/test/utils/mpc/Mux256.test.ts +41 -0
  117. package/test/utils/mpc/NegativeCases.test.ts +478 -0
  118. package/test/utils/mpc/NegativeCases128.test.ts +237 -0
  119. package/test/utils/mpc/NegativeCases256.test.ts +236 -0
  120. package/test/utils/mpc/OnBoard128.test.ts +90 -0
  121. package/test/utils/mpc/OnBoard256.test.ts +114 -0
  122. package/test/utils/mpc/Precompile128.test.ts +51 -0
  123. package/test/utils/mpc/Precompile256.test.ts +115 -0
  124. package/test/utils/mpc/ProxyValidation.test.ts +199 -0
  125. package/test/utils/mpc/Random128.test.ts +47 -0
  126. package/test/utils/mpc/Random256.test.ts +47 -0
  127. package/test/utils/mpc/Shift128.test.ts +41 -0
  128. package/test/utils/mpc/Shift256.test.ts +41 -0
  129. package/test/utils/mpc/Transfer128.test.ts +46 -0
  130. package/test/utils/mpc/Transfer256.test.ts +46 -0
  131. package/test/utils/mpc/TransferWithAllowance128.test.ts +48 -0
  132. package/test/utils/mpc/TransferWithAllowance256.test.ts +48 -0
  133. package/test/utils/mpc/UpgradeablePrivacyProxy.test.ts +307 -0
  134. package/test/utils/mpc/ValidateCiphertext.test.ts +221 -0
  135. package/test/utils/mpc/ValidateCiphertext256.test.ts +161 -0
  136. package/test/utils/mpc/ValidateCiphertext256Decryption.test.ts +383 -0
  137. package/test/utils/mpc/WalletEncryptionFailures.test.ts +494 -0
  138. package/test/utils/privateErc20Helpers.ts +97 -0
  139. package/typechain-types/@openzeppelin/contracts/access/AccessControl.ts +324 -0
  140. package/typechain-types/@openzeppelin/contracts/access/AccessControlEnumerable.ts +361 -0
  141. package/typechain-types/@openzeppelin/contracts/access/IAccessControl.ts +292 -0
  142. package/typechain-types/@openzeppelin/contracts/access/IAccessControlEnumerable.ts +329 -0
  143. package/typechain-types/@openzeppelin/contracts/access/Ownable.ts +153 -0
  144. package/typechain-types/@openzeppelin/contracts/access/index.ts +8 -0
  145. package/typechain-types/@openzeppelin/contracts/index.ts +4 -0
  146. package/typechain-types/@openzeppelin/contracts/security/Pausable.ts +150 -0
  147. package/typechain-types/@openzeppelin/contracts/security/index.ts +4 -0
  148. package/typechain-types/@openzeppelin/contracts/token/ERC20/ERC20.ts +330 -0
  149. package/typechain-types/@openzeppelin/contracts/token/ERC20/IERC20.ts +262 -0
  150. package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.ts +286 -0
  151. package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.ts +143 -0
  152. package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/index.ts +5 -0
  153. package/typechain-types/@openzeppelin/contracts/token/ERC20/index.ts +7 -0
  154. package/typechain-types/@openzeppelin/contracts/token/ERC721/ERC721.ts +420 -0
  155. package/typechain-types/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.ts +420 -0
  156. package/typechain-types/@openzeppelin/contracts/token/ERC721/extensions/index.ts +4 -0
  157. package/typechain-types/@openzeppelin/contracts/token/ERC721/index.ts +3 -0
  158. package/typechain-types/@openzeppelin/contracts/token/index.ts +2 -0
  159. package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperser.ts +679 -0
  160. package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1.ts +735 -0
  161. package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow.ts +734 -0
  162. package/typechain-types/contracts/disperse/coinByRatio/index.ts +6 -0
  163. package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/IMintableERC20.ts +97 -0
  164. package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/IOwnable.ts +95 -0
  165. package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/MintDisperser.ts +228 -0
  166. package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/index.ts +6 -0
  167. package/typechain-types/contracts/disperse/disperseToken/TokenDisperser.ts +191 -0
  168. package/typechain-types/contracts/disperse/disperseToken/index.ts +6 -0
  169. package/typechain-types/contracts/disperse/index.ts +7 -0
  170. package/typechain-types/contracts/index.ts +10 -0
  171. package/typechain-types/contracts/messaging/PrivateMessaging.ts +871 -0
  172. package/typechain-types/contracts/messaging/index.ts +4 -0
  173. package/typechain-types/contracts/mocks/index.ts +4 -0
  174. package/typechain-types/contracts/mocks/messaging/PrivateMessagingHarness.ts +951 -0
  175. package/typechain-types/contracts/mocks/messaging/index.ts +4 -0
  176. package/typechain-types/contracts/mocks/oracle/CotiPriceConsumerMock.ts +163 -0
  177. package/typechain-types/contracts/mocks/oracle/index.ts +4 -0
  178. package/typechain-types/contracts/mocks/token/ERC20DecimalsMock.ts +349 -0
  179. package/typechain-types/contracts/mocks/token/ERC20Mock.ts +368 -0
  180. package/typechain-types/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock.ts +108 -0
  181. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock.ts +1892 -0
  182. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock.ts +1296 -0
  183. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock.ts +1270 -0
  184. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock.ts +166 -0
  185. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20Mock.ts +827 -78
  186. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/IPrivateERC20TransferAndCallEncrypted.ts +102 -0
  187. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/PrivateERC20ReentrantReceiverMock.ts +154 -0
  188. package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/index.ts +5 -0
  189. package/typechain-types/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock.ts +155 -0
  190. package/typechain-types/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock.ts +107 -0
  191. package/typechain-types/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock.ts +100 -0
  192. package/typechain-types/contracts/mocks/token/PrivateERC20/index.ts +10 -0
  193. package/typechain-types/contracts/mocks/token/index.ts +2 -0
  194. package/typechain-types/contracts/mocks/utils/mpc/{Arithmetic128BitTestsContract.ts → Arithmetic128TestsContract.ts} +96 -126
  195. package/typechain-types/contracts/mocks/utils/mpc/{Arithmetic256BitTestsContract.ts → Arithmetic256TestsContract.ts} +97 -133
  196. package/typechain-types/contracts/mocks/utils/mpc/{Bitwise128BitTestsContract.ts → Bitwise128TestsContract.ts} +52 -59
  197. package/typechain-types/contracts/mocks/utils/mpc/{Bitwise256BitTestsContract.ts → Bitwise256TestsContract.ts} +52 -59
  198. package/typechain-types/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract.ts +206 -0
  199. package/typechain-types/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract.ts +206 -0
  200. package/typechain-types/contracts/mocks/utils/mpc/{Comparison128BitTestsContract.ts → Comparison128TestsContract.ts} +115 -97
  201. package/typechain-types/contracts/mocks/utils/mpc/{Comparison256BitTestsContract.ts → Comparison256TestsContract.ts} +115 -97
  202. package/typechain-types/contracts/mocks/utils/mpc/MinMax128TestsContract.ts +142 -0
  203. package/typechain-types/contracts/mocks/utils/mpc/MinMax256TestsContract.ts +142 -0
  204. package/typechain-types/contracts/mocks/utils/mpc/MinimalImplementation.ts +259 -0
  205. package/typechain-types/contracts/mocks/utils/mpc/MinimalProxy.ts +90 -0
  206. package/typechain-types/contracts/mocks/utils/mpc/MpcOperations128TestContract.ts +388 -0
  207. package/typechain-types/contracts/mocks/utils/mpc/MpcOperationsTestContract.ts +455 -0
  208. package/typechain-types/contracts/mocks/utils/mpc/Mux128TestsContract.ts +109 -0
  209. package/typechain-types/contracts/mocks/utils/mpc/Mux256TestsContract.ts +109 -0
  210. package/typechain-types/contracts/mocks/utils/mpc/OnBoard128TestsContract.ts +166 -0
  211. package/typechain-types/contracts/mocks/utils/mpc/OnBoard256TestsContract.ts +194 -0
  212. package/typechain-types/contracts/mocks/utils/mpc/PrivacyImplementationV1.ts +385 -0
  213. package/typechain-types/contracts/mocks/utils/mpc/PrivacyImplementationV2.ts +441 -0
  214. package/typechain-types/contracts/mocks/utils/mpc/PrivacyProxy.ts +201 -0
  215. package/typechain-types/contracts/mocks/utils/mpc/Random128TestsContract.ts +131 -0
  216. package/typechain-types/contracts/mocks/utils/mpc/Random256TestsContract.ts +131 -0
  217. package/typechain-types/contracts/mocks/utils/mpc/Shift128TestsContract.ts +142 -0
  218. package/typechain-types/contracts/mocks/utils/mpc/Shift256TestsContract.ts +142 -0
  219. package/typechain-types/contracts/mocks/utils/mpc/Transfer128TestsContract.ts +109 -0
  220. package/typechain-types/contracts/mocks/utils/mpc/Transfer256TestsContract.ts +109 -0
  221. package/typechain-types/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract.ts +128 -0
  222. package/typechain-types/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract.ts +128 -0
  223. package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract.ts +198 -0
  224. package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract.ts +182 -0
  225. package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract.ts +257 -0
  226. package/typechain-types/contracts/mocks/utils/mpc/index.ts +32 -8
  227. package/typechain-types/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock.ts +6 -6
  228. package/typechain-types/contracts/node/CotiNodeRewards.ts +566 -0
  229. package/typechain-types/contracts/node/SoulboundNodeNFT.sol/IERC5192.ts +123 -0
  230. package/typechain-types/contracts/node/SoulboundNodeNFT.sol/SoulboundNodeNFT.ts +975 -0
  231. package/typechain-types/contracts/node/SoulboundNodeNFT.sol/index.ts +5 -0
  232. package/typechain-types/contracts/node/index.ts +6 -0
  233. package/typechain-types/contracts/oracle/CotiPriceConsumer.ts +256 -0
  234. package/typechain-types/contracts/oracle/ICotiPriceConsumer.ts +119 -0
  235. package/typechain-types/contracts/oracle/IStdReference.ts +136 -0
  236. package/typechain-types/contracts/oracle/index.ts +6 -0
  237. package/typechain-types/contracts/privacyBridge/PrivacyBridge.ts +1628 -0
  238. package/typechain-types/contracts/privacyBridge/PrivacyBridgeCotiNative.ts +1870 -0
  239. package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/IHasDecimals.ts +84 -0
  240. package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/PrivacyBridgeERC20.ts +1892 -0
  241. package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/index.ts +5 -0
  242. package/typechain-types/contracts/privacyBridge/PrivacyBridgeUSDCe.ts +1892 -0
  243. package/typechain-types/contracts/privacyBridge/PrivacyBridgeUSDT.ts +1892 -0
  244. package/typechain-types/contracts/privacyBridge/PrivacyBridgeWADA.ts +1892 -0
  245. package/typechain-types/contracts/privacyBridge/PrivacyBridgeWBTC.ts +1892 -0
  246. package/typechain-types/contracts/privacyBridge/PrivacyBridgeWETH.ts +1892 -0
  247. package/typechain-types/contracts/privacyBridge/PrivacyBridgegCoti.ts +1892 -0
  248. package/typechain-types/contracts/privacyBridge/index.ts +13 -0
  249. package/typechain-types/contracts/token/PrivateERC20/IPrivateERC20.ts +507 -70
  250. package/typechain-types/contracts/token/PrivateERC20/ITokenReceiver.ts +100 -0
  251. package/typechain-types/contracts/token/PrivateERC20/ITokenReceiverEncrypted.ts +99 -0
  252. package/typechain-types/contracts/token/PrivateERC20/PrivateERC20.ts +833 -68
  253. package/typechain-types/contracts/token/PrivateERC20/index.ts +4 -0
  254. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC.ts +1270 -0
  255. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateCOTI.ts +1270 -0
  256. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken.ts +1270 -0
  257. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateTetherUSD.ts +1270 -0
  258. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedADA.ts +1270 -0
  259. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC.ts +1270 -0
  260. package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedEther.ts +1270 -0
  261. package/typechain-types/contracts/token/PrivateERC20/tokens/index.ts +10 -0
  262. package/typechain-types/contracts/utils/mpc/MpcInterface.sol/ExtendedOperations.ts +111 -10
  263. package/typechain-types/factories/@openzeppelin/contracts/access/AccessControlEnumerable__factory.ts +276 -0
  264. package/typechain-types/factories/@openzeppelin/contracts/access/AccessControl__factory.ts +229 -0
  265. package/typechain-types/factories/@openzeppelin/contracts/access/IAccessControlEnumerable__factory.ts +244 -0
  266. package/typechain-types/factories/@openzeppelin/contracts/access/IAccessControl__factory.ts +197 -0
  267. package/typechain-types/factories/@openzeppelin/contracts/access/Ownable__factory.ts +74 -0
  268. package/typechain-types/factories/@openzeppelin/contracts/access/index.ts +8 -0
  269. package/typechain-types/factories/@openzeppelin/contracts/index.ts +2 -0
  270. package/typechain-types/factories/@openzeppelin/contracts/security/Pausable__factory.ts +61 -0
  271. package/typechain-types/factories/@openzeppelin/contracts/security/index.ts +4 -0
  272. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/ERC20__factory.ts +356 -0
  273. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/IERC20__factory.ts +205 -0
  274. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata__factory.ts +247 -0
  275. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit__factory.ts +100 -0
  276. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/index.ts +5 -0
  277. package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/index.ts +6 -0
  278. package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/ERC721__factory.ts +416 -0
  279. package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata__factory.ts +355 -0
  280. package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/extensions/index.ts +4 -0
  281. package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/index.ts +2 -0
  282. package/typechain-types/factories/@openzeppelin/contracts/token/index.ts +1 -0
  283. package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1__factory.ts +791 -0
  284. package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow__factory.ts +711 -0
  285. package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperser__factory.ts +759 -0
  286. package/typechain-types/factories/contracts/disperse/coinByRatio/index.ts +6 -0
  287. package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/IMintableERC20__factory.ts +43 -0
  288. package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/IOwnable__factory.ts +35 -0
  289. package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/MintDisperser__factory.ts +203 -0
  290. package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/index.ts +6 -0
  291. package/typechain-types/factories/contracts/disperse/disperseToken/TokenDisperser__factory.ts +198 -0
  292. package/typechain-types/factories/contracts/disperse/disperseToken/index.ts +5 -0
  293. package/typechain-types/factories/contracts/disperse/index.ts +5 -0
  294. package/typechain-types/factories/contracts/index.ts +5 -0
  295. package/typechain-types/factories/contracts/messaging/PrivateMessaging__factory.ts +1035 -0
  296. package/typechain-types/factories/contracts/messaging/index.ts +4 -0
  297. package/typechain-types/factories/contracts/mocks/access/DataPrivacyFramework/DataPrivacyFrameworkMock__factory.ts +1 -1
  298. package/typechain-types/factories/contracts/mocks/index.ts +2 -0
  299. package/typechain-types/factories/contracts/mocks/messaging/PrivateMessagingHarness__factory.ts +1161 -0
  300. package/typechain-types/factories/contracts/mocks/messaging/index.ts +4 -0
  301. package/typechain-types/factories/contracts/mocks/oracle/CotiPriceConsumerMock__factory.ts +159 -0
  302. package/typechain-types/factories/contracts/mocks/oracle/index.ts +4 -0
  303. package/typechain-types/factories/contracts/mocks/token/ERC20DecimalsMock__factory.ts +394 -0
  304. package/typechain-types/factories/contracts/mocks/token/ERC20Mock__factory.ts +392 -0
  305. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock__factory.ts +125 -0
  306. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock__factory.ts +1797 -0
  307. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock__factory.ts +1514 -0
  308. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock__factory.ts +1466 -0
  309. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock__factory.ts +165 -0
  310. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20Mock__factory.ts +975 -127
  311. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/IPrivateERC20TransferAndCallEncrypted__factory.ts +54 -0
  312. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/PrivateERC20ReentrantReceiverMock__factory.ts +159 -0
  313. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/index.ts +5 -0
  314. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock__factory.ts +164 -0
  315. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock__factory.ts +130 -0
  316. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock__factory.ts +102 -0
  317. package/typechain-types/factories/contracts/mocks/token/PrivateERC20/index.ts +9 -0
  318. package/typechain-types/factories/contracts/mocks/token/PrivateERC721/PrivateERC721URIStorageMock__factory.ts +1 -1
  319. package/typechain-types/factories/contracts/mocks/token/index.ts +2 -0
  320. package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic128TestsContract__factory.ts +330 -0
  321. package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic256TestsContract__factory.ts +330 -0
  322. package/typechain-types/factories/contracts/mocks/utils/mpc/ArithmeticTestsContract__factory.ts +1 -1
  323. package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise128TestsContract__factory.ts +184 -0
  324. package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise256TestsContract__factory.ts +184 -0
  325. package/typechain-types/factories/contracts/mocks/utils/mpc/BitwiseTestsContract__factory.ts +1 -1
  326. package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract__factory.ts +203 -0
  327. package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract__factory.ts +203 -0
  328. package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmeticWIthOverflowBitTestsContract.sol/CheckedArithmeticWithOverflowBitTestsContract__factory.ts +1 -1
  329. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison128TestsContract__factory.ts +295 -0
  330. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison1TestsContract__factory.ts +1 -1
  331. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison256TestsContract__factory.ts +295 -0
  332. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison2TestsContract__factory.ts +1 -1
  333. package/typechain-types/factories/contracts/mocks/utils/mpc/MinMax128TestsContract__factory.ts +147 -0
  334. package/typechain-types/factories/contracts/mocks/utils/mpc/MinMax256TestsContract__factory.ts +147 -0
  335. package/typechain-types/factories/contracts/mocks/utils/mpc/MinMaxTestsContract__factory.ts +1 -1
  336. package/typechain-types/factories/contracts/mocks/utils/mpc/MinimalImplementation__factory.ts +263 -0
  337. package/typechain-types/factories/contracts/mocks/utils/mpc/MinimalProxy__factory.ts +104 -0
  338. package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous1TestsContract__factory.ts +1 -1
  339. package/typechain-types/factories/contracts/mocks/utils/mpc/MiscellaneousTestsContract__factory.ts +1 -1
  340. package/typechain-types/factories/contracts/mocks/utils/mpc/MpcOperations128TestContract__factory.ts +723 -0
  341. package/typechain-types/factories/contracts/mocks/utils/mpc/MpcOperationsTestContract__factory.ts +1410 -0
  342. package/typechain-types/factories/contracts/mocks/utils/mpc/Mux128TestsContract__factory.ts +115 -0
  343. package/typechain-types/factories/contracts/mocks/utils/mpc/Mux256TestsContract__factory.ts +115 -0
  344. package/typechain-types/factories/contracts/mocks/utils/mpc/OffboardToUserKeyTestContract__factory.ts +1 -1
  345. package/typechain-types/factories/contracts/mocks/utils/mpc/OnBoard128TestsContract__factory.ts +175 -0
  346. package/typechain-types/factories/contracts/mocks/utils/mpc/OnBoard256TestsContract__factory.ts +205 -0
  347. package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyImplementationV1__factory.ts +521 -0
  348. package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyImplementationV2__factory.ts +648 -0
  349. package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyProxy__factory.ts +179 -0
  350. package/typechain-types/factories/contracts/mocks/utils/mpc/Random128TestsContract__factory.ts +131 -0
  351. package/typechain-types/factories/contracts/mocks/utils/mpc/Random256TestsContract__factory.ts +131 -0
  352. package/typechain-types/factories/contracts/mocks/utils/mpc/Shift128TestsContract__factory.ts +147 -0
  353. package/typechain-types/factories/contracts/mocks/utils/mpc/Shift256TestsContract__factory.ts +147 -0
  354. package/typechain-types/factories/contracts/mocks/utils/mpc/ShiftTestsContract__factory.ts +1 -1
  355. package/typechain-types/factories/contracts/mocks/utils/mpc/StringTestsContract__factory.ts +1 -1
  356. package/typechain-types/factories/contracts/mocks/utils/mpc/Transfer128TestsContract__factory.ts +135 -0
  357. package/typechain-types/factories/contracts/mocks/utils/mpc/Transfer256TestsContract__factory.ts +135 -0
  358. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferScalarTestsContract__factory.ts +1 -1
  359. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferTestsContract__factory.ts +1 -1
  360. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract__factory.ts +154 -0
  361. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract__factory.ts +154 -0
  362. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_16TestsContract__factory.ts +1 -1
  363. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_32TestsContract__factory.ts +1 -1
  364. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_64TestsContract__factory.ts +1 -1
  365. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_8TestsContract__factory.ts +1 -1
  366. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowanceScalarTestsContract__factory.ts +1 -1
  367. package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowanceTestsContract__factory.ts +1 -1
  368. package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract__factory.ts +190 -0
  369. package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract__factory.ts +210 -0
  370. package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract__factory.ts +260 -0
  371. package/typechain-types/factories/contracts/mocks/utils/mpc/index.ts +32 -8
  372. package/typechain-types/factories/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock__factory.ts +10 -28
  373. package/typechain-types/factories/contracts/node/CotiNodeRewards__factory.ts +549 -0
  374. package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/IERC5192__factory.ts +54 -0
  375. package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/SoulboundNodeNFT__factory.ts +947 -0
  376. package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/index.ts +5 -0
  377. package/typechain-types/factories/contracts/node/index.ts +5 -0
  378. package/typechain-types/factories/contracts/onboard/AccountOnboard__factory.ts +1 -1
  379. package/typechain-types/factories/contracts/oracle/CotiPriceConsumer__factory.ts +295 -0
  380. package/typechain-types/factories/contracts/oracle/ICotiPriceConsumer__factory.ts +73 -0
  381. package/typechain-types/factories/contracts/oracle/IStdReference__factory.ts +107 -0
  382. package/typechain-types/factories/contracts/oracle/index.ts +6 -0
  383. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeCotiNative__factory.ts +1676 -0
  384. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/IHasDecimals__factory.ts +38 -0
  385. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/PrivacyBridgeERC20__factory.ts +1678 -0
  386. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/index.ts +5 -0
  387. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeUSDCe__factory.ts +1782 -0
  388. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeUSDT__factory.ts +1782 -0
  389. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWADA__factory.ts +1782 -0
  390. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWBTC__factory.ts +1782 -0
  391. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWETH__factory.ts +1782 -0
  392. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridge__factory.ts +1367 -0
  393. package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgegCoti__factory.ts +1782 -0
  394. package/typechain-types/factories/contracts/privacyBridge/index.ts +12 -0
  395. package/typechain-types/factories/contracts/token/PrivateERC20/IPrivateERC20__factory.ts +639 -61
  396. package/typechain-types/factories/contracts/token/PrivateERC20/ITokenReceiverEncrypted__factory.ts +53 -0
  397. package/typechain-types/factories/contracts/token/PrivateERC20/ITokenReceiver__factory.ts +54 -0
  398. package/typechain-types/factories/contracts/token/PrivateERC20/PrivateERC20__factory.ts +986 -120
  399. package/typechain-types/factories/contracts/token/PrivateERC20/index.ts +3 -0
  400. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC__factory.ts +1445 -0
  401. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken__factory.ts +1451 -0
  402. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateCOTI__factory.ts +1442 -0
  403. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateTetherUSD__factory.ts +1445 -0
  404. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedADA__factory.ts +1445 -0
  405. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC__factory.ts +1445 -0
  406. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedEther__factory.ts +1451 -0
  407. package/typechain-types/factories/contracts/token/PrivateERC20/tokens/index.ts +10 -0
  408. package/typechain-types/factories/contracts/utils/mpc/MpcCore__factory.ts +1 -1
  409. package/typechain-types/factories/contracts/utils/mpc/MpcInterface.sol/ExtendedOperations__factory.ts +126 -0
  410. package/typechain-types/hardhat.d.ts +1772 -278
  411. package/typechain-types/index.ts +182 -16
  412. package/contracts/mocks/utils/mpc/Arithmetic128BitTestsContract.sol +0 -260
  413. package/contracts/mocks/utils/mpc/Arithmetic256BitTestsContract.sol +0 -264
  414. package/contracts/mocks/utils/mpc/Bitwise128BitTestsContract.sol +0 -91
  415. package/contracts/mocks/utils/mpc/Bitwise256BitTestsContract.sol +0 -91
  416. package/contracts/mocks/utils/mpc/Comparison128BitTestsContract.sol +0 -158
  417. package/contracts/mocks/utils/mpc/Comparison256BitTestsContract.sol +0 -158
  418. package/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract.sol +0 -185
  419. package/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract.sol +0 -190
  420. package/contracts/mocks/utils/mpc/README.md +0 -486
  421. package/test/utils/mpc/Unsigned128BitIntegers.test.ts +0 -945
  422. package/test/utils/mpc/Unsigned256BitIntegers.test.ts +0 -1124
  423. package/test/utils/mpc/helpers.ts +0 -77
  424. package/typechain-types/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract.ts +0 -302
  425. package/typechain-types/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract.ts +0 -322
  426. package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic128BitTestsContract__factory.ts +0 -349
  427. package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic256BitTestsContract__factory.ts +0 -364
  428. package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise128BitTestsContract__factory.ts +0 -182
  429. package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise256BitTestsContract__factory.ts +0 -182
  430. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison128BitTestsContract__factory.ts +0 -255
  431. package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison256BitTestsContract__factory.ts +0 -255
  432. package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract__factory.ts +0 -332
  433. package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract__factory.ts +0 -380
@@ -4,18 +4,96 @@ pragma solidity ^0.8.19;
4
4
 
5
5
  import {Context} from "@openzeppelin/contracts/utils/Context.sol";
6
6
  import {IPrivateERC20} from "./IPrivateERC20.sol";
7
+ import {ITokenReceiver} from "./ITokenReceiver.sol";
8
+ import {ITokenReceiverEncrypted} from "./ITokenReceiverEncrypted.sol";
9
+ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
10
+ import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
11
+ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
7
12
  import "../../utils/mpc/MpcCore.sol";
8
13
 
9
- abstract contract PrivateERC20 is Context, IPrivateERC20 {
10
- uint64 private constant MAX_UINT_64 = type(uint64).max;
14
+ /*
15
+ THIS IS THE 256 BIT VERSION OF PRIVATE ERC20.
16
+
17
+ Key Features:
18
+ - Full uint256 support (no scaling factors)
19
+ - AccessControl (for Bridge Integrations)
20
+ - ERC165 Support - Tokens Discoverability
21
+ - Payable Tokens with TransferAndCall (ERC677-like callback pattern)
22
+ - Encrypted Operations (Mint, Burn, Transfer, Approve)
23
+
24
+ Failure semantics (integrators and contract callers):
25
+ - State-changing operations that move value or change supply/allowances are designed to follow a
26
+ revert-on-failure model: if the MPC layer reports that the core update did not succeed, the
27
+ transaction reverts (enforced in {_update} for mint/transfer/burn, and via explicit requires
28
+ where an operation composes multiple steps). Success is therefore implied by a completed call
29
+ that did not revert. Pure/view helpers and read paths that only return ciphertext or onboarded
30
+ values are not “success/failure” token operations in that sense.
31
+
32
+ Storage encoding ({_safeOnboard} — see implementation NatSpec):
33
+ - Uninitialized or zeroed `ctUint256` slots read as both 128-bit limbs zero. The implementation
34
+ treats that as **canonical empty** and maps it to `MpcCore.setPublic256(0)` without calling
35
+ `MpcCore.onBoard` (gas). Any other pattern is onboarded with `MpcCore.onBoard`.
36
+ - Successful updates in this contract only write ciphertext via `MpcCore.offBoard` after MPC
37
+ success, so balances, allowances, and aggregate supply stay consistent with the precompile.
38
+
39
+ Trust assumptions (deploy only when these hold):
40
+ - Deploy only on chains where the MPC precompile at address(0x64) is part of the trusted base.
41
+ You explicitly trust that network: its consensus, node/precompile implementation, and upgrade
42
+ policy. All balances and transfers are ultimately defined by MPC results this contract cannot
43
+ independently verify. If the precompile were malicious or buggy, accounting integrity could fail
44
+ in ways Solidity cannot fix on-chain. Optional mitigations (e.g. admin pause, monitoring) only
45
+ limit further damage after suspicion; they do not prove past MPC correctness or roll back state.
46
+ If the chain allows precompile upgrades, consider monitoring and circuit-breakers.
47
+ - There is **no** trust-minimized substitute on-chain: auditors and integrators should record which
48
+ chain ID, precompile address, and build/version they rely on, and treat MPC upgrades as **trusted**
49
+ migrations unless your organization runs independent off-chain verification against published specs.
50
+ - MINTER_ROLE must only pass valid amounts to mint/mintGt/mint(itUint256). If the MPC layer
51
+ enforces bounds or validity, that dependency applies.
52
+ - Minting is bounded by {supplyCap} (override in concrete tokens like {decimals}); enforced in {_update}.
53
+ - Integrators — {totalSupply} vs supply: the default {totalSupply} returns 0 and does **not** reflect
54
+ circulating aggregate supply (see {totalSupply} NatSpec). Do not plug this token into protocols that
55
+ assume ERC-20 `totalSupply` semantics. Use {supplyCap} only as the **mint ceiling** parameter; for
56
+ actual supply, use off-chain indexing or a dedicated extension (e.g. auditor reencryption mocks).
57
+ - Gas: multiple precompile calls per transfer/approve; no unbounded loops. Document expected
58
+ gas ranges for common operations if needed for integrators.
59
+ - Reentrancy: balance/allowance-changing entry points use nonReentrant so a transferAndCall
60
+ receiver cannot nest transferFrom/transfer/approve/increaseAllowance/decreaseAllowance/mint/burn in the same transaction.
61
+ - transferAndCall: public-amount overload uses {ITokenReceiver}; encrypted-amount overload uses
62
+ {ITokenReceiverEncrypted} (no plaintext amount in callback). Receivers are still fully trusted
63
+ for callback behavior.
64
+ */
65
+
66
+ /**
67
+ * @title PrivateERC20
68
+ * @notice Privacy-oriented ERC-20 base whose balances and transfer rules are enforced through MPC (`MpcCore`), not plain EVM arithmetic on cleartext balances.
69
+ * @dev **MPC trust model:** This contract treats the MPC precompile as the source of truth for
70
+ * garbled/encrypted `uint256` operations. There is no on-chain fallback that validates MPC
71
+ * soundness. See {IPrivateERC20} trust assumptions and the file-level comment block above for
72
+ * deployment and integration requirements. Pausing and `AccessControl` mitigate operational
73
+ * risk; they do not prove MPC correctness retroactively.
74
+ */
75
+ abstract contract PrivateERC20 is
76
+ Context,
77
+ ERC165,
78
+ IPrivateERC20,
79
+ AccessControl,
80
+ ReentrancyGuard
81
+ {
82
+ uint256 private constant MAX_UINT_256 = type(uint256).max;
83
+
84
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
85
+
86
+ /// @dev Controls whether public uint256 operations are allowed (mint/burn/transfer/transferFrom/approve/transferAndCall with clear values).
87
+ bool public publicAmountsEnabled;
11
88
 
12
89
  mapping(address account => address) private _accountEncryptionAddress;
13
90
 
14
- mapping(address account => utUint64) private _balances;
91
+ mapping(address account => utUint256) private _balances;
15
92
 
16
- mapping(address account => mapping(address spender => Allowance)) private _allowances;
93
+ mapping(address account => mapping(address spender => Allowance))
94
+ private _allowances;
17
95
 
18
- ctUint64 private _totalSupply;
96
+ ctUint256 private _totalSupply;
19
97
 
20
98
  string private _name;
21
99
 
@@ -45,15 +123,54 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
45
123
  */
46
124
  error ERC20InvalidSpender(address spender);
47
125
 
126
+ /**
127
+ * @dev Indicates that clear (public) uint256 operations are disabled for this token.
128
+ */
129
+ error PublicAmountsDisabled();
130
+
131
+ /**
132
+ * @dev Indicates that transferAndCall was used with a non-contract recipient.
133
+ * transferAndCall is for contract-to-contract flows; the recipient must have code.
134
+ */
135
+ error TransferAndCallRequiresContract(address to);
136
+
137
+ /**
138
+ * @dev Indicates that a transfer to self (from == to) was attempted.
139
+ * CRITICAL: Self-transfer is explicitly disallowed. The MPC precompile behavior when
140
+ * from == to is undefined; allowing it could lead to incorrect balance updates or
141
+ * inconsistent state. All transfer/transferFrom paths go through _transfer and are
142
+ * therefore protected.
143
+ */
144
+ error ERC20SelfTransferNotAllowed(address account);
145
+
146
+ /**
147
+ * @dev Indicates that name or symbol was empty in the constructor.
148
+ */
149
+ error ERC20InvalidMetadata();
150
+
151
+ /**
152
+ * @dev Emitted when the admin enables or disables public uint256 operations.
153
+ */
154
+ event PublicAmountsEnabledSet(bool enabled);
155
+
156
+ /**
157
+ * @dev Emitted when an account sets or changes its encryption address for balance reencryption.
158
+ */
159
+ event AccountEncryptionAddressSet(address indexed account, address indexed newAddress);
160
+
48
161
  /**
49
162
  * @dev Sets the values for {name} and {symbol}.
50
163
  *
51
- * All two of these values are immutable: they can only be set once during
164
+ * Both of these values are immutable: they can only be set once during
52
165
  * construction.
53
166
  */
54
167
  constructor(string memory name_, string memory symbol_) {
168
+ if (bytes(name_).length == 0) revert ERC20InvalidMetadata();
169
+ if (bytes(symbol_).length == 0) revert ERC20InvalidMetadata();
55
170
  _name = name_;
56
171
  _symbol = symbol_;
172
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
173
+ publicAmountsEnabled = true;
57
174
  }
58
175
 
59
176
  /**
@@ -85,49 +202,219 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
85
202
  * {IPrivateERC20-balanceOf} and {IPrivateERC20-transfer}.
86
203
  */
87
204
  function decimals() public view virtual returns (uint8) {
88
- return 6;
205
+ return 18;
89
206
  }
90
207
 
91
208
  /**
92
- * @dev See {IPrivateERC20-totalSupply}.
209
+ * @dev Configurable **upper bound** on how much can be minted in aggregate (checked in {_update} against
210
+ * the encrypted total in storage). This is **not** “current circulating supply” and **not** a live
211
+ * substitute for {totalSupply} on a normal ERC-20. Default: `type(uint256).max`.
212
+ *
213
+ * Override in concrete tokens when a fixed cap is required (e.g. with {decimals} or tokenomics).
93
214
  */
94
- function totalSupply() public view virtual returns (uint256) {
215
+ function supplyCap() public view virtual returns (uint256) {
216
+ return type(uint256).max;
217
+ }
218
+
219
+ /**
220
+ * @inheritdoc IPrivateERC20
221
+ * @notice Deliberately **not** standard ERC-20 circulating supply: returns `0` in this implementation.
222
+ *
223
+ * Do **not** use for vault share math, pro-rata rewards, or oracles that expect `totalSupply` to reflect
224
+ * aggregate issuance. For the mint **ceiling**, use {supplyCap}. For optional encrypted aggregate supply
225
+ * to a designated party, see examples under `contracts/mocks/token/PrivateERC20/`.
226
+ */
227
+ function totalSupply() public view virtual override returns (uint256) {
95
228
  return 0;
96
229
  }
97
230
 
98
- function accountEncryptionAddress(address account) public view returns (address) {
231
+ function mint(
232
+ address to,
233
+ uint256 amount
234
+ ) public virtual override onlyRole(MINTER_ROLE) nonReentrant {
235
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
236
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
237
+ gtUint256 gtAmount = MpcCore.setPublic256(amount);
238
+ _mint(to, gtAmount);
239
+ }
240
+
241
+ /**
242
+ * @dev Mint an already-garbled amount without re-wrapping.
243
+ * Intended for contract-to-contract flows that already hold a gtUint256.
244
+ * Trust: MINTER_ROLE must only pass valid amounts. Reverts if {_update} reports MPC failure.
245
+ */
246
+ function mintGt(
247
+ address to,
248
+ gtUint256 gtAmount
249
+ ) public virtual override onlyRole(MINTER_ROLE) nonReentrant {
250
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
251
+ _mint(to, gtAmount);
252
+ }
253
+
254
+ /**
255
+ * @dev Mint with encrypted (itUint256) amount.
256
+ * Trust: MINTER_ROLE must only pass valid amounts. Reverts if {_update} reports MPC failure.
257
+ */
258
+ function mint(
259
+ address to,
260
+ itUint256 calldata amount
261
+ ) public virtual override onlyRole(MINTER_ROLE) nonReentrant {
262
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
263
+ gtUint256 gtAmount = MpcCore.validateCiphertext(amount);
264
+ _mint(to, gtAmount);
265
+ }
266
+
267
+ function burn(uint256 amount) public virtual override nonReentrant {
268
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
269
+ gtUint256 gtAmount = MpcCore.setPublic256(amount);
270
+ _burn(_msgSender(), gtAmount);
271
+ }
272
+
273
+ /**
274
+ * @dev Burn an already-garbled amount without re-wrapping.
275
+ * Intended for contract-to-contract flows that already hold a gtUint256.
276
+ * Reverts if {_update} reports MPC failure.
277
+ */
278
+ function burnGt(gtUint256 gtAmount) public virtual override nonReentrant {
279
+ _burn(_msgSender(), gtAmount);
280
+ }
281
+
282
+ /// @dev Reverts if {_update} reports MPC failure.
283
+ function burn(itUint256 calldata amount) public virtual override nonReentrant {
284
+ gtUint256 gtAmount = MpcCore.validateCiphertext(amount);
285
+ _burn(_msgSender(), gtAmount);
286
+ }
287
+
288
+ /**
289
+ * @dev Transfers tokens to `to` then calls onTokenReceived(to, amount, data).
290
+ * Only use with trusted receivers. `nonReentrant` blocks re-entry into this function and
291
+ * into other guarded entry points (transfer, transferFrom, approve, burn, mint, etc.);
292
+ * the receiver must still be trusted for protocol correctness.
293
+ */
294
+ function transferAndCall(
295
+ address to,
296
+ uint256 amount,
297
+ bytes calldata data
298
+ ) public virtual override nonReentrant {
299
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
300
+ if (to.code.length == 0) revert TransferAndCallRequiresContract(to);
301
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
302
+
303
+ gtUint256 gtAmount = MpcCore.setPublic256(amount);
304
+ address sender = _msgSender();
305
+ _transfer(sender, to, gtAmount);
306
+
307
+ require(
308
+ ITokenReceiver(to).onTokenReceived(sender, amount, data),
309
+ "Callback failed"
310
+ );
311
+ }
312
+
313
+ /**
314
+ * @dev Transfers an encrypted amount to `to`, then calls {ITokenReceiverEncrypted-onPrivateTransferReceived}.
315
+ * The callback does **not** receive a plaintext amount (privacy). Receivers must implement
316
+ * {ITokenReceiverEncrypted}; do not use {ITokenReceiver} for this overload.
317
+ * Only use with **trusted** receivers; see {transferAndCall(address,uint256,bytes)} for reentrancy scope.
318
+ */
319
+ function transferAndCall(
320
+ address to,
321
+ itUint256 calldata amount,
322
+ bytes calldata data
323
+ ) public virtual override nonReentrant {
324
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
325
+ if (to.code.length == 0) revert TransferAndCallRequiresContract(to);
326
+
327
+ gtUint256 gtAmount = MpcCore.validateCiphertext(amount);
328
+ address sender = _msgSender();
329
+ _transfer(sender, to, gtAmount);
330
+
331
+ require(
332
+ ITokenReceiverEncrypted(to).onPrivateTransferReceived(sender, data),
333
+ "Callback failed"
334
+ );
335
+ }
336
+
337
+ function supportsInterface(
338
+ bytes4 interfaceId
339
+ ) public view virtual override(AccessControl, ERC165) returns (bool) {
340
+ return
341
+ interfaceId == type(IPrivateERC20).interfaceId ||
342
+ super.supportsInterface(interfaceId);
343
+ }
344
+
345
+ /**
346
+ * @dev Returns the encryption address set for `account` for balance reencryption.
347
+ *
348
+ * Requirements:
349
+ * - `account` must not be the zero address.
350
+ */
351
+ function accountEncryptionAddress(
352
+ address account
353
+ ) public view returns (address) {
354
+ if (account == address(0)) revert ERC20InvalidReceiver(address(0));
99
355
  return _accountEncryptionAddress[account];
100
- }
356
+ }
101
357
 
102
358
  /**
103
359
  * @dev See {IPrivateERC20-balanceOf}.
360
+ *
361
+ * Requirements:
362
+ * - `account` must not be the zero address.
104
363
  */
105
- function balanceOf(address account) public view virtual returns (ctUint64) {
364
+ function balanceOf(
365
+ address account
366
+ ) public view virtual override returns (ctUint256 memory) {
367
+ if (account == address(0)) revert ERC20InvalidReceiver(address(0));
106
368
  return _balances[account].userCiphertext;
107
369
  }
108
370
 
109
371
  /**
110
372
  * @dev See {IPrivateERC20-balanceOf}.
373
+ * May perform external calls to the MPC precompile via _getBalance.
374
+ * Do not use in staticcall or view contexts; off-chain code must not assume this is view-safe.
111
375
  */
112
- function balanceOf() public virtual returns (gtUint64) {
376
+ function balanceOf() public virtual override returns (gtUint256) {
113
377
  return _getBalance(_msgSender());
114
378
  }
115
379
 
116
380
  /**
117
381
  * @dev See {IPrivateERC20-setAccountEncryptionAddress}.
118
- *
382
+ *
119
383
  * NOTE: This will not reencrypt your allowances until they are changed
120
384
  */
121
- function setAccountEncryptionAddress(address offBoardAddress) public virtual returns (bool) {
122
- gtUint64 gtBalance = _getBalance(_msgSender());
385
+ function setAccountEncryptionAddress(
386
+ address offBoardAddress
387
+ ) public virtual override nonReentrant returns (bool) {
388
+ if (offBoardAddress == address(0)) revert ERC20InvalidReceiver(address(0));
389
+
390
+ gtUint256 gtBalance = _getBalance(_msgSender());
391
+
392
+ // Compute new user ciphertext first; reverts if precompile fails. Only then update storage
393
+ // so that we never leave _accountEncryptionAddress and userCiphertext out of sync.
394
+ ctUint256 memory newUserCiphertext = MpcCore.offBoardToUser(
395
+ gtBalance,
396
+ offBoardAddress
397
+ );
123
398
 
124
- _accountEncryptionAddress[_msgSender()] = offBoardAddress;
399
+ address account = _msgSender();
400
+ _accountEncryptionAddress[account] = offBoardAddress;
401
+ _balances[account].userCiphertext = newUserCiphertext;
125
402
 
126
- _balances[_msgSender()].userCiphertext = MpcCore.offBoardToUser(gtBalance, offBoardAddress);
403
+ emit AccountEncryptionAddressSet(account, offBoardAddress);
127
404
 
128
405
  return true;
129
406
  }
130
-
407
+
408
+ /**
409
+ * @dev Enables or disables operations that use clear public uint256 amounts
410
+ * (mint, burn, transfer, transferFrom, approve, transferAndCall with uint256).
411
+ * Intended to be called by the token admin to disallow public value usage if desired.
412
+ */
413
+ function setPublicAmountsEnabled(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) {
414
+ publicAmountsEnabled = enabled;
415
+ emit PublicAmountsEnabledSet(enabled);
416
+ }
417
+
131
418
  /**
132
419
  * @dev See {IPrivateERC20-transfer}.
133
420
  *
@@ -135,105 +422,252 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
135
422
  *
136
423
  * - `to` cannot be the zero address.
137
424
  * - the caller must have a balance of at least `value`.
425
+ *
426
+ * Reverts if {_update} reports MPC failure. Success is implied by absence of revert.
138
427
  */
139
- function transfer(address to, itUint64 calldata value) public virtual returns (gtBool) {
428
+ /// @notice Transfer with encrypted (itUint256) amount
429
+ function transfer(
430
+ address to,
431
+ itUint256 calldata value
432
+ ) public virtual override nonReentrant {
433
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
140
434
  address owner = _msgSender();
141
435
 
142
- gtUint64 gtValue = MpcCore.validateCiphertext(value);
436
+ gtUint256 gtValue = MpcCore.validateCiphertext(value);
143
437
 
144
- return _transfer(owner, to, gtValue);
438
+ _transfer(owner, to, gtValue);
145
439
  }
146
440
 
147
- /**
148
- * @dev See {IPrivateERC20-transfer}.
149
- *
150
- * Requirements:
151
- *
152
- * - `to` cannot be the zero address.
153
- * - the caller must have a balance of at least `value`.
154
- */
155
- function transfer(address to, gtUint64 value) public virtual returns (gtBool) {
441
+ /// @notice Transfer with garbled-text (gtUint256) amount. Reverts if {_update} reports MPC failure.
442
+ function transferGT(
443
+ address to,
444
+ gtUint256 value
445
+ ) public virtual override nonReentrant {
446
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
447
+ address owner = _msgSender();
448
+
449
+ _transfer(owner, to, value);
450
+ }
451
+
452
+ /// @notice Transfer with plain public uint256 amount
453
+ function transfer(
454
+ address to,
455
+ uint256 value
456
+ ) public virtual override nonReentrant {
457
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
458
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
156
459
  address owner = _msgSender();
157
460
 
158
- return _transfer(owner, to, value);
461
+ gtUint256 gtValue = MpcCore.setPublic256(value);
462
+
463
+ _transfer(owner, to, gtValue);
159
464
  }
160
465
 
161
466
  /**
162
467
  * @dev See {IPrivateERC20-allowance}.
468
+ *
469
+ * Requirements:
470
+ * - `owner` and `spender` must not be the zero address.
163
471
  */
164
- function allowance(address owner, address spender) public view virtual returns (Allowance memory) {
472
+ function allowance(
473
+ address owner,
474
+ address spender
475
+ ) public view virtual override returns (Allowance memory) {
476
+ if (owner == address(0)) revert ERC20InvalidApprover(address(0));
477
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
165
478
  return _allowances[owner][spender];
166
479
  }
167
480
 
168
481
  /**
169
482
  * @dev See {IPrivateERC20-allowance}.
483
+ * May perform external calls to the MPC precompile via _safeOnboard.
484
+ * Do not use in staticcall or view contexts; off-chain code must not assume this is view-safe.
485
+ *
486
+ * Requirements:
487
+ * - `account` must not be the zero address.
170
488
  */
171
- function allowance(address account, bool isSpender) public virtual returns (gtUint64) {
489
+ function allowance(
490
+ address account,
491
+ bool isSpender
492
+ ) public virtual override returns (gtUint256) {
493
+ if (account == address(0)) revert ERC20InvalidReceiver(address(0));
172
494
  if (isSpender) {
173
- return _safeOnboard(_allowances[_msgSender()][account].ciphertext);
174
- } else {
495
+ // Caller is spender; `account` is owner — read _allowances[owner][spender]
175
496
  return _safeOnboard(_allowances[account][_msgSender()].ciphertext);
497
+ } else {
498
+ // Caller is owner; `account` is spender — read _allowances[owner][spender]
499
+ return _safeOnboard(_allowances[_msgSender()][account].ciphertext);
176
500
  }
177
501
  }
178
502
 
179
- function reencryptAllowance(address account, bool isSpender) public virtual returns (bool) {
503
+ /**
504
+ * @dev Reencrypts the caller's view of an allowance (as owner or spender) using the caller's encryption address.
505
+ *
506
+ * Requirements:
507
+ * - `account` must not be the zero address.
508
+ * - Caller must have an encryption address set (EOA or contract with setAccountEncryptionAddress).
509
+ *
510
+ * Emits an {AllowanceReencrypted} event.
511
+ */
512
+ function reencryptAllowance(
513
+ address account,
514
+ bool isSpender
515
+ ) public virtual nonReentrant {
516
+ if (account == address(0)) revert ERC20InvalidReceiver(address(0));
180
517
  address encryptionAddress = _getAccountEncryptionAddress(_msgSender());
518
+ if (encryptionAddress == address(0)) revert ERC20InvalidReceiver(address(0));
519
+
520
+ address owner_;
521
+ address spender_;
181
522
 
182
523
  if (isSpender) {
183
- Allowance storage allowance_ = _allowances[_msgSender()][account];
524
+ // Caller is spender; `account` is owner — _allowances[owner][spender]
525
+ owner_ = account;
526
+ spender_ = _msgSender();
527
+ Allowance storage allowance_ = _allowances[owner_][spender_];
184
528
 
185
- allowance_.ownerCiphertext = MpcCore.offBoardToUser(
529
+ allowance_.spenderCiphertext = MpcCore.offBoardToUser(
186
530
  _safeOnboard(allowance_.ciphertext),
187
531
  encryptionAddress
188
532
  );
189
533
  } else {
190
- Allowance storage allowance_ = _allowances[account][_msgSender()];
534
+ // Caller is owner; `account` is spender — _allowances[owner][spender]
535
+ owner_ = _msgSender();
536
+ spender_ = account;
537
+ Allowance storage allowance_ = _allowances[owner_][spender_];
191
538
 
192
- allowance_.spenderCiphertext = MpcCore.offBoardToUser(
539
+ allowance_.ownerCiphertext = MpcCore.offBoardToUser(
193
540
  _safeOnboard(allowance_.ciphertext),
194
541
  encryptionAddress
195
542
  );
196
543
  }
197
544
 
198
- return true;
545
+ emit AllowanceReencrypted(owner_, spender_, isSpender);
199
546
  }
200
547
 
201
548
  /**
202
549
  * @dev See {IPrivateERC20-approve}.
203
550
  *
204
- * NOTE: If `value` is the maximum `itUint64`, the allowance is not updated on
551
+ * NOTE: If `value` is the maximum `itUint256`, the allowance is not updated on
205
552
  * `transferFrom`. This is semantically equivalent to an infinite approval.
206
553
  *
554
+ * Reverts with {ERC20UnsafeApprove} if both the current allowance and the new value are
555
+ * non-zero (same mitigation as {approve(address,uint256)}). Prefer {increaseAllowance} or
556
+ * {decreaseAllowance} to change a non-zero allowance without a two-step reset.
557
+ *
207
558
  * Requirements:
208
559
  *
209
560
  * - `spender` cannot be the zero address.
210
561
  */
211
- function approve(address spender, itUint64 calldata value) public virtual returns (bool) {
562
+ /// @notice Approve with encrypted (itUint256) amount
563
+ function approve(
564
+ address spender,
565
+ itUint256 calldata value
566
+ ) public virtual override nonReentrant {
567
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
212
568
  address owner = _msgSender();
213
569
 
214
- gtUint64 gtValue = MpcCore.validateCiphertext(value);
570
+ gtUint256 gtValue = MpcCore.validateCiphertext(value);
571
+
572
+ _requireSafeEncryptedApprove(owner, spender, gtValue);
215
573
 
216
574
  _approve(owner, spender, gtValue);
217
575
 
218
- return true;
219
576
  }
220
577
 
221
- /**
222
- * @dev See {IPrivateERC20-approve}.
223
- *
224
- * NOTE: If `value` is the maximum `gtUint64`, the allowance is not updated on
225
- * `transferFrom`. This is semantically equivalent to an infinite approval.
226
- *
227
- * Requirements:
228
- *
229
- * - `spender` cannot be the zero address.
230
- */
231
- function approve(address spender, gtUint64 value) public virtual returns (bool) {
578
+ /// @notice Approve with garbled-text (gtUint256) amount
579
+ function approveGT(
580
+ address spender,
581
+ gtUint256 value
582
+ ) public virtual override nonReentrant {
583
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
232
584
  address owner = _msgSender();
233
585
 
586
+ _requireSafeEncryptedApprove(owner, spender, value);
587
+
234
588
  _approve(owner, spender, value);
235
589
 
236
- return true;
590
+ }
591
+
592
+ /// @notice Approve with plain public uint256 amount
593
+ function approve(
594
+ address spender,
595
+ uint256 value
596
+ ) public virtual override nonReentrant {
597
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
598
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
599
+ address owner = _msgSender();
600
+
601
+ if (value != 0) {
602
+ gtUint256 currentAllowance = _safeOnboard(
603
+ _allowances[owner][spender].ciphertext
604
+ );
605
+ if (!MpcCore.decrypt(MpcCore.eq(currentAllowance, uint256(0)))) {
606
+ revert ERC20UnsafeApprove();
607
+ }
608
+ }
609
+
610
+ gtUint256 gtValue = MpcCore.setPublic256(value);
611
+
612
+ _approve(owner, spender, gtValue);
613
+ }
614
+
615
+ /// @inheritdoc IPrivateERC20
616
+ function increaseAllowance(
617
+ address spender,
618
+ itUint256 calldata addedValue
619
+ ) public virtual override nonReentrant {
620
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
621
+ gtUint256 gtAdded = MpcCore.validateCiphertext(addedValue);
622
+ _increaseAllowance(_msgSender(), spender, gtAdded);
623
+ }
624
+
625
+ /// @inheritdoc IPrivateERC20
626
+ function increaseAllowance(
627
+ address spender,
628
+ uint256 addedValue
629
+ ) public virtual override nonReentrant {
630
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
631
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
632
+ _increaseAllowance(_msgSender(), spender, MpcCore.setPublic256(addedValue));
633
+ }
634
+
635
+ /// @inheritdoc IPrivateERC20
636
+ function increaseAllowanceGT(
637
+ address spender,
638
+ gtUint256 addedValue
639
+ ) public virtual override nonReentrant {
640
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
641
+ _increaseAllowance(_msgSender(), spender, addedValue);
642
+ }
643
+
644
+ /// @inheritdoc IPrivateERC20
645
+ function decreaseAllowance(
646
+ address spender,
647
+ itUint256 calldata subtractedValue
648
+ ) public virtual override nonReentrant {
649
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
650
+ gtUint256 gtSub = MpcCore.validateCiphertext(subtractedValue);
651
+ _decreaseAllowance(_msgSender(), spender, gtSub);
652
+ }
653
+
654
+ /// @inheritdoc IPrivateERC20
655
+ function decreaseAllowance(
656
+ address spender,
657
+ uint256 subtractedValue
658
+ ) public virtual override nonReentrant {
659
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
660
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
661
+ _decreaseAllowance(_msgSender(), spender, MpcCore.setPublic256(subtractedValue));
662
+ }
663
+
664
+ /// @inheritdoc IPrivateERC20
665
+ function decreaseAllowanceGT(
666
+ address spender,
667
+ gtUint256 subtractedValue
668
+ ) public virtual override nonReentrant {
669
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
670
+ _decreaseAllowance(_msgSender(), spender, subtractedValue);
237
671
  }
238
672
 
239
673
  /**
@@ -245,46 +679,106 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
245
679
  * - `from` must have a balance of at least `value`.
246
680
  * - the caller must have allowance for ``from``'s tokens of at least
247
681
  * `value`.
682
+ *
683
+ * Order: (1) check allowance and revert if insufficient, (2) deduct via {_spendAllowance}
684
+ * (reusing the onboarded allowance from step 1), (3) _transfer.
248
685
  */
249
- function transferFrom(address from, address to, itUint64 calldata value) public virtual returns (gtBool) {
686
+ /// @notice transferFrom with encrypted (itUint256) amount
687
+ function transferFrom(
688
+ address from,
689
+ address to,
690
+ itUint256 calldata value
691
+ ) public virtual override nonReentrant {
692
+ if (from == address(0)) revert ERC20InvalidSender(address(0));
693
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
250
694
  address spender = _msgSender();
251
695
 
252
- gtUint64 gtValue = MpcCore.validateCiphertext(value);
696
+ gtUint256 gtValue = MpcCore.validateCiphertext(value);
697
+
698
+ gtUint256 currentAllowance = _safeOnboard(_allowances[from][spender].ciphertext);
699
+ gtBool maxAllowance = _isMaxAllowance(currentAllowance);
700
+ gtBool inSufficientAllowance = MpcCore.lt(currentAllowance, gtValue);
701
+ require(
702
+ MpcCore.decrypt(MpcCore.or(maxAllowance, MpcCore.not(inSufficientAllowance))),
703
+ "ERC20: insufficient allowance"
704
+ );
705
+ bool unlimitedAllowance = MpcCore.decrypt(maxAllowance);
253
706
 
254
- _spendAllowance(from, spender, gtValue);
707
+ _spendAllowance(from, spender, gtValue, currentAllowance, unlimitedAllowance);
255
708
 
256
- return _transfer(from, to, gtValue);
709
+ _transfer(from, to, gtValue);
257
710
  }
258
-
259
- /**
260
- * @dev See {IPrivateERC20-transferFrom}.
261
- *
262
- * Requirements:
263
- *
264
- * - `from` and `to` cannot be the zero address.
265
- * - `from` must have a balance of at least `value`.
266
- * - the caller must have allowance for ``from``'s tokens of at least
267
- * `value`.
268
- */
269
- function transferFrom(address from, address to, gtUint64 value) public virtual returns (gtBool) {
711
+
712
+ /// @notice transferFrom with garbled-text (gtUint256) amount
713
+ function transferFromGT(
714
+ address from,
715
+ address to,
716
+ gtUint256 value
717
+ ) public virtual override nonReentrant {
718
+ if (from == address(0)) revert ERC20InvalidSender(address(0));
719
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
270
720
  address spender = _msgSender();
271
721
 
272
- _spendAllowance(from, spender, value);
722
+ gtUint256 currentAllowance = _safeOnboard(_allowances[from][spender].ciphertext);
723
+ gtBool maxAllowance = _isMaxAllowance(currentAllowance);
724
+ gtBool inSufficientAllowance = MpcCore.lt(currentAllowance, value);
725
+ require(
726
+ MpcCore.decrypt(MpcCore.or(maxAllowance, MpcCore.not(inSufficientAllowance))),
727
+ "ERC20: insufficient allowance"
728
+ );
729
+ bool unlimitedAllowance = MpcCore.decrypt(maxAllowance);
730
+
731
+ _spendAllowance(from, spender, value, currentAllowance, unlimitedAllowance);
273
732
 
274
- return _transfer(from, to, value);
733
+ _transfer(from, to, value);
275
734
  }
276
-
735
+
736
+
737
+ /// @notice transferFrom with plain public uint256 amount
738
+ function transferFrom(
739
+ address from,
740
+ address to,
741
+ uint256 value
742
+ ) public virtual override nonReentrant {
743
+ if (!publicAmountsEnabled) revert PublicAmountsDisabled();
744
+ if (from == address(0)) revert ERC20InvalidSender(address(0));
745
+ if (to == address(0)) revert ERC20InvalidReceiver(address(0));
746
+ address spender = _msgSender();
747
+
748
+ gtUint256 gtValue = MpcCore.setPublic256(value);
749
+
750
+ gtUint256 currentAllowance = _safeOnboard(_allowances[from][spender].ciphertext);
751
+ gtBool maxAllowance = _isMaxAllowance(currentAllowance);
752
+ gtBool inSufficientAllowance = MpcCore.lt(currentAllowance, gtValue);
753
+ require(
754
+ MpcCore.decrypt(MpcCore.or(maxAllowance, MpcCore.not(inSufficientAllowance))),
755
+ "ERC20: insufficient allowance"
756
+ );
757
+ bool unlimitedAllowance = MpcCore.decrypt(maxAllowance);
758
+
759
+ _spendAllowance(from, spender, gtValue, currentAllowance, unlimitedAllowance);
760
+
761
+ _transfer(from, to, gtValue);
762
+ }
763
+
277
764
  /**
278
765
  * @dev Moves a `value` amount of tokens from `from` to `to`.
279
766
  *
280
767
  * This internal function is equivalent to {transfer}, and can be used to
281
768
  * e.g. implement automatic token fees, slashing mechanisms, etc.
282
769
  *
283
- * Emits a {Transfer} event.
770
+ * Self-transfer (from == to) is not allowed and reverts.
771
+ *
772
+ * On success, emits a {Transfer} event via {_update}; on MPC transfer failure, {_update} reverts
773
+ * and no event is emitted.
284
774
  *
285
775
  * NOTE: This function is not virtual, {_update} should be overridden instead.
286
776
  */
287
- function _transfer(address from, address to, gtUint64 value) internal returns (gtBool) {
777
+ function _transfer(
778
+ address from,
779
+ address to,
780
+ gtUint256 value
781
+ ) internal {
288
782
  if (from == address(0)) {
289
783
  revert ERC20InvalidSender(address(0));
290
784
  }
@@ -293,86 +787,172 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
293
787
  revert ERC20InvalidReceiver(address(0));
294
788
  }
295
789
 
296
- return _update(from, to, value);
790
+ if (from == to) {
791
+ revert ERC20SelfTransferNotAllowed(from);
792
+ }
793
+
794
+ _update(from, to, value);
297
795
  }
298
-
796
+
299
797
  /**
300
798
  * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
301
799
  * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
302
800
  * this function.
303
801
  *
304
- * Emits a {Transfer} event.
802
+ * Storage and {Transfer} are updated only when the MPC operation succeeds (transfer success bit, or mint
803
+ * add without overflow and within {supplyCap}). On failure, balances and aggregate supply are unchanged,
804
+ * no event is emitted, and this function reverts so the failed operation does not succeed silently.
305
805
  */
306
- function _update(address from, address to, gtUint64 value) internal virtual returns (gtBool) {
307
- gtUint64 newToBalance;
308
- gtUint64 valueTransferred = value;
309
- gtBool result = MpcCore.setPublic(true);
806
+ function _update(
807
+ address from,
808
+ address to,
809
+ gtUint256 value
810
+ ) internal virtual {
811
+ gtUint256 newToBalance;
812
+ gtUint256 valueTransferred = value;
813
+ gtBool result;
814
+ bool ok;
310
815
 
311
816
  if (from == address(0)) {
312
- gtUint64 totalSupply_ = _safeOnboard(_totalSupply);
313
-
314
- totalSupply_ = MpcCore.add(totalSupply_, value);
817
+ gtUint256 currentBalance = _getBalance(to);
818
+ gtBool balanceOverflow;
819
+ (balanceOverflow, newToBalance) = MpcCore.checkedAddWithOverflowBit(
820
+ currentBalance,
821
+ value
822
+ );
823
+ gtBool balanceOk = MpcCore.not(balanceOverflow);
315
824
 
316
- _totalSupply = MpcCore.offBoard(totalSupply_);
825
+ gtUint256 totalSupply_ = _safeOnboard(_totalSupply);
826
+ (gtBool supplyOverflow, gtUint256 newTotalSupply) = MpcCore.checkedAddWithOverflowBit(
827
+ totalSupply_,
828
+ value
829
+ );
830
+ gtBool supplyOk = MpcCore.not(supplyOverflow);
831
+ gtBool withinCap = MpcCore.le(newTotalSupply, supplyCap());
317
832
 
318
- gtUint64 currentBalance = _getBalance(to);
833
+ result = MpcCore.and(balanceOk, MpcCore.and(supplyOk, withinCap));
834
+ ok = MpcCore.decrypt(result);
835
+ if (ok) {
836
+ _totalSupply = MpcCore.offBoard(newTotalSupply);
319
837
 
320
- newToBalance = MpcCore.add(currentBalance, value);
838
+ _updateBalance(to, newToBalance);
839
+ }
321
840
  } else {
322
- gtUint64 fromBalance = _getBalance(from);
323
- gtUint64 toBalance = _getBalance(to);
324
-
325
- gtUint64 newFromBalance;
841
+ gtUint256 fromBalance = _getBalance(from);
842
+ // Burn (`to == address(0)`): use canonical public zero as the sink balance. Do not read
843
+ // `_balances[address(0)]` — that slot must never affect burn accounting if it were ever corrupted.
844
+ gtUint256 toBalance = to == address(0)
845
+ ? MpcCore.setPublic256(0)
846
+ : _getBalance(to);
847
+
848
+ gtUint256 newFromBalance;
849
+
850
+ (newFromBalance, newToBalance, result) = MpcCore.transfer(
851
+ fromBalance,
852
+ toBalance,
853
+ value
854
+ );
326
855
 
327
- (newFromBalance, newToBalance, result) = MpcCore.transfer(fromBalance, toBalance, value);
856
+ ok = MpcCore.decrypt(result);
857
+ if (ok) {
858
+ _updateBalance(from, newFromBalance);
328
859
 
329
- _updateBalance(from, newFromBalance);
860
+ valueTransferred = MpcCore.sub(newToBalance, toBalance);
330
861
 
331
- valueTransferred = MpcCore.sub(newToBalance, toBalance);
332
- }
862
+ if (to == address(0)) {
863
+ gtUint256 totalSupply_ = _safeOnboard(_totalSupply);
333
864
 
334
- if (to == address(0)) {
335
- gtUint64 totalSupply_ = _safeOnboard(_totalSupply);
865
+ totalSupply_ = MpcCore.sub(totalSupply_, valueTransferred);
336
866
 
337
- totalSupply_ = MpcCore.sub(totalSupply_, valueTransferred);
867
+ _totalSupply = MpcCore.offBoard(totalSupply_);
868
+ } else {
869
+ _updateBalance(to, newToBalance);
870
+ }
871
+ }
872
+ }
338
873
 
339
- _totalSupply = MpcCore.offBoard(totalSupply_);
340
- } else {
341
- _updateBalance(to, newToBalance);
874
+ if (ok) {
875
+ // When minting or transferring to/from a smart contract (which has no AES key),
876
+ // we must bypass offBoardToUser to prevent on-chain reverts.
877
+ ctUint256 memory senderCt;
878
+ address fromEnc = _getAccountEncryptionAddress(from);
879
+ if (fromEnc != address(0)) {
880
+ senderCt = MpcCore.offBoardToUser(valueTransferred, fromEnc);
881
+ } else {
882
+ senderCt = ctUint256({
883
+ ciphertextHigh: ctUint128.wrap(0),
884
+ ciphertextLow: ctUint128.wrap(0)
885
+ });
886
+ }
887
+
888
+ ctUint256 memory receiverCt;
889
+ address toEnc = _getAccountEncryptionAddress(to);
890
+ if (toEnc != address(0)) {
891
+ receiverCt = MpcCore.offBoardToUser(valueTransferred, toEnc);
892
+ } else {
893
+ receiverCt = ctUint256({
894
+ ciphertextHigh: ctUint128.wrap(0),
895
+ ciphertextLow: ctUint128.wrap(0)
896
+ });
897
+ }
898
+
899
+ emit Transfer(from, to, senderCt, receiverCt);
342
900
  }
343
-
344
- emit Transfer(
345
- from,
346
- to,
347
- MpcCore.offBoardToUser(valueTransferred, from),
348
- MpcCore.offBoardToUser(valueTransferred, to)
349
- );
901
+ require(MpcCore.decrypt(result), "ERC20: update failed");
902
+ }
350
903
 
351
- return result;
904
+ /**
905
+ * @dev Aggregate supply as a garbled value (on-boarded from storage ciphertext).
906
+ * Intended for subclasses that expose encrypted total supply to a designated party
907
+ * (e.g. via `MpcCore.offBoardToUser`) without putting plaintext aggregate supply in {totalSupply}.
908
+ */
909
+ function _getTotalSupplyGarbled() internal returns (gtUint256) {
910
+ return _safeOnboard(_totalSupply);
352
911
  }
353
912
 
354
- function _getBalance(address account) internal returns (gtUint64) {
355
- ctUint64 ctBalance = _balances[account].ciphertext;
913
+ function _getBalance(address account) internal returns (gtUint256) {
914
+ ctUint256 memory ctBalance = _balances[account].ciphertext;
356
915
 
357
916
  return _safeOnboard(ctBalance);
358
917
  }
359
918
 
360
- function _getAccountEncryptionAddress(address account) internal view returns (address) {
919
+ function _getAccountEncryptionAddress(
920
+ address account
921
+ ) internal view returns (address) {
922
+ if (account == address(0)) return address(0);
923
+
361
924
  address encryptionAddress = _accountEncryptionAddress[account];
362
925
 
363
926
  if (encryptionAddress == address(0)) {
927
+ if (account.code.length > 0) {
928
+ // Smart contracts don't have AES keys, so we return address(0)
929
+ // as a signal to bypass encryption in offBoardToUser.
930
+ return address(0);
931
+ }
364
932
  encryptionAddress = account;
365
933
  }
366
934
 
367
935
  return encryptionAddress;
368
936
  }
369
937
 
370
- function _updateBalance(address account, gtUint64 balance) internal {
938
+ function _updateBalance(address account, gtUint256 balance) internal {
371
939
  address encryptionAddress = _getAccountEncryptionAddress(account);
372
940
 
373
- _balances[account] = MpcCore.offBoardCombined(balance, encryptionAddress);
941
+ if (encryptionAddress == address(0)) {
942
+ // Contract accounts have no AES key; store ciphertext only, no user reencryption.
943
+ _balances[account].ciphertext = MpcCore.offBoard(balance);
944
+ _balances[account].userCiphertext = ctUint256({
945
+ ciphertextHigh: ctUint128.wrap(0),
946
+ ciphertextLow: ctUint128.wrap(0)
947
+ });
948
+ } else {
949
+ _balances[account] = MpcCore.offBoardCombined(
950
+ balance,
951
+ encryptionAddress
952
+ );
953
+ }
374
954
  }
375
-
955
+
376
956
  /**
377
957
  * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
378
958
  * Relies on the `_update` mechanism
@@ -381,12 +961,12 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
381
961
  *
382
962
  * NOTE: This function is not virtual, {_update} should be overridden instead.
383
963
  */
384
- function _mint(address account, gtUint64 value) internal returns (gtBool) {
964
+ function _mint(address account, gtUint256 value) internal {
385
965
  if (account == address(0)) {
386
966
  revert ERC20InvalidReceiver(address(0));
387
967
  }
388
968
 
389
- return _update(address(0), account, value);
969
+ _update(address(0), account, value);
390
970
  }
391
971
 
392
972
  /**
@@ -397,12 +977,12 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
397
977
  *
398
978
  * NOTE: This function is not virtual, {_update} should be overridden instead
399
979
  */
400
- function _burn(address account, gtUint64 value) internal returns (gtBool) {
980
+ function _burn(address account, gtUint256 value) internal {
401
981
  if (account == address(0)) {
402
982
  revert ERC20InvalidSender(address(0));
403
983
  }
404
984
 
405
- return _update(account, address(0), value);
985
+ _update(account, address(0), value);
406
986
  }
407
987
 
408
988
  /**
@@ -420,7 +1000,86 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
420
1000
  *
421
1001
  * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
422
1002
  */
423
- function _approve(address owner, address spender, gtUint64 value) internal {
1003
+
1004
+ /**
1005
+ * @dev If the new allowance is non-zero, requires the current allowance to be zero.
1006
+ * Same mitigation as {approve(address,uint256)} / {ERC20UnsafeApprove} for the encrypted and GT approve paths.
1007
+ */
1008
+ function _requireSafeEncryptedApprove(
1009
+ address owner,
1010
+ address spender,
1011
+ gtUint256 gtNewValue
1012
+ ) internal {
1013
+ gtBool newIsZero = MpcCore.eq(gtNewValue, MpcCore.setPublic256(0));
1014
+ if (MpcCore.decrypt(newIsZero)) {
1015
+ return;
1016
+ }
1017
+
1018
+ gtUint256 currentAllowance = _safeOnboard(
1019
+ _allowances[owner][spender].ciphertext
1020
+ );
1021
+ if (!MpcCore.decrypt(MpcCore.eq(currentAllowance, uint256(0)))) {
1022
+ revert ERC20UnsafeApprove();
1023
+ }
1024
+ }
1025
+
1026
+ /**
1027
+ * @dev Atomically adds `added` to the current allowance. Reverts on overflow (mirrors
1028
+ * ERC-20 reference behavior for `type(uint256).max + x`). Does not use
1029
+ * {_requireSafeEncryptedApprove} — intended as the safe alternative to resetting via {approve}.
1030
+ */
1031
+ function _increaseAllowance(
1032
+ address owner,
1033
+ address spender,
1034
+ gtUint256 added
1035
+ ) internal {
1036
+ if (owner == address(0)) revert ERC20InvalidApprover(address(0));
1037
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
1038
+
1039
+ gtUint256 currentAllowance = _safeOnboard(
1040
+ _allowances[owner][spender].ciphertext
1041
+ );
1042
+ (gtBool overflow, gtUint256 newAllowance) = MpcCore.checkedAddWithOverflowBit(
1043
+ currentAllowance,
1044
+ added
1045
+ );
1046
+ require(
1047
+ MpcCore.decrypt(MpcCore.not(overflow)),
1048
+ "ERC20: allowance overflow"
1049
+ );
1050
+ _approve(owner, spender, newAllowance);
1051
+ }
1052
+
1053
+ /**
1054
+ * @dev Atomically subtracts `subtracted` from the current allowance. Reverts on underflow.
1055
+ */
1056
+ function _decreaseAllowance(
1057
+ address owner,
1058
+ address spender,
1059
+ gtUint256 subtracted
1060
+ ) internal {
1061
+ if (owner == address(0)) revert ERC20InvalidApprover(address(0));
1062
+ if (spender == address(0)) revert ERC20InvalidSpender(address(0));
1063
+
1064
+ gtUint256 currentAllowance = _safeOnboard(
1065
+ _allowances[owner][spender].ciphertext
1066
+ );
1067
+ (gtBool underflow, gtUint256 newAllowance) = MpcCore.checkedSubWithOverflowBit(
1068
+ currentAllowance,
1069
+ subtracted
1070
+ );
1071
+ require(
1072
+ MpcCore.decrypt(MpcCore.not(underflow)),
1073
+ "ERC20: insufficient allowance"
1074
+ );
1075
+ _approve(owner, spender, newAllowance);
1076
+ }
1077
+
1078
+ function _approve(
1079
+ address owner,
1080
+ address spender,
1081
+ gtUint256 value
1082
+ ) internal {
424
1083
  if (owner == address(0)) {
425
1084
  revert ERC20InvalidApprover(address(0));
426
1085
  }
@@ -429,50 +1088,100 @@ abstract contract PrivateERC20 is Context, IPrivateERC20 {
429
1088
  revert ERC20InvalidSpender(address(0));
430
1089
  }
431
1090
 
432
- ctUint64 ciphertext = MpcCore.offBoard(value);
1091
+ ctUint256 memory ciphertext = MpcCore.offBoard(value);
433
1092
 
434
1093
  address encryptionAddress = _getAccountEncryptionAddress(owner);
435
1094
 
436
- ctUint64 ownerCiphertext = MpcCore.offBoardToUser(value, encryptionAddress);
1095
+ ctUint256 memory ownerCiphertext;
1096
+ if (encryptionAddress != address(0)) {
1097
+ ownerCiphertext = MpcCore.offBoardToUser(value, encryptionAddress);
1098
+ } else {
1099
+ ownerCiphertext = ctUint256({
1100
+ ciphertextHigh: ctUint128.wrap(0),
1101
+ ciphertextLow: ctUint128.wrap(0)
1102
+ });
1103
+ }
437
1104
 
438
1105
  encryptionAddress = _getAccountEncryptionAddress(spender);
439
1106
 
440
- ctUint64 spenderCiphertext = MpcCore.offBoardToUser(value, encryptionAddress);
1107
+ ctUint256 memory spenderCiphertext;
1108
+ if (encryptionAddress != address(0)) {
1109
+ spenderCiphertext = MpcCore.offBoardToUser(
1110
+ value,
1111
+ encryptionAddress
1112
+ );
1113
+ } else {
1114
+ spenderCiphertext = ctUint256({
1115
+ ciphertextHigh: ctUint128.wrap(0),
1116
+ ciphertextLow: ctUint128.wrap(0)
1117
+ });
1118
+ }
441
1119
 
442
- _allowances[owner][spender] = Allowance(ciphertext, ownerCiphertext, spenderCiphertext);
1120
+ _allowances[owner][spender] = Allowance(
1121
+ ciphertext,
1122
+ ownerCiphertext,
1123
+ spenderCiphertext
1124
+ );
443
1125
 
444
1126
  emit Approval(owner, spender, ownerCiphertext, spenderCiphertext);
445
1127
  }
446
1128
 
447
1129
  /**
448
- * @dev Updates `owner` s allowance for `spender` based on spent `value`.
449
- *
450
- * Does not decrease the allowance value in case of infinite allowance.
451
- * Does not decrease the allowance if not enough allowance is available.
452
- *
1130
+ * @dev Unlimited allowance: semantic equality to `type(uint256).max` via secret-vs-public
1131
+ * `eq` (MPC RHS_PUBLIC). Using this instead of `eq(allowance, setPublic256(max))` ensures
1132
+ * any valid garbled encoding of max is recognized, not only one canonical pair.
453
1133
  */
454
- function _spendAllowance(address owner, address spender, gtUint64 value) internal virtual {
455
- gtUint64 currentBalance = _safeOnboard(_balances[owner].ciphertext);
456
- gtUint64 currentAllowance = _safeOnboard(_allowances[owner][spender].ciphertext);
457
-
458
- gtBool maxAllowance = MpcCore.eq(currentAllowance, MpcCore.setPublic64(MAX_UINT_64));
459
- gtBool insufficientBalance = MpcCore.lt(currentBalance, value);
460
- gtBool inSufficientAllowance = MpcCore.lt(currentAllowance, value);
461
-
462
- gtUint64 newAllowance = MpcCore.mux(
463
- MpcCore.or(maxAllowance, MpcCore.or(insufficientBalance, inSufficientAllowance)),
464
- MpcCore.sub(currentAllowance, value),
465
- currentAllowance
466
- );
1134
+ function _isMaxAllowance(gtUint256 gtAllowance) internal returns (gtBool) {
1135
+ return MpcCore.eq(gtAllowance, MAX_UINT_256);
1136
+ }
467
1137
 
1138
+ /**
1139
+ * @dev Deducts `value` from `owner`/`spender` allowance after {transferFrom} prechecks.
1140
+ * @param currentAllowance Garbled allowance from the same `_safeOnboard` pass as the insufficient-allowance check (avoids a second onboard + duplicate `eq`/`lt`).
1141
+ * @param unlimitedAllowance Result of `MpcCore.decrypt(_isMaxAllowance(currentAllowance))` for the same slot.
1142
+ *
1143
+ * If unlimited, returns without writing storage or emitting {Approval} (OZ-style). Otherwise
1144
+ * subtracts `value`, which is safe because {transferFrom} already enforced sufficiency when not unlimited.
1145
+ */
1146
+ function _spendAllowance(
1147
+ address owner,
1148
+ address spender,
1149
+ gtUint256 value,
1150
+ gtUint256 currentAllowance,
1151
+ bool unlimitedAllowance
1152
+ ) internal virtual {
1153
+ if (unlimitedAllowance) {
1154
+ return;
1155
+ }
1156
+ gtUint256 newAllowance = MpcCore.sub(currentAllowance, value);
468
1157
  _approve(owner, spender, newAllowance);
469
1158
  }
470
1159
 
471
- function _safeOnboard(ctUint64 value) internal returns (gtUint64) {
472
- if (ctUint64.unwrap(value) == 0) {
473
- return MpcCore.setPublic64(0);
474
- }
1160
+ /// @dev EVM-uninitialized or fully cleared `ctUint256` storage reads as both limbs zero.
1161
+ function _isCanonicalEmptyCtUint256(
1162
+ ctUint256 memory ct
1163
+ ) private pure returns (bool) {
1164
+ return
1165
+ ctUint128.unwrap(ct.ciphertextHigh) == 0 &&
1166
+ ctUint128.unwrap(ct.ciphertextLow) == 0;
1167
+ }
475
1168
 
1169
+ /**
1170
+ * @dev Converts persisted `ctUint256` into garbled `gtUint256` for MPC operations.
1171
+ *
1172
+ * **Canonical zero:** Storage that has never been written, or reads as all-zero limbs, is
1173
+ * treated as semantic zero via `MpcCore.setPublic256(0)` without `MpcCore.onBoard`, to avoid a
1174
+ * precompile round-trip on empty balances/allowances/supply. Any non–all-zero encoding is
1175
+ * onboarded with `MpcCore.onBoard`.
1176
+ *
1177
+ * Invariant: values persisted by this contract come from `MpcCore.offBoard` after successful MPC
1178
+ * updates, so normal state is consistent with the precompile. Subclasses or integrations must not
1179
+ * inject arbitrary ciphertext into these slots unless it matches MPC encoding expectations.
1180
+ */
1181
+ function _safeOnboard(ctUint256 memory value) internal returns (gtUint256) {
1182
+ if (_isCanonicalEmptyCtUint256(value)) {
1183
+ return MpcCore.setPublic256(0);
1184
+ }
476
1185
  return MpcCore.onBoard(value);
477
1186
  }
478
- }
1187
+ }