@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.
- package/.github/workflows/npm-publish.yml +19 -15
- package/README.md +3 -1
- package/contracts/disperse/coinByRatio/FixedRatioCoinDisperser.sol +452 -0
- package/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1.sol +490 -0
- package/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow.sol +432 -0
- package/contracts/disperse/disperseToken/MintDisperser.sol +120 -0
- package/contracts/disperse/disperseToken/TokenDisperser.sol +139 -0
- package/contracts/messaging/PrivateMessaging.sol +456 -0
- package/contracts/mocks/messaging/PrivateMessagingHarness.sol +54 -0
- package/contracts/mocks/oracle/CotiPriceConsumerMock.sol +31 -0
- package/contracts/mocks/token/ERC20DecimalsMock.sol +21 -0
- package/contracts/mocks/token/ERC20Mock.sol +19 -0
- package/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock.sol +18 -0
- package/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock.sol +18 -0
- package/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock.sol +44 -0
- package/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock.sol +23 -0
- package/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock.sol +29 -0
- package/contracts/mocks/token/PrivateERC20/PrivateERC20Mock.sol +9 -4
- package/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol +39 -0
- package/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock.sol +29 -0
- package/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock.sol +18 -0
- package/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock.sol +12 -0
- package/contracts/mocks/utils/mpc/Arithmetic128TestsContract.sol +123 -0
- package/contracts/mocks/utils/mpc/Arithmetic256TestsContract.sol +122 -0
- package/contracts/mocks/utils/mpc/Bitwise128TestsContract.sol +65 -0
- package/contracts/mocks/utils/mpc/Bitwise256TestsContract.sol +65 -0
- package/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract.sol +64 -0
- package/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract.sol +64 -0
- package/contracts/mocks/utils/mpc/Comparison128TestsContract.sol +98 -0
- package/contracts/mocks/utils/mpc/Comparison256TestsContract.sol +98 -0
- package/contracts/mocks/utils/mpc/MinMax128TestsContract.sol +44 -0
- package/contracts/mocks/utils/mpc/MinMax256TestsContract.sol +44 -0
- package/contracts/mocks/utils/mpc/MinimalImplementation.sol +55 -0
- package/contracts/mocks/utils/mpc/MinimalProxy.sol +27 -0
- package/contracts/mocks/utils/mpc/MpcOperations128TestContract.sol +267 -0
- package/contracts/mocks/utils/mpc/MpcOperationsTestContract.sol +334 -0
- package/contracts/mocks/utils/mpc/Mux128TestsContract.sol +24 -0
- package/contracts/mocks/utils/mpc/Mux256TestsContract.sol +24 -0
- package/contracts/mocks/utils/mpc/OnBoard128TestsContract.sol +93 -0
- package/contracts/mocks/utils/mpc/OnBoard256TestsContract.sol +116 -0
- package/contracts/mocks/utils/mpc/PrivacyImplementationV1.sol +157 -0
- package/contracts/mocks/utils/mpc/PrivacyImplementationV2.sol +133 -0
- package/contracts/mocks/utils/mpc/PrivacyProxy.sol +137 -0
- package/contracts/mocks/utils/mpc/Random128TestsContract.sol +32 -0
- package/contracts/mocks/utils/mpc/Random256TestsContract.sol +32 -0
- package/contracts/mocks/utils/mpc/Shift128TestsContract.sol +36 -0
- package/contracts/mocks/utils/mpc/Shift256TestsContract.sol +36 -0
- package/contracts/mocks/utils/mpc/Transfer128TestsContract.sol +29 -0
- package/contracts/mocks/utils/mpc/Transfer256TestsContract.sol +29 -0
- package/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract.sol +32 -0
- package/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract.sol +32 -0
- package/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract.sol +59 -0
- package/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract.sol +63 -0
- package/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract.sol +99 -0
- package/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock.sol +7 -6
- package/contracts/node/CotiNodeRewards.sol +199 -0
- package/contracts/node/SoulboundNodeNFT.sol +519 -0
- package/contracts/oracle/CotiPriceConsumer.sol +103 -0
- package/contracts/oracle/ICotiPriceConsumer.sol +15 -0
- package/contracts/oracle/IStdReference.sol +26 -0
- package/contracts/privacyBridge/PrivacyBridge.sol +600 -0
- package/contracts/privacyBridge/PrivacyBridgeCotiNative.sol +316 -0
- package/contracts/privacyBridge/PrivacyBridgeERC20.sol +333 -0
- package/contracts/privacyBridge/PrivacyBridgeUSDCe.sol +22 -0
- package/contracts/privacyBridge/PrivacyBridgeUSDT.sol +23 -0
- package/contracts/privacyBridge/PrivacyBridgeWADA.sol +23 -0
- package/contracts/privacyBridge/PrivacyBridgeWBTC.sol +23 -0
- package/contracts/privacyBridge/PrivacyBridgeWETH.sol +23 -0
- package/contracts/privacyBridge/PrivacyBridgegCoti.sol +23 -0
- package/contracts/token/PrivateERC20/IPrivateERC20.sol +273 -40
- package/contracts/token/PrivateERC20/ITokenReceiver.sol +17 -0
- package/contracts/token/PrivateERC20/ITokenReceiverEncrypted.sol +20 -0
- package/contracts/token/PrivateERC20/PrivateERC20.sol +861 -152
- package/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC.sol +17 -0
- package/contracts/token/PrivateERC20/tokens/PrivateCOTI.sol +13 -0
- package/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken.sol +17 -0
- package/contracts/token/PrivateERC20/tokens/PrivateTetherUSD.sol +17 -0
- package/contracts/token/PrivateERC20/tokens/PrivateWrappedADA.sol +17 -0
- package/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC.sol +17 -0
- package/contracts/token/PrivateERC20/tokens/PrivateWrappedEther.sol +13 -0
- package/contracts/utils/mpc/MpcCore.sol +10780 -4962
- package/contracts/utils/mpc/MpcInterface.sol +7 -2
- package/hardhat/gasPriceBump.ts +60 -0
- package/hardhat.config.ts +63 -12
- package/package.json +22 -4
- package/scripts/add-bridge-operators.cjs +87 -0
- package/scripts/deploy-coti-price-consumer-mainnet.cjs +49 -0
- package/scripts/deployCotiNodeRewards.ts +51 -0
- package/scripts/deploySoulboundNodeNFT.ts +57 -0
- package/scripts/redeploy-private-and-bridges.cjs +201 -0
- package/scripts/set-bridge-limits-mainnet.cjs +177 -0
- package/scripts/verify-mainnet-private-bridges.cjs +289 -0
- package/sonar-project.properties +11 -0
- package/test/messaging/PrivateMessaging.test.ts +158 -0
- package/test/privacyBridge/UnifiedPrivacyBridges.test.cjs +1663 -0
- package/test/token/PrivateERC20/PrivacyBridge.fees.test.ts +250 -0
- package/test/token/PrivateERC20/PrivateERC20.test.ts +1109 -268
- package/test/utils/mpc/Bitwise128.test.ts +92 -0
- package/test/utils/mpc/Bitwise256.test.ts +101 -0
- package/test/utils/mpc/BuildInputText128.test.ts +398 -0
- package/test/utils/mpc/CheckedArithmetic128.test.ts +45 -0
- package/test/utils/mpc/CheckedArithmetic256.test.ts +45 -0
- package/test/utils/mpc/Comparison128.test.ts +48 -0
- package/test/utils/mpc/Comparison256.test.ts +48 -0
- package/test/utils/mpc/InvalidCiphertext.test.ts +179 -0
- package/test/utils/mpc/InvalidCiphertext128.test.ts +179 -0
- package/test/utils/mpc/InvalidCiphertext256.test.ts +201 -0
- package/test/utils/mpc/InvalidTransfer128.test.ts +166 -0
- package/test/utils/mpc/InvalidTransfer256.test.ts +158 -0
- package/test/utils/mpc/MinMax128.test.ts +44 -0
- package/test/utils/mpc/MinMax256.test.ts +44 -0
- package/test/utils/mpc/MinimalProxyDemo.test.ts +280 -0
- package/test/utils/mpc/MpcOperations.test.ts +589 -0
- package/test/utils/mpc/MpcOperations128.test.ts +632 -0
- package/test/utils/mpc/Mux128.test.ts +41 -0
- package/test/utils/mpc/Mux256.test.ts +41 -0
- package/test/utils/mpc/NegativeCases.test.ts +478 -0
- package/test/utils/mpc/NegativeCases128.test.ts +237 -0
- package/test/utils/mpc/NegativeCases256.test.ts +236 -0
- package/test/utils/mpc/OnBoard128.test.ts +90 -0
- package/test/utils/mpc/OnBoard256.test.ts +114 -0
- package/test/utils/mpc/Precompile128.test.ts +51 -0
- package/test/utils/mpc/Precompile256.test.ts +115 -0
- package/test/utils/mpc/ProxyValidation.test.ts +199 -0
- package/test/utils/mpc/Random128.test.ts +47 -0
- package/test/utils/mpc/Random256.test.ts +47 -0
- package/test/utils/mpc/Shift128.test.ts +41 -0
- package/test/utils/mpc/Shift256.test.ts +41 -0
- package/test/utils/mpc/Transfer128.test.ts +46 -0
- package/test/utils/mpc/Transfer256.test.ts +46 -0
- package/test/utils/mpc/TransferWithAllowance128.test.ts +48 -0
- package/test/utils/mpc/TransferWithAllowance256.test.ts +48 -0
- package/test/utils/mpc/UpgradeablePrivacyProxy.test.ts +307 -0
- package/test/utils/mpc/ValidateCiphertext.test.ts +221 -0
- package/test/utils/mpc/ValidateCiphertext256.test.ts +161 -0
- package/test/utils/mpc/ValidateCiphertext256Decryption.test.ts +383 -0
- package/test/utils/mpc/WalletEncryptionFailures.test.ts +494 -0
- package/test/utils/privateErc20Helpers.ts +97 -0
- package/typechain-types/@openzeppelin/contracts/access/AccessControl.ts +324 -0
- package/typechain-types/@openzeppelin/contracts/access/AccessControlEnumerable.ts +361 -0
- package/typechain-types/@openzeppelin/contracts/access/IAccessControl.ts +292 -0
- package/typechain-types/@openzeppelin/contracts/access/IAccessControlEnumerable.ts +329 -0
- package/typechain-types/@openzeppelin/contracts/access/Ownable.ts +153 -0
- package/typechain-types/@openzeppelin/contracts/access/index.ts +8 -0
- package/typechain-types/@openzeppelin/contracts/index.ts +4 -0
- package/typechain-types/@openzeppelin/contracts/security/Pausable.ts +150 -0
- package/typechain-types/@openzeppelin/contracts/security/index.ts +4 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/ERC20.ts +330 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/IERC20.ts +262 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.ts +286 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.ts +143 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/extensions/index.ts +5 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC20/index.ts +7 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC721/ERC721.ts +420 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.ts +420 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC721/extensions/index.ts +4 -0
- package/typechain-types/@openzeppelin/contracts/token/ERC721/index.ts +3 -0
- package/typechain-types/@openzeppelin/contracts/token/index.ts +2 -0
- package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperser.ts +679 -0
- package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1.ts +735 -0
- package/typechain-types/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow.ts +734 -0
- package/typechain-types/contracts/disperse/coinByRatio/index.ts +6 -0
- package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/IMintableERC20.ts +97 -0
- package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/IOwnable.ts +95 -0
- package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/MintDisperser.ts +228 -0
- package/typechain-types/contracts/disperse/disperseToken/MintDisperser.sol/index.ts +6 -0
- package/typechain-types/contracts/disperse/disperseToken/TokenDisperser.ts +191 -0
- package/typechain-types/contracts/disperse/disperseToken/index.ts +6 -0
- package/typechain-types/contracts/disperse/index.ts +7 -0
- package/typechain-types/contracts/index.ts +10 -0
- package/typechain-types/contracts/messaging/PrivateMessaging.ts +871 -0
- package/typechain-types/contracts/messaging/index.ts +4 -0
- package/typechain-types/contracts/mocks/index.ts +4 -0
- package/typechain-types/contracts/mocks/messaging/PrivateMessagingHarness.ts +951 -0
- package/typechain-types/contracts/mocks/messaging/index.ts +4 -0
- package/typechain-types/contracts/mocks/oracle/CotiPriceConsumerMock.ts +163 -0
- package/typechain-types/contracts/mocks/oracle/index.ts +4 -0
- package/typechain-types/contracts/mocks/token/ERC20DecimalsMock.ts +349 -0
- package/typechain-types/contracts/mocks/token/ERC20Mock.ts +368 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock.ts +108 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock.ts +1892 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock.ts +1296 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock.ts +1270 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock.ts +166 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20Mock.ts +827 -78
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/IPrivateERC20TransferAndCallEncrypted.ts +102 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/PrivateERC20ReentrantReceiverMock.ts +154 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/index.ts +5 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock.ts +155 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock.ts +107 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock.ts +100 -0
- package/typechain-types/contracts/mocks/token/PrivateERC20/index.ts +10 -0
- package/typechain-types/contracts/mocks/token/index.ts +2 -0
- package/typechain-types/contracts/mocks/utils/mpc/{Arithmetic128BitTestsContract.ts → Arithmetic128TestsContract.ts} +96 -126
- package/typechain-types/contracts/mocks/utils/mpc/{Arithmetic256BitTestsContract.ts → Arithmetic256TestsContract.ts} +97 -133
- package/typechain-types/contracts/mocks/utils/mpc/{Bitwise128BitTestsContract.ts → Bitwise128TestsContract.ts} +52 -59
- package/typechain-types/contracts/mocks/utils/mpc/{Bitwise256BitTestsContract.ts → Bitwise256TestsContract.ts} +52 -59
- package/typechain-types/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract.ts +206 -0
- package/typechain-types/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract.ts +206 -0
- package/typechain-types/contracts/mocks/utils/mpc/{Comparison128BitTestsContract.ts → Comparison128TestsContract.ts} +115 -97
- package/typechain-types/contracts/mocks/utils/mpc/{Comparison256BitTestsContract.ts → Comparison256TestsContract.ts} +115 -97
- package/typechain-types/contracts/mocks/utils/mpc/MinMax128TestsContract.ts +142 -0
- package/typechain-types/contracts/mocks/utils/mpc/MinMax256TestsContract.ts +142 -0
- package/typechain-types/contracts/mocks/utils/mpc/MinimalImplementation.ts +259 -0
- package/typechain-types/contracts/mocks/utils/mpc/MinimalProxy.ts +90 -0
- package/typechain-types/contracts/mocks/utils/mpc/MpcOperations128TestContract.ts +388 -0
- package/typechain-types/contracts/mocks/utils/mpc/MpcOperationsTestContract.ts +455 -0
- package/typechain-types/contracts/mocks/utils/mpc/Mux128TestsContract.ts +109 -0
- package/typechain-types/contracts/mocks/utils/mpc/Mux256TestsContract.ts +109 -0
- package/typechain-types/contracts/mocks/utils/mpc/OnBoard128TestsContract.ts +166 -0
- package/typechain-types/contracts/mocks/utils/mpc/OnBoard256TestsContract.ts +194 -0
- package/typechain-types/contracts/mocks/utils/mpc/PrivacyImplementationV1.ts +385 -0
- package/typechain-types/contracts/mocks/utils/mpc/PrivacyImplementationV2.ts +441 -0
- package/typechain-types/contracts/mocks/utils/mpc/PrivacyProxy.ts +201 -0
- package/typechain-types/contracts/mocks/utils/mpc/Random128TestsContract.ts +131 -0
- package/typechain-types/contracts/mocks/utils/mpc/Random256TestsContract.ts +131 -0
- package/typechain-types/contracts/mocks/utils/mpc/Shift128TestsContract.ts +142 -0
- package/typechain-types/contracts/mocks/utils/mpc/Shift256TestsContract.ts +142 -0
- package/typechain-types/contracts/mocks/utils/mpc/Transfer128TestsContract.ts +109 -0
- package/typechain-types/contracts/mocks/utils/mpc/Transfer256TestsContract.ts +109 -0
- package/typechain-types/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract.ts +128 -0
- package/typechain-types/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract.ts +128 -0
- package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract.ts +198 -0
- package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract.ts +182 -0
- package/typechain-types/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract.ts +257 -0
- package/typechain-types/contracts/mocks/utils/mpc/index.ts +32 -8
- package/typechain-types/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock.ts +6 -6
- package/typechain-types/contracts/node/CotiNodeRewards.ts +566 -0
- package/typechain-types/contracts/node/SoulboundNodeNFT.sol/IERC5192.ts +123 -0
- package/typechain-types/contracts/node/SoulboundNodeNFT.sol/SoulboundNodeNFT.ts +975 -0
- package/typechain-types/contracts/node/SoulboundNodeNFT.sol/index.ts +5 -0
- package/typechain-types/contracts/node/index.ts +6 -0
- package/typechain-types/contracts/oracle/CotiPriceConsumer.ts +256 -0
- package/typechain-types/contracts/oracle/ICotiPriceConsumer.ts +119 -0
- package/typechain-types/contracts/oracle/IStdReference.ts +136 -0
- package/typechain-types/contracts/oracle/index.ts +6 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridge.ts +1628 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeCotiNative.ts +1870 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/IHasDecimals.ts +84 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/PrivacyBridgeERC20.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeERC20.sol/index.ts +5 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeUSDCe.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeUSDT.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeWADA.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeWBTC.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgeWETH.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/PrivacyBridgegCoti.ts +1892 -0
- package/typechain-types/contracts/privacyBridge/index.ts +13 -0
- package/typechain-types/contracts/token/PrivateERC20/IPrivateERC20.ts +507 -70
- package/typechain-types/contracts/token/PrivateERC20/ITokenReceiver.ts +100 -0
- package/typechain-types/contracts/token/PrivateERC20/ITokenReceiverEncrypted.ts +99 -0
- package/typechain-types/contracts/token/PrivateERC20/PrivateERC20.ts +833 -68
- package/typechain-types/contracts/token/PrivateERC20/index.ts +4 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateCOTI.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateTetherUSD.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedADA.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/PrivateWrappedEther.ts +1270 -0
- package/typechain-types/contracts/token/PrivateERC20/tokens/index.ts +10 -0
- package/typechain-types/contracts/utils/mpc/MpcInterface.sol/ExtendedOperations.ts +111 -10
- package/typechain-types/factories/@openzeppelin/contracts/access/AccessControlEnumerable__factory.ts +276 -0
- package/typechain-types/factories/@openzeppelin/contracts/access/AccessControl__factory.ts +229 -0
- package/typechain-types/factories/@openzeppelin/contracts/access/IAccessControlEnumerable__factory.ts +244 -0
- package/typechain-types/factories/@openzeppelin/contracts/access/IAccessControl__factory.ts +197 -0
- package/typechain-types/factories/@openzeppelin/contracts/access/Ownable__factory.ts +74 -0
- package/typechain-types/factories/@openzeppelin/contracts/access/index.ts +8 -0
- package/typechain-types/factories/@openzeppelin/contracts/index.ts +2 -0
- package/typechain-types/factories/@openzeppelin/contracts/security/Pausable__factory.ts +61 -0
- package/typechain-types/factories/@openzeppelin/contracts/security/index.ts +4 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/ERC20__factory.ts +356 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/IERC20__factory.ts +205 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata__factory.ts +247 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit__factory.ts +100 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/extensions/index.ts +5 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC20/index.ts +6 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/ERC721__factory.ts +416 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata__factory.ts +355 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/extensions/index.ts +4 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/ERC721/index.ts +2 -0
- package/typechain-types/factories/@openzeppelin/contracts/token/index.ts +1 -0
- package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperserLeftoverS1__factory.ts +791 -0
- package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperserUnlimitedWindow__factory.ts +711 -0
- package/typechain-types/factories/contracts/disperse/coinByRatio/FixedRatioCoinDisperser__factory.ts +759 -0
- package/typechain-types/factories/contracts/disperse/coinByRatio/index.ts +6 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/IMintableERC20__factory.ts +43 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/IOwnable__factory.ts +35 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/MintDisperser__factory.ts +203 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/MintDisperser.sol/index.ts +6 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/TokenDisperser__factory.ts +198 -0
- package/typechain-types/factories/contracts/disperse/disperseToken/index.ts +5 -0
- package/typechain-types/factories/contracts/disperse/index.ts +5 -0
- package/typechain-types/factories/contracts/index.ts +5 -0
- package/typechain-types/factories/contracts/messaging/PrivateMessaging__factory.ts +1035 -0
- package/typechain-types/factories/contracts/messaging/index.ts +4 -0
- package/typechain-types/factories/contracts/mocks/access/DataPrivacyFramework/DataPrivacyFrameworkMock__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/index.ts +2 -0
- package/typechain-types/factories/contracts/mocks/messaging/PrivateMessagingHarness__factory.ts +1161 -0
- package/typechain-types/factories/contracts/mocks/messaging/index.ts +4 -0
- package/typechain-types/factories/contracts/mocks/oracle/CotiPriceConsumerMock__factory.ts +159 -0
- package/typechain-types/factories/contracts/mocks/oracle/index.ts +4 -0
- package/typechain-types/factories/contracts/mocks/token/ERC20DecimalsMock__factory.ts +394 -0
- package/typechain-types/factories/contracts/mocks/token/ERC20Mock__factory.ts +392 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/EncryptedTokenReceiverMock__factory.ts +125 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivacyBridgeERC20Mock__factory.ts +1797 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20AuditorSupplyMock__factory.ts +1514 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20CappedMock__factory.ts +1466 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20GtCallerMock__factory.ts +165 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20Mock__factory.ts +975 -127
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/IPrivateERC20TransferAndCallEncrypted__factory.ts +54 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/PrivateERC20ReentrantReceiverMock__factory.ts +159 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PrivateERC20ReentrantReceiverMock.sol/index.ts +5 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicReentrantTokenReceiverMock__factory.ts +164 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicTokenReceiverBoolMock__factory.ts +130 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/PublicTokenReceiverMock__factory.ts +102 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC20/index.ts +9 -0
- package/typechain-types/factories/contracts/mocks/token/PrivateERC721/PrivateERC721URIStorageMock__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/token/index.ts +2 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic128TestsContract__factory.ts +330 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic256TestsContract__factory.ts +330 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/ArithmeticTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise128TestsContract__factory.ts +184 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise256TestsContract__factory.ts +184 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/BitwiseTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmetic128WithOverflowBitTestsContract__factory.ts +203 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmetic256WithOverflowBitTestsContract__factory.ts +203 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/CheckedArithmeticWIthOverflowBitTestsContract.sol/CheckedArithmeticWithOverflowBitTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison128TestsContract__factory.ts +295 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison1TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison256TestsContract__factory.ts +295 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison2TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/MinMax128TestsContract__factory.ts +147 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/MinMax256TestsContract__factory.ts +147 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/MinMaxTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/MinimalImplementation__factory.ts +263 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/MinimalProxy__factory.ts +104 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous1TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/MiscellaneousTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/MpcOperations128TestContract__factory.ts +723 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/MpcOperationsTestContract__factory.ts +1410 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Mux128TestsContract__factory.ts +115 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Mux256TestsContract__factory.ts +115 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/OffboardToUserKeyTestContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/OnBoard128TestsContract__factory.ts +175 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/OnBoard256TestsContract__factory.ts +205 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyImplementationV1__factory.ts +521 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyImplementationV2__factory.ts +648 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/PrivacyProxy__factory.ts +179 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Random128TestsContract__factory.ts +131 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Random256TestsContract__factory.ts +131 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Shift128TestsContract__factory.ts +147 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Shift256TestsContract__factory.ts +147 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/ShiftTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/StringTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/Transfer128TestsContract__factory.ts +135 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/Transfer256TestsContract__factory.ts +135 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferScalarTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance128TestsContract__factory.ts +154 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance256TestsContract__factory.ts +154 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_16TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_32TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_64TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowance64_8TestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowanceScalarTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/TransferWithAllowanceTestsContract__factory.ts +1 -1
- package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertext128TestsContract__factory.ts +190 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertext256TestsContract__factory.ts +210 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/ValidateCiphertextTestsContract__factory.ts +260 -0
- package/typechain-types/factories/contracts/mocks/utils/mpc/index.ts +32 -8
- package/typechain-types/factories/contracts/mocks/wallet/PrivateERC20Wallet/PrivateERC20WalletMock__factory.ts +10 -28
- package/typechain-types/factories/contracts/node/CotiNodeRewards__factory.ts +549 -0
- package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/IERC5192__factory.ts +54 -0
- package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/SoulboundNodeNFT__factory.ts +947 -0
- package/typechain-types/factories/contracts/node/SoulboundNodeNFT.sol/index.ts +5 -0
- package/typechain-types/factories/contracts/node/index.ts +5 -0
- package/typechain-types/factories/contracts/onboard/AccountOnboard__factory.ts +1 -1
- package/typechain-types/factories/contracts/oracle/CotiPriceConsumer__factory.ts +295 -0
- package/typechain-types/factories/contracts/oracle/ICotiPriceConsumer__factory.ts +73 -0
- package/typechain-types/factories/contracts/oracle/IStdReference__factory.ts +107 -0
- package/typechain-types/factories/contracts/oracle/index.ts +6 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeCotiNative__factory.ts +1676 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/IHasDecimals__factory.ts +38 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/PrivacyBridgeERC20__factory.ts +1678 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeERC20.sol/index.ts +5 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeUSDCe__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeUSDT__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWADA__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWBTC__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgeWETH__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridge__factory.ts +1367 -0
- package/typechain-types/factories/contracts/privacyBridge/PrivacyBridgegCoti__factory.ts +1782 -0
- package/typechain-types/factories/contracts/privacyBridge/index.ts +12 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/IPrivateERC20__factory.ts +639 -61
- package/typechain-types/factories/contracts/token/PrivateERC20/ITokenReceiverEncrypted__factory.ts +53 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/ITokenReceiver__factory.ts +54 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/PrivateERC20__factory.ts +986 -120
- package/typechain-types/factories/contracts/token/PrivateERC20/index.ts +3 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateBridgedUSDC__factory.ts +1445 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateCOTITreasuryGovernanceToken__factory.ts +1451 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateCOTI__factory.ts +1442 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateTetherUSD__factory.ts +1445 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedADA__factory.ts +1445 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedBTC__factory.ts +1445 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/PrivateWrappedEther__factory.ts +1451 -0
- package/typechain-types/factories/contracts/token/PrivateERC20/tokens/index.ts +10 -0
- package/typechain-types/factories/contracts/utils/mpc/MpcCore__factory.ts +1 -1
- package/typechain-types/factories/contracts/utils/mpc/MpcInterface.sol/ExtendedOperations__factory.ts +126 -0
- package/typechain-types/hardhat.d.ts +1772 -278
- package/typechain-types/index.ts +182 -16
- package/contracts/mocks/utils/mpc/Arithmetic128BitTestsContract.sol +0 -260
- package/contracts/mocks/utils/mpc/Arithmetic256BitTestsContract.sol +0 -264
- package/contracts/mocks/utils/mpc/Bitwise128BitTestsContract.sol +0 -91
- package/contracts/mocks/utils/mpc/Bitwise256BitTestsContract.sol +0 -91
- package/contracts/mocks/utils/mpc/Comparison128BitTestsContract.sol +0 -158
- package/contracts/mocks/utils/mpc/Comparison256BitTestsContract.sol +0 -158
- package/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract.sol +0 -185
- package/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract.sol +0 -190
- package/contracts/mocks/utils/mpc/README.md +0 -486
- package/test/utils/mpc/Unsigned128BitIntegers.test.ts +0 -945
- package/test/utils/mpc/Unsigned256BitIntegers.test.ts +0 -1124
- package/test/utils/mpc/helpers.ts +0 -77
- package/typechain-types/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract.ts +0 -302
- package/typechain-types/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract.ts +0 -322
- package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic128BitTestsContract__factory.ts +0 -349
- package/typechain-types/factories/contracts/mocks/utils/mpc/Arithmetic256BitTestsContract__factory.ts +0 -364
- package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise128BitTestsContract__factory.ts +0 -182
- package/typechain-types/factories/contracts/mocks/utils/mpc/Bitwise256BitTestsContract__factory.ts +0 -182
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison128BitTestsContract__factory.ts +0 -255
- package/typechain-types/factories/contracts/mocks/utils/mpc/Comparison256BitTestsContract__factory.ts +0 -255
- package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous128BitTestsContract__factory.ts +0 -332
- package/typechain-types/factories/contracts/mocks/utils/mpc/Miscellaneous256BitTestsContract__factory.ts +0 -380
|
@@ -0,0 +1,1663 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { expect } = require("chai");
|
|
4
|
+
const { ethers } = require("hardhat");
|
|
5
|
+
const hre = require("hardhat");
|
|
6
|
+
const { prepareIT256 } = require("@coti-io/coti-sdk-typescript");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Unified Privacy Bridge Integration Tests (Full Suite)
|
|
10
|
+
* Consolidates Native and all ERC20 bridge tests into a single suite with numbered outputs.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates environment variables required for test execution.
|
|
15
|
+
* Normalizes PRIVATE_AES_KEY_TESTNET by stripping any accidental 0x prefix.
|
|
16
|
+
* @returns {Object} Environment validation result
|
|
17
|
+
*/
|
|
18
|
+
function validateEnvironment() {
|
|
19
|
+
let aesKey = process.env.PRIVATE_AES_KEY_TESTNET || null;
|
|
20
|
+
// Strip 0x prefix if present — encodeKey expects raw 32 hex chars
|
|
21
|
+
if (aesKey?.startsWith('0x')) aesKey = aesKey.slice(2);
|
|
22
|
+
// Validate: must be exactly 32 hex characters (16 bytes)
|
|
23
|
+
const isValidKey = aesKey && /^[0-9a-fA-F]{32}$/.test(aesKey);
|
|
24
|
+
return {
|
|
25
|
+
hasAesKey: !!isValidKey,
|
|
26
|
+
aesKey: isValidKey ? aesKey : null,
|
|
27
|
+
canRunEncryptedTests: !!isValidKey
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates an ethers Wallet from PRIVATE_KEY env var.
|
|
33
|
+
* Normalizes the key by adding 0x prefix if missing.
|
|
34
|
+
* Uses this instead of passing a HardhatEthersSigner directly — HardhatEthersSigner
|
|
35
|
+
* does NOT expose a public .privateKey property, so getBytes(signer.privateKey)
|
|
36
|
+
* inside prepareIT256 throws "invalid BytesLike value".
|
|
37
|
+
* @returns {ethers.Wallet}
|
|
38
|
+
*/
|
|
39
|
+
function makeSdkWallet() {
|
|
40
|
+
const raw = process.env.PRIVATE_KEY;
|
|
41
|
+
if (!raw) throw new Error('PRIVATE_KEY not set in .env');
|
|
42
|
+
const pk = raw.startsWith('0x') ? raw : '0x' + raw;
|
|
43
|
+
return new ethers.Wallet(pk, ethers.provider);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Shared helper — builds an itUint256 payload for any contract function selector.
|
|
48
|
+
* Centralises all calls to prepareIT256 to avoid copy-pasted implementations
|
|
49
|
+
* that historically passed the HardhatEthersSigner directly and triggered
|
|
50
|
+
* "invalid BytesLike value" from getBytes(sender.wallet.privateKey).
|
|
51
|
+
*
|
|
52
|
+
* @param {bigint|number} plaintext - Plain amount to encrypt
|
|
53
|
+
* @param {string} contractAddress - Target contract address (0x-prefixed)
|
|
54
|
+
* @param {string} selector - 4-byte function selector (0x + 8 hex chars)
|
|
55
|
+
* @returns {Promise<[[bigint,bigint], Uint8Array]>} Encoded itUint256 tuple
|
|
56
|
+
*/
|
|
57
|
+
async function buildItUint256(plaintext, contractAddress, selector) {
|
|
58
|
+
const env = validateEnvironment();
|
|
59
|
+
if (!env.hasAesKey) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'PRIVATE_AES_KEY_TESTNET not set or invalid in .env. ' +
|
|
62
|
+
'Expected a 32-character hex string (no 0x prefix).'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const wallet = makeSdkWallet();
|
|
66
|
+
console.log(` [SDK] prepareIT256 plaintext=${plaintext} contract=${contractAddress} selector=${selector}`);
|
|
67
|
+
try {
|
|
68
|
+
const it = prepareIT256(
|
|
69
|
+
BigInt(plaintext),
|
|
70
|
+
{ wallet, userKey: env.aesKey },
|
|
71
|
+
contractAddress,
|
|
72
|
+
selector
|
|
73
|
+
);
|
|
74
|
+
return [[it.ciphertext.ciphertextHigh, it.ciphertext.ciphertextLow], it.signature];
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(` [SDK ERROR] prepareIT256 failed: ${error.message}`);
|
|
77
|
+
console.error(` [SDK DEBUG] wallet.address=${wallet.address} selector=${selector} keyLen=${env.aesKey?.length}`);
|
|
78
|
+
throw new Error(`prepareIT256 failed: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Waits for transaction receipt with retry logic and exponential backoff
|
|
84
|
+
* @param {Object} tx - Transaction object
|
|
85
|
+
* @param {number} maxRetries - Maximum number of retry attempts (default: 3)
|
|
86
|
+
* @returns {Promise<Object>} Transaction receipt
|
|
87
|
+
*/
|
|
88
|
+
async function waitForReceiptWithRetry(tx, maxRetries = 3) {
|
|
89
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
90
|
+
try {
|
|
91
|
+
const receipt = await tx.wait(1, 120000); // 2-minute timeout per attempt
|
|
92
|
+
if (receipt) return receipt;
|
|
93
|
+
|
|
94
|
+
// Wait with exponential backoff
|
|
95
|
+
await new Promise(r => setTimeout(r, 2000 * (i + 1)));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (i === maxRetries - 1) throw error;
|
|
98
|
+
await new Promise(r => setTimeout(r, 2000 * (i + 1)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Failed to get transaction receipt after retries");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
105
|
+
// SHARED DEPLOYMENT & ASSERTION HELPERS (reduces duplication across suites)
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Deploys a MockCotiPriceConsumer with COTI at $0.05 and optional token price.
|
|
110
|
+
* Sets lastUpdated to the current block timestamp.
|
|
111
|
+
* @param {Object} [opts] - Optional: { tokenSymbol, tokenPrice }
|
|
112
|
+
* @returns {Promise<Contract>} Deployed oracle contract
|
|
113
|
+
*/
|
|
114
|
+
async function deployMockOracle(opts = {}) {
|
|
115
|
+
const OracleFactory = await ethers.getContractFactory("MockCotiPriceConsumer");
|
|
116
|
+
const oracle = await OracleFactory.deploy({ gasLimit: 12000000 });
|
|
117
|
+
await (oracle.waitForDeployment ? oracle.waitForDeployment() : oracle.deployed());
|
|
118
|
+
await oracle.setCotiPrice(ethers.parseEther("0.05"), { gasLimit: 2000000 });
|
|
119
|
+
if (opts.tokenSymbol && opts.tokenPrice) {
|
|
120
|
+
await oracle.setPrice(opts.tokenSymbol, ethers.parseEther(opts.tokenPrice), { gasLimit: 2000000 });
|
|
121
|
+
}
|
|
122
|
+
const currentBlock = await ethers.provider.getBlock("latest");
|
|
123
|
+
await oracle.setLastUpdated(currentBlock.timestamp, { gasLimit: 2000000 });
|
|
124
|
+
return oracle;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Deploys a PrivacyBridgeCotiNative, sets oracle, and grants MINTER_ROLE.
|
|
129
|
+
* @returns {Promise<Contract>} Deployed bridge contract
|
|
130
|
+
*/
|
|
131
|
+
async function deployNativeBridge(privateCoti, mockOracle, owner, logTx, addr, MINTER_ROLE) {
|
|
132
|
+
const BridgeFactory = await ethers.getContractFactory("PrivacyBridgeCotiNative");
|
|
133
|
+
const pCotiAddr = await addr(privateCoti);
|
|
134
|
+
const bridge = await BridgeFactory.deploy(pCotiAddr, owner.address, owner.address, await addr(mockOracle), { gasLimit: 30000000 });
|
|
135
|
+
await (bridge.waitForDeployment ? bridge.waitForDeployment() : bridge.deployed());
|
|
136
|
+
const bridgeAddr = await addr(bridge);
|
|
137
|
+
await logTx(await privateCoti.grantRole(MINTER_ROLE, bridgeAddr, { gasLimit: 12000000 }), "Grant MINTER_ROLE to Native Bridge", "grantRole", ["MINTER_ROLE", bridgeAddr]);
|
|
138
|
+
return bridge;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deploys a PrivacyBridgeWETH, sets oracle, and grants MINTER_ROLE.
|
|
143
|
+
* @returns {Promise<Contract>} Deployed bridge contract
|
|
144
|
+
*/
|
|
145
|
+
async function deployERC20Bridge(publicToken, privateToken, mockOracle, owner, logTx, addr, MINTER_ROLE) {
|
|
146
|
+
const pubAddr = await addr(publicToken);
|
|
147
|
+
const privAddr = await addr(privateToken);
|
|
148
|
+
const bridge = await (await ethers.getContractFactory("PrivacyBridgeWETH")).deploy(pubAddr, privAddr, owner.address, owner.address, await addr(mockOracle), { gasLimit: 12000000 });
|
|
149
|
+
await (bridge.waitForDeployment ? bridge.waitForDeployment() : bridge.deployed());
|
|
150
|
+
const bridgeAddr = await addr(bridge);
|
|
151
|
+
await logTx(await privateToken.grantRole(MINTER_ROLE, bridgeAddr, { gasLimit: 12000000 }), "Grant MINTER_ROLE to ERC20 Bridge", "grantRole", ["MINTER_ROLE", bridgeAddr]);
|
|
152
|
+
return bridge;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Gets or deploys a PrivateCOTI-compatible token based on chain.
|
|
157
|
+
* On testnet (chainId 7082400), attaches to pre-deployed PrivateCOTI.
|
|
158
|
+
* On local/hardhat, deploys a fresh PrivateERC20Mock.
|
|
159
|
+
* @param {string} [factoryName] - Factory to use on local ("PrivateERC20Mock" or "PrivateCOTI")
|
|
160
|
+
* @returns {Promise<Contract>}
|
|
161
|
+
*/
|
|
162
|
+
async function getOrDeployPrivateCoti(factoryName = "PrivateERC20Mock") {
|
|
163
|
+
const chainId = (await ethers.provider.getNetwork()).chainId;
|
|
164
|
+
if (chainId === 7082400n) {
|
|
165
|
+
const token = await ethers.getContractAt("PrivateCOTI", "0x03eeA59b1F0Dfeaece75531b27684DD882f79759");
|
|
166
|
+
console.log(" [Info] Using pre-deployed PrivateCOTI at 0x03eeA59b1F0Dfeaece75531b27684DD882f79759");
|
|
167
|
+
return token;
|
|
168
|
+
}
|
|
169
|
+
const Factory = await ethers.getContractFactory(factoryName);
|
|
170
|
+
const token = await Factory.deploy({ gasLimit: 30000000 });
|
|
171
|
+
await (token.waitForDeployment ? token.waitForDeployment() : token.deployed());
|
|
172
|
+
return token;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Full ERC20 bridge setup: deploys or attaches tokens based on chain, deploys bridge, mints initial supply.
|
|
177
|
+
* Consolidates the repeated testnet-vs-local branching across multiple test suites.
|
|
178
|
+
* @param {Object} opts - { publicFactory, decimals, mockOracle, owner, logTx, addr, MINTER_ROLE, mintAmount }
|
|
179
|
+
* @returns {Promise<{publicToken, privateToken, bridge}>}
|
|
180
|
+
*/
|
|
181
|
+
async function setupERC20BridgeEnv(opts) {
|
|
182
|
+
const { publicFactory, decimals, mockOracle, owner, logTx, addr, MINTER_ROLE, mintAmount } = opts;
|
|
183
|
+
const UNIT = BigInt(10 ** decimals);
|
|
184
|
+
let publicToken, privateToken, bridge;
|
|
185
|
+
|
|
186
|
+
const chainId = (await ethers.provider.getNetwork()).chainId;
|
|
187
|
+
if (chainId === 7082400n) {
|
|
188
|
+
const WETH_ADDRESS = "0x8bca4e6bbE402DB4aD189A316137aD08206154FB";
|
|
189
|
+
const PRIVATE_WETH_ADDRESS = "0x6f7E5eE3a913aa00c6eB9fEeCad57a7d02F7f45c";
|
|
190
|
+
publicToken = await ethers.getContractAt(publicFactory, WETH_ADDRESS);
|
|
191
|
+
privateToken = await ethers.getContractAt("PrivateWrappedEther", PRIVATE_WETH_ADDRESS);
|
|
192
|
+
console.log(` [Info] Using pre-deployed ${publicFactory} at ${WETH_ADDRESS}`);
|
|
193
|
+
console.log(` [Info] Using pre-deployed PrivateWrappedEther at ${PRIVATE_WETH_ADDRESS}`);
|
|
194
|
+
bridge = await deployERC20Bridge(publicToken, privateToken, mockOracle, owner, logTx, addr, MINTER_ROLE);
|
|
195
|
+
} else {
|
|
196
|
+
publicToken = await (await ethers.getContractFactory(publicFactory)).deploy("Wrapped Ether", "WETH", decimals, { gasLimit: 12000000 });
|
|
197
|
+
await (publicToken.waitForDeployment ? publicToken.waitForDeployment() : publicToken.deployed());
|
|
198
|
+
privateToken = await (await ethers.getContractFactory("PrivateERC20Mock")).deploy({ gasLimit: 12000000 });
|
|
199
|
+
await (privateToken.waitForDeployment ? privateToken.waitForDeployment() : privateToken.deployed());
|
|
200
|
+
bridge = await deployERC20Bridge(publicToken, privateToken, mockOracle, owner, logTx, addr, MINTER_ROLE);
|
|
201
|
+
if (mintAmount) {
|
|
202
|
+
await logTx(await publicToken.mint(owner.address, mintAmount * UNIT, { gasLimit: 2000000 }), `Mint ${mintAmount} WETH to owner`, "mint", [owner.address, String(mintAmount)]);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { publicToken, privateToken, bridge };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Asserts that a transaction promise reverts matching the given pattern.
|
|
210
|
+
* @param {Promise} txPromise - The transaction promise expected to revert
|
|
211
|
+
* @param {RegExp} pattern - Regex pattern to match against the error message
|
|
212
|
+
* @param {string} [logMsg] - Optional console message on success
|
|
213
|
+
*/
|
|
214
|
+
async function expectRevert(txPromise, pattern, logMsg) {
|
|
215
|
+
try {
|
|
216
|
+
const tx = await txPromise;
|
|
217
|
+
await waitForReceiptWithRetry(tx);
|
|
218
|
+
expect.fail("Expected revert but succeeded");
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error.message === "Expected revert but succeeded") throw error;
|
|
221
|
+
expect(error.message).to.match(pattern);
|
|
222
|
+
if (logMsg) console.log(logMsg);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handles the encrypted test skip pattern.
|
|
228
|
+
* @param {number} testCounter - Current test counter for log message
|
|
229
|
+
* @param {Object} cfg - Config object with .name property
|
|
230
|
+
* @param {string} testType - Description of the test type (e.g. "encrypted transfer")
|
|
231
|
+
* @returns {boolean} true if test should be skipped
|
|
232
|
+
*/
|
|
233
|
+
function skipIfNoEncryption(testCounter, cfg, testType) {
|
|
234
|
+
const env = validateEnvironment();
|
|
235
|
+
if (!env.canRunEncryptedTests) {
|
|
236
|
+
console.log(` [SKIP] Test ${testCounter} - ${cfg.name} ${testType}: PRIVATE_AES_KEY_TESTNET not configured`);
|
|
237
|
+
console.log(` [INFO] Add PRIVATE_AES_KEY_TESTNET to your .env file to enable encrypted payload tests`);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
describe("Unified Privacy Bridges Suite", function () {
|
|
244
|
+
this.timeout(1200000); // 20 minutes
|
|
245
|
+
|
|
246
|
+
// Ensure tests run on cotiTestnet or hardhat (for coverage)
|
|
247
|
+
before(function () {
|
|
248
|
+
const networkName = hre.network.name;
|
|
249
|
+
if (networkName !== "coti-testnet" && networkName !== "cotiTestnet" && networkName !== "hardhat") {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`These tests must run on coti-testnet, cotiTestnet, or hardhat network (current: ${networkName}). ` +
|
|
252
|
+
`Run with: npx hardhat test --network coti-testnet`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
let testCounter = 0;
|
|
258
|
+
let currentTestFullTitle = '';
|
|
259
|
+
let owner, user1;
|
|
260
|
+
|
|
261
|
+
// Shared map between test file and reporter (same Node.js process).
|
|
262
|
+
// Keyed by test fullTitle → array of call entries.
|
|
263
|
+
if (!process.__testCallLog) process.__testCallLog = {};
|
|
264
|
+
|
|
265
|
+
// Contract address registry — array of { contractName, address, suite }.
|
|
266
|
+
// Populated by registerContract() in each before() block.
|
|
267
|
+
if (!process.__contractAddresses) process.__contractAddresses = [];
|
|
268
|
+
|
|
269
|
+
async function registerContract(contractName, contract, suite) {
|
|
270
|
+
const address = await (contract.getAddress ? contract.getAddress() : Promise.resolve(contract.address));
|
|
271
|
+
process.__contractAddresses.push({ contractName, address, suite });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Fix for Ethers v5/v6 compatibility
|
|
275
|
+
const toBytes = ethers.toUtf8Bytes || ethers.utils?.toUtf8Bytes;
|
|
276
|
+
const keccak = ethers.keccak256 || ethers.utils?.keccak256;
|
|
277
|
+
const MINTER_ROLE = keccak(toBytes("MINTER_ROLE"));
|
|
278
|
+
|
|
279
|
+
const addr = async (contract) =>
|
|
280
|
+
contract.getAddress ? contract.getAddress() : Promise.resolve(contract.address);
|
|
281
|
+
|
|
282
|
+
// Set ONLY_PRIVATE_ERC20=1 to skip all non-PrivateERC20 suites (faster iteration)
|
|
283
|
+
const ONLY_PRIVATE_ERC20 = !!process.env.ONLY_PRIVATE_ERC20;
|
|
284
|
+
|
|
285
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
286
|
+
// DEDUPLICATION HELPERS (reduce repeated before() patterns)
|
|
287
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* One-liner check: skips the current suite if ONLY_PRIVATE_ERC20 is set.
|
|
291
|
+
* Must be called with the Mocha context: skipIfOnlyPrivateERC20.call(this)
|
|
292
|
+
*/
|
|
293
|
+
function skipIfOnlyPrivateERC20() {
|
|
294
|
+
if (ONLY_PRIVATE_ERC20) { this.skip(); return true; }
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Encapsulates the common native bridge before() setup logic.
|
|
300
|
+
* @param {string} suiteName - Name for contract registration
|
|
301
|
+
* @param {string} [factoryName] - Factory to use for PrivateCOTI ("PrivateERC20Mock" or "PrivateCOTI")
|
|
302
|
+
* @returns {Promise<{privateCoti, bridge, mockOracle}>}
|
|
303
|
+
*/
|
|
304
|
+
async function setupNativeBridgeEnv(suiteName, factoryName = "PrivateERC20Mock") {
|
|
305
|
+
const mockOracle = await deployMockOracle();
|
|
306
|
+
const privateCoti = await getOrDeployPrivateCoti(factoryName);
|
|
307
|
+
const bridge = await deployNativeBridge(privateCoti, mockOracle, owner, logTx, addr, MINTER_ROLE);
|
|
308
|
+
await registerContract("PrivacyBridgeCotiNative", bridge, suiteName);
|
|
309
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
310
|
+
return { privateCoti, bridge, mockOracle };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Encapsulates the common ERC20 bridge before() setup logic.
|
|
315
|
+
* @param {string} suiteName - Name for contract registration
|
|
316
|
+
* @param {bigint} [mintAmount] - Amount to mint to owner (default 10000n)
|
|
317
|
+
* @returns {Promise<{publicToken, privateToken, bridge, mockOracle}>}
|
|
318
|
+
*/
|
|
319
|
+
async function setupERC20BridgeSuite(suiteName, mintAmount = 10000n) {
|
|
320
|
+
const mockOracle = await deployMockOracle({ tokenSymbol: "ETH", tokenPrice: "2300" });
|
|
321
|
+
const result = await setupERC20BridgeEnv({
|
|
322
|
+
publicFactory: "ERC20Mock", decimals: 18, mockOracle, owner, logTx, addr, MINTER_ROLE, mintAmount
|
|
323
|
+
});
|
|
324
|
+
await registerContract("PrivacyBridgeWETH", result.bridge, suiteName);
|
|
325
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
326
|
+
return { publicToken: result.publicToken, privateToken: result.privateToken, bridge: result.bridge, mockOracle };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const logTx = async (tx, description, methodName = "Unknown", args = [], expectRevert = false) => {
|
|
330
|
+
const argsStr = args.length > 0 ? `(${args.map(a => (typeof a === 'string' && a.startsWith('0x')) ? a.slice(0, 10) + '...' : a).join(', ')})` : "()";
|
|
331
|
+
console.log(` [Method] ${methodName}${argsStr}`);
|
|
332
|
+
console.log(` [Action] ${description}`);
|
|
333
|
+
console.log(` [Tx] https://testnet.cotiscan.io/tx/${tx.hash}`);
|
|
334
|
+
|
|
335
|
+
// Record call for JSON report — write immediately so the reporter
|
|
336
|
+
// can read it at EVENT_TEST_PASS (which fires before afterEach).
|
|
337
|
+
const callEntry = {
|
|
338
|
+
method: methodName,
|
|
339
|
+
args: args.map(String),
|
|
340
|
+
txHash: tx.hash,
|
|
341
|
+
description,
|
|
342
|
+
};
|
|
343
|
+
if (currentTestFullTitle) {
|
|
344
|
+
if (!process.__testCallLog[currentTestFullTitle]) process.__testCallLog[currentTestFullTitle] = [];
|
|
345
|
+
process.__testCallLog[currentTestFullTitle].push(callEntry);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const receipt = expectRevert
|
|
349
|
+
? await waitForReceiptWithRetry(tx)
|
|
350
|
+
: await tx.wait(1, 300000); // 5-minute timeout; prevents indefinite hang on slow/unresponsive testnet
|
|
351
|
+
|
|
352
|
+
// Mandatory wait for cotiTestnet stability - COTI MPC state needs time to settle
|
|
353
|
+
await new Promise(r => setTimeout(r, 10000));
|
|
354
|
+
return receipt;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
before(async function () {
|
|
358
|
+
const signers = await ethers.getSigners();
|
|
359
|
+
owner = signers[0];
|
|
360
|
+
user1 = signers.length > 1 ? signers[1] : owner;
|
|
361
|
+
console.log("\n===========================================================");
|
|
362
|
+
console.log("STARTING FULL UNIFIED BRIDGE TESTS");
|
|
363
|
+
console.log("Deployer:", owner.address);
|
|
364
|
+
console.log("User1 :", user1.address);
|
|
365
|
+
console.log("===========================================================\n");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
beforeEach(function () {
|
|
369
|
+
testCounter++;
|
|
370
|
+
currentTestFullTitle = this.currentTest.fullTitle();
|
|
371
|
+
process.__testCallLog[currentTestFullTitle] = [];
|
|
372
|
+
console.log(`\nTest ${testCounter} - ${this.currentTest.title}`);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
afterEach(async function () {
|
|
376
|
+
const state = this.currentTest.state;
|
|
377
|
+
let label = 'FAILED';
|
|
378
|
+
if (state === 'passed') label = 'PASSED';
|
|
379
|
+
else if (state === 'pending') label = 'SKIPPED';
|
|
380
|
+
console.log(`Test ${testCounter} - result: ${label}`);
|
|
381
|
+
// Extra wait between separate test cases
|
|
382
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
386
|
+
// NATIVE BRIDGE TESTS
|
|
387
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
388
|
+
describe("Native Bridge (PrivacyBridgeCotiNative)", function () {
|
|
389
|
+
let privateCoti, bridge, mockOracle;
|
|
390
|
+
|
|
391
|
+
before(async function () {
|
|
392
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
393
|
+
const PrivateCotiFactory = await ethers.getContractFactory("PrivateERC20Mock");
|
|
394
|
+
privateCoti = await PrivateCotiFactory.deploy({ gasLimit: 12000000 });
|
|
395
|
+
await (privateCoti.waitForDeployment ? privateCoti.waitForDeployment() : privateCoti.deployed());
|
|
396
|
+
|
|
397
|
+
// Deploy mock oracle and set COTI price ($0.05)
|
|
398
|
+
mockOracle = await deployMockOracle();
|
|
399
|
+
|
|
400
|
+
bridge = await deployNativeBridge(privateCoti, mockOracle, owner, logTx, addr, MINTER_ROLE);
|
|
401
|
+
await registerContract("PrivacyBridgeCotiNative", bridge, "Native Bridge");
|
|
402
|
+
await registerContract("PrivateERC20Mock", privateCoti, "Native Bridge");
|
|
403
|
+
await registerContract("MockCotiPriceConsumer", mockOracle, "Native Bridge");
|
|
404
|
+
await new Promise(r => setTimeout(r, 5000)); // Extra settle time after role grant
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("Test 1: native: Should set correct initial state", async function () {
|
|
408
|
+
const pCotiAddr = await addr(privateCoti);
|
|
409
|
+
expect(await bridge.privateCoti()).to.equal(pCotiAddr);
|
|
410
|
+
expect(await bridge.owner()).to.equal(owner.address);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("Test 2: native: Should allow deposit of native COTI", async function () {
|
|
414
|
+
const amount = ethers.parseEther("100");
|
|
415
|
+
const bridgeAddr = await addr(bridge);
|
|
416
|
+
const initialBalance = await ethers.provider.getBalance(bridgeAddr);
|
|
417
|
+
|
|
418
|
+
// Estimate fee and get oracle timestamp
|
|
419
|
+
const [fee, cotiLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(amount);
|
|
420
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
421
|
+
|
|
422
|
+
const tx = await bridge.connect(user1)["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: amount, gasLimit: 12000000 });
|
|
423
|
+
await logTx(tx, `Deposit ${ethers.formatEther(amount)} Native COTI`, "PrivacyBridgeCotiNative.deposit() -> PrivateERC20Mock.mint", [ethers.formatEther(amount)]);
|
|
424
|
+
|
|
425
|
+
await expect(tx).to.emit(bridge, "Deposit");
|
|
426
|
+
expect(await ethers.provider.getBalance(bridgeAddr)).to.be.at.least(initialBalance + amount);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("Test 3: native: Should allow withdrawal of native COTI", async function () {
|
|
430
|
+
const amount = ethers.parseEther("50");
|
|
431
|
+
const bridgeAddr = await addr(bridge);
|
|
432
|
+
|
|
433
|
+
await logTx(await privateCoti.connect(user1)["approve(address,uint256)"](bridgeAddr, amount, { gasLimit: 2000000 }), "Approve private COTI for withdrawal", "PrivateERC20Mock.approve", [bridgeAddr, ethers.formatEther(amount)]);
|
|
434
|
+
|
|
435
|
+
// Estimate fee and get oracle timestamp
|
|
436
|
+
const [fee, cotiLastUpdated, blockTimestamp] = await bridge.estimateWithdrawFee(amount);
|
|
437
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
438
|
+
|
|
439
|
+
const tx = await bridge.connect(user1)["withdraw(uint256,uint256,uint256)"](amount, cotiLastUpdated, cotiLastUpdated, { gasLimit: 12000000 });
|
|
440
|
+
await logTx(tx, `Withdraw ${ethers.formatEther(amount)} Native COTI`, "PrivacyBridgeCotiNative.withdraw() -> PrivateERC20Mock.burn", [ethers.formatEther(amount)]);
|
|
441
|
+
|
|
442
|
+
await expect(tx).to.emit(bridge, "Withdraw");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("Test 4: native: Should deduct dynamic fee and let owner withdraw fees", async function () {
|
|
446
|
+
// Dynamic fee uses oracle-based calculation. With default params:
|
|
447
|
+
// depositFixedFee=10 COTI, depositPercentageBps=500, depositMaxFee=3000 COTI
|
|
448
|
+
// For a 0.1 COTI deposit at $0.05/COTI: txValueUsd=$0.005, pctFee=$0.0000025, pctFeeCoti=0.00005 COTI
|
|
449
|
+
// fee = max(10 COTI, 0.00005 COTI) = 10 COTI (floor dominates for small amounts)
|
|
450
|
+
// So we need to deposit more than the fixed fee floor (10 COTI)
|
|
451
|
+
const gross = ethers.parseEther("100");
|
|
452
|
+
|
|
453
|
+
const feeBefore = await bridge.accumulatedCotiFees();
|
|
454
|
+
|
|
455
|
+
// Estimate fee and get oracle timestamp
|
|
456
|
+
const [fee, cotiLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(gross);
|
|
457
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
458
|
+
|
|
459
|
+
await logTx(await bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: gross, gasLimit: 12000000 }), "Deposit for dynamic fee accumulation", "PrivacyBridgeCotiNative.deposit()", [ethers.formatEther(gross)]);
|
|
460
|
+
|
|
461
|
+
const feeAfter = await bridge.accumulatedCotiFees();
|
|
462
|
+
const actualFee = feeAfter - feeBefore;
|
|
463
|
+
expect(actualFee).to.be.gt(0n);
|
|
464
|
+
console.log(` [Info] Dynamic fee charged: ${ethers.formatEther(actualFee)} COTI`);
|
|
465
|
+
|
|
466
|
+
// Withdraw fees
|
|
467
|
+
await logTx(await bridge.withdrawFees(actualFee, { gasLimit: 2000000 }), "Withdraw accumulated COTI fees", "PrivacyBridgeCotiNative.withdrawFees", [ethers.formatEther(actualFee)]);
|
|
468
|
+
expect(await bridge.accumulatedCotiFees()).to.equal(feeBefore);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("Test 5: native: Should rescue native COTI", async function () {
|
|
472
|
+
// bridge should already have some balance
|
|
473
|
+
const bridgeAddr = await addr(bridge);
|
|
474
|
+
const bal = await ethers.provider.getBalance(bridgeAddr);
|
|
475
|
+
if (bal > 0n) {
|
|
476
|
+
const amount = bal / 2n;
|
|
477
|
+
const tx = await bridge.rescueNative(amount, { gasLimit: 2000000 });
|
|
478
|
+
await logTx(tx, `rescueNative ${ethers.formatEther(amount)} COTI`, "PrivacyBridgeCotiNative.rescueNative", [ethers.formatEther(amount)]);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
484
|
+
// ERC20 BRIDGE TESTS
|
|
485
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
486
|
+
const BRIDGE_CONFIGS = [
|
|
487
|
+
{ name: "WETH", publicFactory: "ERC20Mock", bridgeFactory: "PrivacyBridgeWETH", decimals: 18, testStart: 6 },
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
for (const cfg of BRIDGE_CONFIGS) {
|
|
491
|
+
describe(`ERC20 Bridge (${cfg.name})`, function () {
|
|
492
|
+
let publicToken, privateToken, bridge, mockOracle;
|
|
493
|
+
const UNIT = BigInt(10 ** cfg.decimals);
|
|
494
|
+
// Generous COTI fee to cover any dynamic fee calculation
|
|
495
|
+
const COTI_FEE_BUFFER = ethers.parseEther("3100");
|
|
496
|
+
|
|
497
|
+
before(async function () {
|
|
498
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
499
|
+
|
|
500
|
+
// Deploy mock oracle
|
|
501
|
+
mockOracle = await deployMockOracle({ tokenSymbol: "ETH", tokenPrice: "2300" });
|
|
502
|
+
|
|
503
|
+
const result = await setupERC20BridgeEnv({
|
|
504
|
+
publicFactory: cfg.publicFactory, decimals: cfg.decimals, mockOracle, owner, logTx, addr, MINTER_ROLE, mintAmount: 1000n
|
|
505
|
+
});
|
|
506
|
+
publicToken = result.publicToken;
|
|
507
|
+
privateToken = result.privateToken;
|
|
508
|
+
bridge = result.bridge;
|
|
509
|
+
|
|
510
|
+
await registerContract(cfg.bridgeFactory, bridge, `ERC20 Bridge (${cfg.name})`);
|
|
511
|
+
await registerContract(cfg.publicFactory, publicToken, `ERC20 Bridge (${cfg.name})`);
|
|
512
|
+
await registerContract("PrivateToken", privateToken, `ERC20 Bridge (${cfg.name})`);
|
|
513
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it(`Test ${cfg.testStart}: ${cfg.name}: Should set correct initial state`, async function () {
|
|
517
|
+
expect(await bridge.token()).to.equal(await addr(publicToken));
|
|
518
|
+
expect(await bridge.privateToken()).to.equal(await addr(privateToken));
|
|
519
|
+
expect(await bridge.owner()).to.equal(owner.address);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it(`Test ${cfg.testStart + 1}: ${cfg.name}: Should allow deposit`, async function () {
|
|
523
|
+
const amount = 10n * UNIT;
|
|
524
|
+
const bridgeAddr = await addr(bridge);
|
|
525
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), `Approve ${cfg.name} for bridge`, "MockWETH.approve", [bridgeAddr, "10"]);
|
|
526
|
+
|
|
527
|
+
// Estimate fee and get oracle timestamp
|
|
528
|
+
const [fee, cotiLastUpdated, tokenLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(amount);
|
|
529
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
530
|
+
|
|
531
|
+
const tx = await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 });
|
|
532
|
+
await logTx(tx, `Deposit ${amount / UNIT} ${cfg.name}`, `PrivacyBridgeERC20.deposit()`, ["10"]);
|
|
533
|
+
await expect(tx).to.emit(bridge, "Deposit");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it(`Test ${cfg.testStart + 2}: ${cfg.name}: Should allow withdrawal`, async function () {
|
|
537
|
+
const amount = 5n * UNIT;
|
|
538
|
+
const bridgeAddr = await addr(bridge);
|
|
539
|
+
await logTx(await privateToken["approve(address,uint256)"](bridgeAddr, amount, { gasLimit: 2000000 }), `Approve private ${cfg.name}`, "PrivateERC20Mock.approve", [bridgeAddr, "5"]);
|
|
540
|
+
|
|
541
|
+
// Estimate fee and get oracle timestamp
|
|
542
|
+
const [fee, cotiLastUpdated, tokenLastUpdated, blockTimestamp] = await bridge.estimateWithdrawFee(amount);
|
|
543
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
544
|
+
|
|
545
|
+
const tx = await bridge["withdraw(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 });
|
|
546
|
+
await logTx(tx, `Withdraw ${amount / UNIT} ${cfg.name}`, "PrivacyBridgeERC20.withdraw()", ["5"]);
|
|
547
|
+
await expect(tx).to.emit(bridge, "Withdraw");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it(`Test ${cfg.testStart + 3}: ${cfg.name}: Should track dynamic fees in accumulatedCotiFees`, async function () {
|
|
551
|
+
const bridgeAddr = await addr(bridge);
|
|
552
|
+
const amount = 100n * UNIT;
|
|
553
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for fee test", "MockWETH.approve", [bridgeAddr, "100"]);
|
|
554
|
+
const feeBefore = await bridge.accumulatedCotiFees();
|
|
555
|
+
|
|
556
|
+
// Estimate fee and get oracle timestamp
|
|
557
|
+
const [fee, cotiLastUpdated, tokenLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(amount);
|
|
558
|
+
console.log(` [Fee Estimation] fee=${ethers.formatEther(fee)} COTI, blockTimestamp=${blockTimestamp}`);
|
|
559
|
+
|
|
560
|
+
await logTx(await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), `Deposit 100 ${cfg.name} for fee test`, "PrivacyBridgeERC20.deposit()", ["100"]);
|
|
561
|
+
const feeAfter = await bridge.accumulatedCotiFees();
|
|
562
|
+
const actualFee = feeAfter - feeBefore;
|
|
563
|
+
expect(actualFee).to.be.gt(0n);
|
|
564
|
+
console.log(` [Info] Dynamic COTI fee charged: ${ethers.formatEther(actualFee)} COTI`);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it(`Test ${cfg.testStart + 4}: ${cfg.name}: Should rescue redundant ERC20 tokens`, async function () {
|
|
568
|
+
const bridgeAddr = await addr(bridge);
|
|
569
|
+
|
|
570
|
+
// Deploy a different ERC20 token (not the bridge token) to test rescue functionality
|
|
571
|
+
const StrayTokenFactory = await ethers.getContractFactory("ERC20Mock");
|
|
572
|
+
const strayToken = await StrayTokenFactory.deploy("Stray Token", "STRAY", 18, { gasLimit: 12000000 });
|
|
573
|
+
await (strayToken.waitForDeployment ? strayToken.waitForDeployment() : strayToken.deployed());
|
|
574
|
+
const strayAddr = await addr(strayToken);
|
|
575
|
+
|
|
576
|
+
// Mint stray tokens to bridge
|
|
577
|
+
await logTx(await strayToken.mint(bridgeAddr, UNIT, { gasLimit: 2000000 }), "Mint stray tokens to bridge", "ERC20Mock.mint", [bridgeAddr, "1"]);
|
|
578
|
+
|
|
579
|
+
// Rescue the stray tokens (not the bridge token)
|
|
580
|
+
await logTx(await bridge.rescueERC20(strayAddr, UNIT, { gasLimit: 2000000 }), "Rescue stray tokens", "PrivacyBridgeERC20.rescueERC20", [strayAddr, "1"]);
|
|
581
|
+
|
|
582
|
+
// Verify owner received the rescued tokens
|
|
583
|
+
const ownerBalance = await strayToken.balanceOf(owner.address);
|
|
584
|
+
expect(ownerBalance).to.be.at.least(UNIT);
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
590
|
+
// PRIVATE ERC20 PUBLIC-AMOUNT FUNCTION TESTS
|
|
591
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
592
|
+
const PRIVATE_TOKEN_CONFIGS = [
|
|
593
|
+
{ name: "PrivateCOTI", factory: "PrivateCOTI", testStart: 22 },
|
|
594
|
+
{ name: "PrivateWrappedEther", factory: "PrivateWrappedEther", testStart: 33 },
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
for (const cfg of PRIVATE_TOKEN_CONFIGS) {
|
|
598
|
+
describe(`PrivateERC20 Public-Amount Functions (${cfg.name})`, function () {
|
|
599
|
+
let token;
|
|
600
|
+
const MINT_AMOUNT = ethers.parseEther("10");
|
|
601
|
+
const BURN_AMOUNT = ethers.parseEther("1");
|
|
602
|
+
const TRANSFER_AMOUNT = ethers.parseEther("2");
|
|
603
|
+
const APPROVE_AMOUNT = ethers.parseEther("2");
|
|
604
|
+
|
|
605
|
+
before(async function () {
|
|
606
|
+
const chainId = (await ethers.provider.getNetwork()).chainId;
|
|
607
|
+
if (chainId === 7082400n) {
|
|
608
|
+
// Pre-deployed testnet contracts — fresh PrivateERC20-based deployments
|
|
609
|
+
// exceed the block gas limit on current COTI testnet
|
|
610
|
+
const TESTNET_TOKENS = {
|
|
611
|
+
"PrivateCOTI": "0x03eeA59b1F0Dfeaece75531b27684DD882f79759",
|
|
612
|
+
"PrivateWrappedEther": "0x6f7E5eE3a913aa00c6eB9fEeCad57a7d02F7f45c"
|
|
613
|
+
};
|
|
614
|
+
token = await ethers.getContractAt(cfg.factory, TESTNET_TOKENS[cfg.name]);
|
|
615
|
+
console.log(` [Info] Using pre-deployed ${cfg.name} at ${TESTNET_TOKENS[cfg.name]}`);
|
|
616
|
+
|
|
617
|
+
// Onboard wallet with COTI MPC so validateCiphertext works for encrypted ops
|
|
618
|
+
const { Wallet: CotiWallet, onboard: cotiOnboard, ONBOARD_CONTRACT_ADDRESS: cotiOnboardAddr } = require('@coti-io/coti-ethers');
|
|
619
|
+
const pk = process.env.PRIVATE_KEY || '';
|
|
620
|
+
const cotiWallet = new CotiWallet(pk.startsWith('0x') ? pk : '0x' + pk, ethers.provider);
|
|
621
|
+
try {
|
|
622
|
+
console.log(` [Info] Onboarding wallet ${cotiWallet.address} for MPC encrypted operations...`);
|
|
623
|
+
const { aesKey } = await cotiOnboard(cotiOnboardAddr, cotiWallet);
|
|
624
|
+
process.env.PRIVATE_AES_KEY_TESTNET = aesKey;
|
|
625
|
+
console.log(` [Info] Onboarding complete. AES key (first 8 chars): ${aesKey.slice(0, 8)}...`);
|
|
626
|
+
} catch(e) {
|
|
627
|
+
console.log(` [Warn] Onboarding failed: ${e.message}. Encrypted tests may fail.`);
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
const Factory = await ethers.getContractFactory(cfg.factory);
|
|
631
|
+
token = await Factory.deploy({ gasLimit: 30000000 });
|
|
632
|
+
await (token.waitForDeployment ? token.waitForDeployment() : token.deployed());
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// deployer holds DEFAULT_ADMIN_ROLE → grant MINTER_ROLE to owner
|
|
636
|
+
await logTx(
|
|
637
|
+
await token.grantRole(MINTER_ROLE, owner.address, { gasLimit: 2000000 }),
|
|
638
|
+
`Grant MINTER_ROLE to owner for ${cfg.name}`,
|
|
639
|
+
`${cfg.name}.grantRole`,
|
|
640
|
+
["MINTER_ROLE", owner.address]
|
|
641
|
+
);
|
|
642
|
+
await registerContract(cfg.factory, token, `PrivateERC20 Public-Amount (${cfg.name})`);
|
|
643
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it(`Test ${cfg.testStart}: ${cfg.name}: mint(address,uint256) should mint tokens and emit Transfer`, async function () {
|
|
647
|
+
const tx = await token.connect(owner)["mint(address,uint256)"](owner.address, MINT_AMOUNT, { gasLimit: 12000000 });
|
|
648
|
+
await logTx(tx, `mint(owner, 10) on ${cfg.name}`, `${cfg.name}.mint(address,uint256)`, [owner.address, "10"]);
|
|
649
|
+
await expect(tx).to.emit(token, "Transfer");
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it(`Test ${cfg.testStart + 1}: ${cfg.name}: mint(address,uint256) should revert for non-MINTER_ROLE`, async function () {
|
|
653
|
+
try {
|
|
654
|
+
const tx = await token.connect(user1)["mint(address,uint256)"](user1.address, MINT_AMOUNT, { gasLimit: 2000000 });
|
|
655
|
+
// If transaction is submitted, wait for it to be processed with retry logic
|
|
656
|
+
await waitForReceiptWithRetry(tx);
|
|
657
|
+
// If we reach here, the transaction didn't revert as expected
|
|
658
|
+
expect.fail("Expected transaction to revert but it succeeded");
|
|
659
|
+
} catch (error) {
|
|
660
|
+
// Transaction should revert - verify it's an expected revert
|
|
661
|
+
expect(error.message).to.match(/revert|AccessControl/i);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it(`Test ${cfg.testStart + 2}: ${cfg.name}: burn(uint256) should burn tokens and emit Transfer`, async function () {
|
|
666
|
+
const tx = await token.connect(owner)["burn(uint256)"](BURN_AMOUNT, { gasLimit: 12000000 });
|
|
667
|
+
await logTx(tx, `burn(1) on ${cfg.name}`, `${cfg.name}.burn(uint256)`, ["1"]);
|
|
668
|
+
await expect(tx).to.emit(token, "Transfer");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it(`Test ${cfg.testStart + 3}: ${cfg.name}: transfer(address,uint256) should transfer tokens and emit Transfer`, async function () {
|
|
672
|
+
const tx = await token.connect(owner)["transfer(address,uint256)"](user1.address, TRANSFER_AMOUNT, { gasLimit: 12000000 });
|
|
673
|
+
await logTx(tx, `transfer(user1, 2) on ${cfg.name}`, `${cfg.name}.transfer(address,uint256)`, [user1.address, "2"]);
|
|
674
|
+
await expect(tx).to.emit(token, "Transfer");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it(`Test ${cfg.testStart + 4}: ${cfg.name}: approve(address,uint256) should set allowance and emit Approval`, async function () {
|
|
678
|
+
const tx = await token.connect(owner)["approve(address,uint256)"](user1.address, APPROVE_AMOUNT, { gasLimit: 12000000 });
|
|
679
|
+
await logTx(tx, `approve(user1, 2) on ${cfg.name}`, `${cfg.name}.approve(address,uint256)`, [user1.address, "2"]);
|
|
680
|
+
await expect(tx).to.emit(token, "Approval");
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it(`Test ${cfg.testStart + 5}: ${cfg.name}: transferFrom(address,address,uint256) should spend allowance and emit Transfer`, async function () {
|
|
684
|
+
const tx = await token.connect(user1)["transferFrom(address,address,uint256)"](owner.address, user1.address, APPROVE_AMOUNT, { gasLimit: 12000000 });
|
|
685
|
+
await logTx(tx, `transferFrom(owner→user1, 2) on ${cfg.name}`, `${cfg.name}.transferFrom(address,address,uint256)`, [owner.address, user1.address, "2"]);
|
|
686
|
+
await expect(tx).to.emit(token, "Transfer");
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// --- NEW EXTENDED EXPERIMENTAL TESTS ---
|
|
690
|
+
|
|
691
|
+
it(`Test ${cfg.testStart + 6}: ${cfg.name} (extend): setAccountEncryptionAddress & accountEncryptionAddress`, async function () {
|
|
692
|
+
// Use a valid Ethereum address to avoid ENS resolution by provider
|
|
693
|
+
const mockEncryptionAddress = ethers.Wallet.createRandom().address;
|
|
694
|
+
const tx = await token.connect(user1).setAccountEncryptionAddress(mockEncryptionAddress, { gasLimit: 2000000 });
|
|
695
|
+
await logTx(tx, `setAccountEncryptionAddress on ${cfg.name}`, `${cfg.name}.setAccountEncryptionAddress`, ["<mock_address>"]);
|
|
696
|
+
|
|
697
|
+
const registeredKey = await token.accountEncryptionAddress(user1.address);
|
|
698
|
+
expect(registeredKey).to.not.be.empty;
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it(`Test ${cfg.testStart + 7}: ${cfg.name} (extend): transferAndCall (ERC677) triggers onTokenReceived`, async function () {
|
|
702
|
+
const ReceiverFactory = await ethers.getContractFactory("PublicTokenReceiverMock");
|
|
703
|
+
const receiver = await ReceiverFactory.deploy({ gasLimit: 12000000 });
|
|
704
|
+
await (receiver.waitForDeployment ? receiver.waitForDeployment() : receiver.deployed());
|
|
705
|
+
const receiverAddr = await addr(receiver);
|
|
706
|
+
|
|
707
|
+
// Allow COTI MPC cluster to settle after the receiver deployment before the
|
|
708
|
+
// transferAndCall transaction (mirrors the fix applied to the encrypted variant).
|
|
709
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
710
|
+
|
|
711
|
+
const amount = ethers.parseEther("0.1");
|
|
712
|
+
const data = ethers.toUtf8Bytes("hello world");
|
|
713
|
+
|
|
714
|
+
const tx = await token.connect(owner)["transferAndCall(address,uint256,bytes)"](receiverAddr, amount, data, { gasLimit: 12000000 });
|
|
715
|
+
// logTx awaits tx.wait() + mandatory 10-second MPC settle. Store the receipt so
|
|
716
|
+
// that event assertions below use it directly and avoid a second tx.wait() call —
|
|
717
|
+
// on COTI testnet the RPC can return null for a receipt that was just retrieved
|
|
718
|
+
// moments ago (MPC state still finalizing), which would cause tx.wait() to poll
|
|
719
|
+
// indefinitely and make the test appear to loop in the terminal.
|
|
720
|
+
const receipt = await logTx(tx, `transferAndCall to PublicTokenReceiverMock on ${cfg.name}`, `${cfg.name}.transferAndCall`, [receiverAddr, "0.1", "0x68656c..."]);
|
|
721
|
+
|
|
722
|
+
// PublicTokenReceiverMock returns true and does not emit a callback event.
|
|
723
|
+
// Verify token transfer event from the token logs.
|
|
724
|
+
const transferEvents = receipt.logs.filter(log => {
|
|
725
|
+
try { return token.interface.parseLog(log)?.name === "Transfer"; } catch { return false; }
|
|
726
|
+
});
|
|
727
|
+
expect(transferEvents.length, "Expected Transfer event on token").to.be.greaterThan(0);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it(`Test ${cfg.testStart + 8}: ${cfg.name} (extend): transfer(itUint256,address) with encrypted payload`, async function () {
|
|
731
|
+
if (skipIfNoEncryption(testCounter, cfg, "encrypted transfer")) {
|
|
732
|
+
this.skip();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const amount = ethers.parseEther("0.5");
|
|
737
|
+
const tokenAddr = await addr(token);
|
|
738
|
+
// The signature for the overloaded encrypted transfer in PrivateERC20 (address first, itUint256 second)
|
|
739
|
+
const selector = ethers.id("transfer(address,((uint256,uint256),bytes))").slice(0, 10);
|
|
740
|
+
|
|
741
|
+
const itAmount = await buildItUint256(amount, tokenAddr, selector);
|
|
742
|
+
|
|
743
|
+
const tx = await token.connect(owner)["transfer(address,((uint256,uint256),bytes))"](user1.address, itAmount, { gasLimit: 12000000 });
|
|
744
|
+
await logTx(tx, `transfer(itUint256) on ${cfg.name}`, `${cfg.name}.transfer(to, itAmount)`, [user1.address, "<encrypted>"]);
|
|
745
|
+
expect(tx.hash).to.not.be.empty;
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it(`Test ${cfg.testStart + 9}: ${cfg.name} (extend): approve(address,itUint256) and allowance checks`, async function () {
|
|
749
|
+
if (skipIfNoEncryption(testCounter, cfg, "encrypted approve")) {
|
|
750
|
+
this.skip();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const amount = ethers.parseEther("2");
|
|
755
|
+
const tokenAddr = await addr(token);
|
|
756
|
+
const selector = ethers.id("approve(address,((uint256,uint256),bytes))").slice(0, 10);
|
|
757
|
+
|
|
758
|
+
const itAmount = await buildItUint256(amount, tokenAddr, selector);
|
|
759
|
+
|
|
760
|
+
const tx = await token.connect(owner)["approve(address,((uint256,uint256),bytes))"](user1.address, itAmount, { gasLimit: 12000000 });
|
|
761
|
+
await logTx(tx, `approve(itUint256) on ${cfg.name}`, `${cfg.name}.approve(spender, itAmount)`, [user1.address, "<encrypted>"]);
|
|
762
|
+
|
|
763
|
+
// Run allowance re-encryption
|
|
764
|
+
const reencryptTx = await token.connect(owner)["reencryptAllowance(address,bool)"](user1.address, true, { gasLimit: 2000000 });
|
|
765
|
+
await logTx(reencryptTx, `reencryptAllowance on ${cfg.name}`, `${cfg.name}.reencryptAllowance`, [user1.address, true]);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
it(`Test ${cfg.testStart + 10}: ${cfg.name} (extend): transferFrom(address,address,itUint256)`, async function () {
|
|
769
|
+
if (skipIfNoEncryption(testCounter, cfg, "encrypted transferFrom")) {
|
|
770
|
+
this.skip();
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const amount = ethers.parseEther("0.1");
|
|
775
|
+
const tokenAddr = await addr(token);
|
|
776
|
+
// msg.sender (owner) acts as both from and spender — ciphertext is signed for owner's wallet.
|
|
777
|
+
const selector = ethers.id("transferFrom(address,address,((uint256,uint256),bytes))").slice(0, 10);
|
|
778
|
+
|
|
779
|
+
// Approve first, then build the ciphertext so the AES key and on-chain state
|
|
780
|
+
// are both settled before validateCiphertext runs.
|
|
781
|
+
// Use logTx so the mandatory 10-second MPC state-settle window runs before
|
|
782
|
+
// transferFrom reads _allowances[owner][owner] from the MPC precompile. A bare
|
|
783
|
+
// 3-second wait is insufficient on COTI testnet and causes validateCiphertext /
|
|
784
|
+
// _safeOnboard to read a stale (zero) allowance, reverting with
|
|
785
|
+
// "ERC20: insufficient allowance".
|
|
786
|
+
await logTx(
|
|
787
|
+
await token.connect(owner)["approve(address,uint256)"](owner.address, ethers.parseEther("1"), { gasLimit: 2000000 }),
|
|
788
|
+
`Approve owner as spender for ${cfg.name} encrypted transferFrom`,
|
|
789
|
+
`${cfg.name}.approve(address,uint256)`,
|
|
790
|
+
[owner.address, "1"]
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
// Build the itUint256 AFTER the approve has settled so the ciphertext is
|
|
794
|
+
// freshly signed against the confirmed on-chain state.
|
|
795
|
+
const itAmount = await buildItUint256(amount, tokenAddr, selector);
|
|
796
|
+
|
|
797
|
+
const tx = await token.connect(owner)["transferFrom(address,address,((uint256,uint256),bytes))"](owner.address, user1.address, itAmount, { gasLimit: 12000000 });
|
|
798
|
+
await logTx(tx, `transferFrom(itUint256) on ${cfg.name}`, `${cfg.name}.transferFrom(from, to, itAmount)`, [owner.address, user1.address, "<encrypted>"]);
|
|
799
|
+
expect(tx.hash).to.not.be.empty;
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
805
|
+
// COVERAGE IMPROVEMENT TESTS
|
|
806
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
807
|
+
|
|
808
|
+
describe("Coverage Improvements - PrivacyBridgeCotiNative", function () {
|
|
809
|
+
let privateCoti, bridge, mockOracle;
|
|
810
|
+
|
|
811
|
+
before(async function () {
|
|
812
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
813
|
+
const env = await setupNativeBridgeEnv("Dynamic Fee - Native", "PrivateCOTI");
|
|
814
|
+
privateCoti = env.privateCoti; bridge = env.bridge; mockOracle = env.mockOracle;
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it("Test 63: dynamic-fee: Should verify default fee parameters", async function () {
|
|
818
|
+
const depositFixed = await bridge.depositFixedFee();
|
|
819
|
+
const depositPct = await bridge.depositPercentageBps();
|
|
820
|
+
const depositMax = await bridge.depositMaxFee();
|
|
821
|
+
const withdrawFixed = await bridge.withdrawFixedFee();
|
|
822
|
+
const withdrawPct = await bridge.withdrawPercentageBps();
|
|
823
|
+
const withdrawMax = await bridge.withdrawMaxFee();
|
|
824
|
+
|
|
825
|
+
console.log(` [Info] Deposit: fixed=${ethers.formatEther(depositFixed)}, pct=${depositPct}, max=${ethers.formatEther(depositMax)}`);
|
|
826
|
+
console.log(` [Info] Withdraw: fixed=${ethers.formatEther(withdrawFixed)}, pct=${withdrawPct}, max=${ethers.formatEther(withdrawMax)}`);
|
|
827
|
+
|
|
828
|
+
expect(depositFixed).to.equal(ethers.parseEther("10"));
|
|
829
|
+
expect(depositPct).to.equal(500n);
|
|
830
|
+
expect(depositMax).to.equal(ethers.parseEther("3000"));
|
|
831
|
+
expect(withdrawFixed).to.equal(ethers.parseEther("3"));
|
|
832
|
+
expect(withdrawPct).to.equal(250n);
|
|
833
|
+
expect(withdrawMax).to.equal(ethers.parseEther("1500"));
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("Test 64: dynamic-fee: estimateDepositFee returns correct fee and timestamps", async function () {
|
|
837
|
+
const amount = ethers.parseEther("100");
|
|
838
|
+
const [fee, cotiLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(amount);
|
|
839
|
+
|
|
840
|
+
console.log(` [Info] estimateDepositFee(100 COTI): fee=${ethers.formatEther(fee)}, lastUpdated=${cotiLastUpdated}, blockTs=${blockTimestamp}`);
|
|
841
|
+
|
|
842
|
+
// 100 COTI at $0.05 = $5 USD value
|
|
843
|
+
// pctFee = $5 * 500/1000000 = $0.0025
|
|
844
|
+
// pctFeeCoti = $0.0025 / $0.05 = 0.05 COTI
|
|
845
|
+
// fee = max(10 COTI, 0.05 COTI) = 10 COTI (floor dominates)
|
|
846
|
+
expect(fee).to.equal(ethers.parseEther("10"));
|
|
847
|
+
expect(cotiLastUpdated).to.be.gt(0n);
|
|
848
|
+
expect(blockTimestamp).to.be.gt(0n);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it("Test 65: dynamic-fee: estimateWithdrawFee returns correct fee and timestamps", async function () {
|
|
852
|
+
const amount = ethers.parseEther("100");
|
|
853
|
+
const [fee, cotiLastUpdated, blockTimestamp] = await bridge.estimateWithdrawFee(amount);
|
|
854
|
+
|
|
855
|
+
console.log(` [Info] estimateWithdrawFee(100 COTI): fee=${ethers.formatEther(fee)}, lastUpdated=${cotiLastUpdated}, blockTs=${blockTimestamp}`);
|
|
856
|
+
|
|
857
|
+
// 100 COTI at $0.05 = $5 USD value
|
|
858
|
+
// pctFee = $5 * 250/1000000 = $0.00125
|
|
859
|
+
// pctFeeCoti = $0.00125 / $0.05 = 0.025 COTI
|
|
860
|
+
// fee = max(3 COTI, 0.025 COTI) = 3 COTI (floor dominates)
|
|
861
|
+
expect(fee).to.equal(ethers.parseEther("3"));
|
|
862
|
+
expect(cotiLastUpdated).to.be.gt(0n);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it("Test 66: dynamic-fee: Percentage fee dominates for large deposits", async function () {
|
|
866
|
+
// 1,000,000 COTI at $0.05 = $50,000 USD
|
|
867
|
+
// pctFee = $50,000 * 500/1,000,000 = $25
|
|
868
|
+
// pctFeeCoti = $25 / $0.05 = 500 COTI
|
|
869
|
+
// fee = max(10, 500) = 500, min(500, 3000) = 500 COTI
|
|
870
|
+
const amount = ethers.parseEther("1000000");
|
|
871
|
+
const [fee] = await bridge.estimateDepositFee(amount);
|
|
872
|
+
console.log(` [Info] estimateDepositFee(1M COTI): fee=${ethers.formatEther(fee)} COTI`);
|
|
873
|
+
expect(fee).to.equal(ethers.parseEther("500"));
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("Test 67: dynamic-fee: Max fee cap applies for very large deposits", async function () {
|
|
877
|
+
// 100,000,000 COTI at $0.05 = $5,000,000 USD
|
|
878
|
+
// pctFee = $5M * 500/1M = $2,500
|
|
879
|
+
// pctFeeCoti = $2,500 / $0.05 = 50,000 COTI
|
|
880
|
+
// fee = max(10, 50000) = 50000, min(50000, 3000) = 3000 COTI (cap)
|
|
881
|
+
const amount = ethers.parseEther("100000000");
|
|
882
|
+
const [fee] = await bridge.estimateDepositFee(amount);
|
|
883
|
+
console.log(` [Info] estimateDepositFee(100M COTI): fee=${ethers.formatEther(fee)} COTI (capped)`);
|
|
884
|
+
expect(fee).to.equal(ethers.parseEther("3000"));
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it("Test 68: dynamic-fee: setDepositDynamicFee updates parameters", async function () {
|
|
888
|
+
const newFixed = ethers.parseEther("20");
|
|
889
|
+
const newPct = 1000n; // 0.1%
|
|
890
|
+
const newMax = ethers.parseEther("5000");
|
|
891
|
+
|
|
892
|
+
const tx = await bridge.setDepositDynamicFee(newFixed, newPct, newMax, { gasLimit: 2000000 });
|
|
893
|
+
await logTx(tx, "setDepositDynamicFee(20, 1000, 5000)", "setDepositDynamicFee", ["20", "1000", "5000"]);
|
|
894
|
+
|
|
895
|
+
expect(await bridge.depositFixedFee()).to.equal(newFixed);
|
|
896
|
+
expect(await bridge.depositPercentageBps()).to.equal(newPct);
|
|
897
|
+
expect(await bridge.depositMaxFee()).to.equal(newMax);
|
|
898
|
+
|
|
899
|
+
// Restore defaults
|
|
900
|
+
const restoreTx = await bridge.setDepositDynamicFee(ethers.parseEther("10"), 500n, ethers.parseEther("3000"), { gasLimit: 2000000 });
|
|
901
|
+
await logTx(restoreTx, "Restore deposit fee defaults", "setDepositDynamicFee", ["10", "500", "3000"]);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it("Test 69: dynamic-fee: setWithdrawDynamicFee updates parameters", async function () {
|
|
905
|
+
const newFixed = ethers.parseEther("5");
|
|
906
|
+
const newPct = 500n;
|
|
907
|
+
const newMax = ethers.parseEther("2000");
|
|
908
|
+
|
|
909
|
+
const tx = await bridge.setWithdrawDynamicFee(newFixed, newPct, newMax, { gasLimit: 2000000 });
|
|
910
|
+
await logTx(tx, "setWithdrawDynamicFee(5, 500, 2000)", "setWithdrawDynamicFee", ["5", "500", "2000"]);
|
|
911
|
+
|
|
912
|
+
expect(await bridge.withdrawFixedFee()).to.equal(newFixed);
|
|
913
|
+
expect(await bridge.withdrawPercentageBps()).to.equal(newPct);
|
|
914
|
+
expect(await bridge.withdrawMaxFee()).to.equal(newMax);
|
|
915
|
+
|
|
916
|
+
// Restore defaults
|
|
917
|
+
const restoreTx = await bridge.setWithdrawDynamicFee(ethers.parseEther("3"), 250n, ethers.parseEther("1500"), { gasLimit: 2000000 });
|
|
918
|
+
await logTx(restoreTx, "Restore withdraw fee defaults", "setWithdrawDynamicFee", ["3", "250", "1500"]);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
it("Test 70: dynamic-fee: setDepositDynamicFee reverts if fixedFee > maxFee", async function () {
|
|
922
|
+
await expectRevert(bridge.setDepositDynamicFee(ethers.parseEther("5000"), 500n, ethers.parseEther("3000"), { gasLimit: 2000000 }), /InvalidFeeConfiguration|revert/i, " [Info] Correctly reverted: fixedFee > maxFee");
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it("Test 71: dynamic-fee: setDepositDynamicFee reverts if maxFee is 0", async function () {
|
|
926
|
+
await expectRevert(bridge.setDepositDynamicFee(0n, 500n, 0n, { gasLimit: 2000000 }), /InvalidFeeConfiguration|revert/i, " [Info] Correctly reverted: maxFee == 0");
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
it("Test 72: dynamic-fee: setDepositDynamicFee reverts if percentageBps > MAX_FEE_UNITS", async function () {
|
|
930
|
+
await expectRevert(bridge.setDepositDynamicFee(ethers.parseEther("10"), 200000n, ethers.parseEther("3000"), { gasLimit: 2000000 }), /InvalidFee|revert/i, " [Info] Correctly reverted: percentageBps > MAX_FEE_UNITS");
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
it("Test 73: dynamic-fee: setPriceOracle reverts for zero address", async function () {
|
|
934
|
+
await expectRevert(bridge.setPriceOracle(ethers.ZeroAddress, { gasLimit: 2000000 }), /InvalidAddress|revert/i, " [Info] Correctly reverted: oracle = address(0)");
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it("Test 74: dynamic-fee: setPriceOracle emits PriceOracleUpdated", async function () {
|
|
938
|
+
const oracleAddr = await addr(mockOracle);
|
|
939
|
+
// Set to a new address then restore
|
|
940
|
+
const newOracle = ethers.Wallet.createRandom().address;
|
|
941
|
+
const tx = await bridge.setPriceOracle(newOracle, { gasLimit: 2000000 });
|
|
942
|
+
await logTx(tx, "setPriceOracle to random address", "setPriceOracle", [newOracle]);
|
|
943
|
+
await expect(tx).to.emit(bridge, "PriceOracleUpdated");
|
|
944
|
+
|
|
945
|
+
// Restore original oracle
|
|
946
|
+
const restoreTx = await bridge.setPriceOracle(oracleAddr, { gasLimit: 2000000 });
|
|
947
|
+
await logTx(restoreTx, "Restore original oracle", "setPriceOracle", [oracleAddr]);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it("Test 75: dynamic-fee: totalUserLiability increases on deposit", async function () {
|
|
951
|
+
const liabilityBefore = await bridge.totalUserLiability();
|
|
952
|
+
const amount = ethers.parseEther("100");
|
|
953
|
+
|
|
954
|
+
const [fee, cotiLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
955
|
+
const tx = await bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: amount, gasLimit: 12000000 });
|
|
956
|
+
await logTx(tx, "Deposit 100 COTI for liability tracking", "deposit", ["100"]);
|
|
957
|
+
|
|
958
|
+
const liabilityAfter = await bridge.totalUserLiability();
|
|
959
|
+
const netDeposit = amount - fee;
|
|
960
|
+
console.log(` [Info] Liability before=${ethers.formatEther(liabilityBefore)}, after=${ethers.formatEther(liabilityAfter)}, net=${ethers.formatEther(netDeposit)}`);
|
|
961
|
+
expect(liabilityAfter - liabilityBefore).to.equal(netDeposit);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it("Test 76: dynamic-fee: totalUserLiability decreases on withdraw", async function () {
|
|
965
|
+
const amount = ethers.parseEther("50");
|
|
966
|
+
const bridgeAddr = await addr(bridge);
|
|
967
|
+
|
|
968
|
+
await logTx(await privateCoti.connect(owner)["approve(address,uint256)"](bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for liability withdraw test", "approve", [bridgeAddr, "50"]);
|
|
969
|
+
|
|
970
|
+
const liabilityBefore = await bridge.totalUserLiability();
|
|
971
|
+
const [fee, cotiLastUpdated] = await bridge.estimateWithdrawFee(amount);
|
|
972
|
+
|
|
973
|
+
const tx = await bridge["withdraw(uint256,uint256,uint256)"](amount, cotiLastUpdated, cotiLastUpdated, { gasLimit: 12000000 });
|
|
974
|
+
await logTx(tx, "Withdraw 50 COTI for liability tracking", "withdraw", ["50"]);
|
|
975
|
+
|
|
976
|
+
const liabilityAfter = await bridge.totalUserLiability();
|
|
977
|
+
const netWithdraw = amount - fee;
|
|
978
|
+
console.log(` [Info] Liability before=${ethers.formatEther(liabilityBefore)}, after=${ethers.formatEther(liabilityAfter)}, net=${ethers.formatEther(netWithdraw)}`);
|
|
979
|
+
expect(liabilityBefore - liabilityAfter).to.equal(netWithdraw);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it("Test 77: dynamic-fee: feeRecipient and rescueRecipient are set correctly", async function () {
|
|
983
|
+
const feeRecip = await bridge.feeRecipient();
|
|
984
|
+
const rescueRecip = await bridge.rescueRecipient();
|
|
985
|
+
console.log(` [Info] feeRecipient=${feeRecip}, rescueRecipient=${rescueRecip}`);
|
|
986
|
+
expect(feeRecip).to.equal(owner.address);
|
|
987
|
+
expect(rescueRecip).to.equal(owner.address);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it("Test 78: dynamic-fee: AccessControlEnumerable role member tracking", async function () {
|
|
991
|
+
const OPERATOR_ROLE = await bridge.OPERATOR_ROLE();
|
|
992
|
+
const operatorCount = await bridge.getRoleMemberCount(OPERATOR_ROLE);
|
|
993
|
+
console.log(` [Info] Operator count: ${operatorCount}`);
|
|
994
|
+
expect(operatorCount).to.be.gte(1n);
|
|
995
|
+
|
|
996
|
+
const firstOperator = await bridge.getRoleMember(OPERATOR_ROLE, 0);
|
|
997
|
+
console.log(` [Info] First operator: ${firstOperator}`);
|
|
998
|
+
expect(firstOperator).to.equal(owner.address);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it("Test 79: dynamic-fee: DynamicFeeUpdated event emitted on setDepositDynamicFee", async function () {
|
|
1002
|
+
const tx = await bridge.setDepositDynamicFee(ethers.parseEther("10"), 500n, ethers.parseEther("3000"), { gasLimit: 2000000 });
|
|
1003
|
+
await logTx(tx, "setDepositDynamicFee for event check", "setDepositDynamicFee", ["10", "500", "3000"]);
|
|
1004
|
+
await expect(tx).to.emit(bridge, "DynamicFeeUpdated");
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
it("Test 80: dynamic-fee: receive() direct deposit works without timestamps", async function () {
|
|
1008
|
+
const bridgeAddr = await addr(bridge);
|
|
1009
|
+
const amount = ethers.parseEther("100");
|
|
1010
|
+
const feeBefore = await bridge.accumulatedCotiFees();
|
|
1011
|
+
|
|
1012
|
+
const tx = await owner.sendTransaction({ to: bridgeAddr, value: amount, gasLimit: 2000000 });
|
|
1013
|
+
await logTx(tx, "Direct COTI transfer (receive fallback) for fee check", "receive()", [ethers.formatEther(amount)]);
|
|
1014
|
+
|
|
1015
|
+
const feeAfter = await bridge.accumulatedCotiFees();
|
|
1016
|
+
const feeCharged = feeAfter - feeBefore;
|
|
1017
|
+
console.log(` [Info] Fee charged via receive(): ${ethers.formatEther(feeCharged)} COTI`);
|
|
1018
|
+
expect(feeCharged).to.be.gt(0n);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it("Test 81: dynamic-fee: withdrawFees sends to feeRecipient", async function () {
|
|
1022
|
+
const fees = await bridge.accumulatedCotiFees();
|
|
1023
|
+
if (fees === 0n) {
|
|
1024
|
+
console.log(" [Info] No accumulated fees to withdraw, skipping");
|
|
1025
|
+
this.skip();
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const withdrawAmount = fees / 2n > 0n ? fees / 2n : fees;
|
|
1029
|
+
|
|
1030
|
+
const tx = await bridge.withdrawFees(withdrawAmount, { gasLimit: 2000000 });
|
|
1031
|
+
await logTx(tx, `withdrawFees(${ethers.formatEther(withdrawAmount)})`, "withdrawFees", [ethers.formatEther(withdrawAmount)]);
|
|
1032
|
+
|
|
1033
|
+
await expect(tx).to.emit(bridge, "FeesWithdrawn");
|
|
1034
|
+
console.log(` [Info] Withdrew ${ethers.formatEther(withdrawAmount)} COTI fees to feeRecipient`);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("Test 82: dynamic-fee: setNativeCotiFee reverts on native bridge", async function () {
|
|
1038
|
+
await expectRevert(bridge.setNativeCotiFee(ethers.parseEther("1"), { gasLimit: 2000000 }), /NativeCotiFeeNotApplicable|revert/i, " [Info] Correctly reverted: NativeCotiFeeNotApplicable");
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1043
|
+
// DYNAMIC FEE FEATURES - ERC20 BRIDGE
|
|
1044
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1045
|
+
|
|
1046
|
+
describe("Dynamic Fee Features - ERC20 Bridge", function () {
|
|
1047
|
+
let publicToken, bridge;
|
|
1048
|
+
const UNIT = BigInt(10 ** 18);
|
|
1049
|
+
const COTI_FEE_BUFFER = ethers.parseEther("3100");
|
|
1050
|
+
|
|
1051
|
+
before(async function () {
|
|
1052
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
1053
|
+
const env = await setupERC20BridgeSuite("Dynamic Fee - ERC20", 10000n);
|
|
1054
|
+
publicToken = env.publicToken;
|
|
1055
|
+
bridge = env.bridge;
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
it("Test 83: dynamic-fee-erc20: estimateDepositFee returns correct fee for ERC20", async function () {
|
|
1059
|
+
// 10 WETH at $2300 = $23,000 USD
|
|
1060
|
+
// pctFee = $23,000 * 500/1,000,000 = $11.50
|
|
1061
|
+
// pctFeeCoti = $11.50 / $0.05 = 230 COTI
|
|
1062
|
+
// fee = max(10, 230) = 230, min(230, 3000) = 230 COTI
|
|
1063
|
+
const amount = 10n * UNIT;
|
|
1064
|
+
const [fee, cotiLastUpdated, tokenLastUpdated, blockTimestamp] = await bridge.estimateDepositFee(amount);
|
|
1065
|
+
console.log(` [Info] estimateDepositFee(10 WETH): fee=${ethers.formatEther(fee)} COTI`);
|
|
1066
|
+
expect(fee).to.equal(ethers.parseEther("230"));
|
|
1067
|
+
expect(cotiLastUpdated).to.be.gt(0n);
|
|
1068
|
+
expect(tokenLastUpdated).to.be.gt(0n);
|
|
1069
|
+
expect(blockTimestamp).to.be.gt(0n);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
it("Test 84: dynamic-fee-erc20: estimateWithdrawFee returns correct fee for ERC20", async function () {
|
|
1073
|
+
// 10 WETH at $2300 = $23,000 USD
|
|
1074
|
+
// pctFee = $23,000 * 250/1,000,000 = $5.75
|
|
1075
|
+
// pctFeeCoti = $5.75 / $0.05 = 115 COTI
|
|
1076
|
+
// fee = max(3, 115) = 115, min(115, 1500) = 115 COTI
|
|
1077
|
+
const amount = 10n * UNIT;
|
|
1078
|
+
const [fee] = await bridge.estimateWithdrawFee(amount);
|
|
1079
|
+
console.log(` [Info] estimateWithdrawFee(10 WETH): fee=${ethers.formatEther(fee)} COTI`);
|
|
1080
|
+
expect(fee).to.equal(ethers.parseEther("115"));
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
it("Test 85: dynamic-fee-erc20: Max fee cap applies for large ERC20 deposits", async function () {
|
|
1084
|
+
// 1000 WETH at $2300 = $2,300,000 USD
|
|
1085
|
+
// pctFee = $2,300,000 * 500/1,000,000 = $1,150
|
|
1086
|
+
// pctFeeCoti = $1,150 / $0.05 = 23,000 COTI
|
|
1087
|
+
// fee = max(10, 23000) = 23000, min(23000, 3000) = 3000 COTI (capped)
|
|
1088
|
+
const amount = 1000n * UNIT;
|
|
1089
|
+
const [fee] = await bridge.estimateDepositFee(amount);
|
|
1090
|
+
console.log(` [Info] estimateDepositFee(1000 WETH): fee=${ethers.formatEther(fee)} COTI (capped)`);
|
|
1091
|
+
expect(fee).to.equal(ethers.parseEther("3000"));
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
it("Test 86: dynamic-fee-erc20: tokenSymbol returns correct oracle symbol", async function () {
|
|
1095
|
+
const symbol = await bridge.tokenSymbol();
|
|
1096
|
+
console.log(` [Info] tokenSymbol: ${symbol}`);
|
|
1097
|
+
expect(symbol).to.equal("ETH");
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
it("Test 87: dynamic-fee-erc20: Full deposit collects COTI fee from msg.value", async function () {
|
|
1101
|
+
const amount = 10n * UNIT;
|
|
1102
|
+
const bridgeAddr = await addr(bridge);
|
|
1103
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve WETH for deposit", "approve", [bridgeAddr, "10"]);
|
|
1104
|
+
|
|
1105
|
+
const feeBefore = await bridge.accumulatedCotiFees();
|
|
1106
|
+
const [fee, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
1107
|
+
console.log(` [Info] Estimated fee: ${ethers.formatEther(fee)} COTI`);
|
|
1108
|
+
|
|
1109
|
+
const tx = await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 });
|
|
1110
|
+
await logTx(tx, "Deposit 10 WETH with COTI fee", "deposit", ["10"]);
|
|
1111
|
+
|
|
1112
|
+
const feeAfter = await bridge.accumulatedCotiFees();
|
|
1113
|
+
const actualFee = feeAfter - feeBefore;
|
|
1114
|
+
console.log(` [Info] Actual COTI fee collected: ${ethers.formatEther(actualFee)}`);
|
|
1115
|
+
expect(actualFee).to.equal(fee);
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
it("Test 88: dynamic-fee-erc20: Deposit reverts with insufficient COTI fee", async function () {
|
|
1119
|
+
const amount = 10n * UNIT;
|
|
1120
|
+
const bridgeAddr = await addr(bridge);
|
|
1121
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve WETH for insufficient fee test", "approve", [bridgeAddr, "10"]);
|
|
1122
|
+
|
|
1123
|
+
const [fee, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
// Send less than the required fee
|
|
1127
|
+
const insufficientFee = fee / 2n;
|
|
1128
|
+
const tx = await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: insufficientFee, gasLimit: 12000000 });
|
|
1129
|
+
await waitForReceiptWithRetry(tx);
|
|
1130
|
+
expect.fail("Expected revert but succeeded");
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
expect(error.message).to.match(/InsufficientCotiFee|revert/i);
|
|
1133
|
+
console.log(" [Info] Correctly reverted: InsufficientCotiFee");
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1139
|
+
// BLACKLIST & ACCESS CONTROL TESTS
|
|
1140
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1141
|
+
|
|
1142
|
+
describe("Blacklist & Access Control", function () {
|
|
1143
|
+
let bridge;
|
|
1144
|
+
|
|
1145
|
+
before(async function () {
|
|
1146
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
1147
|
+
const env = await setupNativeBridgeEnv("Blacklist & Access Control");
|
|
1148
|
+
bridge = env.bridge;
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
it("Test 89: blacklist: addToBlacklist blocks deposit", async function () {
|
|
1152
|
+
// Blacklist the owner address
|
|
1153
|
+
const tx = await bridge.addToBlacklist(owner.address, { gasLimit: 2000000 });
|
|
1154
|
+
await logTx(tx, "addToBlacklist(owner)", "addToBlacklist", [owner.address]);
|
|
1155
|
+
await expect(tx).to.emit(bridge, "Blacklisted");
|
|
1156
|
+
|
|
1157
|
+
expect(await bridge.blacklisted(owner.address)).to.equal(true);
|
|
1158
|
+
|
|
1159
|
+
// Try to deposit — should revert
|
|
1160
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1161
|
+
await expectRevert(bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("100"), gasLimit: 12000000 }), /AddressBlacklisted|revert/i, " [Info] Correctly reverted: AddressBlacklisted on deposit");
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
it("Test 90: blacklist: removeFromBlacklist restores access", async function () {
|
|
1165
|
+
const tx = await bridge.removeFromBlacklist(owner.address, { gasLimit: 2000000 });
|
|
1166
|
+
await logTx(tx, "removeFromBlacklist(owner)", "removeFromBlacklist", [owner.address]);
|
|
1167
|
+
await expect(tx).to.emit(bridge, "UnBlacklisted");
|
|
1168
|
+
|
|
1169
|
+
expect(await bridge.blacklisted(owner.address)).to.equal(false);
|
|
1170
|
+
|
|
1171
|
+
// Deposit should now work
|
|
1172
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1173
|
+
const depositTx = await bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("100"), gasLimit: 12000000 });
|
|
1174
|
+
await logTx(depositTx, "Deposit after unblacklist", "deposit", ["100"]);
|
|
1175
|
+
await expect(depositTx).to.emit(bridge, "Deposit");
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
it("Test 91: blacklist: addToBlacklist reverts for zero address", async function () {
|
|
1179
|
+
await expectRevert(bridge.addToBlacklist(ethers.ZeroAddress, { gasLimit: 2000000 }), /InvalidAddress|revert/i, " [Info] Correctly reverted: addToBlacklist(address(0))");
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
it("Test 92: access-control: addOperator and removeOperator work correctly", async function () {
|
|
1183
|
+
const randomAddr = ethers.Wallet.createRandom().address;
|
|
1184
|
+
|
|
1185
|
+
const addTx = await bridge.addOperator(randomAddr, { gasLimit: 2000000 });
|
|
1186
|
+
await logTx(addTx, "addOperator(random)", "addOperator", [randomAddr]);
|
|
1187
|
+
await expect(addTx).to.emit(bridge, "OperatorAdded");
|
|
1188
|
+
|
|
1189
|
+
const isOp = await bridge.isOperator(randomAddr);
|
|
1190
|
+
expect(isOp).to.equal(true);
|
|
1191
|
+
|
|
1192
|
+
const removeTx = await bridge.removeOperator(randomAddr, { gasLimit: 2000000 });
|
|
1193
|
+
await logTx(removeTx, "removeOperator(random)", "removeOperator", [randomAddr]);
|
|
1194
|
+
await expect(removeTx).to.emit(bridge, "OperatorRemoved");
|
|
1195
|
+
|
|
1196
|
+
const isOpAfter = await bridge.isOperator(randomAddr);
|
|
1197
|
+
expect(isOpAfter).to.equal(false);
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
it("Test 93: access-control: pause blocks deposits, unpause restores", async function () {
|
|
1201
|
+
const pauseTx = await bridge.pause({ gasLimit: 2000000 });
|
|
1202
|
+
await logTx(pauseTx, "pause()", "pause", []);
|
|
1203
|
+
|
|
1204
|
+
// Try deposit while paused
|
|
1205
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1206
|
+
await expectRevert(bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("100"), gasLimit: 12000000 }), /Pausable|paused|revert/i, " [Info] Correctly reverted: deposit while paused");
|
|
1207
|
+
|
|
1208
|
+
// Unpause
|
|
1209
|
+
const unpauseTx = await bridge.unpause({ gasLimit: 2000000 });
|
|
1210
|
+
await logTx(unpauseTx, "unpause()", "unpause", []);
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it("Test 94: access-control: renounceOwnership is disabled", async function () {
|
|
1214
|
+
await expectRevert(bridge.renounceOwnership({ gasLimit: 2000000 }), /renounceOwnership disabled|revert/i, " [Info] Correctly reverted: renounceOwnership disabled");
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
it("Test 95: access-control: setIsDepositEnabled toggles deposits", async function () {
|
|
1218
|
+
// Disable deposits
|
|
1219
|
+
const disableTx = await bridge.setIsDepositEnabled(false, { gasLimit: 2000000 });
|
|
1220
|
+
await logTx(disableTx, "setIsDepositEnabled(false)", "setIsDepositEnabled", ["false"]);
|
|
1221
|
+
expect(await bridge.isDepositEnabled()).to.equal(false);
|
|
1222
|
+
|
|
1223
|
+
// Try deposit — should revert
|
|
1224
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1225
|
+
await expectRevert(bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("100"), gasLimit: 12000000 }), /DepositDisabled|revert/i, " [Info] Correctly reverted: DepositDisabled");
|
|
1226
|
+
|
|
1227
|
+
// Re-enable
|
|
1228
|
+
const enableTx = await bridge.setIsDepositEnabled(true, { gasLimit: 2000000 });
|
|
1229
|
+
await logTx(enableTx, "setIsDepositEnabled(true)", "setIsDepositEnabled", ["true"]);
|
|
1230
|
+
expect(await bridge.isDepositEnabled()).to.equal(true);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it("Test 96: access-control: setLimits enforces deposit/withdraw bounds", async function () {
|
|
1234
|
+
const minDep = ethers.parseEther("50");
|
|
1235
|
+
const maxDep = ethers.parseEther("10000");
|
|
1236
|
+
const minWith = ethers.parseEther("10");
|
|
1237
|
+
const maxWith = ethers.parseEther("5000");
|
|
1238
|
+
|
|
1239
|
+
const tx = await bridge.setLimits(minDep, maxDep, minWith, maxWith, { gasLimit: 2000000 });
|
|
1240
|
+
await logTx(tx, "setLimits(50, 10000, 10, 5000)", "setLimits", ["50", "10000", "10", "5000"]);
|
|
1241
|
+
await expect(tx).to.emit(bridge, "LimitsUpdated");
|
|
1242
|
+
|
|
1243
|
+
expect(await bridge.minDepositAmount()).to.equal(minDep);
|
|
1244
|
+
expect(await bridge.maxDepositAmount()).to.equal(maxDep);
|
|
1245
|
+
|
|
1246
|
+
// Try deposit below minimum
|
|
1247
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("10"));
|
|
1248
|
+
await expectRevert(bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("10"), gasLimit: 12000000 }), /DepositBelowMinimum|revert/i, " [Info] Correctly reverted: DepositBelowMinimum");
|
|
1249
|
+
|
|
1250
|
+
// Restore defaults
|
|
1251
|
+
const restoreTx = await bridge.setLimits(1n, ethers.MaxUint256, 1n, ethers.MaxUint256, { gasLimit: 2000000 });
|
|
1252
|
+
await logTx(restoreTx, "Restore default limits", "setLimits", ["1", "max", "1", "max"]);
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1258
|
+
// FULL COVERAGE — NATIVE BRIDGE REVERT PATHS
|
|
1259
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1260
|
+
|
|
1261
|
+
describe("Full Coverage - Native Bridge Reverts", function () {
|
|
1262
|
+
let bridge;
|
|
1263
|
+
|
|
1264
|
+
before(async function () {
|
|
1265
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
1266
|
+
const env = await setupNativeBridgeEnv("Full Coverage - Native Reverts");
|
|
1267
|
+
bridge = env.bridge;
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it("Test 97: revert: native deposit with zero value reverts AmountZero", async function () {
|
|
1271
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1272
|
+
await expectRevert(bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: 0n, gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: deposit with value=0");
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
it("Test 98: revert: native withdraw with amount=0 reverts AmountZero", async function () {
|
|
1276
|
+
const [, cotiLastUpdated] = await bridge.estimateWithdrawFee(ethers.parseEther("100"));
|
|
1277
|
+
await expectRevert(bridge["withdraw(uint256,uint256,uint256)"](0n, cotiLastUpdated, cotiLastUpdated, { gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: withdraw with amount=0");
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it("Test 99: revert: withdrawFees(0) reverts AmountZero", async function () {
|
|
1281
|
+
await expectRevert(bridge.withdrawFees(0n, { gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: withdrawFees(0)");
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
it("Test 100: revert: withdrawFees exceeding accumulated reverts InsufficientAccumulatedFees", async function () {
|
|
1285
|
+
await expectRevert(bridge.withdrawFees(ethers.parseEther("999999"), { gasLimit: 2000000 }), /InsufficientAccumulatedFees|revert/i, " [Info] Correctly reverted: withdrawFees > accumulated");
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it("Test 101: revert: rescueNative(0) reverts AmountZero", async function () {
|
|
1289
|
+
await expectRevert(bridge.rescueNative(0n, { gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: rescueNative(0)");
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
it("Test 102: revert: rescueNative exceeding rescueable reverts ExceedsRescueableAmount", async function () {
|
|
1293
|
+
// First deposit to create some balance and fees
|
|
1294
|
+
const [, cotiLastUpdated] = await bridge.estimateDepositFee(ethers.parseEther("100"));
|
|
1295
|
+
await logTx(await bridge["deposit(uint256,uint256)"](cotiLastUpdated, cotiLastUpdated, { value: ethers.parseEther("100"), gasLimit: 12000000 }), "Deposit for rescue test", "deposit", ["100"]);
|
|
1296
|
+
|
|
1297
|
+
// Try to rescue more than balance minus fees
|
|
1298
|
+
const bal = await ethers.provider.getBalance(await addr(bridge));
|
|
1299
|
+
await expectRevert(bridge.rescueNative(bal, { gasLimit: 2000000 }), /ExceedsRescueableAmount|InsufficientEthBalance|revert/i, " [Info] Correctly reverted: rescueNative exceeds rescueable");
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
it("Test 103: revert: receive() reverts when deposits disabled", async function () {
|
|
1303
|
+
await logTx(await bridge.setIsDepositEnabled(false, { gasLimit: 2000000 }), "Disable deposits for receive test", "setIsDepositEnabled", ["false"]);
|
|
1304
|
+
|
|
1305
|
+
try {
|
|
1306
|
+
const tx = await owner.sendTransaction({ to: await addr(bridge), value: ethers.parseEther("10"), gasLimit: 2000000 });
|
|
1307
|
+
await waitForReceiptWithRetry(tx);
|
|
1308
|
+
expect.fail("Expected revert");
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
expect(error.message).to.match(/DepositDisabled|revert/i);
|
|
1311
|
+
console.log(" [Info] Correctly reverted: receive() when deposits disabled");
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
await logTx(await bridge.setIsDepositEnabled(true, { gasLimit: 2000000 }), "Re-enable deposits", "setIsDepositEnabled", ["true"]);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it("Test 104: revert: setLimits with min > max reverts InvalidLimitConfiguration", async function () {
|
|
1318
|
+
await expectRevert(bridge.setLimits(ethers.parseEther("100"), ethers.parseEther("10"), 1n, ethers.MaxUint256, { gasLimit: 2000000 }), /InvalidLimitConfiguration|revert/i, " [Info] Correctly reverted: minDeposit > maxDeposit");
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
it("Test 105: revert: setDepositFee exceeding MAX_FEE_UNITS reverts InvalidFee", async function () {
|
|
1322
|
+
await expectRevert(bridge.setDepositFee(200000n, { gasLimit: 2000000 }), /InvalidFee|revert/i, " [Info] Correctly reverted: setDepositFee > MAX_FEE_UNITS");
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("Test 106: legacy: setDepositFee and setWithdrawFee update basis points", async function () {
|
|
1326
|
+
const tx1 = await bridge.setDepositFee(1000n, { gasLimit: 2000000 });
|
|
1327
|
+
await logTx(tx1, "setDepositFee(1000)", "setDepositFee", ["1000"]);
|
|
1328
|
+
expect(await bridge.depositFeeBasisPoints()).to.equal(1000n);
|
|
1329
|
+
|
|
1330
|
+
const tx2 = await bridge.setWithdrawFee(500n, { gasLimit: 2000000 });
|
|
1331
|
+
await logTx(tx2, "setWithdrawFee(500)", "setWithdrawFee", ["500"]);
|
|
1332
|
+
expect(await bridge.withdrawFeeBasisPoints()).to.equal(500n);
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
it("Test 107: revert: setWithdrawFee exceeding MAX_FEE_UNITS reverts InvalidFee", async function () {
|
|
1336
|
+
await expectRevert(bridge.setWithdrawFee(200000n, { gasLimit: 2000000 }), /InvalidFee|revert/i, " [Info] Correctly reverted: setWithdrawFee > MAX_FEE_UNITS");
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
it("Test 108: transferOwnership revokes old operators and grants to new owner", async function () {
|
|
1340
|
+
const newOwner = ethers.Wallet.createRandom().address;
|
|
1341
|
+
const OPERATOR_ROLE = await bridge.OPERATOR_ROLE();
|
|
1342
|
+
|
|
1343
|
+
// Check current operator count
|
|
1344
|
+
const opCountBefore = await bridge.getRoleMemberCount(OPERATOR_ROLE);
|
|
1345
|
+
console.log(` [Info] Operators before transfer: ${opCountBefore}`);
|
|
1346
|
+
|
|
1347
|
+
const tx = await bridge.transferOwnership(newOwner, { gasLimit: 2000000 });
|
|
1348
|
+
await logTx(tx, `transferOwnership to ${newOwner}`, "transferOwnership", [newOwner]);
|
|
1349
|
+
|
|
1350
|
+
// New owner should have operator role
|
|
1351
|
+
expect(await bridge.isOperator(newOwner)).to.equal(true);
|
|
1352
|
+
// Old owner should NOT have operator role
|
|
1353
|
+
expect(await bridge.isOperator(owner.address)).to.equal(false);
|
|
1354
|
+
// New owner is the owner
|
|
1355
|
+
expect(await bridge.owner()).to.equal(newOwner);
|
|
1356
|
+
|
|
1357
|
+
console.log(" [Info] Ownership transferred, old operators revoked, new owner has roles");
|
|
1358
|
+
// NOTE: We can't transfer back since we don't have the new owner's private key.
|
|
1359
|
+
// This bridge instance is now owned by a random address — subsequent tests use different bridges.
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1364
|
+
// FULL COVERAGE — ERC20 BRIDGE REVERT PATHS
|
|
1365
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1366
|
+
|
|
1367
|
+
describe("Full Coverage - ERC20 Bridge Reverts", function () {
|
|
1368
|
+
let publicToken, privateToken, bridge;
|
|
1369
|
+
const UNIT = BigInt(10 ** 18);
|
|
1370
|
+
const COTI_FEE_BUFFER = ethers.parseEther("3100");
|
|
1371
|
+
|
|
1372
|
+
before(async function () {
|
|
1373
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
1374
|
+
const env = await setupERC20BridgeSuite("Full Coverage - ERC20 Reverts", 10000n);
|
|
1375
|
+
publicToken = env.publicToken; privateToken = env.privateToken;
|
|
1376
|
+
bridge = env.bridge;
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
it("Test 109: revert: ERC20 deposit with amount=0 reverts AmountZero", async function () {
|
|
1380
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(1n * UNIT);
|
|
1381
|
+
await expectRevert(bridge["deposit(uint256,uint256,uint256)"](0n, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: ERC20 deposit amount=0");
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
it("Test 110: revert: ERC20 withdraw with amount=0 reverts AmountZero", async function () {
|
|
1385
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateWithdrawFee(1n * UNIT);
|
|
1386
|
+
await expectRevert(bridge["withdraw(uint256,uint256,uint256)"](0n, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: ERC20 withdraw amount=0");
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
it("Test 111: revert: ERC20 deposit when deposits disabled reverts DepositDisabled", async function () {
|
|
1390
|
+
await logTx(await bridge.setIsDepositEnabled(false, { gasLimit: 2000000 }), "Disable deposits (ERC20)", "setIsDepositEnabled", ["false"]);
|
|
1391
|
+
|
|
1392
|
+
const amount = 1n * UNIT;
|
|
1393
|
+
const bridgeAddr = await addr(bridge);
|
|
1394
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for disabled deposit test", "approve", [bridgeAddr, "1"]);
|
|
1395
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
1396
|
+
|
|
1397
|
+
await expectRevert(bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), /DepositDisabled|revert/i, " [Info] Correctly reverted: ERC20 deposit when disabled");
|
|
1398
|
+
|
|
1399
|
+
await logTx(await bridge.setIsDepositEnabled(true, { gasLimit: 2000000 }), "Re-enable deposits (ERC20)", "setIsDepositEnabled", ["true"]);
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
it("Test 112: revert: ERC20 withdraw with InsufficientBridgeLiquidity", async function () {
|
|
1403
|
+
// Try to withdraw more than the bridge holds (bridge has 0 tokens)
|
|
1404
|
+
const amount = 1000n * UNIT;
|
|
1405
|
+
const bridgeAddr = await addr(bridge);
|
|
1406
|
+
await logTx(await privateToken["approve(address,uint256)"](bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for liquidity test", "approve", [bridgeAddr, "1000"]);
|
|
1407
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateWithdrawFee(amount);
|
|
1408
|
+
|
|
1409
|
+
await expectRevert(bridge["withdraw(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), /InsufficientBridgeLiquidity|revert/i, " [Info] Correctly reverted: InsufficientBridgeLiquidity");
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
it("Test 113: revert: rescueERC20 with private token reverts CannotRescueBridgeToken", async function () {
|
|
1413
|
+
const privAddr = await addr(privateToken);
|
|
1414
|
+
await expectRevert(bridge.rescueERC20(privAddr, 1n, { gasLimit: 2000000 }), /CannotRescueBridgeToken|revert/i, " [Info] Correctly reverted: rescueERC20(privateToken)");
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
it("Test 114: revert: rescueERC20 with amount=0 reverts AmountZero", async function () {
|
|
1418
|
+
const pubAddr = await addr(publicToken);
|
|
1419
|
+
await expectRevert(bridge.rescueERC20(pubAddr, 0n, { gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: rescueERC20 amount=0");
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
it("Test 115: ERC20 setNativeCotiFee works (not native bridge)", async function () {
|
|
1423
|
+
const tx = await bridge.setNativeCotiFee(ethers.parseEther("5"), { gasLimit: 2000000 });
|
|
1424
|
+
await logTx(tx, "setNativeCotiFee(5) on ERC20 bridge", "setNativeCotiFee", ["5"]);
|
|
1425
|
+
expect(await bridge.nativeCotiFee()).to.equal(ethers.parseEther("5"));
|
|
1426
|
+
await expect(tx).to.emit(bridge, "NativeCotiFeeUpdated");
|
|
1427
|
+
|
|
1428
|
+
// Reset
|
|
1429
|
+
const resetTx = await bridge.setNativeCotiFee(0n, { gasLimit: 2000000 });
|
|
1430
|
+
await logTx(resetTx, "Reset nativeCotiFee to 0", "setNativeCotiFee", ["0"]);
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it("Test 116: ERC20 withdrawCotiFees happy path", async function () {
|
|
1434
|
+
// First deposit to accumulate some COTI fees
|
|
1435
|
+
const amount = 10n * UNIT;
|
|
1436
|
+
const bridgeAddr = await addr(bridge);
|
|
1437
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for withdrawCotiFees test", "approve", [bridgeAddr, "10"]);
|
|
1438
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
1439
|
+
await logTx(await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), "Deposit for COTI fee accumulation", "deposit", ["10"]);
|
|
1440
|
+
|
|
1441
|
+
const fees = await bridge.accumulatedCotiFees();
|
|
1442
|
+
console.log(` [Info] Accumulated COTI fees: ${ethers.formatEther(fees)}`);
|
|
1443
|
+
expect(fees).to.be.gt(0n);
|
|
1444
|
+
|
|
1445
|
+
const withdrawAmount = fees / 2n;
|
|
1446
|
+
const tx = await bridge.withdrawCotiFees(withdrawAmount, { gasLimit: 2000000 });
|
|
1447
|
+
await logTx(tx, `withdrawCotiFees(${ethers.formatEther(withdrawAmount)})`, "withdrawCotiFees", [ethers.formatEther(withdrawAmount)]);
|
|
1448
|
+
await expect(tx).to.emit(bridge, "CotiFeesWithdrawn");
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
it("Test 117: revert: withdrawCotiFees(0) reverts AmountZero", async function () {
|
|
1452
|
+
await expectRevert(bridge.withdrawCotiFees(0n, { gasLimit: 2000000 }), /AmountZero|revert/i, " [Info] Correctly reverted: withdrawCotiFees(0)");
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
it("Test 118: revert: withdrawCotiFees exceeding accumulated reverts", async function () {
|
|
1456
|
+
await expectRevert(bridge.withdrawCotiFees(ethers.parseEther("999999"), { gasLimit: 2000000 }), /InsufficientAccumulatedFees|revert/i, " [Info] Correctly reverted: withdrawCotiFees > accumulated");
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it("Test 119: ERC20 totalUserLiability tracks deposit and withdraw", async function () {
|
|
1460
|
+
const amount = 5n * UNIT;
|
|
1461
|
+
const bridgeAddr = await addr(bridge);
|
|
1462
|
+
|
|
1463
|
+
// Deposit
|
|
1464
|
+
await logTx(await publicToken.approve(bridgeAddr, amount, { gasLimit: 2000000 }), "Approve for liability test", "approve", [bridgeAddr, "5"]);
|
|
1465
|
+
const liabilityBefore = await bridge.totalUserLiability();
|
|
1466
|
+
const [, cotiLastUpdated, tokenLastUpdated] = await bridge.estimateDepositFee(amount);
|
|
1467
|
+
await logTx(await bridge["deposit(uint256,uint256,uint256)"](amount, cotiLastUpdated, tokenLastUpdated, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), "Deposit for ERC20 liability test", "deposit", ["5"]);
|
|
1468
|
+
const liabilityAfterDeposit = await bridge.totalUserLiability();
|
|
1469
|
+
expect(liabilityAfterDeposit).to.be.gt(liabilityBefore);
|
|
1470
|
+
console.log(` [Info] Liability after deposit: ${ethers.formatEther(liabilityAfterDeposit)}`);
|
|
1471
|
+
|
|
1472
|
+
// Withdraw
|
|
1473
|
+
await logTx(await privateToken["approve(address,uint256)"](bridgeAddr, amount, { gasLimit: 2000000 }), "Approve private for liability withdraw", "approve", [bridgeAddr, "5"]);
|
|
1474
|
+
const [, cotiLastUpdated2, tokenLastUpdated2] = await bridge.estimateWithdrawFee(amount);
|
|
1475
|
+
await logTx(await bridge["withdraw(uint256,uint256,uint256)"](amount, cotiLastUpdated2, tokenLastUpdated2, { value: COTI_FEE_BUFFER, gasLimit: 12000000 }), "Withdraw for ERC20 liability test", "withdraw", ["5"]);
|
|
1476
|
+
const liabilityAfterWithdraw = await bridge.totalUserLiability();
|
|
1477
|
+
expect(liabilityAfterWithdraw).to.be.lt(liabilityAfterDeposit);
|
|
1478
|
+
console.log(` [Info] Liability after withdraw: ${ethers.formatEther(liabilityAfterWithdraw)}`);
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
it("Test 120: revert: addOperator with zero address reverts InvalidAddress", async function () {
|
|
1482
|
+
await expectRevert(bridge.addOperator(ethers.ZeroAddress, { gasLimit: 2000000 }), /InvalidAddress|revert/i, " [Info] Correctly reverted: addOperator(address(0))");
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
it("Test 121: revert: removeOperator with zero address reverts InvalidAddress", async function () {
|
|
1486
|
+
await expectRevert(bridge.removeOperator(ethers.ZeroAddress, { gasLimit: 2000000 }), /InvalidAddress|revert/i, " [Info] Correctly reverted: removeOperator(address(0))");
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
it("Test 122: revert: removeFromBlacklist with zero address reverts InvalidAddress", async function () {
|
|
1490
|
+
await expectRevert(bridge.removeFromBlacklist(ethers.ZeroAddress, { gasLimit: 2000000 }), /InvalidAddress|revert/i, " [Info] Correctly reverted: removeFromBlacklist(address(0))");
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
it("Test 123: revert: setWithdrawDynamicFee with fixedFee > maxFee reverts", async function () {
|
|
1494
|
+
await expectRevert(bridge.setWithdrawDynamicFee(ethers.parseEther("5000"), 250n, ethers.parseEther("1500"), { gasLimit: 2000000 }), /InvalidFeeConfiguration|revert/i, " [Info] Correctly reverted: setWithdrawDynamicFee fixedFee > maxFee");
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
it("Test 124: revert: setWithdrawDynamicFee with maxFee=0 reverts", async function () {
|
|
1498
|
+
await expectRevert(bridge.setWithdrawDynamicFee(0n, 250n, 0n, { gasLimit: 2000000 }), /InvalidFeeConfiguration|revert/i, " [Info] Correctly reverted: setWithdrawDynamicFee maxFee=0");
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
it("Test 125: revert: setWithdrawDynamicFee with pctBps > MAX_FEE_UNITS reverts", async function () {
|
|
1502
|
+
await expectRevert(bridge.setWithdrawDynamicFee(ethers.parseEther("3"), 200000n, ethers.parseEther("1500"), { gasLimit: 2000000 }), /InvalidFee|revert/i, " [Info] Correctly reverted: setWithdrawDynamicFee pctBps > MAX");
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1507
|
+
// FULL COVERAGE — CotiPriceConsumer
|
|
1508
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1509
|
+
|
|
1510
|
+
describe("Full Coverage - CotiPriceConsumer", function () {
|
|
1511
|
+
let oracle, mockRef;
|
|
1512
|
+
|
|
1513
|
+
before(async function () {
|
|
1514
|
+
if (skipIfOnlyPrivateERC20.call(this)) return;
|
|
1515
|
+
|
|
1516
|
+
// Deploy MockStdReference (Band Protocol mock)
|
|
1517
|
+
const MockRefFactory = await ethers.getContractFactory("MockStdReference");
|
|
1518
|
+
mockRef = await MockRefFactory.deploy({ gasLimit: 12000000 });
|
|
1519
|
+
await (mockRef.waitForDeployment ? mockRef.waitForDeployment() : mockRef.deployed());
|
|
1520
|
+
|
|
1521
|
+
// Set COTI price and ETH price
|
|
1522
|
+
await mockRef.setRate("COTI", ethers.parseEther("0.05"), { gasLimit: 2000000 });
|
|
1523
|
+
await mockRef.setRate("ETH", ethers.parseEther("2300"), { gasLimit: 2000000 });
|
|
1524
|
+
// Set lastUpdated to current block timestamp
|
|
1525
|
+
const currentBlock = await ethers.provider.getBlock("latest");
|
|
1526
|
+
await mockRef.setLastUpdatedBase("COTI", currentBlock.timestamp, { gasLimit: 2000000 });
|
|
1527
|
+
await mockRef.setLastUpdatedBase("ETH", currentBlock.timestamp, { gasLimit: 2000000 });
|
|
1528
|
+
|
|
1529
|
+
// Deploy real CotiPriceConsumer with MockStdReference
|
|
1530
|
+
const OracleFactory = await ethers.getContractFactory("CotiPriceConsumer");
|
|
1531
|
+
oracle = await OracleFactory.deploy(await addr(mockRef), 3600, { gasLimit: 12000000 });
|
|
1532
|
+
await (oracle.waitForDeployment ? oracle.waitForDeployment() : oracle.deployed());
|
|
1533
|
+
|
|
1534
|
+
await registerContract("CotiPriceConsumer", oracle, "Full Coverage - CotiPriceConsumer");
|
|
1535
|
+
await registerContract("MockStdReference", mockRef, "Full Coverage - CotiPriceConsumer");
|
|
1536
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
it("Test 126: oracle: owner is deployer", async function () {
|
|
1540
|
+
const oracleOwner = await oracle.owner();
|
|
1541
|
+
console.log(` [Info] Oracle owner: ${oracleOwner}`);
|
|
1542
|
+
expect(oracleOwner).to.equal(owner.address);
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
it("Test 127: oracle: ref is immutable and set correctly", async function () {
|
|
1546
|
+
const refAddr = await oracle.ref();
|
|
1547
|
+
console.log(` [Info] Oracle ref: ${refAddr}`);
|
|
1548
|
+
expect(refAddr).to.equal(await addr(mockRef));
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it("Test 128: oracle: maxStaleness is set correctly", async function () {
|
|
1552
|
+
const staleness = await oracle.maxStaleness();
|
|
1553
|
+
console.log(` [Info] maxStaleness: ${staleness}`);
|
|
1554
|
+
expect(staleness).to.equal(3600n);
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
it("Test 129: oracle: MIN_STALENESS constant is 3600", async function () {
|
|
1558
|
+
const minStaleness = await oracle.MIN_STALENESS();
|
|
1559
|
+
console.log(` [Info] MIN_STALENESS: ${minStaleness}`);
|
|
1560
|
+
expect(minStaleness).to.equal(3600n);
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
it("Test 130: oracle: getPrice returns correct rate", async function () {
|
|
1564
|
+
const cotiPrice = await oracle.getPrice("COTI");
|
|
1565
|
+
console.log(` [Info] COTI price: ${ethers.formatEther(cotiPrice)}`);
|
|
1566
|
+
expect(cotiPrice).to.equal(ethers.parseEther("0.05"));
|
|
1567
|
+
|
|
1568
|
+
const ethPrice = await oracle.getPrice("ETH");
|
|
1569
|
+
console.log(` [Info] ETH price: ${ethers.formatEther(ethPrice)}`);
|
|
1570
|
+
expect(ethPrice).to.equal(ethers.parseEther("2300"));
|
|
1571
|
+
});
|
|
1572
|
+
|
|
1573
|
+
it("Test 131: oracle: getPriceWithMeta returns rate, lastUpdated, blockTimestamp", async function () {
|
|
1574
|
+
const [rate, lastUpdated, blockTimestamp] = await oracle.getPriceWithMeta("COTI");
|
|
1575
|
+
console.log(` [Info] getPriceWithMeta: rate=${ethers.formatEther(rate)}, lastUpdated=${lastUpdated}, blockTs=${blockTimestamp}`);
|
|
1576
|
+
expect(rate).to.equal(ethers.parseEther("0.05"));
|
|
1577
|
+
expect(lastUpdated).to.be.gt(0n);
|
|
1578
|
+
expect(blockTimestamp).to.be.gt(0n);
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
it("Test 132: oracle: getPriceData returns full ReferenceData struct", async function () {
|
|
1582
|
+
const data = await oracle.getPriceData("COTI");
|
|
1583
|
+
console.log(` [Info] getPriceData: rate=${ethers.formatEther(data.rate)}, lastUpdatedBase=${data.lastUpdatedBase}`);
|
|
1584
|
+
expect(data.rate).to.equal(ethers.parseEther("0.05"));
|
|
1585
|
+
expect(data.lastUpdatedBase).to.be.gt(0n);
|
|
1586
|
+
expect(data.lastUpdatedQuote).to.be.gt(0n);
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
it("Test 133: oracle: setMaxStaleness updates threshold and emits event", async function () {
|
|
1590
|
+
const tx = await oracle.setMaxStaleness(7200, { gasLimit: 2000000 });
|
|
1591
|
+
await logTx(tx, "setMaxStaleness(7200)", "setMaxStaleness", ["7200"]);
|
|
1592
|
+
await expect(tx).to.emit(oracle, "MaxStalenessUpdated");
|
|
1593
|
+
expect(await oracle.maxStaleness()).to.equal(7200n);
|
|
1594
|
+
|
|
1595
|
+
// Restore
|
|
1596
|
+
const restoreTx = await oracle.setMaxStaleness(3600, { gasLimit: 2000000 });
|
|
1597
|
+
await logTx(restoreTx, "Restore maxStaleness(3600)", "setMaxStaleness", ["3600"]);
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
it("Test 134: oracle: setMaxStaleness reverts if below MIN_STALENESS", async function () {
|
|
1601
|
+
await expectRevert(oracle.setMaxStaleness(100, { gasLimit: 2000000 }), /StalenessTooLow|revert/i, " [Info] Correctly reverted: staleness < MIN_STALENESS");
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
it("Test 135: oracle: getPrice reverts on stale data", async function () {
|
|
1605
|
+
// Set lastUpdated to a very old timestamp (2 hours ago)
|
|
1606
|
+
const currentBlock = await ethers.provider.getBlock("latest");
|
|
1607
|
+
const staleTimestamp = currentBlock.timestamp - 7200;
|
|
1608
|
+
await mockRef.setLastUpdatedBase("COTI", staleTimestamp, { gasLimit: 2000000 });
|
|
1609
|
+
|
|
1610
|
+
try {
|
|
1611
|
+
await oracle.getPrice("COTI");
|
|
1612
|
+
expect.fail("Expected revert");
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
expect(error.message).to.match(/StaleOracleData|revert/i);
|
|
1615
|
+
console.log(" [Info] Correctly reverted: StaleOracleData");
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// Restore fresh timestamp
|
|
1619
|
+
const freshBlock = await ethers.provider.getBlock("latest");
|
|
1620
|
+
await mockRef.setLastUpdatedBase("COTI", freshBlock.timestamp, { gasLimit: 2000000 });
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
it("Test 136: oracle: constructor reverts with zero ref address", async function () {
|
|
1624
|
+
const OracleFactory = await ethers.getContractFactory("CotiPriceConsumer");
|
|
1625
|
+
try {
|
|
1626
|
+
const badOracle = await OracleFactory.deploy(ethers.ZeroAddress, 3600, { gasLimit: 12000000 });
|
|
1627
|
+
await (badOracle.waitForDeployment ? badOracle.waitForDeployment() : badOracle.deployed());
|
|
1628
|
+
expect.fail("Expected revert");
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
expect(error.message).to.match(/zero ref address|revert/i);
|
|
1631
|
+
console.log(" [Info] Correctly reverted: constructor with zero ref");
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
it("Test 137: oracle: constructor reverts with staleness below minimum", async function () {
|
|
1636
|
+
const OracleFactory = await ethers.getContractFactory("CotiPriceConsumer");
|
|
1637
|
+
try {
|
|
1638
|
+
const badOracle = await OracleFactory.deploy(await addr(mockRef), 100, { gasLimit: 12000000 });
|
|
1639
|
+
await (badOracle.waitForDeployment ? badOracle.waitForDeployment() : badOracle.deployed());
|
|
1640
|
+
expect.fail("Expected revert");
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
expect(error.message).to.match(/StalenessTooLow|revert/i);
|
|
1643
|
+
console.log(" [Info] Correctly reverted: constructor with staleness < MIN_STALENESS");
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
it("Test 138: oracle: setMaxStaleness reverts for non-owner", async function () {
|
|
1648
|
+
// user1 is not the owner
|
|
1649
|
+
if (user1.address === owner.address) {
|
|
1650
|
+
console.log(" [Info] Skipping: user1 === owner on single-signer testnet");
|
|
1651
|
+
this.skip();
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
await expectRevert(oracle.connect(user1).setMaxStaleness(7200, { gasLimit: 2000000 }), /caller is not the owner|revert/i, " [Info] Correctly reverted: non-owner setMaxStaleness");
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
after(function () {
|
|
1659
|
+
console.log("\n===========================================================");
|
|
1660
|
+
console.log(`TOTAL TESTS RUN: ${testCounter}`);
|
|
1661
|
+
console.log("===========================================================\n");
|
|
1662
|
+
});
|
|
1663
|
+
});
|