diff --git a/contracts/.changeset/poor-ears-hear.md b/contracts/.changeset/poor-ears-hear.md new file mode 100644 index 00000000000..6e0fbe26663 --- /dev/null +++ b/contracts/.changeset/poor-ears-hear.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal split onRamp and feeQuoter tests diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index b5b42e26d06..e6e2a6e9945 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -67,8 +67,8 @@ CCIPHome_setCandidate:test_setCandidate_CanOnlySelfCall_reverts() (gas: 29383) CCIPHome_setCandidate:test_setCandidate_ConfigDigestMismatch_reverts() (gas: 1395154) CCIPHome_setCandidate:test_setCandidate_success() (gas: 1365439) DefensiveExampleTest:test_HappyPath_Success() (gas: 200473) -DefensiveExampleTest:test_Recovery() (gas: 424859) -E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1520821) +DefensiveExampleTest:test_Recovery() (gas: 424876) +E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1519829) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_fallbackToWethTransfer() (gas: 96962) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 49812) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongToken() (gas: 17457) @@ -152,10 +152,10 @@ FeeQuoter_getTokenAndGasPrices:test_ZeroGasPrice_Success() (gas: 109131) FeeQuoter_getTokenPrice:test_GetTokenPriceFromFeed_Success() (gas: 68015) FeeQuoter_getTokenPrice:test_GetTokenPrice_LocalMoreRecent_Success() (gas: 33463) FeeQuoter_getTokenPrices:test_GetTokenPrices_Success() (gas: 78498) -FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee_Success() (gas: 39502) +FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee_Success() (gas: 37372) FeeQuoter_getTokenTransferCost:test_FeeTokenBpsFee_Success() (gas: 35151) FeeQuoter_getTokenTransferCost:test_LargeTokenTransferChargesMaxFeeAndGas_Success() (gas: 28241) -FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee_Success() (gas: 98330) +FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee_Success() (gas: 96218) FeeQuoter_getTokenTransferCost:test_NoTokenTransferChargesZeroFee_Success() (gas: 20702) FeeQuoter_getTokenTransferCost:test_SmallTokenTransferChargesMinFeeAndGas_Success() (gas: 28049) FeeQuoter_getTokenTransferCost:test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() (gas: 28072) @@ -349,213 +349,213 @@ NonceManager_NonceIncrementation:test_getIncrementedOutboundNonce_Success() (gas NonceManager_NonceIncrementation:test_incrementInboundNonce_Skip() (gas: 23706) NonceManager_NonceIncrementation:test_incrementInboundNonce_Success() (gas: 38778) NonceManager_NonceIncrementation:test_incrementNoncesInboundAndOutbound_Success() (gas: 71901) -NonceManager_OffRampUpgrade:test_NoPrevOffRampForChain_Success() (gas: 185976) -NonceManager_OffRampUpgrade:test_UpgradedNonceNewSenderStartsAtZero_Success() (gas: 189423) -NonceManager_OffRampUpgrade:test_UpgradedNonceStartsAtV1Nonce_Success() (gas: 252593) -NonceManager_OffRampUpgrade:test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() (gas: 220830) -NonceManager_OffRampUpgrade:test_UpgradedSenderNoncesReadsPreviousRamp_Success() (gas: 60591) -NonceManager_OffRampUpgrade:test_Upgraded_Success() (gas: 153010) +NonceManager_OffRampUpgrade:test_NoPrevOffRampForChain_Success() (gas: 185739) +NonceManager_OffRampUpgrade:test_UpgradedNonceNewSenderStartsAtZero_Success() (gas: 189192) +NonceManager_OffRampUpgrade:test_UpgradedNonceStartsAtV1Nonce_Success() (gas: 252176) +NonceManager_OffRampUpgrade:test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() (gas: 220541) +NonceManager_OffRampUpgrade:test_UpgradedSenderNoncesReadsPreviousRamp_Success() (gas: 60497) +NonceManager_OffRampUpgrade:test_Upgraded_Success() (gas: 152904) NonceManager_OnRampUpgrade:test_UpgradeNonceNewSenderStartsAtZero_Success() (gas: 166101) -NonceManager_OnRampUpgrade:test_UpgradeNonceStartsAtV1Nonce_Success() (gas: 195806) +NonceManager_OnRampUpgrade:test_UpgradeNonceStartsAtV1Nonce_Success() (gas: 195828) NonceManager_OnRampUpgrade:test_UpgradeSenderNoncesReadsPreviousRamp_Success() (gas: 139098) -NonceManager_OnRampUpgrade:test_Upgrade_Success() (gas: 105257) +NonceManager_OnRampUpgrade:test_Upgrade_Success() (gas: 105168) NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates_success() (gas: 123604) NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOffRamp_Revert() (gas: 43403) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() (gas: 64775) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRamp_Revert() (gas: 43201) +NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() (gas: 64752) +NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRamp_Revert() (gas: 43245) NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySet_overrideAllowed_success() (gas: 45941) NonceManager_applyPreviousRampsUpdates:test_SingleRampUpdate_success() (gas: 66889) NonceManager_applyPreviousRampsUpdates:test_ZeroInput_success() (gas: 12213) NonceManager_typeAndVersion:test_typeAndVersion() (gas: 9705) -OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5880084) +OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5880050) OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains_Success() (gas: 626115) -OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 166515) -OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates_Success() (gas: 16763) -OffRamp_applySourceChainConfigUpdates:test_InvalidOnRampUpdate_Revert() (gas: 274803) -OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChainOnRamp_Success() (gas: 168560) +OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 166493) +OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates_Success() (gas: 16741) +OffRamp_applySourceChainConfigUpdates:test_InvalidOnRampUpdate_Revert() (gas: 274735) +OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChainOnRamp_Success() (gas: 168604) OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChain_Success() (gas: 181059) OffRamp_applySourceChainConfigUpdates:test_RouterAddress_Revert() (gas: 13463) OffRamp_applySourceChainConfigUpdates:test_ZeroOnRampAddress_Revert() (gas: 72746) OffRamp_applySourceChainConfigUpdates:test_ZeroSourceChainSelector_Revert() (gas: 15476) -OffRamp_applySourceChainConfigUpdates:test_allowNonOnRampUpdateAfterLaneIsUsed_success() (gas: 285153) -OffRamp_batchExecute:test_MultipleReportsDifferentChainsSkipCursedChain_Success() (gas: 177564) -OffRamp_batchExecute:test_MultipleReportsDifferentChains_Success() (gas: 333809) -OffRamp_batchExecute:test_MultipleReportsSameChain_Success() (gas: 277075) -OffRamp_batchExecute:test_MultipleReportsSkipDuplicate_Success() (gas: 168494) +OffRamp_applySourceChainConfigUpdates:test_allowNonOnRampUpdateAfterLaneIsUsed_success() (gas: 285063) +OffRamp_batchExecute:test_MultipleReportsDifferentChainsSkipCursedChain_Success() (gas: 177349) +OffRamp_batchExecute:test_MultipleReportsDifferentChains_Success() (gas: 333175) +OffRamp_batchExecute:test_MultipleReportsSameChain_Success() (gas: 276441) +OffRamp_batchExecute:test_MultipleReportsSkipDuplicate_Success() (gas: 168334) OffRamp_batchExecute:test_OutOfBoundsGasLimitsAccess_Revert() (gas: 187853) -OffRamp_batchExecute:test_SingleReport_Success() (gas: 156555) -OffRamp_batchExecute:test_Unhealthy_Success() (gas: 553993) +OffRamp_batchExecute:test_SingleReport_Success() (gas: 156369) +OffRamp_batchExecute:test_Unhealthy_Success() (gas: 553439) OffRamp_batchExecute:test_ZeroReports_Revert() (gas: 10600) -OffRamp_ccipReceive:test_RevertWhen_Always() (gas: 15407) -OffRamp_commit:test_CommitOnRampMismatch_Revert() (gas: 92834) -OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63500) -OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 70146) -OffRamp_commit:test_InvalidInterval_Revert() (gas: 66209) -OffRamp_commit:test_InvalidRootRevert() (gas: 65304) -OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6641216) -OffRamp_commit:test_NoConfig_Revert() (gas: 6224546) -OffRamp_commit:test_OnlyGasPriceUpdates_Success() (gas: 112980) -OffRamp_commit:test_OnlyPriceUpdateStaleReport_Revert() (gas: 121333) -OffRamp_commit:test_OnlyTokenPriceUpdates_Success() (gas: 112979) -OffRamp_commit:test_PriceSequenceNumberCleared_Success() (gas: 355372) -OffRamp_commit:test_ReportAndPriceUpdate_Success() (gas: 164388) -OffRamp_commit:test_ReportOnlyRootSuccess_gas() (gas: 141416) -OffRamp_commit:test_RootAlreadyCommitted_Revert() (gas: 148426) -OffRamp_commit:test_RootWithRMNDisabled_success() (gas: 154111) -OffRamp_commit:test_SourceChainNotEnabled_Revert() (gas: 61771) -OffRamp_commit:test_StaleReportWithRoot_Success() (gas: 232626) -OffRamp_commit:test_UnauthorizedTransmitter_Revert() (gas: 125320) -OffRamp_commit:test_Unhealthy_Revert() (gas: 60572) -OffRamp_commit:test_ValidPriceUpdateThenStaleReportWithRoot_Success() (gas: 207009) -OffRamp_commit:test_ZeroEpochAndRound_Revert() (gas: 53689) -OffRamp_constructor:test_Constructor_Success() (gas: 6186775) -OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 136553) -OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103634) +OffRamp_ccipReceive:test_RevertWhen_Always() (gas: 9303) +OffRamp_commit:test_CommitOnRampMismatch_Revert() (gas: 92744) +OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63432) +OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 69993) +OffRamp_commit:test_InvalidInterval_Revert() (gas: 66119) +OffRamp_commit:test_InvalidRootRevert() (gas: 65214) +OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6641148) +OffRamp_commit:test_NoConfig_Revert() (gas: 6224566) +OffRamp_commit:test_OnlyGasPriceUpdates_Success() (gas: 112985) +OffRamp_commit:test_OnlyPriceUpdateStaleReport_Revert() (gas: 121175) +OffRamp_commit:test_OnlyTokenPriceUpdates_Success() (gas: 112917) +OffRamp_commit:test_PriceSequenceNumberCleared_Success() (gas: 355254) +OffRamp_commit:test_ReportAndPriceUpdate_Success() (gas: 164263) +OffRamp_commit:test_ReportOnlyRootSuccess_gas() (gas: 141269) +OffRamp_commit:test_RootAlreadyCommitted_Revert() (gas: 148268) +OffRamp_commit:test_RootWithRMNDisabled_success() (gas: 153986) +OffRamp_commit:test_SourceChainNotEnabled_Revert() (gas: 61681) +OffRamp_commit:test_StaleReportWithRoot_Success() (gas: 232354) +OffRamp_commit:test_UnauthorizedTransmitter_Revert() (gas: 125230) +OffRamp_commit:test_Unhealthy_Revert() (gas: 60482) +OffRamp_commit:test_ValidPriceUpdateThenStaleReportWithRoot_Success() (gas: 206800) +OffRamp_commit:test_ZeroEpochAndRound_Revert() (gas: 53621) +OffRamp_constructor:test_Constructor_Success() (gas: 6186663) +OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 136575) +OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103612) OffRamp_constructor:test_ZeroNonceManager_Revert() (gas: 101461) OffRamp_constructor:test_ZeroOnRampAddress_Revert() (gas: 162055) OffRamp_constructor:test_ZeroRMNRemote_Revert() (gas: 101378) -OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101427) +OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101382) OffRamp_execute:test_IncorrectArrayType_Revert() (gas: 17639) -OffRamp_execute:test_LargeBatch_Success() (gas: 3406667) -OffRamp_execute:test_MultipleReportsWithPartialValidationFailures_Success() (gas: 371505) -OffRamp_execute:test_MultipleReports_Success() (gas: 299194) -OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7049301) +OffRamp_execute:test_LargeBatch_Success() (gas: 3374933) +OffRamp_execute:test_MultipleReportsWithPartialValidationFailures_Success() (gas: 371025) +OffRamp_execute:test_MultipleReports_Success() (gas: 298564) +OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7049279) OffRamp_execute:test_NoConfig_Revert() (gas: 6273749) OffRamp_execute:test_NonArray_Revert() (gas: 27643) -OffRamp_execute:test_SingleReport_Success() (gas: 175809) -OffRamp_execute:test_UnauthorizedTransmitter_Revert() (gas: 147805) +OffRamp_execute:test_SingleReport_Success() (gas: 175627) +OffRamp_execute:test_UnauthorizedTransmitter_Revert() (gas: 147783) OffRamp_execute:test_WrongConfigWithSigners_Revert() (gas: 6940958) -OffRamp_execute:test_ZeroReports_Revert() (gas: 17317) -OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18537) -OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 244193) -OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20389) -OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205666) -OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48884) -OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 56065) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 212828) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidation_Revert() (gas: 85455) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 274305) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithVInterception_Success() (gas: 91944) -OffRamp_executeSingleReport:test_DisabledSourceChain_Revert() (gas: 28658) +OffRamp_execute:test_ZeroReports_Revert() (gas: 17361) +OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18533) +OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 244171) +OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20363) +OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205647) +OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48880) +OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 56102) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 212824) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidation_Revert() (gas: 85495) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 274279) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithVInterception_Success() (gas: 91918) +OffRamp_executeSingleReport:test_DisabledSourceChain_Revert() (gas: 28636) OffRamp_executeSingleReport:test_EmptyReport_Revert() (gas: 15580) -OffRamp_executeSingleReport:test_InvalidSourcePoolAddress_Success() (gas: 481795) -OffRamp_executeSingleReport:test_ManualExecutionNotYetEnabled_Revert() (gas: 48273) -OffRamp_executeSingleReport:test_MismatchingDestChainSelector_Revert() (gas: 34100) +OffRamp_executeSingleReport:test_InvalidSourcePoolAddress_Success() (gas: 481411) +OffRamp_executeSingleReport:test_ManualExecutionNotYetEnabled_Revert() (gas: 48295) +OffRamp_executeSingleReport:test_MismatchingDestChainSelector_Revert() (gas: 34188) OffRamp_executeSingleReport:test_NonExistingSourceChain_Revert() (gas: 28823) -OffRamp_executeSingleReport:test_ReceiverError_Success() (gas: 187698) -OffRamp_executeSingleReport:test_RetryFailedMessageWithoutManualExecution_Revert() (gas: 197829) -OffRamp_executeSingleReport:test_RootNotCommitted_Revert() (gas: 40686) -OffRamp_executeSingleReport:test_RouterYULCall_Revert() (gas: 404997) -OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas: 248698) -OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 192576) -OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 212587) -OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 243705) -OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 141547) -OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 408961) +OffRamp_executeSingleReport:test_ReceiverError_Success() (gas: 187522) +OffRamp_executeSingleReport:test_RetryFailedMessageWithoutManualExecution_Revert() (gas: 197799) +OffRamp_executeSingleReport:test_RootNotCommitted_Revert() (gas: 40664) +OffRamp_executeSingleReport:test_RouterYULCall_Revert() (gas: 404911) +OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas: 248582) +OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 192204) +OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 212228) +OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 243641) +OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 141397) +OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 408631) OffRamp_executeSingleReport:test_SkippedIncorrectNonce_Success() (gas: 58241) -OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 73808) -OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 583208) -OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 531794) -OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 26774) -OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 549633) -OffRamp_executeSingleReport:test_Unhealthy_Success() (gas: 549580) -OffRamp_executeSingleReport:test_WithCurseOnAnotherSourceChain_Success() (gas: 460249) -OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessageUnordered_Success() (gas: 135267) -OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessage_Success() (gas: 164910) -OffRamp_getExecutionState:test_FillExecutionState_Success() (gas: 3911118) -OffRamp_getExecutionState:test_GetDifferentChainExecutionState_Success() (gas: 121222) -OffRamp_getExecutionState:test_GetExecutionState_Success() (gas: 89706) +OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 73786) +OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 582446) +OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 531112) +OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 26751) +OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 549057) +OffRamp_executeSingleReport:test_Unhealthy_Success() (gas: 549093) +OffRamp_executeSingleReport:test_WithCurseOnAnotherSourceChain_Success() (gas: 460204) +OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessageUnordered_Success() (gas: 135139) +OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessage_Success() (gas: 164782) +OffRamp_getExecutionState:test_FillExecutionState_Success() (gas: 3888824) +OffRamp_getExecutionState:test_GetDifferentChainExecutionState_Success() (gas: 121048) +OffRamp_getExecutionState:test_GetExecutionState_Success() (gas: 89561) OffRamp_manuallyExecute:test_ManualExecGasLimitMismatchSingleReport_Revert() (gas: 81178) OffRamp_manuallyExecute:test_manuallyExecute_DestinationGasAmountCountMismatch_Revert() (gas: 74108) -OffRamp_manuallyExecute:test_manuallyExecute_DoesNotRevertIfUntouched_Success() (gas: 172634) +OffRamp_manuallyExecute:test_manuallyExecute_DoesNotRevertIfUntouched_Success() (gas: 172480) OffRamp_manuallyExecute:test_manuallyExecute_FailedTx_Revert() (gas: 212935) OffRamp_manuallyExecute:test_manuallyExecute_ForkedChain_Revert() (gas: 27166) OffRamp_manuallyExecute:test_manuallyExecute_GasLimitMismatchMultipleReports_Revert() (gas: 164939) OffRamp_manuallyExecute:test_manuallyExecute_InvalidReceiverExecutionGasLimit_Revert() (gas: 27703) OffRamp_manuallyExecute:test_manuallyExecute_InvalidTokenGasOverride_Revert() (gas: 55274) -OffRamp_manuallyExecute:test_manuallyExecute_LowGasLimit_Success() (gas: 489576) -OffRamp_manuallyExecute:test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() (gas: 314392) -OffRamp_manuallyExecute:test_manuallyExecute_ReentrancyFails_Success() (gas: 2227930) +OffRamp_manuallyExecute:test_manuallyExecute_LowGasLimit_Success() (gas: 489352) +OffRamp_manuallyExecute:test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() (gas: 314370) +OffRamp_manuallyExecute:test_manuallyExecute_ReentrancyFails_Success() (gas: 2227706) OffRamp_manuallyExecute:test_manuallyExecute_SourceChainSelectorMismatch_Revert() (gas: 165133) -OffRamp_manuallyExecute:test_manuallyExecute_Success() (gas: 225972) -OffRamp_manuallyExecute:test_manuallyExecute_WithGasOverride_Success() (gas: 226534) -OffRamp_manuallyExecute:test_manuallyExecute_WithMultiReportGasOverride_Success() (gas: 774706) -OffRamp_manuallyExecute:test_manuallyExecute_WithPartialMessages_Success() (gas: 344831) -OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 37632) -OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 104648) +OffRamp_manuallyExecute:test_manuallyExecute_Success() (gas: 225844) +OffRamp_manuallyExecute:test_manuallyExecute_WithGasOverride_Success() (gas: 226384) +OffRamp_manuallyExecute:test_manuallyExecute_WithMultiReportGasOverride_Success() (gas: 773426) +OffRamp_manuallyExecute:test_manuallyExecute_WithPartialMessages_Success() (gas: 344159) +OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 37654) +OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 104625) OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 83092) OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 36812) -OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94648) -OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37301) +OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94670) +OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37323) OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 86760) OffRamp_releaseOrMintTokens:test_TokenHandlingError_Reverts() (gas: 162911) -OffRamp_releaseOrMintTokens:test__releaseOrMintTokens_PoolIsNotAPool_Reverts() (gas: 23881) +OffRamp_releaseOrMintTokens:test__releaseOrMintTokens_PoolIsNotAPool_Reverts() (gas: 23836) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_InvalidDataLengthReturnData_Revert() (gas: 62844) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_PoolDoesNotSupportDest_Reverts() (gas: 80014) -OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_Success() (gas: 175034) +OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_Success() (gas: 174989) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_WithGasOverride_Success() (gas: 176901) -OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_destDenominatedDecimals_Success() (gas: 188145) +OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_destDenominatedDecimals_Success() (gas: 188167) OffRamp_setDynamicConfig:test_FeeQuoterZeroAddress_Revert() (gas: 11509) OffRamp_setDynamicConfig:test_NonOwner_Revert() (gas: 14019) -OffRamp_setDynamicConfig:test_SetDynamicConfigWithInterceptor_Success() (gas: 47591) -OffRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 25564) -OffRamp_trialExecute:test_RateLimitError_Success() (gas: 219989) -OffRamp_trialExecute:test_TokenHandlingErrorIsCaught_Success() (gas: 228644) -OffRamp_trialExecute:test_TokenPoolIsNotAContract_Success() (gas: 295794) -OffRamp_trialExecute:test_trialExecute_Success() (gas: 278096) +OffRamp_setDynamicConfig:test_SetDynamicConfigWithInterceptor_Success() (gas: 47579) +OffRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 25552) +OffRamp_trialExecute:test_RateLimitError_Success() (gas: 219928) +OffRamp_trialExecute:test_TokenHandlingErrorIsCaught_Success() (gas: 228561) +OffRamp_trialExecute:test_TokenPoolIsNotAContract_Success() (gas: 295602) +OffRamp_trialExecute:test_trialExecute_Success() (gas: 278032) OnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy_Success() (gas: 251573) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() (gas: 17227) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_Revert() (gas: 67101) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_Success() (gas: 325983) OnRamp_applyDestChainConfigUpdates:test_ApplyDestChainConfigUpdates_Success() (gas: 65892) OnRamp_applyDestChainConfigUpdates:test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() (gas: 12902) -OnRamp_constructor:test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() (gas: 2569385) +OnRamp_constructor:test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() (gas: 2569362) OnRamp_constructor:test_Constructor_InvalidConfigChainSelectorEqZero_Revert() (gas: 95148) OnRamp_constructor:test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() (gas: 93090) OnRamp_constructor:test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() (gas: 98066) -OnRamp_constructor:test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() (gas: 93124) -OnRamp_constructor:test_Constructor_Success() (gas: 2647534) -OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() (gas: 115376) +OnRamp_constructor:test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() (gas: 93146) +OnRamp_constructor:test_Constructor_Success() (gas: 2647459) +OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() (gas: 115398) OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2_Success() (gas: 146244) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 145819) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 144046) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 146016) -OnRamp_forwardFromRouter:test_ForwardFromRouter_Success() (gas: 145414) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 145841) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 143957) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 146038) +OnRamp_forwardFromRouter:test_ForwardFromRouter_Success() (gas: 145436) OnRamp_forwardFromRouter:test_ForwardFromRouter_Success_ConfigurableSourceRouter() (gas: 140697) OnRamp_forwardFromRouter:test_InvalidExtraArgsTag_Revert() (gas: 38504) -OnRamp_forwardFromRouter:test_MessageInterceptionError_Revert() (gas: 143100) +OnRamp_forwardFromRouter:test_MessageInterceptionError_Revert() (gas: 143122) OnRamp_forwardFromRouter:test_MesssageFeeTooHigh_Revert() (gas: 36611) -OnRamp_forwardFromRouter:test_MultiCannotSendZeroTokens_Revert() (gas: 36471) -OnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 18268) -OnRamp_forwardFromRouter:test_Paused_Revert() (gas: 38412) -OnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 23629) +OnRamp_forwardFromRouter:test_MultiCannotSendZeroTokens_Revert() (gas: 36493) +OnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 18290) +OnRamp_forwardFromRouter:test_Paused_Revert() (gas: 38434) +OnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 23651) OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas: 186649) OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 213078) -OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 147003) +OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 147025) OnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 161214) -OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3566334) +OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3566245) OnRamp_forwardFromRouter:test_UnAllowedOriginalSender_Revert() (gas: 24015) -OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 75832) -OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 38588) +OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 75854) +OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 38610) OnRamp_forwardFromRouter:test_forwardFromRouter_WithInterception_Success() (gas: 280344) -OnRamp_getFee:test_EmptyMessage_Success() (gas: 98782) -OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 65498) -OnRamp_getFee:test_GetFeeOfZeroForTokenMessage_Success() (gas: 87208) -OnRamp_getFee:test_NotAFeeTokenButPricedToken_Revert() (gas: 35167) -OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 113990) -OnRamp_getFee:test_Unhealthy_Revert() (gas: 17108) +OnRamp_getFee:test_EmptyMessage_Success() (gas: 98692) +OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 65453) +OnRamp_getFee:test_GetFeeOfZeroForTokenMessage_Success() (gas: 87185) +OnRamp_getFee:test_NotAFeeTokenButPricedToken_Revert() (gas: 35166) +OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 113865) +OnRamp_getFee:test_Unhealthy_Revert() (gas: 17040) OnRamp_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10565) OnRamp_getTokenPool:test_GetTokenPool_Success() (gas: 35405) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() (gas: 11558) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() (gas: 11535) OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() (gas: 13194) OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() (gas: 11499) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() (gas: 16648) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() (gas: 13220) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() (gas: 11938) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() (gas: 13264) OnRamp_setDynamicConfig:test_setDynamicConfig_Success() (gas: 56440) OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens_Success() (gas: 125867) PingPong_ccipReceive:test_CcipReceive_Success() (gas: 172841) -PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20328) -PingPong_plumbing:test_Pausing_Success() (gas: 17760) +PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20283) +PingPong_plumbing:test_Pausing_Success() (gas: 17738) PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 151954) PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered_Success() (gas: 177569) RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() (gas: 18850) @@ -627,35 +627,35 @@ Router_applyRampUpdates:test_OffRampUpdatesWithRouting() (gas: 10749462) Router_applyRampUpdates:test_OnRampDisable() (gas: 56428) Router_applyRampUpdates:test_OnlyOwner_Revert() (gas: 12414) Router_ccipSend:test_CCIPSendLinkFeeNoTokenSuccess_gas() (gas: 131413) -Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 221307) +Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 221240) Router_ccipSend:test_FeeTokenAmountTooLow_Revert() (gas: 71841) Router_ccipSend:test_InvalidMsgValue() (gas: 32411) -Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 69502) -Router_ccipSend:test_NativeFeeTokenOverpay_Success() (gas: 193274) +Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 69524) +Router_ccipSend:test_NativeFeeTokenOverpay_Success() (gas: 193296) Router_ccipSend:test_NativeFeeTokenZeroValue() (gas: 61550) Router_ccipSend:test_NativeFeeToken_Success() (gas: 191900) -Router_ccipSend:test_NonLinkFeeToken_Success() (gas: 226510) +Router_ccipSend:test_NonLinkFeeToken_Success() (gas: 226532) Router_ccipSend:test_UnsupportedDestinationChain_Revert() (gas: 25056) -Router_ccipSend:test_WhenNotHealthy_Revert() (gas: 45034) +Router_ccipSend:test_WhenNotHealthy_Revert() (gas: 45056) Router_ccipSend:test_WrappedNativeFeeToken_Success() (gas: 194209) Router_ccipSend:test_ccipSend_nativeFeeNoTokenSuccess_gas() (gas: 140674) -Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 230481) -Router_constructor:test_Constructor_Success() (gas: 13155) +Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 230436) +Router_constructor:test_Constructor_Success() (gas: 13222) Router_getArmProxy:test_getArmProxy() (gas: 10573) -Router_getFee:test_GetFeeSupportedChain_Success() (gas: 51979) -Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17430) +Router_getFee:test_GetFeeSupportedChain_Success() (gas: 51934) +Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17385) Router_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10565) -Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11344) +Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11410) Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 20199) -Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11214) +Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11236) Router_recoverTokens:test_RecoverTokensValueReceiver_Revert() (gas: 349502) -Router_recoverTokens:test_RecoverTokens_Success() (gas: 52622) -Router_routeMessage:test_routeMessage_AutoExec_Success() (gas: 43367) -Router_routeMessage:test_routeMessage_ExecutionEvent_Success() (gas: 159649) -Router_routeMessage:test_routeMessage_ManualExec_Success() (gas: 35845) -Router_routeMessage:test_routeMessage_OnlyOffRamp_Revert() (gas: 25431) -Router_routeMessage:test_routeMessage_WhenNotHealthy_Revert() (gas: 44889) -Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 10986) +Router_recoverTokens:test_RecoverTokens_Success() (gas: 52640) +Router_routeMessage:test_routeMessage_AutoExec_Success() (gas: 43213) +Router_routeMessage:test_routeMessage_ExecutionEvent_Success() (gas: 159418) +Router_routeMessage:test_routeMessage_ManualExec_Success() (gas: 35723) +Router_routeMessage:test_routeMessage_OnlyOffRamp_Revert() (gas: 25376) +Router_routeMessage:test_routeMessage_WhenNotHealthy_Revert() (gas: 44812) +Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 11008) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_OnlyPendingAdministrator_Revert() (gas: 51433) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_Success() (gas: 44189) TokenAdminRegistry_addRegistryModule:test_addRegistryModule_OnlyOwner_Revert() (gas: 12662) diff --git a/contracts/src/v0.8/ccip/test/BaseTest.t.sol b/contracts/src/v0.8/ccip/test/BaseTest.t.sol index 2c54a49744f..2770f0fb4d6 100644 --- a/contracts/src/v0.8/ccip/test/BaseTest.t.sol +++ b/contracts/src/v0.8/ccip/test/BaseTest.t.sol @@ -14,15 +14,8 @@ contract BaseTest is Test { // Addresses address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; address internal constant STRANGER = address(999999); - address internal constant DUMMY_CONTRACT_ADDRESS = 0x1111111111111111111111111111111111111112; - address internal constant ON_RAMP_ADDRESS = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; - address internal constant ZERO_ADDRESS = address(0); - address internal constant FEE_AGGREGATOR = 0xa33CDB32eAEce34F6affEfF4899cef45744EDea3; address internal constant USER_1 = address(1); - address internal constant USER_2 = address(2); - address internal constant USER_3 = address(3); - address internal constant USER_4 = address(4); // Message info uint64 internal constant SOURCE_CHAIN_SELECTOR = 1; @@ -34,7 +27,6 @@ contract BaseTest is Test { uint32 internal constant TWELVE_HOURS = 60 * 60 * 12; // Onramp - uint96 internal constant MAX_NOP_FEES_JUELS = 1e27; uint96 internal constant MAX_MSG_FEES_JUELS = 1_000e18; uint32 internal constant DEST_GAS_OVERHEAD = 300_000; uint16 internal constant DEST_GAS_PER_PAYLOAD_BYTE = 16; @@ -45,31 +37,12 @@ contract BaseTest is Test { bool private s_baseTestInitialized; - // Use 16 gas per data availability byte in our tests. - // This is an overestimation in OP stack, it ignores 4 gas per 0 byte rule. - // Arbitrum on the other hand, does always use 16 gas per data availability byte. - // This value may be substantially decreased after EIP 4844. - uint16 internal constant DEST_GAS_PER_DATA_AVAILABILITY_BYTE = 16; - - // Total L1 data availability overhead estimate is 33_596 gas. - // This value includes complete CommitStore and OffRamp call data. - uint32 internal constant DEST_DATA_AVAILABILITY_OVERHEAD_GAS = 188 // Fixed data availability overhead in OP stack. - + (32 * 31 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE // CommitStore single-root transmission takes up about 31 slots, plus selector. - + (32 * 34 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE; // OffRamp transmission excluding EVM2EVMMessage takes up about 34 slots, plus selector. - - // Multiples of bps, or 0.0001, use 6840 to be same as OP mainnet compression factor of 0.684. - uint16 internal constant DEST_GAS_DATA_AVAILABILITY_MULTIPLIER_BPS = 6840; - // OffRamp uint32 internal constant MAX_DATA_SIZE = 30_000; uint16 internal constant MAX_TOKENS_LENGTH = 5; uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5000; - uint32 internal constant PERMISSION_LESS_EXECUTION_THRESHOLD_SECONDS = 500; uint32 internal constant MAX_GAS_LIMIT = 4_000_000; - // Rate limiter - address internal constant ADMIN = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; - MockRMN internal s_mockRMN; IRMNRemote internal s_mockRMNRemote; diff --git a/contracts/src/v0.8/ccip/test/NonceManager.t.sol b/contracts/src/v0.8/ccip/test/NonceManager.t.sol index 4c395e1dc54..f560b5be593 100644 --- a/contracts/src/v0.8/ccip/test/NonceManager.t.sol +++ b/contracts/src/v0.8/ccip/test/NonceManager.t.sol @@ -12,7 +12,7 @@ import {BaseTest} from "./BaseTest.t.sol"; import {EVM2EVMOffRampHelper} from "./helpers/EVM2EVMOffRampHelper.sol"; import {OnRampHelper} from "./helpers/OnRampHelper.sol"; import {OffRampSetup} from "./offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "./onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "./onRamp/onRamp/OnRampSetup.t.sol"; import {Test} from "forge-std/Test.sol"; @@ -377,7 +377,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -405,7 +405,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain3), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_3, messagesChain3[0].header.sequenceNumber, messagesChain3[0].header.messageId, @@ -453,7 +453,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -474,7 +474,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -507,7 +507,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -554,7 +554,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol index 203145881e3..42d10190f1e 100644 --- a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; import {BurnMintERC677} from "../../shared/token/ERC677/BurnMintERC677.sol"; -import {Client} from "../libraries/Client.sol"; import {BurnMintTokenPool} from "../pools/BurnMintTokenPool.sol"; import {LockReleaseTokenPool} from "../pools/LockReleaseTokenPool.sol"; import {TokenPool} from "../pools/TokenPool.sol"; @@ -136,18 +135,6 @@ contract TokenSetup is RouterSetup { } } - function _getCastedSourceEVMTokenAmountsWithZeroAmounts() - internal - view - returns (Client.EVMTokenAmount[] memory tokenAmounts) - { - tokenAmounts = new Client.EVMTokenAmount[](s_sourceTokens.length); - for (uint256 i = 0; i < tokenAmounts.length; ++i) { - tokenAmounts[i].token = s_sourceTokens[i]; - } - return tokenAmounts; - } - function _setPool( TokenAdminRegistry tokenAdminRegistry, address token, diff --git a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol index 70cbc9c950b..b4829668ce3 100644 --- a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {DefensiveExample} from "../../applications/DefensiveExample.sol"; import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol b/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol index 61b0204e7d8..f3f09ecc78c 100644 --- a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol @@ -4,7 +4,7 @@ import {IAny2EVMMessageReceiver} from "../../interfaces/IAny2EVMMessageReceiver. import {CCIPClientExample} from "../../applications/CCIPClientExample.sol"; import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {ERC165Checker} from diff --git a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol index 308e2c087c4..f645bd88cb5 100644 --- a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol @@ -5,7 +5,7 @@ import {PingPongDemo} from "../../applications/PingPongDemo.sol"; import {Client} from "../../libraries/Client.sol"; import {Internal} from "../../libraries/Internal.sol"; import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol index 3f262e2feb3..c50d86cad7d 100644 --- a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol +++ b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import {Client} from "../../../libraries/Client.sol"; import {OnRamp} from "../../../onRamp/OnRamp.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; -import {OnRampSetup} from "../../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../../onRamp/onRamp/OnRampSetup.t.sol"; import {FacadeClient} from "./FacadeClient.sol"; import {ReentrantMaliciousTokenPool} from "./ReentrantMaliciousTokenPool.sol"; diff --git a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol index 4a0c05933ca..610bf311cd8 100644 --- a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol +++ b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol @@ -16,7 +16,7 @@ import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.so import {MerkleHelper} from "../helpers/MerkleHelper.sol"; import {OnRampHelper} from "../helpers/OnRampHelper.sol"; import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; @@ -28,6 +28,9 @@ import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/tok contract E2E is OnRampSetup, OffRampSetup { using Internal for Internal.Any2EVMRampMessage; + uint256 internal constant TOKEN_AMOUNT_1 = 9; + uint256 internal constant TOKEN_AMOUNT_2 = 7; + Router internal s_sourceRouter2; OnRampHelper internal s_onRamp2; TokenAdminRegistry internal s_tokenAdminRegistry2; @@ -137,9 +140,9 @@ contract E2E is OnRampSetup, OffRampSetup { uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, _generateTokenMessage()); // Asserts that the tokens have been sent and the fee has been paid. assertEq( - balance0Pre - (messages1.length + messages2.length) * (i_tokenAmount0 + expectedFee), token0.balanceOf(OWNER) + balance0Pre - (messages1.length + messages2.length) * (TOKEN_AMOUNT_1 + expectedFee), token0.balanceOf(OWNER) ); - assertEq(balance1Pre - (messages1.length + messages2.length) * i_tokenAmount1, token1.balanceOf(OWNER)); + assertEq(balance1Pre - (messages1.length + messages2.length) * TOKEN_AMOUNT_2, token1.balanceOf(OWNER)); } // Commit @@ -215,7 +218,7 @@ contract E2E is OnRampSetup, OffRampSetup { vm.recordLogs(); _execute(reports); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR, messages1[0].header.sequenceNumber, messages1[0].header.messageId, @@ -224,7 +227,7 @@ contract E2E is OnRampSetup, OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR, messages1[1].header.sequenceNumber, messages1[1].header.messageId, @@ -233,7 +236,7 @@ contract E2E is OnRampSetup, OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR + 1, messages2[0].header.sequenceNumber, messages2[0].header.messageId, @@ -252,8 +255,8 @@ contract E2E is OnRampSetup, OffRampSetup { TokenAdminRegistry tokenAdminRegistry ) public returns (Internal.Any2EVMRampMessage memory) { Client.EVM2AnyMessage memory message = _generateTokenMessage(); - IERC20(s_sourceTokens[0]).approve(address(router), i_tokenAmount0 + router.getFee(DEST_CHAIN_SELECTOR, message)); - IERC20(s_sourceTokens[1]).approve(address(router), i_tokenAmount1); + IERC20(s_sourceTokens[0]).approve(address(router), TOKEN_AMOUNT_1 + router.getFee(DEST_CHAIN_SELECTOR, message)); + IERC20(s_sourceTokens[1]).approve(address(router), TOKEN_AMOUNT_2); uint256 feeAmount = router.getFee(DEST_CHAIN_SELECTOR, message); @@ -306,4 +309,17 @@ contract E2E is OnRampSetup, OffRampSetup { tokenAmounts: any2EVMTokenTransfer }); } + + function _generateTokenMessage() public view returns (Client.EVM2AnyMessage memory) { + Client.EVMTokenAmount[] memory tokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + tokenAmounts[0].amount = TOKEN_AMOUNT_1; + tokenAmounts[1].amount = TOKEN_AMOUNT_2; + return Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: s_sourceFeeToken, + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + } } diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol new file mode 100644 index 00000000000..44fe0e33eba --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyDestChainConfigUpdates is FeeQuoterSetup { + function test_Fuzz_applyDestChainConfigUpdates_Success( + FeeQuoter.DestChainConfigArgs memory destChainConfigArgs + ) public { + vm.assume(destChainConfigArgs.destChainSelector != 0); + vm.assume(destChainConfigArgs.destChainConfig.maxPerMsgGasLimit != 0); + destChainConfigArgs.destChainConfig.defaultTxGasLimit = uint32( + bound( + destChainConfigArgs.destChainConfig.defaultTxGasLimit, 1, destChainConfigArgs.destChainConfig.maxPerMsgGasLimit + ) + ); + destChainConfigArgs.destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; + + bool isNewChain = destChainConfigArgs.destChainSelector != DEST_CHAIN_SELECTOR; + + FeeQuoter.DestChainConfigArgs[] memory newDestChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + newDestChainConfigArgs[0] = destChainConfigArgs; + + if (isNewChain) { + vm.expectEmit(); + emit FeeQuoter.DestChainAdded(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); + } else { + vm.expectEmit(); + emit FeeQuoter.DestChainConfigUpdated(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); + } + + s_feeQuoter.applyDestChainConfigUpdates(newDestChainConfigArgs); + + _assertFeeQuoterDestChainConfigsEqual( + destChainConfigArgs.destChainConfig, s_feeQuoter.getDestChainConfig(destChainConfigArgs.destChainSelector) + ); + } + + function test_applyDestChainConfigUpdates_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](2); + destChainConfigArgs[0] = _generateFeeQuoterDestChainConfigArgs()[0]; + destChainConfigArgs[0].destChainConfig.isEnabled = false; + destChainConfigArgs[1] = _generateFeeQuoterDestChainConfigArgs()[0]; + destChainConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; + + vm.expectEmit(); + emit FeeQuoter.DestChainConfigUpdated(DEST_CHAIN_SELECTOR, destChainConfigArgs[0].destChainConfig); + vm.expectEmit(); + emit FeeQuoter.DestChainAdded(DEST_CHAIN_SELECTOR + 1, destChainConfigArgs[1].destChainConfig); + + vm.recordLogs(); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + FeeQuoter.DestChainConfig memory gotDestChainConfig0 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + FeeQuoter.DestChainConfig memory gotDestChainConfig1 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); + + assertEq(vm.getRecordedLogs().length, 2); + _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[0].destChainConfig, gotDestChainConfig0); + _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[1].destChainConfig, gotDestChainConfig1); + } + + function test_applyDestChainConfigUpdatesZeroInput_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](0); + + vm.recordLogs(); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_applyDestChainConfigUpdatesDefaultTxGasLimitEqZero_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainConfig.defaultTxGasLimit = 0; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_applyDestChainConfigUpdatesDefaultTxGasLimitGtMaxPerMessageGasLimit_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + // Allow setting to the max value + destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + // Revert when exceeding max value + destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit + 1; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_InvalidDestChainConfigDestChainSelectorEqZero_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainSelector = 0; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_InvalidChainFamilySelector_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainConfig.chainFamilySelector = bytes4(uint32(1)); + + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol new file mode 100644 index 00000000000..a32e50bb3e4 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyFeeTokensUpdates is FeeQuoterSetup { + function test_ApplyFeeTokensUpdates_Success() public { + address[] memory feeTokens = new address[](1); + feeTokens[0] = s_sourceTokens[1]; + + vm.expectEmit(); + emit FeeQuoter.FeeTokenAdded(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + assertEq(s_feeQuoter.getFeeTokens().length, 3); + assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); + + // add same feeToken is no-op + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + assertEq(s_feeQuoter.getFeeTokens().length, 3); + assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); + + vm.expectEmit(); + emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + assertEq(s_feeQuoter.getFeeTokens().length, 2); + + // removing already removed feeToken is no-op and does not emit an event + vm.recordLogs(); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + assertEq(s_feeQuoter.getFeeTokens().length, 2); + + vm.assertEq(vm.getRecordedLogs().length, 0); + + // Removing and adding the same fee token is allowed and emits both events + // Add it first + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + + vm.expectEmit(); + emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); + vm.expectEmit(); + emit FeeQuoter.FeeTokenAdded(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, feeTokens); + } + + function test_OnlyCallableByOwner_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), new address[](0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol new file mode 100644 index 00000000000..d31202e8a99 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates is FeeQuoterSetup { + function test_Fuzz_applyPremiumMultiplierWeiPerEthUpdates_Success( + FeeQuoter.PremiumMultiplierWeiPerEthArgs memory premiumMultiplierWeiPerEthArg + ) public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + premiumMultiplierWeiPerEthArgs[0] = premiumMultiplierWeiPerEthArg; + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + premiumMultiplierWeiPerEthArg.token, premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(premiumMultiplierWeiPerEthArg.token) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesSingleToken_Success() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; + premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesMultipleTokens_Success() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](2); + premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; + premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); + premiumMultiplierWeiPerEthArgs[1].token = vm.addr(2); + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth + ); + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(2), premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) + ); + assertEq( + premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(2)) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesZeroInput() public { + vm.recordLogs(); + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](0)); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_OnlyCallableByOwnerOrAdmin_Revert() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs; + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol new file mode 100644 index 00000000000..72ef7b89a75 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyTokenTransferFeeConfigUpdates is FeeQuoterSetup { + function test_Fuzz_ApplyTokenTransferFeeConfig_Success( + FeeQuoter.TokenTransferFeeConfig[2] memory tokenTransferFeeConfigs + ) public { + // To prevent Invalid Fee Range error from the fuzzer, bound the results to a valid range that + // where minFee < maxFee + tokenTransferFeeConfigs[0].minFeeUSDCents = + uint32(bound(tokenTransferFeeConfigs[0].minFeeUSDCents, 0, type(uint8).max)); + tokenTransferFeeConfigs[1].minFeeUSDCents = + uint32(bound(tokenTransferFeeConfigs[1].minFeeUSDCents, 0, type(uint8).max)); + + tokenTransferFeeConfigs[0].maxFeeUSDCents = uint32( + bound(tokenTransferFeeConfigs[0].maxFeeUSDCents, tokenTransferFeeConfigs[0].minFeeUSDCents + 1, type(uint32).max) + ); + tokenTransferFeeConfigs[1].maxFeeUSDCents = uint32( + bound(tokenTransferFeeConfigs[1].maxFeeUSDCents, tokenTransferFeeConfigs[1].minFeeUSDCents + 1, type(uint32).max) + ); + + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(2, 2); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; + + for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) { + for (uint256 j = 0; j < tokenTransferFeeConfigs.length; ++j) { + tokenTransferFeeConfigs[j].destBytesOverhead = uint32( + bound(tokenTransferFeeConfigs[j].destBytesOverhead, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, type(uint32).max) + ); + address feeToken = s_sourceTokens[j]; + tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].token = feeToken; + tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].tokenTransferFeeConfig = tokenTransferFeeConfigs[j]; + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[i].destChainSelector, feeToken, tokenTransferFeeConfigs[j] + ); + } + } + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + for (uint256 i = 0; i < tokenTransferFeeConfigs.length; ++i) { + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigs[i], + s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i].token + ) + ); + } + } + + function test_ApplyTokenTransferFeeConfig_Success() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 2); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 6, + maxFeeUSDCents: 7, + deciBps: 8, + destGasOverhead: 9, + destBytesOverhead: 312, + isEnabled: true + }); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token = address(11); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 12, + maxFeeUSDCents: 13, + deciBps: 14, + destGasOverhead: 15, + destBytesOverhead: 394, + isEnabled: true + }); + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig + ); + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig + ); + + FeeQuoter.TokenTransferFeeConfigRemoveArgs[] memory tokensToRemove = + new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0); + s_feeQuoter.applyTokenTransferFeeConfigUpdates(tokenTransferFeeConfigArgs, tokensToRemove); + + FeeQuoter.TokenTransferFeeConfig memory config0 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + FeeQuoter.TokenTransferFeeConfig memory config1 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token + ); + + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig, config0 + ); + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 + ); + + // Remove only the first token and validate only the first token is removed + tokensToRemove = new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](1); + tokensToRemove[0] = FeeQuoter.TokenTransferFeeConfigRemoveArgs({ + destChainSelector: tokenTransferFeeConfigArgs[0].destChainSelector, + token: tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + }); + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigDeleted( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates(new FeeQuoter.TokenTransferFeeConfigArgs[](0), tokensToRemove); + + config0 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + config1 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token + ); + + FeeQuoter.TokenTransferFeeConfig memory emptyConfig; + + _assertTokenTransferFeeConfigEqual(emptyConfig, config0); + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 + ); + } + + function test_ApplyTokenTransferFeeZeroInput() public { + vm.recordLogs(); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + new FeeQuoter.TokenTransferFeeConfigArgs[](0), new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_OnlyCallableByOwnerOrAdmin_Revert() public { + vm.startPrank(STRANGER); + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs; + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } + + function test_InvalidDestBytesOverhead_Revert() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 6, + maxFeeUSDCents: 7, + deciBps: 8, + destGasOverhead: 9, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES - 1), + isEnabled: true + }); + + vm.expectRevert( + abi.encodeWithSelector( + FeeQuoter.InvalidDestBytesOverhead.selector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destBytesOverhead + ) + ); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol new file mode 100644 index 00000000000..c2d36bee96d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterHelper} from "../helpers/FeeQuoterHelper.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_constructor is FeeQuoterSetup { + function test_Setup_Success() public virtual { + address[] memory priceUpdaters = new address[](2); + priceUpdaters[0] = STRANGER; + priceUpdaters[1] = OWNER; + address[] memory feeTokens = new address[](2); + feeTokens[0] = s_sourceTokens[0]; + feeTokens[1] = s_sourceTokens[1]; + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + tokenPriceFeedUpdates[1] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[1], s_dataFeedByToken[s_sourceTokens[1]], 6); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + priceUpdaters, + feeTokens, + tokenPriceFeedUpdates, + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + destChainConfigArgs + ); + + _assertFeeQuoterStaticConfigsEqual(s_feeQuoter.getStaticConfig(), staticConfig); + assertEq(feeTokens, s_feeQuoter.getFeeTokens()); + assertEq(priceUpdaters, s_feeQuoter.getAllAuthorizedCallers()); + assertEq(s_feeQuoter.typeAndVersion(), "FeeQuoter 1.6.0-dev"); + + _assertTokenPriceFeedConfigEquality( + tokenPriceFeedUpdates[0].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[0]) + ); + + _assertTokenPriceFeedConfigEquality( + tokenPriceFeedUpdates[1].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[1]) + ); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].token) + ); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].token) + ); + + FeeQuoter.TokenTransferFeeConfigArgs memory tokenTransferFeeConfigArg = s_feeQuoterTokenTransferFeeConfigArgs[0]; + for (uint256 i = 0; i < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++i) { + FeeQuoter.TokenTransferFeeConfigSingleTokenArgs memory tokenFeeArgs = + s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i]; + + _assertTokenTransferFeeConfigEqual( + tokenFeeArgs.tokenTransferFeeConfig, + s_feeQuoter.getTokenTransferFeeConfig(tokenTransferFeeConfigArg.destChainSelector, tokenFeeArgs.token) + ); + } + + for (uint256 i = 0; i < destChainConfigArgs.length; ++i) { + FeeQuoter.DestChainConfig memory expectedConfig = destChainConfigArgs[i].destChainConfig; + uint64 destChainSelector = destChainConfigArgs[i].destChainSelector; + + _assertFeeQuoterDestChainConfigsEqual(expectedConfig, s_feeQuoter.getDestChainConfig(destChainSelector)); + } + } + + function test_InvalidStalenessThreshold_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: 0 + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function test_InvalidLinkTokenEqZeroAddress_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: address(0), + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function test_InvalidMaxFeeJuelsPerMsg_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: 0, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function _assertFeeQuoterStaticConfigsEqual( + FeeQuoter.StaticConfig memory a, + FeeQuoter.StaticConfig memory b + ) internal pure { + assertEq(a.linkToken, b.linkToken); + assertEq(a.maxFeeJuelsPerMsg, b.maxFeeJuelsPerMsg); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol new file mode 100644 index 00000000000..ca6a7e16126 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_convertTokenAmount is FeeQuoterSetup { + function test_ConvertTokenAmount_Success() public view { + Internal.PriceUpdates memory initialPriceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + uint256 amount = 3e16; + uint256 conversionRate = (uint256(initialPriceUpdates.tokenPriceUpdates[2].usdPerToken) * 1e18) + / uint256(initialPriceUpdates.tokenPriceUpdates[0].usdPerToken); + uint256 expected = (amount * conversionRate) / 1e18; + assertEq(s_feeQuoter.convertTokenAmount(s_weth, amount, s_sourceTokens[0]), expected); + } + + function test_Fuzz_ConvertTokenAmount_Success( + uint256 feeTokenAmount, + uint224 usdPerFeeToken, + uint160 usdPerLinkToken, + uint224 usdPerUnitGas + ) public { + vm.assume(usdPerFeeToken > 0); + vm.assume(usdPerLinkToken > 0); + // We bound the max fees to be at most uint96.max link. + feeTokenAmount = bound(feeTokenAmount, 0, (uint256(type(uint96).max) * usdPerLinkToken) / usdPerFeeToken); + + address feeToken = address(1); + address linkToken = address(2); + address[] memory feeTokens = new address[](1); + feeTokens[0] = feeToken; + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + + Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](2); + tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: feeToken, usdPerToken: usdPerFeeToken}); + tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: linkToken, usdPerToken: usdPerLinkToken}); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: usdPerUnitGas}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); + + s_feeQuoter.updatePrices(priceUpdates); + + uint256 linkFee = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, linkToken); + assertEq(linkFee, (feeTokenAmount * usdPerFeeToken) / usdPerLinkToken); + } + + // Reverts + + function test_LinkTokenNotSupported_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.convertTokenAmount(DUMMY_CONTRACT_ADDRESS, 3e16, s_sourceTokens[0]); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.convertTokenAmount(s_sourceTokens[0], 3e16, DUMMY_CONTRACT_ADDRESS); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol new file mode 100644 index 00000000000..2e498746c3d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getDataAvailabilityCost is FeeQuoterSetup { + function test_EmptyMessageCalculatesDataAvailabilityCost_Success() public { + uint256 dataAvailabilityCostUSD = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); + + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; + uint256 expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + + // Test that the cost is destination chain specific + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR + 1; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = + destChainConfig.destDataAvailabilityOverheadGas * 2; + destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = + destChainConfig.destGasPerDataAvailabilityByte * 2; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = + destChainConfig.destDataAvailabilityMultiplierBps * 2; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); + uint256 dataAvailabilityCostUSD2 = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR + 1, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); + dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; + expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD2); + assertFalse(dataAvailabilityCostUSD == dataAvailabilityCostUSD2); + } + + function test_SimpleMessageCalculatesDataAvailabilityCost_Success() public view { + uint256 dataAvailabilityCostUSD = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); + + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 dataAvailabilityLengthBytes = + Internal.MESSAGE_FIXED_BYTES + 100 + (5 * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + 50; + uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; + uint256 expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + } + + function test_SimpleMessageCalculatesDataAvailabilityCostUnsupportedDestChainSelector_Success() public view { + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost(0, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); + + assertEq(dataAvailabilityCostUSD, 0); + } + + function test_Fuzz_ZeroDataAvailabilityGasPriceAlwaysCalculatesZeroDataAvailabilityCost_Success( + uint64 messageDataLength, + uint32 numberOfTokens, + uint32 tokenTransferBytesOverhead + ) public view { + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, 0, messageDataLength, numberOfTokens, tokenTransferBytesOverhead + ); + + assertEq(0, dataAvailabilityCostUSD); + } + + function test_Fuzz_CalculateDataAvailabilityCost_Success( + uint64 destChainSelector, + uint32 destDataAvailabilityOverheadGas, + uint16 destGasPerDataAvailabilityByte, + uint16 destDataAvailabilityMultiplierBps, + uint112 dataAvailabilityGasPrice, + uint64 messageDataLength, + uint32 numberOfTokens, + uint32 tokenTransferBytesOverhead + ) public { + vm.assume(destChainSelector != 0); + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(destChainSelector); + destChainConfigArgs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: destChainSelector, destChainConfig: destChainConfig}); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = destDataAvailabilityOverheadGas; + destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = destGasPerDataAvailabilityByte; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = destDataAvailabilityMultiplierBps; + destChainConfigArgs[0].destChainConfig.defaultTxGasLimit = GAS_LIMIT; + destChainConfigArgs[0].destChainConfig.maxPerMsgGasLimit = GAS_LIMIT; + destChainConfigArgs[0].destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( + destChainConfigArgs[0].destChainSelector, + dataAvailabilityGasPrice, + messageDataLength, + numberOfTokens, + tokenTransferBytesOverhead + ); + + uint256 dataAvailabilityLengthBytes = Internal.MESSAGE_FIXED_BYTES + messageDataLength + + (numberOfTokens * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + tokenTransferBytesOverhead; + + uint256 dataAvailabilityGas = + destDataAvailabilityOverheadGas + destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; + uint256 expectedDataAvailabilityCostUSD = + dataAvailabilityGasPrice * dataAvailabilityGas * destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol new file mode 100644 index 00000000000..18ccf5efa79 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenAndGasPrices is FeeQuoterSetup { + function test_GetFeeTokenAndGasPrices_Success() public view { + (uint224 feeTokenPrice, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, DEST_CHAIN_SELECTOR); + + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + + assertEq(feeTokenPrice, s_sourceTokenPrices[0]); + assertEq(gasPrice, priceUpdates.gasPriceUpdates[0].usdPerUnitGas); + } + + function test_StalenessCheckDisabled_Success() public { + uint64 neverStaleChainSelector = 345678; + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = neverStaleChainSelector; + destChainConfigArgs[0].destChainConfig.gasPriceStalenessThreshold = 0; // disables the staleness check + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: neverStaleChainSelector, usdPerUnitGas: 999}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); + s_feeQuoter.updatePrices(priceUpdates); + + // this should have no affect! But we do it anyway to make sure the staleness check is disabled + vm.warp(block.timestamp + 52_000_000 weeks); // 1M-ish years + + (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, neverStaleChainSelector); + + assertEq(gasPrice, 999); + } + + function test_ZeroGasPrice_Success() public { + uint64 zeroGasDestChainSelector = 345678; + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = zeroGasDestChainSelector; + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: zeroGasDestChainSelector, usdPerUnitGas: 0}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); + s_feeQuoter.updatePrices(priceUpdates); + + (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, zeroGasDestChainSelector); + + assertEq(gasPrice, 0); + } + + function test_UnsupportedChain_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); + s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR + 1); + } + + function test_StaleGasPrice_Revert() public { + uint256 diff = TWELVE_HOURS + 1; + vm.warp(block.timestamp + diff); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.StaleGasPrice.selector, DEST_CHAIN_SELECTOR, TWELVE_HOURS, diff)); + s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol new file mode 100644 index 00000000000..c00e750d27f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenPrice is FeeQuoterSetup { + function test_GetTokenPriceFromFeed_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Above staleness threshold + vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 1); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + + vm.expectCall(s_dataFeedByToken[sourceToken], abi.encodeWithSelector(MockV3Aggregator.latestRoundData.selector)); + + Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer.value, uint224(1e18)); + assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); + } + + function test_GetTokenPrice_LocalMoreRecent_Success() public { + uint256 originalTimestampValue = block.timestamp; + + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + + update.tokenPriceUpdates[0] = + Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: uint32(originalTimestampValue + 5)}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 10); + + Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + + //Assert that the returned price is the local price, not the oracle price + assertEq(tokenPriceAnswer.value, update.tokenPriceUpdates[0].usdPerToken); + assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol new file mode 100644 index 00000000000..63f936332fd --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenPrices is FeeQuoterSetup { + function test_GetTokenPrices_Success() public view { + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + + address[] memory tokens = new address[](3); + tokens[0] = s_sourceTokens[0]; + tokens[1] = s_sourceTokens[1]; + tokens[2] = s_weth; + + Internal.TimestampedPackedUint224[] memory tokenPrices = s_feeQuoter.getTokenPrices(tokens); + + assertEq(tokenPrices.length, 3); + assertEq(tokenPrices[0].value, priceUpdates.tokenPriceUpdates[0].usdPerToken); + assertEq(tokenPrices[1].value, priceUpdates.tokenPriceUpdates[1].usdPerToken); + assertEq(tokenPrices[2].value, priceUpdates.tokenPriceUpdates[2].usdPerToken); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol new file mode 100644 index 00000000000..a426775ca63 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenTransferCost is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + address internal s_selfServeTokenDefaultPricing = makeAddr("self-serve-token-default-pricing"); + + function test_NoTokenTransferChargesZeroFee_Success() public view { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(0, feeUSDWei); + assertEq(0, destGasOverhead); + assertEq(0, destBytesOverhead); + } + + function test_getTokenTransferCost_selfServeUsesDefaults_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_selfServeTokenDefaultPricing, 1000); + + // Get config to assert it isn't set + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + assertFalse(transferFeeConfig.isEnabled); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + // Assert that the default values are used + assertEq(uint256(DEFAULT_TOKEN_FEE_USD_CENTS) * 1e16, feeUSDWei); + assertEq(DEFAULT_TOKEN_DEST_GAS_OVERHEAD, destGasOverhead); + assertEq(DEFAULT_TOKEN_BYTES_OVERHEAD, destBytesOverhead); + } + + function test_SmallTokenTransferChargesMinFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1000); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 0); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_LargeTokenTransferChargesMaxFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.maxFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_FeeTokenBpsFee_Success() public view { + uint256 tokenAmount = 10000e18; + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + uint256 usdWei = _calcUSDValueFromTokenAmount(s_feeTokenPrice, tokenAmount); + uint256 bpsUSDWei = _applyBpsRatio( + usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.deciBps + ); + + assertEq(bpsUSDWei, feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_CustomTokenBpsFee_Success() public view { + uint256 tokenAmount = 200000e18; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](1), + feeToken: s_sourceFeeToken, + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + message.tokenAmounts[0] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: tokenAmount}); + + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + uint256 usdWei = _calcUSDValueFromTokenAmount(CUSTOM_TOKEN_PRICE, tokenAmount); + uint256 bpsUSDWei = _applyBpsRatio( + usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig.deciBps + ); + + assertEq(bpsUSDWei, feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_ZeroFeeConfigChargesMinFee_Success() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = s_sourceFeeToken; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES), + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + // if token charges 0 bps, it should cost minFee to transfer + assertEq( + _configUSDCentToWei( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.minFeeUSDCents + ), + feeUSDWei + ); + assertEq(0, destGasOverhead); + assertEq(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, destBytesOverhead); + } + + function test_Fuzz_TokenTransferFeeDuplicateTokens_Success(uint256 transfers, uint256 amount) public view { + // It shouldn't be possible to pay materially lower fees by splitting up the transfers. + // Note it is possible to pay higher fees since the minimum fees are added. + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + transfers = bound(transfers, 1, destChainConfig.maxNumberOfTokensPerMsg); + // Cap amount to avoid overflow + amount = bound(amount, 0, 1e36); + Client.EVMTokenAmount[] memory multiple = new Client.EVMTokenAmount[](transfers); + for (uint256 i = 0; i < transfers; ++i) { + multiple[i] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount}); + } + Client.EVMTokenAmount[] memory single = new Client.EVMTokenAmount[](1); + single[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount * transfers}); + + address feeToken = s_sourceRouter.getWrappedNative(); + + (uint256 feeSingleUSDWei, uint32 gasOverheadSingle, uint32 bytesOverheadSingle) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, single); + (uint256 feeMultipleUSDWei, uint32 gasOverheadMultiple, uint32 bytesOverheadMultiple) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, multiple); + + // Note that there can be a rounding error once per split. + assertGe(feeMultipleUSDWei, (feeSingleUSDWei - destChainConfig.maxNumberOfTokensPerMsg)); + assertEq(gasOverheadMultiple, gasOverheadSingle * transfers); + assertEq(bytesOverheadMultiple, bytesOverheadSingle * transfers); + } + + function test_MixedTokenTransferFee_Success() public view { + address[3] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative(), CUSTOM_TOKEN]; + uint224[3] memory tokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice, CUSTOM_TOKEN_PRICE]; + FeeQuoter.TokenTransferFeeConfig[3] memory tokenTransferFeeConfigs = [ + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[0]), + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[1]), + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[2]) + ]; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](3), + feeToken: s_sourceRouter.getWrappedNative(), + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + uint256 expectedTotalGas = 0; + uint256 expectedTotalBytes = 0; + + // Start with small token transfers, total bps fee is lower than min token transfer fee + for (uint256 i = 0; i < testTokens.length; ++i) { + message.tokenAmounts[i] = Client.EVMTokenAmount({token: testTokens[i], amount: 1e14}); + FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[i]); + + expectedTotalGas += tokenTransferFeeConfig.destGasOverhead == 0 + ? DEFAULT_TOKEN_DEST_GAS_OVERHEAD + : tokenTransferFeeConfig.destGasOverhead; + expectedTotalBytes += tokenTransferFeeConfig.destBytesOverhead == 0 + ? DEFAULT_TOKEN_BYTES_OVERHEAD + : tokenTransferFeeConfig.destBytesOverhead; + } + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + + uint256 expectedFeeUSDWei = 0; + for (uint256 i = 0; i < testTokens.length; ++i) { + expectedFeeUSDWei += _configUSDCentToWei( + tokenTransferFeeConfigs[i].minFeeUSDCents == 0 + ? DEFAULT_TOKEN_FEE_USD_CENTS + : tokenTransferFeeConfigs[i].minFeeUSDCents + ); + } + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 1"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 1"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 1"); + + // Set 1st token transfer to a meaningful amount so its bps fee is now between min and max fee + message.tokenAmounts[0] = Client.EVMTokenAmount({token: testTokens[0], amount: 10000e18}); + + uint256 token0USDWei = _applyBpsRatio( + _calcUSDValueFromTokenAmount(tokenPrices[0], message.tokenAmounts[0].amount), tokenTransferFeeConfigs[0].deciBps + ); + uint256 token1USDWei = _configUSDCentToWei(DEFAULT_TOKEN_FEE_USD_CENTS); + + (feeUSDWei, destGasOverhead, destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].minFeeUSDCents); + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 2"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 2"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 2"); + + // Set 2nd token transfer to a large amount that is higher than maxFeeUSD + message.tokenAmounts[2] = Client.EVMTokenAmount({token: testTokens[2], amount: 1e36}); + + (feeUSDWei, destGasOverhead, destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].maxFeeUSDCents); + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 3"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 3"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 3"); + } + + function _applyBpsRatio(uint256 tokenAmount, uint16 ratio) internal pure returns (uint256) { + return (tokenAmount * ratio) / 1e5; + } + + function _calcUSDValueFromTokenAmount(uint224 tokenPrice, uint256 tokenAmount) internal pure returns (uint256) { + return (tokenPrice * tokenAmount) / 1e18; + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol new file mode 100644 index 00000000000..fdafde91f62 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getValidatedFee is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + function test_EmptyMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = testTokens[i]; + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_ZeroDataAvailabilityMultiplier_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + destChainConfigArgs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: destChainConfig}); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD) / s_feeTokenPrice; + assertEq(totalPriceInFeeToken, feeAmount); + } + + function test_HighGasMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 customGasLimit = MAX_GAS_LIMIT; + uint256 customDataSize = MAX_DATA_SIZE; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: new bytes(customDataSize), + tokenAmounts: new Client.EVMTokenAmount[](0), + feeToken: testTokens[i], + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) + }); + + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + uint256 gasUsed = customGasLimit + DEST_GAS_OVERHEAD + customDataSize * DEST_GAS_PER_PAYLOAD_BYTE; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_SingleTokenMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 tokenAmount = 10000e18; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + message.feeToken = testTokens[i]; + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + uint32 destBytesOverhead = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destBytesOverhead; + uint32 tokenBytesOverhead = + destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD + + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destGasOverhead; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + (uint256 transferFeeUSD,,) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); + uint256 messageFeeUSD = (transferFeeUSD * s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken)); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, + USD_PER_DATA_AVAILABILITY_GAS, + message.data.length, + message.tokenAmounts.length, + tokenBytesOverhead + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_MessageWithDataAndTokenTransfer_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 customGasLimit = 1_000_000; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](2), + feeToken: testTokens[i], + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) + }); + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + message.tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceFeeToken, amount: 10000e18}); // feeTokenAmount + message.tokenAmounts[1] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: 200000e18}); // customTokenAmount + message.data = "random bits and bytes that should be factored into the cost of the message"; + + uint32 tokenGasOverhead = 0; + uint32 tokenBytesOverhead = 0; + for (uint256 j = 0; j < message.tokenAmounts.length; ++j) { + tokenGasOverhead += + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destGasOverhead; + uint32 destBytesOverhead = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destBytesOverhead; + tokenBytesOverhead += destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; + } + + uint256 gasUsed = + customGasLimit + DEST_GAS_OVERHEAD + message.data.length * DEST_GAS_PER_PAYLOAD_BYTE + tokenGasOverhead; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + (uint256 transferFeeUSD,,) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); + uint256 messageFeeUSD = (transferFeeUSD * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, + USD_PER_DATA_AVAILABILITY_GAS, + message.data.length, + message.tokenAmounts.length, + tokenBytesOverhead + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message)); + } + } + + function test_Fuzz_EnforceOutOfOrder(bool enforce, bool allowOutOfOrderExecution) public { + // Update config to enforce allowOutOfOrderExecution = defaultVal. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = enforce; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: allowOutOfOrderExecution}) + ); + + // If enforcement is on, only true should be allowed. + if (enforce && !allowOutOfOrderExecution) { + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + } + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + // Reverts + + function test_DestinationChainNotEnabled_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR + 1, _generateEmptyMessage()); + } + + function test_EnforceOutOfOrder_Revert() public { + // Update config to enforce allowOutOfOrderExecution = true. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + vm.stopPrank(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + // Empty extraArgs to should revert since it enforceOutOfOrder is true. + message.extraArgs = ""; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_MessageTooLarge_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.data = new bytes(MAX_DATA_SIZE + 1); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageTooLarge.selector, MAX_DATA_SIZE, message.data.length)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_TooManyTokens_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint256 tooMany = MAX_TOKENS_LENGTH + 1; + message.tokenAmounts = new Client.EVMTokenAmount[](tooMany); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.UnsupportedNumberOfTokens.selector, tooMany, MAX_TOKENS_LENGTH)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + // Asserts gasLimit must be <=maxGasLimit + function test_MessageGasLimitTooHigh_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: MAX_GAS_LIMIT + 1})); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageGasLimitTooHigh.selector)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_NotAFeeToken_Revert() public { + address notAFeeToken = address(0x111111); + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(notAFeeToken, 1); + message.feeToken = notAFeeToken; + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, notAFeeToken)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_InvalidEVMAddress_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.receiver = abi.encode(type(uint208).max); + + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, message.receiver)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol new file mode 100644 index 00000000000..6d508bc9116 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getValidatedTokenPrice is FeeQuoterSetup { + function test_GetValidatedTokenPrice_Success() public view { + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + address token = priceUpdates.tokenPriceUpdates[0].sourceToken; + + uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); + + assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); + } + + function test_GetValidatedTokenPriceFromFeed_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Right below staleness threshold + vm.warp(originalTimestampValue + TWELVE_HOURS); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedOverStalenessPeriod_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Right above staleness threshold + vm.warp(originalTimestampValue + TWELVE_HOURS + 1); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedMaxInt224Value_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max))); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is: uint224.MAX_VALUE * (10 ** (36 - 18 - 18)) + assertEq(tokenPriceAnswer, uint224(type(uint224).max)); + } + + function test_GetValidatedTokenPriceFromFeedErc20Below18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 6); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 6); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e6) -> expected 1e30 + assertEq(tokenPriceAnswer, uint224(1e30)); + } + + function test_GetValidatedTokenPriceFromFeedErc20Above18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 24); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 24); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e24) -> expected 1e12 + assertEq(tokenPriceAnswer, uint224(1e12)); + } + + function test_GetValidatedTokenPriceFromFeedFeedAt18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, 1e18); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedFeedAt0Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 0); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 0, 1e31); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 0); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e31 (0 decimal token) - unit is (1e18 * 1e18 / 1e0) -> expected 1e36 + assertEq(tokenPriceAnswer, uint224(1e67)); + } + + function test_GetValidatedTokenPriceFromFeedFlippedDecimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 20); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 20, 1e18); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 20); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e20) -> expected 1e14 + assertEq(tokenPriceAnswer, uint224(1e14)); + } + + function test_StaleFeeToken_Success() public { + vm.warp(block.timestamp + TWELVE_HOURS + 1); + + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + address token = priceUpdates.tokenPriceUpdates[0].sourceToken; + + uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); + + assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); + } + + // Reverts + + function test_OverflowFeedPrice_Revert() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max) + 1)); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); + s_feeQuoter.getValidatedTokenPrice(tokenAddress); + } + + function test_UnderflowFeedPrice_Revert() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, -1); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); + s_feeQuoter.getValidatedTokenPrice(tokenAddress); + } + + function test_TokenNotSupported_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.getValidatedTokenPrice(DUMMY_CONTRACT_ADDRESS); + } + + function test_TokenNotSupportedFeed_Revert() public { + address sourceToken = _initialiseSingleTokenPriceFeed(); + MockV3Aggregator(s_dataFeedByToken[sourceToken]).updateAnswer(0); + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: sourceToken, usdPerToken: 0}); + + s_feeQuoter.updatePrices(priceUpdates); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, sourceToken)); + s_feeQuoter.getValidatedTokenPrice(sourceToken); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol new file mode 100644 index 00000000000..aba4e178865 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_onReport is FeeQuoterSetup { + address internal constant FORWARDER_1 = address(0x1); + address internal constant WORKFLOW_OWNER_1 = address(0x3); + bytes10 internal constant WORKFLOW_NAME_1 = "workflow1"; + bytes2 internal constant REPORT_NAME_1 = "01"; + address internal s_onReportTestToken1; + address internal s_onReportTestToken2; + + function setUp() public virtual override { + super.setUp(); + s_onReportTestToken1 = s_sourceTokens[0]; + s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); + + KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1); + permissions[0] = KeystoneFeedsPermissionHandler.Permission({ + forwarder: FORWARDER_1, + workflowOwner: WORKFLOW_OWNER_1, + workflowName: WORKFLOW_NAME_1, + reportName: REPORT_NAME_1, + isAllowed: true + }); + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2); + tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({ + sourceToken: s_onReportTestToken1, + feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 18, isEnabled: true}) + }); + tokenPriceFeeds[1] = FeeQuoter.TokenPriceFeedUpdate({ + sourceToken: s_onReportTestToken2, + feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 20, isEnabled: true}) + }); + s_feeQuoter.setReportPermissions(permissions); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeeds); + } + + function test_onReport_Success() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + report[1] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); + + uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); + uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, report[1].price); + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp); + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp); + + changePrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).value, expectedStoredToken1Price); + vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).timestamp, report[0].timestamp); + + vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).value, expectedStoredToken2Price); + vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).timestamp, report[1].timestamp); + } + + function test_OnReport_StaleUpdate_SkipPriceUpdate_Success() public { + //Creating a correct report + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + + uint224 expectedStoredTokenPrice = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp); + + changePrank(FORWARDER_1); + //setting the correct price and time with the correct report + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + //create a stale report + report[0] = FeeQuoter.ReceivedCCIPFeedReport({ + token: s_onReportTestToken1, + price: 4e18, + timestamp: uint32(block.timestamp - 1) + }); + + //record logs to check no events were emitted + vm.recordLogs(); + + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_onReport_TokenNotSupported_Revert() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); + + // Revert due to token config not being set with the isEnabled flag + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); + vm.startPrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } + + function test_onReport_InvalidForwarder_Reverts() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[0], price: 4e18, timestamp: uint32(block.timestamp)}); + + vm.expectRevert( + abi.encodeWithSelector( + KeystoneFeedsPermissionHandler.ReportForwarderUnauthorized.selector, + STRANGER, + WORKFLOW_OWNER_1, + WORKFLOW_NAME_1, + REPORT_NAME_1 + ) + ); + changePrank(STRANGER); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } + + function test_onReport_UnsupportedToken_Reverts() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); + changePrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol new file mode 100644 index 00000000000..8f4e3f954ca --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { + FeeQuoter.DestChainConfig private s_destChainConfig; + + function setUp() public virtual override { + super.setUp(); + s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; + } + + function test_EVMExtraArgsV1_Success() public view { + Client.EVMExtraArgsV1 memory inputArgs = Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + Client.EVMExtraArgsV2 memory expectedOutputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), + abi.encode(expectedOutputArgs) + ); + } + + function test_EVMExtraArgsV2_Success() public view { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), abi.encode(inputArgs) + ); + } + + function test_EVMExtraArgsDefault_Success() public view { + Client.EVMExtraArgsV2 memory expectedOutputArgs = + Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.defaultTxGasLimit, allowOutOfOrderExecution: false}); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes("", s_destChainConfig)), abi.encode(expectedOutputArgs) + ); + } + + // Reverts + + function test_EVMExtraArgsInvalidExtraArgsTag_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + // Invalidate selector + inputExtraArgs[0] = bytes1(uint8(0)); + + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_EVMExtraArgsEnforceOutOfOrder_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + s_destChainConfig.enforceOutOfOrder = true; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_EVMExtraArgsGasLimitTooHigh_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.maxPerMsgGasLimit + 1, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + + vm.expectRevert(FeeQuoter.MessageGasLimitTooHigh.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol new file mode 100644 index 00000000000..65baa576ead --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_processMessageArgs is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + function setUp() public virtual override { + super.setUp(); + } + + function test_processMessageArgs_WithLinkTokenAmount_Success() public view { + ( + uint256 msgFeeJuels, + /* bool isOutOfOrderExecution */ + , + /* bytes memory convertedExtraArgs */ + , + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + // LINK + s_sourceTokens[0], + MAX_MSG_FEES_JUELS, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(msgFeeJuels, MAX_MSG_FEES_JUELS); + } + + function test_processMessageArgs_WithConvertedTokenAmount_Success() public view { + address feeToken = s_sourceTokens[1]; + uint256 feeTokenAmount = 10_000 gwei; + uint256 expectedConvertedAmount = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, s_sourceTokens[0]); + + ( + uint256 msgFeeJuels, + /* bool isOutOfOrderExecution */ + , + /* bytes memory convertedExtraArgs */ + , + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + feeToken, + feeTokenAmount, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(msgFeeJuels, expectedConvertedAmount); + } + + function test_processMessageArgs_WithEmptyEVMExtraArgs_Success() public view { + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, false); + assertEq(convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes("", DEST_CHAIN_SELECTOR))); + } + + function test_processMessageArgs_WithEVMExtraArgsV1_Success() public view { + bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 1000})); + + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + extraArgs, + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, false); + assertEq( + convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) + ); + } + + function test_processMessageArgs_WitEVMExtraArgsV2_Success() public view { + bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV2({gasLimit: 0, allowOutOfOrderExecution: true})); + + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + extraArgs, + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, true); + assertEq( + convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) + ); + } + + // Reverts + + function test_processMessageArgs_MessageFeeTooHigh_Revert() public { + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) + ); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + MAX_MSG_FEES_JUELS + 1, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_InvalidExtraArgs_Revert() public { + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + "wrong extra args", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_MalformedEVMExtraArgs_Revert() public { + // abi.decode error + vm.expectRevert(); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV1({gasLimit: 100})), + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_WithCorrectPoolReturnData_Success() public view { + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + sourceTokenAmounts[1].amount = 1e18; + sourceTokenAmounts[1].token = CUSTOM_TOKEN_2; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](2); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + tokenAmounts[1] = _getSourceTokenData(sourceTokenAmounts[1], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + bytes[] memory expectedDestExecData = new bytes[](2); + expectedDestExecData[0] = abi.encode( + s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destGasOverhead + ); + expectedDestExecData[1] = abi.encode(DEFAULT_TOKEN_DEST_GAS_OVERHEAD); //expected return data should be abi.encoded default as isEnabled is false + + // No revert - successful + ( /* msgFeeJuels */ , /* isOutOfOrderExecution */, /* convertedExtraArgs */, bytes[] memory destExecData) = + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + for (uint256 i = 0; i < destExecData.length; ++i) { + assertEq(destExecData[i], expectedDestExecData[i]); + } + } + + function test_processMessageArgs_TokenAmountArraysMismatching_Revert() public { + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + + // Revert due to index out of bounds access + vm.expectRevert(); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + MAX_MSG_FEES_JUELS, + "", + new Internal.EVM2AnyTokenTransfer[](1), + new Client.EVMTokenAmount[](0) + ); + } + + function test_applyTokensTransferFeeConfigUpdates_InvalidFeeRange_Revert() public { + address sourceETH = s_sourceTokens[1]; + + // Set token config to allow larger data + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 1, + maxFeeUSDCents: 0, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidFeeRange.selector, 1, 0)); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } + + function test_processMessageArgs_SourceTokenDataTooLarge_Revert() public { + address sourceETH = s_sourceTokens[1]; + + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); + sourceTokenAmounts[0].amount = 1000; + sourceTokenAmounts[0].token = sourceETH; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + + // No data set, should succeed + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set max data length, should succeed + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set data to max length +1, should revert + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set token config to allow larger data + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set the token data larger than the configured token data, should revert + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 32 + 1); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + } + + function test_processMessageArgs_InvalidEVMAddressDestToken_Revert() public { + bytes memory nonEvmAddress = abi.encode(type(uint208).max); + + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + tokenAmounts[0].destTokenAddress = nonEvmAddress; + + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, nonEvmAddress)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol deleted file mode 100644 index 4cd34db04a3..00000000000 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol +++ /dev/null @@ -1,2378 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol"; -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; -import {FeeQuoter} from "../../FeeQuoter.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; -import {FeeQuoterHelper} from "../helpers/FeeQuoterHelper.sol"; -import {FeeQuoterFeeSetup, FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; - -import {Vm} from "forge-std/Vm.sol"; - -contract FeeQuoter_constructor is FeeQuoterSetup { - function test_Setup_Success() public virtual { - address[] memory priceUpdaters = new address[](2); - priceUpdaters[0] = STRANGER; - priceUpdaters[1] = OWNER; - address[] memory feeTokens = new address[](2); - feeTokens[0] = s_sourceTokens[0]; - feeTokens[1] = s_sourceTokens[1]; - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - tokenPriceFeedUpdates[1] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[1], s_dataFeedByToken[s_sourceTokens[1]], 6); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - priceUpdaters, - feeTokens, - tokenPriceFeedUpdates, - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - destChainConfigArgs - ); - - _assertFeeQuoterStaticConfigsEqual(s_feeQuoter.getStaticConfig(), staticConfig); - assertEq(feeTokens, s_feeQuoter.getFeeTokens()); - assertEq(priceUpdaters, s_feeQuoter.getAllAuthorizedCallers()); - assertEq(s_feeQuoter.typeAndVersion(), "FeeQuoter 1.6.0-dev"); - - _assertTokenPriceFeedConfigEquality( - tokenPriceFeedUpdates[0].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[0]) - ); - - _assertTokenPriceFeedConfigEquality( - tokenPriceFeedUpdates[1].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[1]) - ); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].token) - ); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].token) - ); - - FeeQuoter.TokenTransferFeeConfigArgs memory tokenTransferFeeConfigArg = s_feeQuoterTokenTransferFeeConfigArgs[0]; - for (uint256 i = 0; i < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++i) { - FeeQuoter.TokenTransferFeeConfigSingleTokenArgs memory tokenFeeArgs = - s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i]; - - _assertTokenTransferFeeConfigEqual( - tokenFeeArgs.tokenTransferFeeConfig, - s_feeQuoter.getTokenTransferFeeConfig(tokenTransferFeeConfigArg.destChainSelector, tokenFeeArgs.token) - ); - } - - for (uint256 i = 0; i < destChainConfigArgs.length; ++i) { - FeeQuoter.DestChainConfig memory expectedConfig = destChainConfigArgs[i].destChainConfig; - uint64 destChainSelector = destChainConfigArgs[i].destChainSelector; - - _assertFeeQuoterDestChainConfigsEqual(expectedConfig, s_feeQuoter.getDestChainConfig(destChainSelector)); - } - } - - function test_InvalidStalenessThreshold_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: 0 - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } - - function test_InvalidLinkTokenEqZeroAddress_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: address(0), - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } - - function test_InvalidMaxFeeJuelsPerMsg_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: 0, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } -} - -contract FeeQuoter_getTokenPrices is FeeQuoterSetup { - function test_GetTokenPrices_Success() public view { - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - - address[] memory tokens = new address[](3); - tokens[0] = s_sourceTokens[0]; - tokens[1] = s_sourceTokens[1]; - tokens[2] = s_weth; - - Internal.TimestampedPackedUint224[] memory tokenPrices = s_feeQuoter.getTokenPrices(tokens); - - assertEq(tokenPrices.length, 3); - assertEq(tokenPrices[0].value, priceUpdates.tokenPriceUpdates[0].usdPerToken); - assertEq(tokenPrices[1].value, priceUpdates.tokenPriceUpdates[1].usdPerToken); - assertEq(tokenPrices[2].value, priceUpdates.tokenPriceUpdates[2].usdPerToken); - } -} - -contract FeeQuoter_getTokenPrice is FeeQuoterSetup { - function test_GetTokenPriceFromFeed_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Above staleness threshold - vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 1); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - - vm.expectCall(s_dataFeedByToken[sourceToken], abi.encodeWithSelector(MockV3Aggregator.latestRoundData.selector)); - - Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer.value, uint224(1e18)); - assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); - } - - function test_GetTokenPrice_LocalMoreRecent_Success() public { - uint256 originalTimestampValue = block.timestamp; - - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - - update.tokenPriceUpdates[0] = - Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: uint32(originalTimestampValue + 5)}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 10); - - Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - - //Assert that the returned price is the local price, not the oracle price - assertEq(tokenPriceAnswer.value, update.tokenPriceUpdates[0].usdPerToken); - assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); - } -} - -contract FeeQuoter_getValidatedTokenPrice is FeeQuoterSetup { - function test_GetValidatedTokenPrice_Success() public view { - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - address token = priceUpdates.tokenPriceUpdates[0].sourceToken; - - uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); - - assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); - } - - function test_GetValidatedTokenPriceFromFeed_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Right below staleness threshold - vm.warp(originalTimestampValue + TWELVE_HOURS); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedOverStalenessPeriod_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Right above staleness threshold - vm.warp(originalTimestampValue + TWELVE_HOURS + 1); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedMaxInt224Value_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max))); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is: uint224.MAX_VALUE * (10 ** (36 - 18 - 18)) - assertEq(tokenPriceAnswer, uint224(type(uint224).max)); - } - - function test_GetValidatedTokenPriceFromFeedErc20Below18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 6); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 6); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e6) -> expected 1e30 - assertEq(tokenPriceAnswer, uint224(1e30)); - } - - function test_GetValidatedTokenPriceFromFeedErc20Above18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 24); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 24); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e24) -> expected 1e12 - assertEq(tokenPriceAnswer, uint224(1e12)); - } - - function test_GetValidatedTokenPriceFromFeedFeedAt18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, 1e18); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedFeedAt0Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 0); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 0, 1e31); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 0); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e31 (0 decimal token) - unit is (1e18 * 1e18 / 1e0) -> expected 1e36 - assertEq(tokenPriceAnswer, uint224(1e67)); - } - - function test_GetValidatedTokenPriceFromFeedFlippedDecimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 20); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 20, 1e18); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 20); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e20) -> expected 1e14 - assertEq(tokenPriceAnswer, uint224(1e14)); - } - - function test_StaleFeeToken_Success() public { - vm.warp(block.timestamp + TWELVE_HOURS + 1); - - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - address token = priceUpdates.tokenPriceUpdates[0].sourceToken; - - uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); - - assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); - } - - // Reverts - - function test_OverflowFeedPrice_Revert() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max) + 1)); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); - s_feeQuoter.getValidatedTokenPrice(tokenAddress); - } - - function test_UnderflowFeedPrice_Revert() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, -1); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); - s_feeQuoter.getValidatedTokenPrice(tokenAddress); - } - - function test_TokenNotSupported_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.getValidatedTokenPrice(DUMMY_CONTRACT_ADDRESS); - } - - function test_TokenNotSupportedFeed_Revert() public { - address sourceToken = _initialiseSingleTokenPriceFeed(); - MockV3Aggregator(s_dataFeedByToken[sourceToken]).updateAnswer(0); - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: sourceToken, usdPerToken: 0}); - - s_feeQuoter.updatePrices(priceUpdates); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, sourceToken)); - s_feeQuoter.getValidatedTokenPrice(sourceToken); - } -} - -contract FeeQuoter_applyFeeTokensUpdates is FeeQuoterSetup { - function test_ApplyFeeTokensUpdates_Success() public { - address[] memory feeTokens = new address[](1); - feeTokens[0] = s_sourceTokens[1]; - - vm.expectEmit(); - emit FeeQuoter.FeeTokenAdded(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - assertEq(s_feeQuoter.getFeeTokens().length, 3); - assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); - - // add same feeToken is no-op - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - assertEq(s_feeQuoter.getFeeTokens().length, 3); - assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); - - vm.expectEmit(); - emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - assertEq(s_feeQuoter.getFeeTokens().length, 2); - - // removing already removed feeToken is no-op and does not emit an event - vm.recordLogs(); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - assertEq(s_feeQuoter.getFeeTokens().length, 2); - - vm.assertEq(vm.getRecordedLogs().length, 0); - - // Removing and adding the same fee token is allowed and emits both events - // Add it first - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - - vm.expectEmit(); - emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); - vm.expectEmit(); - emit FeeQuoter.FeeTokenAdded(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, feeTokens); - } - - function test_OnlyCallableByOwner_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), new address[](0)); - } -} - -contract FeeQuoter_updatePrices is FeeQuoterSetup { - function test_OnlyTokenPrice_Success() public { - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - update.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, update.tokenPriceUpdates[0].usdPerToken); - } - - function test_OnlyGasPrice_Success() public { - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](1) - }); - update.gasPriceUpdates[0] = - Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerUnitGasUpdated( - update.gasPriceUpdates[0].destChainSelector, update.gasPriceUpdates[0].usdPerUnitGas, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - assertEq( - s_feeQuoter.getDestinationChainGasPrice(DEST_CHAIN_SELECTOR).value, update.gasPriceUpdates[0].usdPerUnitGas - ); - } - - function test_UpdateMultiplePrices_Success() public { - Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](3); - tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[1], usdPerToken: 1800e18}); - tokenPriceUpdates[2] = Internal.TokenPriceUpdate({sourceToken: address(12345), usdPerToken: 1e18}); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](3); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2e6}); - gasPriceUpdates[1] = Internal.GasPriceUpdate({destChainSelector: SOURCE_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); - gasPriceUpdates[2] = Internal.GasPriceUpdate({destChainSelector: 12345, usdPerUnitGas: 1e18}); - - Internal.PriceUpdates memory update = - Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); - - for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[i].sourceToken, update.tokenPriceUpdates[i].usdPerToken, block.timestamp - ); - } - for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { - vm.expectEmit(); - emit FeeQuoter.UsdPerUnitGasUpdated( - update.gasPriceUpdates[i].destChainSelector, update.gasPriceUpdates[i].usdPerUnitGas, block.timestamp - ); - } - - s_feeQuoter.updatePrices(update); - - for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { - assertEq( - s_feeQuoter.getTokenPrice(update.tokenPriceUpdates[i].sourceToken).value, tokenPriceUpdates[i].usdPerToken - ); - } - for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { - assertEq( - s_feeQuoter.getDestinationChainGasPrice(update.gasPriceUpdates[i].destChainSelector).value, - gasPriceUpdates[i].usdPerUnitGas - ); - } - } - - function test_UpdatableByAuthorizedCaller_Success() public { - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - - // Revert when caller is not authorized - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - - address[] memory priceUpdaters = new address[](1); - priceUpdaters[0] = STRANGER; - vm.startPrank(OWNER); - s_feeQuoter.applyAuthorizedCallerUpdates( - AuthorizedCallers.AuthorizedCallerArgs({addedCallers: priceUpdaters, removedCallers: new address[](0)}) - ); - - // Stranger is now an authorized caller to update prices - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - priceUpdates.tokenPriceUpdates[0].sourceToken, priceUpdates.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - s_feeQuoter.updatePrices(priceUpdates); - - assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, priceUpdates.tokenPriceUpdates[0].usdPerToken); - - vm.startPrank(OWNER); - s_feeQuoter.applyAuthorizedCallerUpdates( - AuthorizedCallers.AuthorizedCallerArgs({addedCallers: new address[](0), removedCallers: priceUpdaters}) - ); - - // Revert when authorized caller is removed - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - } - - // Reverts - - function test_OnlyCallableByUpdater_Revert() public { - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - } -} - -contract FeeQuoter_convertTokenAmount is FeeQuoterSetup { - function test_ConvertTokenAmount_Success() public view { - Internal.PriceUpdates memory initialPriceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - uint256 amount = 3e16; - uint256 conversionRate = (uint256(initialPriceUpdates.tokenPriceUpdates[2].usdPerToken) * 1e18) - / uint256(initialPriceUpdates.tokenPriceUpdates[0].usdPerToken); - uint256 expected = (amount * conversionRate) / 1e18; - assertEq(s_feeQuoter.convertTokenAmount(s_weth, amount, s_sourceTokens[0]), expected); - } - - function test_Fuzz_ConvertTokenAmount_Success( - uint256 feeTokenAmount, - uint224 usdPerFeeToken, - uint160 usdPerLinkToken, - uint224 usdPerUnitGas - ) public { - vm.assume(usdPerFeeToken > 0); - vm.assume(usdPerLinkToken > 0); - // We bound the max fees to be at most uint96.max link. - feeTokenAmount = bound(feeTokenAmount, 0, (uint256(type(uint96).max) * usdPerLinkToken) / usdPerFeeToken); - - address feeToken = address(1); - address linkToken = address(2); - address[] memory feeTokens = new address[](1); - feeTokens[0] = feeToken; - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - - Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](2); - tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: feeToken, usdPerToken: usdPerFeeToken}); - tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: linkToken, usdPerToken: usdPerLinkToken}); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: usdPerUnitGas}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); - - s_feeQuoter.updatePrices(priceUpdates); - - uint256 linkFee = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, linkToken); - assertEq(linkFee, (feeTokenAmount * usdPerFeeToken) / usdPerLinkToken); - } - - // Reverts - - function test_LinkTokenNotSupported_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.convertTokenAmount(DUMMY_CONTRACT_ADDRESS, 3e16, s_sourceTokens[0]); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.convertTokenAmount(s_sourceTokens[0], 3e16, DUMMY_CONTRACT_ADDRESS); - } -} - -contract FeeQuoter_getTokenAndGasPrices is FeeQuoterSetup { - function test_GetFeeTokenAndGasPrices_Success() public view { - (uint224 feeTokenPrice, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, DEST_CHAIN_SELECTOR); - - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - - assertEq(feeTokenPrice, s_sourceTokenPrices[0]); - assertEq(gasPrice, priceUpdates.gasPriceUpdates[0].usdPerUnitGas); - } - - function test_StalenessCheckDisabled_Success() public { - uint64 neverStaleChainSelector = 345678; - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = neverStaleChainSelector; - destChainConfigArgs[0].destChainConfig.gasPriceStalenessThreshold = 0; // disables the staleness check - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: neverStaleChainSelector, usdPerUnitGas: 999}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); - s_feeQuoter.updatePrices(priceUpdates); - - // this should have no affect! But we do it anyway to make sure the staleness check is disabled - vm.warp(block.timestamp + 52_000_000 weeks); // 1M-ish years - - (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, neverStaleChainSelector); - - assertEq(gasPrice, 999); - } - - function test_ZeroGasPrice_Success() public { - uint64 zeroGasDestChainSelector = 345678; - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = zeroGasDestChainSelector; - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: zeroGasDestChainSelector, usdPerUnitGas: 0}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); - s_feeQuoter.updatePrices(priceUpdates); - - (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, zeroGasDestChainSelector); - - assertEq(gasPrice, 0); - } - - function test_UnsupportedChain_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); - s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR + 1); - } - - function test_StaleGasPrice_Revert() public { - uint256 diff = TWELVE_HOURS + 1; - vm.warp(block.timestamp + diff); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.StaleGasPrice.selector, DEST_CHAIN_SELECTOR, TWELVE_HOURS, diff)); - s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR); - } -} - -contract FeeQuoter_updateTokenPriceFeeds is FeeQuoterSetup { - function test_ZeroFeeds_Success() public { - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); - vm.recordLogs(); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - // Verify no log emissions - assertEq(logEntries.length, 0); - } - - function test_SingleFeedUpdate_Success() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - _assertTokenPriceFeedConfigNotConfigured(s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken)); - - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - } - - function test_MultipleFeedUpdate_Success() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); - - for (uint256 i = 0; i < 2; ++i) { - tokenPriceFeedUpdates[i] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[i], s_dataFeedByToken[s_sourceTokens[i]], 18); - - _assertTokenPriceFeedConfigNotConfigured( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[i].sourceToken) - ); - - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[i].sourceToken, tokenPriceFeedUpdates[i].feedConfig); - } - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[1].sourceToken), tokenPriceFeedUpdates[1].feedConfig - ); - } - - function test_FeedUnset_Success() public { - Internal.TimestampedPackedUint224 memory priceQueryInitial = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - assertFalse(priceQueryInitial.value == 0); - assertFalse(priceQueryInitial.timestamp == 0); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - - tokenPriceFeedUpdates[0].feedConfig.dataFeedAddress = address(0); - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - - // Price data should remain after a feed has been set->unset - Internal.TimestampedPackedUint224 memory priceQueryPostUnsetFeed = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - assertEq(priceQueryPostUnsetFeed.value, priceQueryInitial.value); - assertEq(priceQueryPostUnsetFeed.timestamp, priceQueryInitial.timestamp); - } - - function test_FeedNotUpdated() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - } - - // Reverts - - function test_FeedUpdatedByNonOwner_Revert() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - } -} - -contract FeeQuoter_applyDestChainConfigUpdates is FeeQuoterSetup { - function test_Fuzz_applyDestChainConfigUpdates_Success( - FeeQuoter.DestChainConfigArgs memory destChainConfigArgs - ) public { - vm.assume(destChainConfigArgs.destChainSelector != 0); - vm.assume(destChainConfigArgs.destChainConfig.maxPerMsgGasLimit != 0); - destChainConfigArgs.destChainConfig.defaultTxGasLimit = uint32( - bound( - destChainConfigArgs.destChainConfig.defaultTxGasLimit, 1, destChainConfigArgs.destChainConfig.maxPerMsgGasLimit - ) - ); - destChainConfigArgs.destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; - - bool isNewChain = destChainConfigArgs.destChainSelector != DEST_CHAIN_SELECTOR; - - FeeQuoter.DestChainConfigArgs[] memory newDestChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - newDestChainConfigArgs[0] = destChainConfigArgs; - - if (isNewChain) { - vm.expectEmit(); - emit FeeQuoter.DestChainAdded(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); - } else { - vm.expectEmit(); - emit FeeQuoter.DestChainConfigUpdated(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); - } - - s_feeQuoter.applyDestChainConfigUpdates(newDestChainConfigArgs); - - _assertFeeQuoterDestChainConfigsEqual( - destChainConfigArgs.destChainConfig, s_feeQuoter.getDestChainConfig(destChainConfigArgs.destChainSelector) - ); - } - - function test_applyDestChainConfigUpdates_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](2); - destChainConfigArgs[0] = _generateFeeQuoterDestChainConfigArgs()[0]; - destChainConfigArgs[0].destChainConfig.isEnabled = false; - destChainConfigArgs[1] = _generateFeeQuoterDestChainConfigArgs()[0]; - destChainConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; - - vm.expectEmit(); - emit FeeQuoter.DestChainConfigUpdated(DEST_CHAIN_SELECTOR, destChainConfigArgs[0].destChainConfig); - vm.expectEmit(); - emit FeeQuoter.DestChainAdded(DEST_CHAIN_SELECTOR + 1, destChainConfigArgs[1].destChainConfig); - - vm.recordLogs(); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - FeeQuoter.DestChainConfig memory gotDestChainConfig0 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - FeeQuoter.DestChainConfig memory gotDestChainConfig1 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); - - assertEq(vm.getRecordedLogs().length, 2); - _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[0].destChainConfig, gotDestChainConfig0); - _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[1].destChainConfig, gotDestChainConfig1); - } - - function test_applyDestChainConfigUpdatesZeroInput_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](0); - - vm.recordLogs(); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_applyDestChainConfigUpdatesDefaultTxGasLimitEqZero_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainConfig.defaultTxGasLimit = 0; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_applyDestChainConfigUpdatesDefaultTxGasLimitGtMaxPerMessageGasLimit_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - // Allow setting to the max value - destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - // Revert when exceeding max value - destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit + 1; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_InvalidDestChainConfigDestChainSelectorEqZero_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainSelector = 0; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_InvalidChainFamilySelector_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainConfig.chainFamilySelector = bytes4(uint32(1)); - - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } -} - -contract FeeQuoter_getDataAvailabilityCost is FeeQuoterSetup { - function test_EmptyMessageCalculatesDataAvailabilityCost_Success() public { - uint256 dataAvailabilityCostUSD = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); - - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; - uint256 expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - - // Test that the cost is destination chain specific - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR + 1; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = - destChainConfig.destDataAvailabilityOverheadGas * 2; - destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = - destChainConfig.destGasPerDataAvailabilityByte * 2; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = - destChainConfig.destDataAvailabilityMultiplierBps * 2; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); - uint256 dataAvailabilityCostUSD2 = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR + 1, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); - dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; - expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD2); - assertFalse(dataAvailabilityCostUSD == dataAvailabilityCostUSD2); - } - - function test_SimpleMessageCalculatesDataAvailabilityCost_Success() public view { - uint256 dataAvailabilityCostUSD = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); - - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 dataAvailabilityLengthBytes = - Internal.MESSAGE_FIXED_BYTES + 100 + (5 * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + 50; - uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; - uint256 expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - } - - function test_SimpleMessageCalculatesDataAvailabilityCostUnsupportedDestChainSelector_Success() public view { - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost(0, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); - - assertEq(dataAvailabilityCostUSD, 0); - } - - function test_Fuzz_ZeroDataAvailabilityGasPriceAlwaysCalculatesZeroDataAvailabilityCost_Success( - uint64 messageDataLength, - uint32 numberOfTokens, - uint32 tokenTransferBytesOverhead - ) public view { - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, 0, messageDataLength, numberOfTokens, tokenTransferBytesOverhead - ); - - assertEq(0, dataAvailabilityCostUSD); - } - - function test_Fuzz_CalculateDataAvailabilityCost_Success( - uint64 destChainSelector, - uint32 destDataAvailabilityOverheadGas, - uint16 destGasPerDataAvailabilityByte, - uint16 destDataAvailabilityMultiplierBps, - uint112 dataAvailabilityGasPrice, - uint64 messageDataLength, - uint32 numberOfTokens, - uint32 tokenTransferBytesOverhead - ) public { - vm.assume(destChainSelector != 0); - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(destChainSelector); - destChainConfigArgs[0] = - FeeQuoter.DestChainConfigArgs({destChainSelector: destChainSelector, destChainConfig: destChainConfig}); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = destDataAvailabilityOverheadGas; - destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = destGasPerDataAvailabilityByte; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = destDataAvailabilityMultiplierBps; - destChainConfigArgs[0].destChainConfig.defaultTxGasLimit = GAS_LIMIT; - destChainConfigArgs[0].destChainConfig.maxPerMsgGasLimit = GAS_LIMIT; - destChainConfigArgs[0].destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( - destChainConfigArgs[0].destChainSelector, - dataAvailabilityGasPrice, - messageDataLength, - numberOfTokens, - tokenTransferBytesOverhead - ); - - uint256 dataAvailabilityLengthBytes = Internal.MESSAGE_FIXED_BYTES + messageDataLength - + (numberOfTokens * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + tokenTransferBytesOverhead; - - uint256 dataAvailabilityGas = - destDataAvailabilityOverheadGas + destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; - uint256 expectedDataAvailabilityCostUSD = - dataAvailabilityGasPrice * dataAvailabilityGas * destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - } -} - -contract FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates is FeeQuoterSetup { - function test_Fuzz_applyPremiumMultiplierWeiPerEthUpdates_Success( - FeeQuoter.PremiumMultiplierWeiPerEthArgs memory premiumMultiplierWeiPerEthArg - ) public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - premiumMultiplierWeiPerEthArgs[0] = premiumMultiplierWeiPerEthArg; - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - premiumMultiplierWeiPerEthArg.token, premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(premiumMultiplierWeiPerEthArg.token) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesSingleToken_Success() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; - premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesMultipleTokens_Success() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](2); - premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; - premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); - premiumMultiplierWeiPerEthArgs[1].token = vm.addr(2); - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth - ); - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(2), premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) - ); - assertEq( - premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(2)) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesZeroInput() public { - vm.recordLogs(); - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](0)); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_OnlyCallableByOwnerOrAdmin_Revert() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs; - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - } -} - -contract FeeQuoter_applyTokenTransferFeeConfigUpdates is FeeQuoterSetup { - function test_Fuzz_ApplyTokenTransferFeeConfig_Success( - FeeQuoter.TokenTransferFeeConfig[2] memory tokenTransferFeeConfigs - ) public { - // To prevent Invalid Fee Range error from the fuzzer, bound the results to a valid range that - // where minFee < maxFee - tokenTransferFeeConfigs[0].minFeeUSDCents = - uint32(bound(tokenTransferFeeConfigs[0].minFeeUSDCents, 0, type(uint8).max)); - tokenTransferFeeConfigs[1].minFeeUSDCents = - uint32(bound(tokenTransferFeeConfigs[1].minFeeUSDCents, 0, type(uint8).max)); - - tokenTransferFeeConfigs[0].maxFeeUSDCents = uint32( - bound(tokenTransferFeeConfigs[0].maxFeeUSDCents, tokenTransferFeeConfigs[0].minFeeUSDCents + 1, type(uint32).max) - ); - tokenTransferFeeConfigs[1].maxFeeUSDCents = uint32( - bound(tokenTransferFeeConfigs[1].maxFeeUSDCents, tokenTransferFeeConfigs[1].minFeeUSDCents + 1, type(uint32).max) - ); - - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(2, 2); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; - - for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) { - for (uint256 j = 0; j < tokenTransferFeeConfigs.length; ++j) { - tokenTransferFeeConfigs[j].destBytesOverhead = uint32( - bound(tokenTransferFeeConfigs[j].destBytesOverhead, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, type(uint32).max) - ); - address feeToken = s_sourceTokens[j]; - tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].token = feeToken; - tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].tokenTransferFeeConfig = tokenTransferFeeConfigs[j]; - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[i].destChainSelector, feeToken, tokenTransferFeeConfigs[j] - ); - } - } - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - for (uint256 i = 0; i < tokenTransferFeeConfigs.length; ++i) { - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigs[i], - s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i].token - ) - ); - } - } - - function test_ApplyTokenTransferFeeConfig_Success() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 2); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 6, - maxFeeUSDCents: 7, - deciBps: 8, - destGasOverhead: 9, - destBytesOverhead: 312, - isEnabled: true - }); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token = address(11); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 12, - maxFeeUSDCents: 13, - deciBps: 14, - destGasOverhead: 15, - destBytesOverhead: 394, - isEnabled: true - }); - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig - ); - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig - ); - - FeeQuoter.TokenTransferFeeConfigRemoveArgs[] memory tokensToRemove = - new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0); - s_feeQuoter.applyTokenTransferFeeConfigUpdates(tokenTransferFeeConfigArgs, tokensToRemove); - - FeeQuoter.TokenTransferFeeConfig memory config0 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - FeeQuoter.TokenTransferFeeConfig memory config1 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token - ); - - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig, config0 - ); - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 - ); - - // Remove only the first token and validate only the first token is removed - tokensToRemove = new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](1); - tokensToRemove[0] = FeeQuoter.TokenTransferFeeConfigRemoveArgs({ - destChainSelector: tokenTransferFeeConfigArgs[0].destChainSelector, - token: tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - }); - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigDeleted( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates(new FeeQuoter.TokenTransferFeeConfigArgs[](0), tokensToRemove); - - config0 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - config1 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token - ); - - FeeQuoter.TokenTransferFeeConfig memory emptyConfig; - - _assertTokenTransferFeeConfigEqual(emptyConfig, config0); - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 - ); - } - - function test_ApplyTokenTransferFeeZeroInput() public { - vm.recordLogs(); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - new FeeQuoter.TokenTransferFeeConfigArgs[](0), new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_OnlyCallableByOwnerOrAdmin_Revert() public { - vm.startPrank(STRANGER); - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs; - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } - - function test_InvalidDestBytesOverhead_Revert() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 6, - maxFeeUSDCents: 7, - deciBps: 8, - destGasOverhead: 9, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES - 1), - isEnabled: true - }); - - vm.expectRevert( - abi.encodeWithSelector( - FeeQuoter.InvalidDestBytesOverhead.selector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destBytesOverhead - ) - ); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } -} - -contract FeeQuoter_getTokenTransferCost is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function test_NoTokenTransferChargesZeroFee_Success() public view { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(0, feeUSDWei); - assertEq(0, destGasOverhead); - assertEq(0, destBytesOverhead); - } - - function test_getTokenTransferCost_selfServeUsesDefaults_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_selfServeTokenDefaultPricing, 1000); - - // Get config to assert it isn't set - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - assertFalse(transferFeeConfig.isEnabled); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - // Assert that the default values are used - assertEq(uint256(DEFAULT_TOKEN_FEE_USD_CENTS) * 1e16, feeUSDWei); - assertEq(DEFAULT_TOKEN_DEST_GAS_OVERHEAD, destGasOverhead); - assertEq(DEFAULT_TOKEN_BYTES_OVERHEAD, destBytesOverhead); - } - - function test_SmallTokenTransferChargesMinFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1000); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 0); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_LargeTokenTransferChargesMaxFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.maxFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_FeeTokenBpsFee_Success() public view { - uint256 tokenAmount = 10000e18; - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - uint256 usdWei = _calcUSDValueFromTokenAmount(s_feeTokenPrice, tokenAmount); - uint256 bpsUSDWei = _applyBpsRatio( - usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.deciBps - ); - - assertEq(bpsUSDWei, feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_CustomTokenBpsFee_Success() public view { - uint256 tokenAmount = 200000e18; - - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](1), - feeToken: s_sourceFeeToken, - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - message.tokenAmounts[0] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: tokenAmount}); - - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - uint256 usdWei = _calcUSDValueFromTokenAmount(s_customTokenPrice, tokenAmount); - uint256 bpsUSDWei = _applyBpsRatio( - usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig.deciBps - ); - - assertEq(bpsUSDWei, feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_ZeroFeeConfigChargesMinFee_Success() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = s_sourceFeeToken; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES), - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - // if token charges 0 bps, it should cost minFee to transfer - assertEq( - _configUSDCentToWei( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.minFeeUSDCents - ), - feeUSDWei - ); - assertEq(0, destGasOverhead); - assertEq(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, destBytesOverhead); - } - - function test_Fuzz_TokenTransferFeeDuplicateTokens_Success(uint256 transfers, uint256 amount) public view { - // It shouldn't be possible to pay materially lower fees by splitting up the transfers. - // Note it is possible to pay higher fees since the minimum fees are added. - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - transfers = bound(transfers, 1, destChainConfig.maxNumberOfTokensPerMsg); - // Cap amount to avoid overflow - amount = bound(amount, 0, 1e36); - Client.EVMTokenAmount[] memory multiple = new Client.EVMTokenAmount[](transfers); - for (uint256 i = 0; i < transfers; ++i) { - multiple[i] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount}); - } - Client.EVMTokenAmount[] memory single = new Client.EVMTokenAmount[](1); - single[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount * transfers}); - - address feeToken = s_sourceRouter.getWrappedNative(); - - (uint256 feeSingleUSDWei, uint32 gasOverheadSingle, uint32 bytesOverheadSingle) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, single); - (uint256 feeMultipleUSDWei, uint32 gasOverheadMultiple, uint32 bytesOverheadMultiple) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, multiple); - - // Note that there can be a rounding error once per split. - assertGe(feeMultipleUSDWei, (feeSingleUSDWei - destChainConfig.maxNumberOfTokensPerMsg)); - assertEq(gasOverheadMultiple, gasOverheadSingle * transfers); - assertEq(bytesOverheadMultiple, bytesOverheadSingle * transfers); - } - - function test_MixedTokenTransferFee_Success() public view { - address[3] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative(), CUSTOM_TOKEN]; - uint224[3] memory tokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice, s_customTokenPrice]; - FeeQuoter.TokenTransferFeeConfig[3] memory tokenTransferFeeConfigs = [ - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[0]), - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[1]), - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[2]) - ]; - - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](3), - feeToken: s_sourceRouter.getWrappedNative(), - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - uint256 expectedTotalGas = 0; - uint256 expectedTotalBytes = 0; - - // Start with small token transfers, total bps fee is lower than min token transfer fee - for (uint256 i = 0; i < testTokens.length; ++i) { - message.tokenAmounts[i] = Client.EVMTokenAmount({token: testTokens[i], amount: 1e14}); - FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[i]); - - expectedTotalGas += tokenTransferFeeConfig.destGasOverhead == 0 - ? DEFAULT_TOKEN_DEST_GAS_OVERHEAD - : tokenTransferFeeConfig.destGasOverhead; - expectedTotalBytes += tokenTransferFeeConfig.destBytesOverhead == 0 - ? DEFAULT_TOKEN_BYTES_OVERHEAD - : tokenTransferFeeConfig.destBytesOverhead; - } - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - - uint256 expectedFeeUSDWei = 0; - for (uint256 i = 0; i < testTokens.length; ++i) { - expectedFeeUSDWei += _configUSDCentToWei( - tokenTransferFeeConfigs[i].minFeeUSDCents == 0 - ? DEFAULT_TOKEN_FEE_USD_CENTS - : tokenTransferFeeConfigs[i].minFeeUSDCents - ); - } - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 1"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 1"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 1"); - - // Set 1st token transfer to a meaningful amount so its bps fee is now between min and max fee - message.tokenAmounts[0] = Client.EVMTokenAmount({token: testTokens[0], amount: 10000e18}); - - uint256 token0USDWei = _applyBpsRatio( - _calcUSDValueFromTokenAmount(tokenPrices[0], message.tokenAmounts[0].amount), tokenTransferFeeConfigs[0].deciBps - ); - uint256 token1USDWei = _configUSDCentToWei(DEFAULT_TOKEN_FEE_USD_CENTS); - - (feeUSDWei, destGasOverhead, destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].minFeeUSDCents); - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 2"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 2"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 2"); - - // Set 2nd token transfer to a large amount that is higher than maxFeeUSD - message.tokenAmounts[2] = Client.EVMTokenAmount({token: testTokens[2], amount: 1e36}); - - (feeUSDWei, destGasOverhead, destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].maxFeeUSDCents); - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 3"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 3"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 3"); - } -} - -contract FeeQuoter_getValidatedFee is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function test_EmptyMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = testTokens[i]; - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_ZeroDataAvailabilityMultiplier_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - destChainConfigArgs[0] = - FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: destChainConfig}); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD) / s_feeTokenPrice; - assertEq(totalPriceInFeeToken, feeAmount); - } - - function test_HighGasMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 customGasLimit = MAX_GAS_LIMIT; - uint256 customDataSize = MAX_DATA_SIZE; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: new bytes(customDataSize), - tokenAmounts: new Client.EVMTokenAmount[](0), - feeToken: testTokens[i], - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) - }); - - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - uint256 gasUsed = customGasLimit + DEST_GAS_OVERHEAD + customDataSize * DEST_GAS_PER_PAYLOAD_BYTE; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_SingleTokenMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 tokenAmount = 10000e18; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - message.feeToken = testTokens[i]; - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - uint32 destBytesOverhead = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destBytesOverhead; - uint32 tokenBytesOverhead = - destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD - + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destGasOverhead; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - (uint256 transferFeeUSD,,) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); - uint256 messageFeeUSD = (transferFeeUSD * s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken)); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, - USD_PER_DATA_AVAILABILITY_GAS, - message.data.length, - message.tokenAmounts.length, - tokenBytesOverhead - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_MessageWithDataAndTokenTransfer_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 customGasLimit = 1_000_000; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](2), - feeToken: testTokens[i], - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) - }); - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - message.tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceFeeToken, amount: 10000e18}); // feeTokenAmount - message.tokenAmounts[1] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: 200000e18}); // customTokenAmount - message.data = "random bits and bytes that should be factored into the cost of the message"; - - uint32 tokenGasOverhead = 0; - uint32 tokenBytesOverhead = 0; - for (uint256 j = 0; j < message.tokenAmounts.length; ++j) { - tokenGasOverhead += - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destGasOverhead; - uint32 destBytesOverhead = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destBytesOverhead; - tokenBytesOverhead += destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; - } - - uint256 gasUsed = - customGasLimit + DEST_GAS_OVERHEAD + message.data.length * DEST_GAS_PER_PAYLOAD_BYTE + tokenGasOverhead; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - (uint256 transferFeeUSD,,) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); - uint256 messageFeeUSD = (transferFeeUSD * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, - USD_PER_DATA_AVAILABILITY_GAS, - message.data.length, - message.tokenAmounts.length, - tokenBytesOverhead - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message)); - } - } - - function test_Fuzz_EnforceOutOfOrder(bool enforce, bool allowOutOfOrderExecution) public { - // Update config to enforce allowOutOfOrderExecution = defaultVal. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = enforce; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: allowOutOfOrderExecution}) - ); - - // If enforcement is on, only true should be allowed. - if (enforce && !allowOutOfOrderExecution) { - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - } - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - // Reverts - - function test_DestinationChainNotEnabled_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR + 1, _generateEmptyMessage()); - } - - function test_EnforceOutOfOrder_Revert() public { - // Update config to enforce allowOutOfOrderExecution = true. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - vm.stopPrank(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - // Empty extraArgs to should revert since it enforceOutOfOrder is true. - message.extraArgs = ""; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_MessageTooLarge_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.data = new bytes(MAX_DATA_SIZE + 1); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageTooLarge.selector, MAX_DATA_SIZE, message.data.length)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_TooManyTokens_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint256 tooMany = MAX_TOKENS_LENGTH + 1; - message.tokenAmounts = new Client.EVMTokenAmount[](tooMany); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.UnsupportedNumberOfTokens.selector, tooMany, MAX_TOKENS_LENGTH)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - // Asserts gasLimit must be <=maxGasLimit - function test_MessageGasLimitTooHigh_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: MAX_GAS_LIMIT + 1})); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageGasLimitTooHigh.selector)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_NotAFeeToken_Revert() public { - address notAFeeToken = address(0x111111); - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(notAFeeToken, 1); - message.feeToken = notAFeeToken; - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, notAFeeToken)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_InvalidEVMAddress_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.receiver = abi.encode(type(uint208).max); - - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, message.receiver)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } -} - -contract FeeQuoter_processMessageArgs is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function setUp() public virtual override { - super.setUp(); - } - - function test_processMessageArgs_WithLinkTokenAmount_Success() public view { - ( - uint256 msgFeeJuels, - /* bool isOutOfOrderExecution */ - , - /* bytes memory convertedExtraArgs */ - , - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - // LINK - s_sourceTokens[0], - MAX_MSG_FEES_JUELS, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(msgFeeJuels, MAX_MSG_FEES_JUELS); - } - - function test_processMessageArgs_WithConvertedTokenAmount_Success() public view { - address feeToken = s_sourceTokens[1]; - uint256 feeTokenAmount = 10_000 gwei; - uint256 expectedConvertedAmount = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, s_sourceTokens[0]); - - ( - uint256 msgFeeJuels, - /* bool isOutOfOrderExecution */ - , - /* bytes memory convertedExtraArgs */ - , - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - feeToken, - feeTokenAmount, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(msgFeeJuels, expectedConvertedAmount); - } - - function test_processMessageArgs_WithEmptyEVMExtraArgs_Success() public view { - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, false); - assertEq(convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes("", DEST_CHAIN_SELECTOR))); - } - - function test_processMessageArgs_WithEVMExtraArgsV1_Success() public view { - bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 1000})); - - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - extraArgs, - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, false); - assertEq( - convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) - ); - } - - function test_processMessageArgs_WitEVMExtraArgsV2_Success() public view { - bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV2({gasLimit: 0, allowOutOfOrderExecution: true})); - - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - extraArgs, - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, true); - assertEq( - convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) - ); - } - - // Reverts - - function test_processMessageArgs_MessageFeeTooHigh_Revert() public { - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) - ); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - MAX_MSG_FEES_JUELS + 1, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_InvalidExtraArgs_Revert() public { - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - "wrong extra args", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_MalformedEVMExtraArgs_Revert() public { - // abi.decode error - vm.expectRevert(); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV1({gasLimit: 100})), - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_WithCorrectPoolReturnData_Success() public view { - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - sourceTokenAmounts[1].amount = 1e18; - sourceTokenAmounts[1].token = CUSTOM_TOKEN_2; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](2); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - tokenAmounts[1] = _getSourceTokenData(sourceTokenAmounts[1], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - bytes[] memory expectedDestExecData = new bytes[](2); - expectedDestExecData[0] = abi.encode( - s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destGasOverhead - ); - expectedDestExecData[1] = abi.encode(DEFAULT_TOKEN_DEST_GAS_OVERHEAD); //expected return data should be abi.encoded default as isEnabled is false - - // No revert - successful - ( /* msgFeeJuels */ , /* isOutOfOrderExecution */, /* convertedExtraArgs */, bytes[] memory destExecData) = - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - for (uint256 i = 0; i < destExecData.length; ++i) { - assertEq(destExecData[i], expectedDestExecData[i]); - } - } - - function test_processMessageArgs_TokenAmountArraysMismatching_Revert() public { - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - - // Revert due to index out of bounds access - vm.expectRevert(); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - MAX_MSG_FEES_JUELS, - "", - new Internal.EVM2AnyTokenTransfer[](1), - new Client.EVMTokenAmount[](0) - ); - } - - function test_applyTokensTransferFeeConfigUpdates_InvalidFeeRange_Revert() public { - address sourceETH = s_sourceTokens[1]; - - // Set token config to allow larger data - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 1, - maxFeeUSDCents: 0, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidFeeRange.selector, 1, 0)); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } - - function test_processMessageArgs_SourceTokenDataTooLarge_Revert() public { - address sourceETH = s_sourceTokens[1]; - - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); - sourceTokenAmounts[0].amount = 1000; - sourceTokenAmounts[0].token = sourceETH; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - - // No data set, should succeed - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set max data length, should succeed - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set data to max length +1, should revert - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set token config to allow larger data - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set the token data larger than the configured token data, should revert - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 32 + 1); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - } - - function test_processMessageArgs_InvalidEVMAddressDestToken_Revert() public { - bytes memory nonEvmAddress = abi.encode(type(uint208).max); - - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - tokenAmounts[0].destTokenAddress = nonEvmAddress; - - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, nonEvmAddress)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - } -} - -contract FeeQuoter_validateDestFamilyAddress is FeeQuoterSetup { - function test_ValidEVMAddress_Success() public view { - bytes memory encodedAddress = abi.encode(address(10000)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress); - } - - function test_ValidNonEVMAddress_Success() public view { - s_feeQuoter.validateDestFamilyAddress(bytes4(uint32(1)), abi.encode(type(uint208).max)); - } - - // Reverts - - function test_InvalidEVMAddress_Revert() public { - bytes memory invalidAddress = abi.encode(type(uint208).max); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - function test_InvalidEVMAddressEncodePacked_Revert() public { - bytes memory invalidAddress = abi.encodePacked(address(234)); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - function test_InvalidEVMAddressPrecompiles_Revert() public { - for (uint160 i = 0; i < Internal.PRECOMPILE_SPACE; ++i) { - bytes memory invalidAddress = abi.encode(address(i)); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - s_feeQuoter.validateDestFamilyAddress( - Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))) - ); - } -} - -contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { - FeeQuoter.DestChainConfig private s_destChainConfig; - - function setUp() public virtual override { - super.setUp(); - s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; - } - - function test_EVMExtraArgsV1_Success() public view { - Client.EVMExtraArgsV1 memory inputArgs = Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - Client.EVMExtraArgsV2 memory expectedOutputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), - abi.encode(expectedOutputArgs) - ); - } - - function test_EVMExtraArgsV2_Success() public view { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), abi.encode(inputArgs) - ); - } - - function test_EVMExtraArgsDefault_Success() public view { - Client.EVMExtraArgsV2 memory expectedOutputArgs = - Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.defaultTxGasLimit, allowOutOfOrderExecution: false}); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes("", s_destChainConfig)), abi.encode(expectedOutputArgs) - ); - } - - // Reverts - - function test_EVMExtraArgsInvalidExtraArgsTag_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - // Invalidate selector - inputExtraArgs[0] = bytes1(uint8(0)); - - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } - - function test_EVMExtraArgsEnforceOutOfOrder_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - s_destChainConfig.enforceOutOfOrder = true; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } - - function test_EVMExtraArgsGasLimitTooHigh_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.maxPerMsgGasLimit + 1, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - - vm.expectRevert(FeeQuoter.MessageGasLimitTooHigh.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } -} - -contract FeeQuoter_KeystoneSetup is FeeQuoterSetup { - address internal constant FORWARDER_1 = address(0x1); - address internal constant WORKFLOW_OWNER_1 = address(0x3); - bytes10 internal constant WORKFLOW_NAME_1 = "workflow1"; - bytes2 internal constant REPORT_NAME_1 = "01"; - address internal s_onReportTestToken1; - address internal s_onReportTestToken2; - - function setUp() public virtual override { - super.setUp(); - s_onReportTestToken1 = s_sourceTokens[0]; - s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); - - KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1); - permissions[0] = KeystoneFeedsPermissionHandler.Permission({ - forwarder: FORWARDER_1, - workflowOwner: WORKFLOW_OWNER_1, - workflowName: WORKFLOW_NAME_1, - reportName: REPORT_NAME_1, - isAllowed: true - }); - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2); - tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: s_onReportTestToken1, - feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 18, isEnabled: true}) - }); - tokenPriceFeeds[1] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: s_onReportTestToken2, - feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 20, isEnabled: true}) - }); - s_feeQuoter.setReportPermissions(permissions); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeeds); - } -} - -contract FeeQuoter_onReport is FeeQuoter_KeystoneSetup { - function test_onReport_Success() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); - report[1] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); - - uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); - uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, report[1].price); - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp); - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp); - - changePrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).value, expectedStoredToken1Price); - vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).timestamp, report[0].timestamp); - - vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).value, expectedStoredToken2Price); - vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).timestamp, report[1].timestamp); - } - - function test_OnReport_StaleUpdate_SkipPriceUpdate_Success() public { - //Creating a correct report - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); - - uint224 expectedStoredTokenPrice = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp); - - changePrank(FORWARDER_1); - //setting the correct price and time with the correct report - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - //create a stale report - report[0] = FeeQuoter.ReceivedCCIPFeedReport({ - token: s_onReportTestToken1, - price: 4e18, - timestamp: uint32(block.timestamp - 1) - }); - - //record logs to check no events were emitted - vm.recordLogs(); - - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - //no logs should have been emitted - assertEq(vm.getRecordedLogs().length, 0); - } - - function test_onReport_TokenNotSupported_Revert() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); - - // Revert due to token config not being set with the isEnabled flag - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); - vm.startPrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } - - function test_onReport_InvalidForwarder_Reverts() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[0], price: 4e18, timestamp: uint32(block.timestamp)}); - - vm.expectRevert( - abi.encodeWithSelector( - KeystoneFeedsPermissionHandler.ReportForwarderUnauthorized.selector, - STRANGER, - WORKFLOW_OWNER_1, - WORKFLOW_NAME_1, - REPORT_NAME_1 - ) - ); - changePrank(STRANGER); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } - - function test_onReport_UnsupportedToken_Reverts() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); - changePrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } -} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol new file mode 100644 index 00000000000..d40ac7d33ad --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_updatePrices is FeeQuoterSetup { + function test_OnlyTokenPrice_Success() public { + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + update.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, update.tokenPriceUpdates[0].usdPerToken); + } + + function test_OnlyGasPrice_Success() public { + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](1) + }); + update.gasPriceUpdates[0] = + Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerUnitGasUpdated( + update.gasPriceUpdates[0].destChainSelector, update.gasPriceUpdates[0].usdPerUnitGas, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + assertEq( + s_feeQuoter.getDestinationChainGasPrice(DEST_CHAIN_SELECTOR).value, update.gasPriceUpdates[0].usdPerUnitGas + ); + } + + function test_UpdateMultiplePrices_Success() public { + Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](3); + tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[1], usdPerToken: 1800e18}); + tokenPriceUpdates[2] = Internal.TokenPriceUpdate({sourceToken: address(12345), usdPerToken: 1e18}); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](3); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2e6}); + gasPriceUpdates[1] = Internal.GasPriceUpdate({destChainSelector: SOURCE_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); + gasPriceUpdates[2] = Internal.GasPriceUpdate({destChainSelector: 12345, usdPerUnitGas: 1e18}); + + Internal.PriceUpdates memory update = + Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); + + for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[i].sourceToken, update.tokenPriceUpdates[i].usdPerToken, block.timestamp + ); + } + for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { + vm.expectEmit(); + emit FeeQuoter.UsdPerUnitGasUpdated( + update.gasPriceUpdates[i].destChainSelector, update.gasPriceUpdates[i].usdPerUnitGas, block.timestamp + ); + } + + s_feeQuoter.updatePrices(update); + + for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { + assertEq( + s_feeQuoter.getTokenPrice(update.tokenPriceUpdates[i].sourceToken).value, tokenPriceUpdates[i].usdPerToken + ); + } + for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { + assertEq( + s_feeQuoter.getDestinationChainGasPrice(update.gasPriceUpdates[i].destChainSelector).value, + gasPriceUpdates[i].usdPerUnitGas + ); + } + } + + function test_UpdatableByAuthorizedCaller_Success() public { + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + + // Revert when caller is not authorized + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + + address[] memory priceUpdaters = new address[](1); + priceUpdaters[0] = STRANGER; + vm.startPrank(OWNER); + s_feeQuoter.applyAuthorizedCallerUpdates( + AuthorizedCallers.AuthorizedCallerArgs({addedCallers: priceUpdaters, removedCallers: new address[](0)}) + ); + + // Stranger is now an authorized caller to update prices + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + priceUpdates.tokenPriceUpdates[0].sourceToken, priceUpdates.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + s_feeQuoter.updatePrices(priceUpdates); + + assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, priceUpdates.tokenPriceUpdates[0].usdPerToken); + + vm.startPrank(OWNER); + s_feeQuoter.applyAuthorizedCallerUpdates( + AuthorizedCallers.AuthorizedCallerArgs({addedCallers: new address[](0), removedCallers: priceUpdaters}) + ); + + // Revert when authorized caller is removed + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + } + + // Reverts + + function test_OnlyCallableByUpdater_Revert() public { + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol new file mode 100644 index 00000000000..9341fab121b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract FeeQuoter_updateTokenPriceFeeds is FeeQuoterSetup { + function test_ZeroFeeds_Success() public { + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); + vm.recordLogs(); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + // Verify no log emissions + assertEq(logEntries.length, 0); + } + + function test_SingleFeedUpdate_Success() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + _assertTokenPriceFeedConfigNotConfigured(s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken)); + + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + } + + function test_MultipleFeedUpdate_Success() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); + + for (uint256 i = 0; i < 2; ++i) { + tokenPriceFeedUpdates[i] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[i], s_dataFeedByToken[s_sourceTokens[i]], 18); + + _assertTokenPriceFeedConfigNotConfigured( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[i].sourceToken) + ); + + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[i].sourceToken, tokenPriceFeedUpdates[i].feedConfig); + } + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[1].sourceToken), tokenPriceFeedUpdates[1].feedConfig + ); + } + + function test_FeedUnset_Success() public { + Internal.TimestampedPackedUint224 memory priceQueryInitial = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + assertFalse(priceQueryInitial.value == 0); + assertFalse(priceQueryInitial.timestamp == 0); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + + tokenPriceFeedUpdates[0].feedConfig.dataFeedAddress = address(0); + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + + // Price data should remain after a feed has been set->unset + Internal.TimestampedPackedUint224 memory priceQueryPostUnsetFeed = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + assertEq(priceQueryPostUnsetFeed.value, priceQueryInitial.value); + assertEq(priceQueryPostUnsetFeed.timestamp, priceQueryInitial.timestamp); + } + + function test_FeedNotUpdated() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + } + + // Reverts + + function test_FeedUpdatedByNonOwner_Revert() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + } + + function _assertTokenPriceFeedConfigNotConfigured( + FeeQuoter.TokenPriceFeedConfig memory config + ) internal pure virtual { + _assertTokenPriceFeedConfigEquality( + config, FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0), tokenDecimals: 0, isEnabled: false}) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol new file mode 100644 index 00000000000..761cb7546a9 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_validateDestFamilyAddress is FeeQuoterSetup { + function test_ValidEVMAddress_Success() public view { + bytes memory encodedAddress = abi.encode(address(10000)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress); + } + + function test_ValidNonEVMAddress_Success() public view { + s_feeQuoter.validateDestFamilyAddress(bytes4(uint32(1)), abi.encode(type(uint208).max)); + } + + // Reverts + + function test_InvalidEVMAddress_Revert() public { + bytes memory invalidAddress = abi.encode(type(uint208).max); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + function test_InvalidEVMAddressEncodePacked_Revert() public { + bytes memory invalidAddress = abi.encodePacked(address(234)); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + function test_InvalidEVMAddressPrecompiles_Revert() public { + for (uint160 i = 0; i < Internal.PRECOMPILE_SPACE; ++i) { + bytes memory invalidAddress = abi.encode(address(i)); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + s_feeQuoter.validateDestFamilyAddress( + Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol index 12f535f9985..a6551c554e6 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol @@ -13,11 +13,26 @@ contract FeeQuoterSetup is TokenSetup { uint112 internal constant USD_PER_GAS = 1e6; // 0.001 gwei uint112 internal constant USD_PER_DATA_AVAILABILITY_GAS = 1e9; // 1 gwei + address internal constant DUMMY_CONTRACT_ADDRESS = 0x1111111111111111111111111111111111111112; address internal constant CUSTOM_TOKEN = address(12345); address internal constant CUSTOM_TOKEN_2 = address(bytes20(keccak256("CUSTOM_TOKEN_2"))); + // Use 16 gas per data availability byte in our tests. + // This is an overestimation in OP stack, it ignores 4 gas per 0 byte rule. + // Arbitrum on the other hand, does always use 16 gas per data availability byte. + // This value may be substantially decreased after EIP 4844. + uint16 internal constant DEST_GAS_PER_DATA_AVAILABILITY_BYTE = 16; + + // Total L1 data availability overhead estimate is 33_596 gas. + // This value includes complete CommitStore and OffRamp call data. + uint32 internal constant DEST_DATA_AVAILABILITY_OVERHEAD_GAS = 188 // Fixed data availability overhead in OP stack. + + (32 * 31 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE // CommitStore single-root transmission takes up about 31 slots, plus selector. + + (32 * 34 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE; // OffRamp transmission excluding EVM2EVMMessage takes up about 34 slots, plus selector. + + // Multiples of bps, or 0.0001, use 6840 to be same as OP mainnet compression factor of 0.684. + uint16 internal constant DEST_GAS_DATA_AVAILABILITY_MULTIPLIER_BPS = 6840; + uint224 internal constant CUSTOM_TOKEN_PRICE = 1e17; // $0.1 CUSTOM - uint224 internal constant CUSTOM_TOKEN_PRICE_2 = 1e18; // $1 CUSTOM // Encode L1 gas price and L2 gas price into a packed price. // L1 gas price is left-shifted to the higher-order bits. @@ -31,8 +46,6 @@ contract FeeQuoterSetup is TokenSetup { address[] internal s_sourceFeeTokens; uint224[] internal s_sourceTokenPrices; - address[] internal s_destFeeTokens; - uint224[] internal s_destTokenPrices; FeeQuoter.PremiumMultiplierWeiPerEthArgs[] internal s_feeQuoterPremiumMultiplierWeiPerEthArgs; FeeQuoter.TokenTransferFeeConfigArgs[] internal s_feeQuoterTokenTransferFeeConfigArgs; @@ -40,7 +53,7 @@ contract FeeQuoterSetup is TokenSetup { mapping(address token => address dataFeedAddress) internal s_dataFeedByToken; function setUp() public virtual override { - TokenSetup.setUp(); + super.setUp(); _deployTokenPriceDataFeed(s_sourceFeeToken, 8, 1e8); @@ -63,13 +76,11 @@ contract FeeQuoterSetup is TokenSetup { destFeeTokens[0] = s_destTokens[0]; destFeeTokens[1] = s_destTokens[1]; destFeeTokens[2] = s_destRouter.getWrappedNative(); - s_destFeeTokens = destFeeTokens; uint224[] memory destTokenPrices = new uint224[](3); destTokenPrices[0] = 5e18; destTokenPrices[1] = 2000e18; destTokenPrices[2] = 2000e18; - s_destTokenPrices = destTokenPrices; uint256 sourceTokenCount = sourceFeeTokens.length; uint256 destTokenCount = destFeeTokens.length; @@ -96,7 +107,6 @@ contract FeeQuoterSetup is TokenSetup { address[] memory feeTokens = new address[](2); feeTokens[0] = s_sourceTokens[0]; feeTokens[1] = s_weth; - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); s_feeQuoterPremiumMultiplierWeiPerEthArgs.push( FeeQuoter.PremiumMultiplierWeiPerEthArgs({ @@ -164,7 +174,7 @@ contract FeeQuoterSetup is TokenSetup { }), priceUpdaters, feeTokens, - tokenPriceFeedUpdates, + new FeeQuoter.TokenPriceFeedUpdate[](0), s_feeQuoterTokenTransferFeeConfigArgs, s_feeQuoterPremiumMultiplierWeiPerEthArgs, _generateFeeQuoterDestChainConfigArgs() @@ -194,13 +204,6 @@ contract FeeQuoterSetup is TokenSetup { return priceUpdates; } - function _getEmptyPriceUpdates() internal pure returns (Internal.PriceUpdates memory priceUpdates) { - return Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - } - function _getSingleTokenPriceFeedUpdateStruct( address sourceToken, address dataFeedAddress, @@ -273,14 +276,6 @@ contract FeeQuoterSetup is TokenSetup { assertEq(config1.isEnabled, config2.isEnabled); } - function _assertTokenPriceFeedConfigNotConfigured( - FeeQuoter.TokenPriceFeedConfig memory config - ) internal pure virtual { - _assertTokenPriceFeedConfigEquality( - config, FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0), tokenDecimals: 0, isEnabled: false}) - ); - } - function _assertTokenTransferFeeConfigEqual( FeeQuoter.TokenTransferFeeConfig memory a, FeeQuoter.TokenTransferFeeConfig memory b @@ -293,14 +288,6 @@ contract FeeQuoterSetup is TokenSetup { assertEq(a.isEnabled, b.isEnabled); } - function _assertFeeQuoterStaticConfigsEqual( - FeeQuoter.StaticConfig memory a, - FeeQuoter.StaticConfig memory b - ) internal pure { - assertEq(a.linkToken, b.linkToken); - assertEq(a.maxFeeJuelsPerMsg, b.maxFeeJuelsPerMsg); - } - function _assertFeeQuoterDestChainConfigsEqual( FeeQuoter.DestChainConfig memory a, FeeQuoter.DestChainConfig memory b @@ -323,19 +310,12 @@ contract FeeQuoterSetup is TokenSetup { contract FeeQuoterFeeSetup is FeeQuoterSetup { uint224 internal s_feeTokenPrice; uint224 internal s_wrappedTokenPrice; - uint224 internal s_customTokenPrice; - - address internal s_selfServeTokenDefaultPricing = makeAddr("self-serve-token-default-pricing"); - - address internal s_destTokenPool = makeAddr("destTokenPool"); - address internal s_destToken = makeAddr("destToken"); function setUp() public virtual override { super.setUp(); s_feeTokenPrice = s_sourceTokenPrices[0]; s_wrappedTokenPrice = s_sourceTokenPrices[2]; - s_customTokenPrice = CUSTOM_TOKEN_PRICE; s_feeQuoter.updatePrices(_getSingleTokenPriceUpdateStruct(CUSTOM_TOKEN, CUSTOM_TOKEN_PRICE)); } @@ -366,48 +346,6 @@ contract FeeQuoterFeeSetup is FeeQuoterSetup { }); } - function _messageToEvent( - Client.EVM2AnyMessage memory message, - uint64 sourceChainSelector, - uint64 destChainSelector, - uint64 seqNum, - uint64 nonce, - uint256 feeTokenAmount, - uint256 feeValueJuels, - address originalSender, - bytes32 metadataHash, - TokenAdminRegistry tokenAdminRegistry - ) internal view returns (Internal.EVM2AnyRampMessage memory) { - Client.EVMExtraArgsV2 memory extraArgs = - s_feeQuoter.parseEVMExtraArgsFromBytes(message.extraArgs, destChainSelector); - - Internal.EVM2AnyRampMessage memory messageEvent = Internal.EVM2AnyRampMessage({ - header: Internal.RampMessageHeader({ - messageId: "", - sourceChainSelector: sourceChainSelector, - destChainSelector: destChainSelector, - sequenceNumber: seqNum, - nonce: extraArgs.allowOutOfOrderExecution ? 0 : nonce - }), - sender: originalSender, - data: message.data, - receiver: message.receiver, - extraArgs: Client._argsToBytes(extraArgs), - feeToken: message.feeToken, - feeTokenAmount: feeTokenAmount, - feeValueJuels: feeValueJuels, - tokenAmounts: new Internal.EVM2AnyTokenTransfer[](message.tokenAmounts.length) - }); - - for (uint256 i = 0; i < message.tokenAmounts.length; ++i) { - messageEvent.tokenAmounts[i] = - _getSourceTokenData(message.tokenAmounts[i], tokenAdminRegistry, DEST_CHAIN_SELECTOR); - } - - messageEvent.header.messageId = Internal._hash(messageEvent, metadataHash); - return messageEvent; - } - function _getSourceTokenData( Client.EVMTokenAmount memory tokenAmount, TokenAdminRegistry tokenAdminRegistry, @@ -430,14 +368,6 @@ contract FeeQuoterFeeSetup is FeeQuoterSetup { }); } - function _calcUSDValueFromTokenAmount(uint224 tokenPrice, uint256 tokenAmount) internal pure returns (uint256) { - return (tokenPrice * tokenAmount) / 1e18; - } - - function _applyBpsRatio(uint256 tokenAmount, uint16 ratio) internal pure returns (uint256) { - return (tokenAmount * ratio) / 1e5; - } - function _configUSDCentToWei( uint256 usdCent ) internal pure returns (uint256) { diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol index 6dade484aee..aef54612945 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol @@ -25,7 +25,7 @@ contract OffRamp_batchExecute is OffRampSetup { s_offRamp.batchExecute( _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -54,7 +54,7 @@ contract OffRamp_batchExecute is OffRampSetup { s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -64,7 +64,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -74,7 +74,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -105,7 +105,7 @@ contract OffRamp_batchExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -115,7 +115,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -125,7 +125,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -195,7 +195,7 @@ contract OffRamp_batchExecute is OffRampSetup { vm.recordLogs(); s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol index f4e6be1b8aa..c05d8ec476a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol @@ -6,8 +6,7 @@ import {OffRampSetup} from "./OffRampSetup.t.sol"; contract OffRamp_ccipReceive is OffRampSetup { function test_RevertWhen_Always() public { - Client.Any2EVMMessage memory message = - _convertToGeneralMessage(_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1)); + Client.Any2EVMMessage memory message; vm.expectRevert(); diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol index da23daac0ed..bd7bb94344c 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol @@ -188,7 +188,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: IRMNRemote(ZERO_ADDRESS), + rmnRemote: IRMNRemote(address(0)), tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) }), @@ -229,7 +229,7 @@ contract OffRamp_constructor is OffRampSetup { OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: ZERO_ADDRESS, + tokenAdminRegistry: address(0), nonceManager: address(s_inboundNonceManager) }), _generateDynamicOffRampConfig(address(s_feeQuoter)), @@ -250,7 +250,7 @@ contract OffRamp_constructor is OffRampSetup { chainSelector: DEST_CHAIN_SELECTOR, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: ZERO_ADDRESS + nonceManager: address(0) }), _generateDynamicOffRampConfig(address(s_feeQuoter)), sourceChainConfigs diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol index b1c33efb106..9fd2499ef28 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol @@ -32,7 +32,7 @@ contract OffRamp_execute is OffRampSetup { _execute(reports); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -64,7 +64,7 @@ contract OffRamp_execute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -74,7 +74,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -84,7 +84,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -118,7 +118,7 @@ contract OffRamp_execute is OffRampSetup { for (uint64 i = 0; i < reports.length; ++i) { for (uint64 j = 0; j < reports[i].messages.length; ++j) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, reports[i].messages[j].header.sourceChainSelector, reports[i].messages[j].header.sequenceNumber, @@ -158,7 +158,7 @@ contract OffRamp_execute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -171,7 +171,7 @@ contract OffRamp_execute is OffRampSetup { ) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -181,7 +181,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol index aa5b2e93d5a..e651ad3836a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol @@ -30,7 +30,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -48,7 +48,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -71,7 +71,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -95,7 +95,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -151,7 +151,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -196,7 +196,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -214,7 +214,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -241,7 +241,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -271,7 +271,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -291,7 +291,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { vm.resumeGasMetering(); vm.recordLogs(); s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -316,7 +316,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -326,7 +326,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -353,7 +353,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -362,7 +362,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { Internal.MessageExecutionState.SUCCESS, "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -410,7 +410,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { uint256(Internal.MessageExecutionState.SUCCESS) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[i].header.sequenceNumber, @@ -440,7 +440,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -623,7 +623,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { vm.recordLogs(); s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -649,7 +649,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol index 91afbdfac8c..0422053bdd7 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol @@ -13,6 +13,8 @@ import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/ import {Vm} from "forge-std/Vm.sol"; contract OffRamp_manuallyExecute is OffRampSetup { + uint32 internal constant MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS = 200_000; + function setUp() public virtual override { super.setUp(); _setupMultipleOffRamps(); @@ -37,7 +39,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -63,7 +65,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -90,7 +92,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -148,7 +150,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); for (uint256 j = 0; j < 3; ++j) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages1[j].header.sequenceNumber, @@ -160,7 +162,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { } for (uint256 k = 0; k < 2; ++k) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_3, messages2[k].header.sequenceNumber, @@ -189,7 +191,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -199,7 +201,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -212,7 +214,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { ) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[2].header.sequenceNumber, @@ -234,7 +236,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, newMessages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -255,7 +257,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { s_offRamp.batchExecute( _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -273,7 +275,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -516,7 +518,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol index 40a4514eb70..22f82bdf694 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol @@ -257,4 +257,20 @@ contract OffRamp_releaseOrMintTokens is OffRampSetup { } } } + + function _getDefaultSourceTokenData( + Client.EVMTokenAmount[] memory srcTokenAmounts + ) internal view returns (Internal.Any2EVMTokenTransfer[] memory) { + Internal.Any2EVMTokenTransfer[] memory sourceTokenData = new Internal.Any2EVMTokenTransfer[](srcTokenAmounts.length); + for (uint256 i = 0; i < srcTokenAmounts.length; ++i) { + sourceTokenData[i] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[srcTokenAmounts[i].token]), + destTokenAddress: s_destTokenBySourceToken[srcTokenAmounts[i].token], + extraData: "", + amount: srcTokenAmounts[i].amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + } + return sourceTokenData; + } } diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol index 2b67f09c0e1..384d9b446aa 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol @@ -43,7 +43,7 @@ contract OffRamp_setDynamicConfig is OffRampSetup { } function test_FeeQuoterZeroAddress_Revert() public { - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(ZERO_ADDRESS); + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(0)); vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol index 7b9d422df15..68b32390c0a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol @@ -11,7 +11,6 @@ import {Client} from "../../../libraries/Client.sol"; import {Internal} from "../../../libraries/Internal.sol"; import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; import {OffRamp} from "../../../offRamp/OffRamp.sol"; -import {TokenPool} from "../../../pools/TokenPool.sol"; import {FeeQuoterSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; @@ -25,12 +24,12 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { uint64 internal constant SOURCE_CHAIN_SELECTOR_2 = 6433500567565415381; uint64 internal constant SOURCE_CHAIN_SELECTOR_3 = 4051577828743386545; + address internal constant ON_RAMP_ADDRESS = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; + bytes internal constant ON_RAMP_ADDRESS_1 = abi.encode(ON_RAMP_ADDRESS); bytes internal constant ON_RAMP_ADDRESS_2 = abi.encode(0xaA3f843Cf8E33B1F02dd28303b6bD87B1aBF8AE4); bytes internal constant ON_RAMP_ADDRESS_3 = abi.encode(0x71830C37Cb193e820de488Da111cfbFcC680a1b9); - address internal constant BLESS_VOTE_ADDR = address(8888); - IAny2EVMMessageReceiver internal s_receiver; IAny2EVMMessageReceiver internal s_secondary_receiver; MaybeRevertMessageReceiver internal s_reverting_receiver; @@ -40,11 +39,9 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { OffRampHelper internal s_offRamp; MessageInterceptorHelper internal s_inboundMessageInterceptor; NonceManager internal s_inboundNonceManager; - address internal s_sourceTokenPool = makeAddr("sourceTokenPool"); bytes32 internal s_configDigestExec; bytes32 internal s_configDigestCommit; - uint64 internal constant OFFCHAIN_CONFIG_VERSION = 3; uint8 internal constant F = 1; uint64 internal s_latestSequenceNumber; @@ -165,44 +162,17 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { s_destRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); } - uint32 internal constant MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS = 200_000; - uint32 internal constant MAX_TOKEN_POOL_TRANSFER_GAS = 50_000; - function _generateDynamicOffRampConfig( address feeQuoter ) internal pure returns (OffRamp.DynamicConfig memory) { return OffRamp.DynamicConfig({ feeQuoter: feeQuoter, - permissionLessExecutionThresholdSeconds: PERMISSION_LESS_EXECUTION_THRESHOLD_SECONDS, + permissionLessExecutionThresholdSeconds: 60 * 60, isRMNVerificationDisabled: false, messageInterceptor: address(0) }); } - function _convertToGeneralMessage( - Internal.Any2EVMRampMessage memory original - ) internal view returns (Client.Any2EVMMessage memory message) { - uint256 numberOfTokens = original.tokenAmounts.length; - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](numberOfTokens); - - for (uint256 i = 0; i < numberOfTokens; ++i) { - Internal.Any2EVMTokenTransfer memory tokenAmount = original.tokenAmounts[i]; - - address destPoolAddress = tokenAmount.destTokenAddress; - TokenPool pool = TokenPool(destPoolAddress); - destTokenAmounts[i].token = address(pool.getToken()); - destTokenAmounts[i].amount = tokenAmount.amount; - } - - return Client.Any2EVMMessage({ - messageId: original.header.messageId, - sourceChainSelector: original.header.sourceChainSelector, - sender: abi.encode(original.sender), - data: original.data, - destTokenAmounts: destTokenAmounts - }); - } - function _generateAny2EVMMessageNoTokens( uint64 sourceChainSelector, bytes memory onRamp, @@ -290,6 +260,18 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { return messages; } + function _getCastedSourceEVMTokenAmountsWithZeroAmounts() + internal + view + returns (Client.EVMTokenAmount[] memory tokenAmounts) + { + tokenAmounts = new Client.EVMTokenAmount[](s_sourceTokens.length); + for (uint256 i = 0; i < tokenAmounts.length; ++i) { + tokenAmounts[i].token = s_sourceTokens[i]; + } + return tokenAmounts; + } + function _generateReportFromMessages( uint64 sourceChainSelector, Internal.Any2EVMRampMessage[] memory messages @@ -345,22 +327,6 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { assertEq(address(config1.router), address(config2.router)); } - function _getDefaultSourceTokenData( - Client.EVMTokenAmount[] memory srcTokenAmounts - ) internal view returns (Internal.Any2EVMTokenTransfer[] memory) { - Internal.Any2EVMTokenTransfer[] memory sourceTokenData = new Internal.Any2EVMTokenTransfer[](srcTokenAmounts.length); - for (uint256 i = 0; i < srcTokenAmounts.length; ++i) { - sourceTokenData[i] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[srcTokenAmounts[i].token]), - destTokenAddress: s_destTokenBySourceToken[srcTokenAmounts[i].token], - extraData: "", - amount: srcTokenAmounts[i].amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - } - return sourceTokenData; - } - function _enableInboundMessageInterceptor() internal { OffRamp.DynamicConfig memory dynamicConfig = s_offRamp.getDynamicConfig(); dynamicConfig.messageInterceptor = address(s_inboundMessageInterceptor); @@ -412,14 +378,14 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { s_offRamp.execute(reportContext, abi.encode(reports)); } - function assertExecutionStateChangedEventLogs( + function _assertExecutionStateChangedEventLogs( uint64 sourceChainSelector, uint64 sequenceNumber, bytes32 messageId, bytes32 messageHash, Internal.MessageExecutionState state, bytes memory returnData - ) public { + ) internal { Vm.Log[] memory logs = vm.getRecordedLogs(); for (uint256 i = 0; i < logs.length; ++i) { if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { @@ -440,7 +406,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { } } - function assertExecutionStateChangedEventLogs( + function _assertExecutionStateChangedEventLogs( Vm.Log[] memory logs, uint64 sourceChainSelector, uint64 sequenceNumber, @@ -448,7 +414,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { bytes32 messageHash, Internal.MessageExecutionState state, bytes memory returnData - ) public pure { + ) internal pure { for (uint256 i = 0; i < logs.length; ++i) { if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { uint64 logSourceChainSelector = uint64(uint256(logs[i].topics[1])); @@ -473,7 +439,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { ) internal { Vm.Log[] memory logs = vm.getRecordedLogs(); - for (uint256 i = 0; i < logs.length; i++) { + for (uint256 i = 0; i < logs.length; ++i) { assertTrue(logs[i].topics[0] != eventSelector); } } @@ -494,4 +460,11 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { ) ); } + + function _getEmptyPriceUpdates() internal pure returns (Internal.PriceUpdates memory priceUpdates) { + return Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + } } diff --git a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol deleted file mode 100644 index b2687063ea6..00000000000 --- a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol +++ /dev/null @@ -1,1087 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IMessageInterceptor} from "../../interfaces/IMessageInterceptor.sol"; -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; -import {IRouter} from "../../interfaces/IRouter.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {FeeQuoter} from "../../FeeQuoter.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {MaybeRevertingBurnMintTokenPool} from "../helpers/MaybeRevertingBurnMintTokenPool.sol"; -import {OnRampHelper} from "../helpers/OnRampHelper.sol"; -import {OnRampSetup} from "./OnRampSetup.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract OnRamp_constructor is OnRampSetup { - function test_Constructor_Success() public { - OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }); - OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, dynamicConfig); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - - _deployOnRamp(SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry)); - - OnRamp.StaticConfig memory gotStaticConfig = s_onRamp.getStaticConfig(); - _assertStaticConfigsEqual(staticConfig, gotStaticConfig); - - OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); - _assertDynamicConfigsEqual(dynamicConfig, gotDynamicConfig); - - // Initial values - assertEq("OnRamp 1.6.0-dev", s_onRamp.typeAndVersion()); - assertEq(OWNER, s_onRamp.owner()); - assertEq(1, s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR)); - } - - function test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() public { - OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }); - - OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); - - // Creating a DestChainConfig and setting allowlistEnabled : true - OnRamp.DestChainConfigArgs[] memory destChainConfigs = new OnRamp.DestChainConfigArgs[](1); - destChainConfigs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: true - }); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, dynamicConfig); - - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, true); - - OnRampHelper tempOnRamp = new OnRampHelper(staticConfig, dynamicConfig, destChainConfigs); - - // Sending a message and expecting revert as allowlist is enabled with no address in allowlist - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, OWNER)); - tempOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_Constructor_InvalidConfigChainSelectorEqZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: 0, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp = new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: IRMNRemote(address(0)), - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(0), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(0) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } -} - -contract OnRamp_forwardFromRouter is OnRampSetup { - struct LegacyExtraArgs { - uint256 gasLimit; - bool strict; - } - - function setUp() public virtual override { - super.setUp(); - - address[] memory feeTokens = new address[](1); - feeTokens[0] = s_sourceTokens[1]; - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - - uint64[] memory destinationChainSelectors = new uint64[](1); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - address[] memory addAllowedList = new address[](1); - addAllowedList[0] = OWNER; - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addAllowedList, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - - // Since we'll mostly be testing for valid calls from the router we'll - // mock all calls to be originating from the router and re-mock in - // tests that require failure. - vm.startPrank(address(s_sourceRouter)); - } - - function test_ForwardFromRouterSuccessCustomExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouter_Success_ConfigurableSourceRouter() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - // Change the source router for this lane - IRouter newRouter = IRouter(makeAddr("NEW ROUTER")); - vm.stopPrank(); - vm.prank(OWNER); - s_onRamp.applyDestChainConfigUpdates(_generateDestChainConfigArgs(newRouter)); - - // forward fails from wrong router - vm.prank(address(s_sourceRouter)); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - // forward succeeds from correct router - vm.prank(address(newRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterSuccessLegacyExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = - abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V1_TAG, LegacyExtraArgs({gasLimit: GAS_LIMIT * 2, strict: true})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - // We expect the message to be emitted with strict = false. - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterSuccessEmptyExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = ""; - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - // We expect the message to be emitted with strict = false. - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouter_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterExtraArgsV2_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: false}) - ); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) - ); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ShouldIncrementSeqNumAndNonce_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - for (uint64 i = 1; i < 4; ++i) { - uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - assertEq(nonceAfter, nonceBefore + 1); - assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); - } - } - - function test_ShouldIncrementNonceOnlyOnOrdered_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) - ); - - for (uint64 i = 1; i < 4; ++i) { - uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - assertEq(nonceAfter, nonceBefore); - assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); - } - } - - function test_ShouldStoreLinkFees() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), feeAmount); - } - - function test_ShouldStoreNonLinkFees() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceTokens[1]; - - uint256 feeAmount = 1234567890; - IERC20(s_sourceTokens[1]).transferFrom(OWNER, address(s_onRamp), feeAmount); - - // Calculate conversion done by prices contract - uint256 feeTokenPrice = s_feeQuoter.getTokenPrice(s_sourceTokens[1]).value; - uint256 linkTokenPrice = s_feeQuoter.getTokenPrice(s_sourceFeeToken).value; - uint256 conversionRate = (feeTokenPrice * 1e18) / linkTokenPrice; - uint256 expectedJuels = (feeAmount * conversionRate) / 1e18; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, expectedJuels, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - assertEq(IERC20(s_sourceTokens[1]).balanceOf(address(s_onRamp)), feeAmount); - } - - // Make sure any valid sender, receiver and feeAmount can be handled. - // @TODO Temporarily setting lower fuzz run as 256 triggers snapshot gas off by 1 error. - // https://github.com/foundry-rs/foundry/issues/5689 - /// forge-dynamicConfig: default.fuzz.runs = 32 - /// forge-dynamicConfig: ccip.fuzz.runs = 32 - function test_Fuzz_ForwardFromRouter_Success(address originalSender, address receiver, uint96 feeTokenAmount) public { - // To avoid RouterMustSetOriginalSender - vm.assume(originalSender != address(0)); - vm.assume(uint160(receiver) >= Internal.PRECOMPILE_SPACE); - feeTokenAmount = uint96(bound(feeTokenAmount, 0, MAX_MSG_FEES_JUELS)); - vm.stopPrank(); - - vm.startPrank(OWNER); - uint64[] memory destinationChainSelectors = new uint64[](1); - destinationChainSelectors[0] = uint64(DEST_CHAIN_SELECTOR); - address[] memory addAllowedList = new address[](1); - addAllowedList[0] = originalSender; - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addAllowedList, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - - vm.startPrank(address(s_sourceRouter)); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.receiver = abi.encode(receiver); - - // Make sure the tokens are in the contract - deal(s_sourceFeeToken, address(s_onRamp), feeTokenAmount); - - Internal.EVM2AnyRampMessage memory expectedEvent = _messageToEvent(message, 1, 1, feeTokenAmount, originalSender); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, expectedEvent.header.sequenceNumber, expectedEvent); - - // Assert the message Id is correct - assertEq( - expectedEvent.header.messageId, - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeTokenAmount, originalSender) - ); - } - - function test_forwardFromRouter_WithInterception_Success() public { - _enableOutboundMessageInterceptor(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1e18; - message.tokenAmounts[0].token = s_sourceTokens[0]; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), false); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - // Reverts - - function test_Paused_Revert() public { - // We pause by disabling the whitelist - vm.stopPrank(); - vm.startPrank(OWNER); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); - } - - function test_InvalidExtraArgsTag_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = bytes("bad args"); - - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_Permissions_Revert() public { - vm.stopPrank(); - vm.startPrank(OWNER); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); - } - - function test_OriginalSender_Revert() public { - vm.expectRevert(OnRamp.RouterMustSetOriginalSender.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, address(0)); - } - - function test_UnAllowedOriginalSender_Revert() public { - vm.stopPrank(); - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, STRANGER)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, STRANGER); - } - - function test_MessageInterceptionError_Revert() public { - _enableOutboundMessageInterceptor(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1e18; - message.tokenAmounts[0].token = s_sourceTokens[0]; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), true); - - vm.expectRevert( - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_MultiCannotSendZeroTokens_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 0; - message.tokenAmounts[0].token = s_sourceTokens[0]; - vm.expectRevert(OnRamp.CannotSendZeroTokens.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_UnsupportedToken_Revert() public { - address wrongToken = address(1); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].token = wrongToken; - message.tokenAmounts[0].amount = 1; - - // We need to set the price of this new token to be able to reach - // the proper revert point. This must be called by the owner. - vm.stopPrank(); - vm.startPrank(OWNER); - - Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(wrongToken, 1); - s_feeQuoter.updatePrices(priceUpdates); - - // Change back to the router - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, wrongToken)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_forwardFromRouter_UnsupportedToken_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1; - message.tokenAmounts[0].token = address(1); - - vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, message.tokenAmounts[0].token)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_MesssageFeeTooHigh_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) - ); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, MAX_MSG_FEES_JUELS + 1, OWNER); - } - - function test_SourceTokenDataTooLarge_Revert() public { - address sourceETH = s_sourceTokens[1]; - vm.stopPrank(); - vm.startPrank(OWNER); - - MaybeRevertingBurnMintTokenPool newPool = new MaybeRevertingBurnMintTokenPool( - BurnMintERC677(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter) - ); - BurnMintERC677(sourceETH).grantMintAndBurnRoles(address(newPool)); - deal(address(sourceETH), address(newPool), type(uint256).max); - - // Add TokenPool to OnRamp - s_tokenAdminRegistry.setPool(sourceETH, address(newPool)); - - // Allow chain in TokenPool - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(s_destTokenPool), - remoteTokenAddress: abi.encode(s_destToken), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - newPool.applyChainUpdates(chainUpdates); - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(address(sourceETH), 1000); - - // No data set, should succeed - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set max data length, should succeed - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES)); - - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set data to max length +1, should revert - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1)); - - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set token config to allow larger data - vm.startPrank(OWNER); - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set the token data larger than the configured token data, should revert - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32 + 1)); - - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } -} - -contract OnRamp_getSupportedTokens is OnRampSetup { - function test_GetSupportedTokens_Revert() public { - vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); - s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); - } -} - -contract OnRamp_getFee is OnRampSetup { - using USDPriceWith18Decimals for uint224; - - function test_EmptyMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = testTokens[i]; - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - assertEq(expectedFeeAmount, feeAmount); - } - } - - function test_SingleTokenMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 tokenAmount = 10000e18; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - message.feeToken = testTokens[i]; - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - assertEq(expectedFeeAmount, feeAmount); - } - } - - function test_GetFeeOfZeroForTokenMessage_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - assertTrue(feeAmount > 0); - - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory tokenMults = new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - tokenMults[0] = FeeQuoter.PremiumMultiplierWeiPerEthArgs({token: message.feeToken, premiumMultiplierWeiPerEth: 0}); - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(tokenMults); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; - destChainConfigArgs[0].destChainConfig.gasMultiplierWeiPerEth = 0; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - - assertEq(0, feeAmount); - } - - // Reverts - - function test_Unhealthy_Revert() public { - _setMockRMNChainCurse(DEST_CHAIN_SELECTOR, true); - vm.expectRevert(abi.encodeWithSelector(OnRamp.CursedByRMN.selector, DEST_CHAIN_SELECTOR)); - s_onRamp.getFee(DEST_CHAIN_SELECTOR, _generateEmptyMessage()); - } - - function test_EnforceOutOfOrder_Revert() public { - // Update dynamic config to enforce allowOutOfOrderExecution = true. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - vm.stopPrank(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - // Empty extraArgs to should revert since it enforceOutOfOrder is true. - message.extraArgs = ""; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - } - - function test_NotAFeeTokenButPricedToken_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceTokens[1]; - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, message.feeToken)); - - s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - } -} - -contract OnRamp_setDynamicConfig is OnRampSetup { - function test_setDynamicConfig_Success() public { - OnRamp.StaticConfig memory staticConfig = s_onRamp.getStaticConfig(); - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: makeAddr("messageInterceptor"), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, newConfig); - - s_onRamp.setDynamicConfig(newConfig); - - OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); - assertEq(newConfig.feeQuoter, gotDynamicConfig.feeQuoter); - } - - // Reverts - - function test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(0), - reentrancyGuardEntered: false, - feeAggregator: FEE_AGGREGATOR, - messageInterceptor: makeAddr("messageInterceptor"), - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: address(0), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - // Invalid price reg reverts. - newConfig.feeQuoter = address(0); - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: address(0), - feeAggregator: address(0), - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - vm.startPrank(ADMIN); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - } - - function test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: true, - messageInterceptor: makeAddr("messageInterceptor"), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } -} - -contract OnRamp_withdrawFeeTokens is OnRampSetup { - mapping(address => uint256) internal s_nopFees; - - function setUp() public virtual override { - super.setUp(); - - // Since we'll mostly be testing for valid calls from the router we'll - // mock all calls to be originating from the router and re-mock in - // tests that require failure. - vm.startPrank(address(s_sourceRouter)); - - uint256 feeAmount = 1234567890; - - // Send a bunch of messages, increasing the juels in the contract - for (uint256 i = 0; i < s_sourceFeeTokens.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceFeeTokens[i % s_sourceFeeTokens.length]; - uint256 newFeeTokenBalance = IERC20(message.feeToken).balanceOf(address(s_onRamp)) + feeAmount; - deal(message.feeToken, address(s_onRamp), newFeeTokenBalance); - s_nopFees[message.feeToken] = newFeeTokenBalance; - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - } - - function test_Fuzz_WithdrawFeeTokens_Success( - uint256[5] memory amounts - ) public { - vm.startPrank(OWNER); - address[] memory feeTokens = new address[](amounts.length); - for (uint256 i = 0; i < amounts.length; ++i) { - vm.assume(amounts[i] > 0); - feeTokens[i] = _deploySourceToken("", amounts[i], 18); - IERC20(feeTokens[i]).transfer(address(s_onRamp), amounts[i]); - } - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - - for (uint256 i = 0; i < feeTokens.length; ++i) { - vm.expectEmit(); - emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, feeTokens[i], amounts[i]); - } - - s_onRamp.withdrawFeeTokens(feeTokens); - - for (uint256 i = 0; i < feeTokens.length; ++i) { - assertEq(IERC20(feeTokens[i]).balanceOf(FEE_AGGREGATOR), amounts[i]); - assertEq(IERC20(feeTokens[i]).balanceOf(address(s_onRamp)), 0); - } - } - - function test_WithdrawFeeTokens_Success() public { - vm.expectEmit(); - emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, s_sourceFeeToken, s_nopFees[s_sourceFeeToken]); - - s_onRamp.withdrawFeeTokens(s_sourceFeeTokens); - - assertEq(IERC20(s_sourceFeeToken).balanceOf(FEE_AGGREGATOR), s_nopFees[s_sourceFeeToken]); - assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), 0); - } -} - -contract OnRamp_getTokenPool is OnRampSetup { - function test_GetTokenPool_Success() public view { - assertEq( - s_sourcePoolByToken[s_sourceTokens[0]], - address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[0]))) - ); - assertEq( - s_sourcePoolByToken[s_sourceTokens[1]], - address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[1]))) - ); - - address wrongToken = address(123); - address nonExistentPool = address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(wrongToken))); - - assertEq(address(0), nonExistentPool); - } -} - -contract OnRamp_applyDestChainConfigUpdates is OnRampSetup { - function test_ApplyDestChainConfigUpdates_Success() external { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); - configArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - - // supports disabling a lane by setting a router to zero - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, IRouter(address(0)), false); - - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (,, address router) = s_onRamp.getDestChainConfig(DEST_CHAIN_SELECTOR); - assertEq(address(0), router); - - // supports updating and adding lanes simultaneously - configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - uint64 newDestChainSelector = 99999; - address newRouter = makeAddr("newRouter"); - - configArgs[1] = OnRamp.DestChainConfigArgs({ - destChainSelector: newDestChainSelector, - router: IRouter(newRouter), - allowlistEnabled: false - }); - - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(newDestChainSelector, 0, IRouter(newRouter), false); - - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (,, address newGotRouter) = s_onRamp.getDestChainConfig(newDestChainSelector); - assertEq(newRouter, newGotRouter); - - // handles empty list - uint256 numLogs = vm.getRecordedLogs().length; - configArgs = new OnRamp.DestChainConfigArgs[](0); - s_onRamp.applyDestChainConfigUpdates(configArgs); - assertEq(numLogs, vm.getRecordedLogs().length); // indicates no changes made - } - - function test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() external { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); - configArgs[0].destChainSelector = 0; // invalid - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidDestChainConfig.selector, 0)); - s_onRamp.applyDestChainConfigUpdates(configArgs); - } -} - -contract OnRamp_applyAllowlistUpdates is OnRampSetup { - function test_applyAllowlistUpdates_Success() public { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - configArgs[1] = - OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (uint64 sequenceNumber, bool allowlistEnabled, address router) = s_onRamp.getDestChainConfig(9999); - assertEq(sequenceNumber, 0); - assertEq(allowlistEnabled, false); - assertEq(router, address(9999)); - - uint64[] memory destinationChainSelectors = new uint64[](2); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - destinationChainSelectors[1] = uint64(99999); - - address[] memory addedAllowlistedSenders = new address[](4); - addedAllowlistedSenders[0] = vm.addr(1); - addedAllowlistedSenders[1] = vm.addr(2); - addedAllowlistedSenders[2] = vm.addr(3); - addedAllowlistedSenders[3] = vm.addr(4); - - vm.expectEmit(); - emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - - (bool isActive, address[] memory gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - assertEq(4, gotAllowList.length); - assertEq(addedAllowlistedSenders, gotAllowList); - assertEq(true, isActive); - - address[] memory removedAllowlistedSenders = new address[](1); - removedAllowlistedSenders[0] = vm.addr(2); - - vm.expectEmit(); - emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); - - allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: false, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: new address[](0), - removedAllowlistedSenders: removedAllowlistedSenders - }); - - OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_2 = new OnRamp.AllowlistConfigArgs[](1); - allowlistConfigArgsItems_2[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_2); - (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - assertEq(3, gotAllowList.length); - assertFalse(isActive); - - addedAllowlistedSenders = new address[](2); - addedAllowlistedSenders[0] = vm.addr(5); - addedAllowlistedSenders[1] = vm.addr(6); - - removedAllowlistedSenders = new address[](2); - removedAllowlistedSenders[0] = vm.addr(1); - removedAllowlistedSenders[1] = vm.addr(3); - - vm.expectEmit(); - emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); - emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); - - allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: removedAllowlistedSenders - }); - - OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_3 = new OnRamp.AllowlistConfigArgs[](1); - allowlistConfigArgsItems_3[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_3); - (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - - assertEq(3, gotAllowList.length); - assertTrue(isActive); - } - - function test_applyAllowlistUpdates_Revert() public { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - configArgs[1] = - OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); - s_onRamp.applyDestChainConfigUpdates(configArgs); - - uint64[] memory destinationChainSelectors = new uint64[](2); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - destinationChainSelectors[1] = uint64(99999); - - address[] memory addedAllowlistedSenders = new address[](4); - addedAllowlistedSenders[0] = vm.addr(1); - addedAllowlistedSenders[1] = vm.addr(2); - addedAllowlistedSenders[2] = vm.addr(3); - addedAllowlistedSenders[3] = vm.addr(4); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - vm.startPrank(STRANGER); - vm.expectRevert(OnRamp.OnlyCallableByOwnerOrAllowlistAdmin.selector); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - - applyAllowlistConfigArgsItems[0].addedAllowlistedSenders[0] = address(0); - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); - vm.startPrank(OWNER); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - } - - function test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() public { - address[] memory addedAllowlistedSenders = new address[](1); - addedAllowlistedSenders[0] = vm.addr(1); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: false, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - } -} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol new file mode 100644 index 00000000000..2b99fd423be --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_applyDestChainConfigUpdates is OnRampSetup { + function test_ApplyDestChainConfigUpdates_Success() external { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); + configArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + + // supports disabling a lane by setting a router to zero + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, IRouter(address(0)), false); + + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (,, address router) = s_onRamp.getDestChainConfig(DEST_CHAIN_SELECTOR); + assertEq(address(0), router); + + // supports updating and adding lanes simultaneously + configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + uint64 newDestChainSelector = 99999; + address newRouter = makeAddr("newRouter"); + + configArgs[1] = OnRamp.DestChainConfigArgs({ + destChainSelector: newDestChainSelector, + router: IRouter(newRouter), + allowlistEnabled: false + }); + + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(newDestChainSelector, 0, IRouter(newRouter), false); + + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (,, address newGotRouter) = s_onRamp.getDestChainConfig(newDestChainSelector); + assertEq(newRouter, newGotRouter); + + // handles empty list + uint256 numLogs = vm.getRecordedLogs().length; + configArgs = new OnRamp.DestChainConfigArgs[](0); + s_onRamp.applyDestChainConfigUpdates(configArgs); + assertEq(numLogs, vm.getRecordedLogs().length); // indicates no changes made + } + + function test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() external { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); + configArgs[0].destChainSelector = 0; // invalid + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidDestChainConfig.selector, 0)); + s_onRamp.applyDestChainConfigUpdates(configArgs); + } +} + +contract OnRamp_applyAllowlistUpdates is OnRampSetup { + function test_applyAllowlistUpdates_Success() public { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + configArgs[1] = + OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (uint64 sequenceNumber, bool allowlistEnabled, address router) = s_onRamp.getDestChainConfig(9999); + assertEq(sequenceNumber, 0); + assertEq(allowlistEnabled, false); + assertEq(router, address(9999)); + + uint64[] memory destinationChainSelectors = new uint64[](2); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + destinationChainSelectors[1] = uint64(99999); + + address[] memory addedAllowlistedSenders = new address[](4); + addedAllowlistedSenders[0] = vm.addr(1); + addedAllowlistedSenders[1] = vm.addr(2); + addedAllowlistedSenders[2] = vm.addr(3); + addedAllowlistedSenders[3] = vm.addr(4); + + vm.expectEmit(); + emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + + (bool isActive, address[] memory gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + assertEq(4, gotAllowList.length); + assertEq(addedAllowlistedSenders, gotAllowList); + assertEq(true, isActive); + + address[] memory removedAllowlistedSenders = new address[](1); + removedAllowlistedSenders[0] = vm.addr(2); + + vm.expectEmit(); + emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); + + allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: false, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: new address[](0), + removedAllowlistedSenders: removedAllowlistedSenders + }); + + OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_2 = new OnRamp.AllowlistConfigArgs[](1); + allowlistConfigArgsItems_2[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_2); + (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + assertEq(3, gotAllowList.length); + assertFalse(isActive); + + addedAllowlistedSenders = new address[](2); + addedAllowlistedSenders[0] = vm.addr(5); + addedAllowlistedSenders[1] = vm.addr(6); + + removedAllowlistedSenders = new address[](2); + removedAllowlistedSenders[0] = vm.addr(1); + removedAllowlistedSenders[1] = vm.addr(3); + + vm.expectEmit(); + emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); + emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); + + allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: removedAllowlistedSenders + }); + + OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_3 = new OnRamp.AllowlistConfigArgs[](1); + allowlistConfigArgsItems_3[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_3); + (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + + assertEq(3, gotAllowList.length); + assertTrue(isActive); + } + + function test_applyAllowlistUpdates_Revert() public { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + configArgs[1] = + OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); + s_onRamp.applyDestChainConfigUpdates(configArgs); + + uint64[] memory destinationChainSelectors = new uint64[](2); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + destinationChainSelectors[1] = uint64(99999); + + address[] memory addedAllowlistedSenders = new address[](4); + addedAllowlistedSenders[0] = vm.addr(1); + addedAllowlistedSenders[1] = vm.addr(2); + addedAllowlistedSenders[2] = vm.addr(3); + addedAllowlistedSenders[3] = vm.addr(4); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + vm.startPrank(STRANGER); + vm.expectRevert(OnRamp.OnlyCallableByOwnerOrAllowlistAdmin.selector); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + + applyAllowlistConfigArgsItems[0].addedAllowlistedSenders[0] = address(0); + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); + vm.startPrank(OWNER); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + } + + function test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() public { + address[] memory addedAllowlistedSenders = new address[](1); + addedAllowlistedSenders[0] = vm.addr(1); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: false, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol new file mode 100644 index 00000000000..1e31a2a1377 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {Client} from "../../../libraries/Client.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampHelper} from "../../helpers/OnRampHelper.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_constructor is OnRampSetup { + function test_Constructor_Success() public { + OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }); + OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, dynamicConfig); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + + _deployOnRamp(SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry)); + + OnRamp.StaticConfig memory gotStaticConfig = s_onRamp.getStaticConfig(); + + assertEq(staticConfig.chainSelector, gotStaticConfig.chainSelector); + assertEq(address(staticConfig.rmnRemote), address(gotStaticConfig.rmnRemote)); + assertEq(staticConfig.tokenAdminRegistry, gotStaticConfig.tokenAdminRegistry); + + OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); + assertEq(dynamicConfig.feeQuoter, gotDynamicConfig.feeQuoter); + + // Initial values + assertEq("OnRamp 1.6.0-dev", s_onRamp.typeAndVersion()); + assertEq(OWNER, s_onRamp.owner()); + assertEq(1, s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR)); + } + + function test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() public { + OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }); + + OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); + + // Creating a DestChainConfig and setting allowlistEnabled : true + OnRamp.DestChainConfigArgs[] memory destChainConfigs = new OnRamp.DestChainConfigArgs[](1); + destChainConfigs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: true + }); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, dynamicConfig); + + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, true); + + OnRampHelper tempOnRamp = new OnRampHelper(staticConfig, dynamicConfig, destChainConfigs); + + // Sending a message and expecting revert as allowlist is enabled with no address in allowlist + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, OWNER)); + tempOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_Constructor_InvalidConfigChainSelectorEqZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: 0, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp = new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: IRMNRemote(address(0)), + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(0), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(0) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol new file mode 100644 index 00000000000..076377c34c5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {FeeQuoter} from "../../../FeeQuoter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; +import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_forwardFromRouter is OnRampSetup { + struct LegacyExtraArgs { + uint256 gasLimit; + bool strict; + } + + MessageInterceptorHelper internal s_outboundMessageInterceptor; + + address internal s_destTokenPool = makeAddr("destTokenPool"); + address internal s_destToken = makeAddr("destToken"); + + function setUp() public virtual override { + super.setUp(); + s_outboundMessageInterceptor = new MessageInterceptorHelper(); + + address[] memory feeTokens = new address[](1); + feeTokens[0] = s_sourceTokens[1]; + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + + uint64[] memory destinationChainSelectors = new uint64[](1); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + address[] memory addAllowedList = new address[](1); + addAllowedList[0] = OWNER; + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addAllowedList, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + + // Since we'll mostly be testing for valid calls from the router we'll + // mock all calls to be originating from the router and re-mock in + // tests that require failure. + vm.startPrank(address(s_sourceRouter)); + } + + function test_ForwardFromRouterSuccessCustomExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouter_Success_ConfigurableSourceRouter() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + // Change the source router for this lane + IRouter newRouter = IRouter(makeAddr("NEW ROUTER")); + vm.stopPrank(); + vm.prank(OWNER); + s_onRamp.applyDestChainConfigUpdates(_generateDestChainConfigArgs(newRouter)); + + // forward fails from wrong router + vm.prank(address(s_sourceRouter)); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + // forward succeeds from correct router + vm.prank(address(newRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterSuccessLegacyExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = + abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V1_TAG, LegacyExtraArgs({gasLimit: GAS_LIMIT * 2, strict: true})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + // We expect the message to be emitted with strict = false. + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterSuccessEmptyExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = ""; + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + // We expect the message to be emitted with strict = false. + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouter_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterExtraArgsV2_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: false}) + ); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) + ); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ShouldIncrementSeqNumAndNonce_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + for (uint64 i = 1; i < 4; ++i) { + uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + assertEq(nonceAfter, nonceBefore + 1); + assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); + } + } + + function test_ShouldIncrementNonceOnlyOnOrdered_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) + ); + + for (uint64 i = 1; i < 4; ++i) { + uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + assertEq(nonceAfter, nonceBefore); + assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); + } + } + + function test_ShouldStoreLinkFees() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), feeAmount); + } + + function test_ShouldStoreNonLinkFees() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceTokens[1]; + + uint256 feeAmount = 1234567890; + IERC20(s_sourceTokens[1]).transferFrom(OWNER, address(s_onRamp), feeAmount); + + // Calculate conversion done by prices contract + uint256 feeTokenPrice = s_feeQuoter.getTokenPrice(s_sourceTokens[1]).value; + uint256 linkTokenPrice = s_feeQuoter.getTokenPrice(s_sourceFeeToken).value; + uint256 conversionRate = (feeTokenPrice * 1e18) / linkTokenPrice; + uint256 expectedJuels = (feeAmount * conversionRate) / 1e18; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, expectedJuels, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + assertEq(IERC20(s_sourceTokens[1]).balanceOf(address(s_onRamp)), feeAmount); + } + + // Make sure any valid sender, receiver and feeAmount can be handled. + // @TODO Temporarily setting lower fuzz run as 256 triggers snapshot gas off by 1 error. + // https://github.com/foundry-rs/foundry/issues/5689 + /// forge-dynamicConfig: default.fuzz.runs = 32 + /// forge-dynamicConfig: ccip.fuzz.runs = 32 + function test_Fuzz_ForwardFromRouter_Success(address originalSender, address receiver, uint96 feeTokenAmount) public { + // To avoid RouterMustSetOriginalSender + vm.assume(originalSender != address(0)); + vm.assume(uint160(receiver) >= Internal.PRECOMPILE_SPACE); + feeTokenAmount = uint96(bound(feeTokenAmount, 0, MAX_MSG_FEES_JUELS)); + vm.stopPrank(); + + vm.startPrank(OWNER); + uint64[] memory destinationChainSelectors = new uint64[](1); + destinationChainSelectors[0] = uint64(DEST_CHAIN_SELECTOR); + address[] memory addAllowedList = new address[](1); + addAllowedList[0] = originalSender; + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addAllowedList, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + + vm.startPrank(address(s_sourceRouter)); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.receiver = abi.encode(receiver); + + // Make sure the tokens are in the contract + deal(s_sourceFeeToken, address(s_onRamp), feeTokenAmount); + + Internal.EVM2AnyRampMessage memory expectedEvent = _messageToEvent(message, 1, 1, feeTokenAmount, originalSender); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, expectedEvent.header.sequenceNumber, expectedEvent); + + // Assert the message Id is correct + assertEq( + expectedEvent.header.messageId, + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeTokenAmount, originalSender) + ); + } + + function test_forwardFromRouter_WithInterception_Success() public { + _enableOutboundMessageInterceptor(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1e18; + message.tokenAmounts[0].token = s_sourceTokens[0]; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), false); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + // Reverts + + function test_Paused_Revert() public { + // We pause by disabling the whitelist + vm.stopPrank(); + vm.startPrank(OWNER); + s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); + } + + function test_InvalidExtraArgsTag_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = bytes("bad args"); + + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_Permissions_Revert() public { + vm.stopPrank(); + vm.startPrank(OWNER); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); + } + + function test_OriginalSender_Revert() public { + vm.expectRevert(OnRamp.RouterMustSetOriginalSender.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, address(0)); + } + + function test_UnAllowedOriginalSender_Revert() public { + vm.stopPrank(); + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, STRANGER)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, STRANGER); + } + + function test_MessageInterceptionError_Revert() public { + _enableOutboundMessageInterceptor(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1e18; + message.tokenAmounts[0].token = s_sourceTokens[0]; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), true); + + vm.expectRevert( + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_MultiCannotSendZeroTokens_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 0; + message.tokenAmounts[0].token = s_sourceTokens[0]; + vm.expectRevert(OnRamp.CannotSendZeroTokens.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_UnsupportedToken_Revert() public { + address wrongToken = address(1); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].token = wrongToken; + message.tokenAmounts[0].amount = 1; + + // We need to set the price of this new token to be able to reach + // the proper revert point. This must be called by the owner. + vm.stopPrank(); + vm.startPrank(OWNER); + + Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(wrongToken, 1); + s_feeQuoter.updatePrices(priceUpdates); + + // Change back to the router + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, wrongToken)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_forwardFromRouter_UnsupportedToken_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1; + message.tokenAmounts[0].token = address(1); + + vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, message.tokenAmounts[0].token)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_MesssageFeeTooHigh_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) + ); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, MAX_MSG_FEES_JUELS + 1, OWNER); + } + + function test_SourceTokenDataTooLarge_Revert() public { + address sourceETH = s_sourceTokens[1]; + vm.stopPrank(); + vm.startPrank(OWNER); + + MaybeRevertingBurnMintTokenPool newPool = new MaybeRevertingBurnMintTokenPool( + BurnMintERC677(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter) + ); + BurnMintERC677(sourceETH).grantMintAndBurnRoles(address(newPool)); + deal(address(sourceETH), address(newPool), type(uint256).max); + + // Add TokenPool to OnRamp + s_tokenAdminRegistry.setPool(sourceETH, address(newPool)); + + // Allow chain in TokenPool + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(s_destTokenPool), + remoteTokenAddress: abi.encode(s_destToken), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + newPool.applyChainUpdates(chainUpdates); + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(address(sourceETH), 1000); + + // No data set, should succeed + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set max data length, should succeed + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES)); + + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set data to max length +1, should revert + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1)); + + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set token config to allow larger data + vm.startPrank(OWNER); + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set the token data larger than the configured token data, should revert + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32 + 1)); + + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function _enableOutboundMessageInterceptor() internal { + (, address msgSender,) = vm.readCallers(); + + bool resetPrank = false; + + if (msgSender != OWNER) { + vm.stopPrank(); + vm.startPrank(OWNER); + resetPrank = true; + } + + OnRamp.DynamicConfig memory dynamicConfig = s_onRamp.getDynamicConfig(); + dynamicConfig.messageInterceptor = address(s_outboundMessageInterceptor); + s_onRamp.setDynamicConfig(dynamicConfig); + + if (resetPrank) { + vm.stopPrank(); + vm.startPrank(msgSender); + } + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol new file mode 100644 index 00000000000..63a4c0c322e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../../FeeQuoter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {USDPriceWith18Decimals} from "../../../libraries/USDPriceWith18Decimals.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_getFee is OnRampSetup { + using USDPriceWith18Decimals for uint224; + + function test_EmptyMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = testTokens[i]; + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + assertEq(expectedFeeAmount, feeAmount); + } + } + + function test_SingleTokenMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 tokenAmount = 10000e18; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + message.feeToken = testTokens[i]; + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + assertEq(expectedFeeAmount, feeAmount); + } + } + + function test_GetFeeOfZeroForTokenMessage_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + assertTrue(feeAmount > 0); + + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory tokenMults = new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + tokenMults[0] = FeeQuoter.PremiumMultiplierWeiPerEthArgs({token: message.feeToken, premiumMultiplierWeiPerEth: 0}); + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(tokenMults); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; + destChainConfigArgs[0].destChainConfig.gasMultiplierWeiPerEth = 0; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + + assertEq(0, feeAmount); + } + + // Reverts + + function test_Unhealthy_Revert() public { + _setMockRMNChainCurse(DEST_CHAIN_SELECTOR, true); + vm.expectRevert(abi.encodeWithSelector(OnRamp.CursedByRMN.selector, DEST_CHAIN_SELECTOR)); + s_onRamp.getFee(DEST_CHAIN_SELECTOR, _generateEmptyMessage()); + } + + function test_EnforceOutOfOrder_Revert() public { + // Update dynamic config to enforce allowOutOfOrderExecution = true. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + vm.stopPrank(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + // Empty extraArgs to should revert since it enforceOutOfOrder is true. + message.extraArgs = ""; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + } + + function test_NotAFeeTokenButPricedToken_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceTokens[1]; + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, message.feeToken)); + + s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol new file mode 100644 index 00000000000..c04f3cf3d51 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_getSupportedTokens is OnRampSetup { + function test_GetSupportedTokens_Revert() public { + vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); + s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol new file mode 100644 index 00000000000..8612ce86e36 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_getTokenPool is OnRampSetup { + function test_GetTokenPool_Success() public view { + assertEq( + s_sourcePoolByToken[s_sourceTokens[0]], + address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[0]))) + ); + assertEq( + s_sourcePoolByToken[s_sourceTokens[1]], + address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[1]))) + ); + + address wrongToken = address(123); + address nonExistentPool = address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(wrongToken))); + + assertEq(address(0), nonExistentPool); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol new file mode 100644 index 00000000000..057ed0a79dd --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_setDynamicConfig is OnRampSetup { + function test_setDynamicConfig_Success() public { + OnRamp.StaticConfig memory staticConfig = s_onRamp.getStaticConfig(); + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: makeAddr("messageInterceptor"), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, newConfig); + + s_onRamp.setDynamicConfig(newConfig); + + OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); + assertEq(newConfig.feeQuoter, gotDynamicConfig.feeQuoter); + } + + // Reverts + + function test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(0), + reentrancyGuardEntered: false, + feeAggregator: FEE_AGGREGATOR, + messageInterceptor: makeAddr("messageInterceptor"), + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: address(0), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + // Invalid price reg reverts. + newConfig.feeQuoter = address(0); + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: address(0), + feeAggregator: address(0), + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); + } + + function test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: true, + messageInterceptor: makeAddr("messageInterceptor"), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol new file mode 100644 index 00000000000..d4a297c103c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Client} from "../../../libraries/Client.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_withdrawFeeTokens is OnRampSetup { + mapping(address => uint256) internal s_nopFees; + + function setUp() public virtual override { + super.setUp(); + + // Since we'll mostly be testing for valid calls from the router we'll + // mock all calls to be originating from the router and re-mock in + // tests that require failure. + vm.startPrank(address(s_sourceRouter)); + + uint256 feeAmount = 1234567890; + + // Send a bunch of messages, increasing the juels in the contract + for (uint256 i = 0; i < s_sourceFeeTokens.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceFeeTokens[i % s_sourceFeeTokens.length]; + uint256 newFeeTokenBalance = IERC20(message.feeToken).balanceOf(address(s_onRamp)) + feeAmount; + deal(message.feeToken, address(s_onRamp), newFeeTokenBalance); + s_nopFees[message.feeToken] = newFeeTokenBalance; + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + } + + function test_Fuzz_WithdrawFeeTokens_Success( + uint256[5] memory amounts + ) public { + vm.startPrank(OWNER); + address[] memory feeTokens = new address[](amounts.length); + for (uint256 i = 0; i < amounts.length; ++i) { + vm.assume(amounts[i] > 0); + feeTokens[i] = _deploySourceToken("", amounts[i], 18); + IERC20(feeTokens[i]).transfer(address(s_onRamp), amounts[i]); + } + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + + for (uint256 i = 0; i < feeTokens.length; ++i) { + vm.expectEmit(); + emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, feeTokens[i], amounts[i]); + } + + s_onRamp.withdrawFeeTokens(feeTokens); + + for (uint256 i = 0; i < feeTokens.length; ++i) { + assertEq(IERC20(feeTokens[i]).balanceOf(FEE_AGGREGATOR), amounts[i]); + assertEq(IERC20(feeTokens[i]).balanceOf(address(s_onRamp)), 0); + } + } + + function test_WithdrawFeeTokens_Success() public { + vm.expectEmit(); + emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, s_sourceFeeToken, s_nopFees[s_sourceFeeToken]); + + s_onRamp.withdrawFeeTokens(s_sourceFeeTokens); + + assertEq(IERC20(s_sourceFeeToken).balanceOf(FEE_AGGREGATOR), s_nopFees[s_sourceFeeToken]); + assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), 0); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol similarity index 58% rename from contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol index 8254dd977f7..ead9e7088ce 100644 --- a/contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol @@ -1,48 +1,42 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IRouter} from "../../interfaces/IRouter.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {NonceManager} from "../../NonceManager.sol"; -import {Router} from "../../Router.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {FeeQuoterFeeSetup} from "../feeQuoter/FeeQuoterSetup.t.sol"; -import {MessageInterceptorHelper} from "../helpers/MessageInterceptorHelper.sol"; -import {OnRampHelper} from "../helpers/OnRampHelper.sol"; +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {NonceManager} from "../../../NonceManager.sol"; +import {Router} from "../../../Router.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {FeeQuoterFeeSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; +import {OnRampHelper} from "../../helpers/OnRampHelper.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract OnRampSetup is FeeQuoterFeeSetup { - uint256 internal immutable i_tokenAmount0 = 9; - uint256 internal immutable i_tokenAmount1 = 7; + address internal constant FEE_AGGREGATOR = 0xa33CDB32eAEce34F6affEfF4899cef45744EDea3; bytes32 internal s_metadataHash; OnRampHelper internal s_onRamp; - MessageInterceptorHelper internal s_outboundMessageInterceptor; - address[] internal s_offRamps; NonceManager internal s_outboundNonceManager; function setUp() public virtual override { super.setUp(); - s_outboundMessageInterceptor = new MessageInterceptorHelper(); s_outboundNonceManager = new NonceManager(new address[](0)); (s_onRamp, s_metadataHash) = _deployOnRamp( SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) ); - s_offRamps = new address[](2); - s_offRamps[0] = address(10); - s_offRamps[1] = address(11); Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](2); onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: address(s_onRamp)}); - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_offRamps[0]}); - offRampUpdates[1] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_offRamps[1]}); + + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](2); + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: makeAddr("offRamp0")}); + offRampUpdates[1] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: makeAddr("offRamp1")}); s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); // Pre approve the first token so the gas estimates of the tests @@ -51,19 +45,6 @@ contract OnRampSetup is FeeQuoterFeeSetup { IERC20(s_sourceTokens[1]).approve(address(s_sourceRouter), 2 ** 128); } - function _generateTokenMessage() public view returns (Client.EVM2AnyMessage memory) { - Client.EVMTokenAmount[] memory tokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - tokenAmounts[0].amount = i_tokenAmount0; - tokenAmounts[1].amount = i_tokenAmount1; - return Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: s_sourceFeeToken, - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - } - /// @dev a helper function to compose EVM2AnyRampMessage messages /// @dev it is assumed that LINK is the payment token because feeTokenAmount == feeValueJuels function _messageToEvent( @@ -105,6 +86,48 @@ contract OnRampSetup is FeeQuoterFeeSetup { ); } + function _messageToEvent( + Client.EVM2AnyMessage memory message, + uint64 sourceChainSelector, + uint64 destChainSelector, + uint64 seqNum, + uint64 nonce, + uint256 feeTokenAmount, + uint256 feeValueJuels, + address originalSender, + bytes32 metadataHash, + TokenAdminRegistry tokenAdminRegistry + ) internal view returns (Internal.EVM2AnyRampMessage memory) { + Client.EVMExtraArgsV2 memory extraArgs = + s_feeQuoter.parseEVMExtraArgsFromBytes(message.extraArgs, destChainSelector); + + Internal.EVM2AnyRampMessage memory messageEvent = Internal.EVM2AnyRampMessage({ + header: Internal.RampMessageHeader({ + messageId: "", + sourceChainSelector: sourceChainSelector, + destChainSelector: destChainSelector, + sequenceNumber: seqNum, + nonce: extraArgs.allowOutOfOrderExecution ? 0 : nonce + }), + sender: originalSender, + data: message.data, + receiver: message.receiver, + extraArgs: Client._argsToBytes(extraArgs), + feeToken: message.feeToken, + feeTokenAmount: feeTokenAmount, + feeValueJuels: feeValueJuels, + tokenAmounts: new Internal.EVM2AnyTokenTransfer[](message.tokenAmounts.length) + }); + + for (uint256 i = 0; i < message.tokenAmounts.length; ++i) { + messageEvent.tokenAmounts[i] = + _getSourceTokenData(message.tokenAmounts[i], tokenAdminRegistry, DEST_CHAIN_SELECTOR); + } + + messageEvent.header.messageId = Internal._hash(messageEvent, metadataHash); + return messageEvent; + } + function _generateDynamicOnRampConfig( address feeQuoter ) internal pure returns (OnRamp.DynamicConfig memory) { @@ -117,17 +140,6 @@ contract OnRampSetup is FeeQuoterFeeSetup { }); } - // Slicing is only available for calldata. So we have to build a new bytes array. - function _removeFirst4Bytes( - bytes memory data - ) internal pure returns (bytes memory) { - bytes memory result = new bytes(data.length - 4); - for (uint256 i = 4; i < data.length; ++i) { - result[i - 4] = data[i]; - } - return result; - } - function _generateDestChainConfigArgs( IRouter router ) internal pure returns (OnRamp.DestChainConfigArgs[] memory) { @@ -166,35 +178,4 @@ contract OnRampSetup is FeeQuoterFeeSetup { keccak256(abi.encode(Internal.EVM_2_ANY_MESSAGE_HASH, sourceChainSelector, DEST_CHAIN_SELECTOR, address(onRamp))) ); } - - function _enableOutboundMessageInterceptor() internal { - (, address msgSender,) = vm.readCallers(); - - bool resetPrank = false; - - if (msgSender != OWNER) { - vm.stopPrank(); - vm.startPrank(OWNER); - resetPrank = true; - } - - OnRamp.DynamicConfig memory dynamicConfig = s_onRamp.getDynamicConfig(); - dynamicConfig.messageInterceptor = address(s_outboundMessageInterceptor); - s_onRamp.setDynamicConfig(dynamicConfig); - - if (resetPrank) { - vm.stopPrank(); - vm.startPrank(msgSender); - } - } - - function _assertStaticConfigsEqual(OnRamp.StaticConfig memory a, OnRamp.StaticConfig memory b) internal pure { - assertEq(a.chainSelector, b.chainSelector); - assertEq(address(a.rmnRemote), address(b.rmnRemote)); - assertEq(a.tokenAdminRegistry, b.tokenAdminRegistry); - } - - function _assertDynamicConfigsEqual(OnRamp.DynamicConfig memory a, OnRamp.DynamicConfig memory b) internal pure { - assertEq(a.feeQuoter, b.feeQuoter); - } } diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol index 98ef26aeb08..ce1104246dd 100644 --- a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol @@ -29,7 +29,7 @@ contract LockReleaseTokenPoolSetup is RouterSetup { new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); s_allowedList.push(USER_1); - s_allowedList.push(DUMMY_CONTRACT_ADDRESS); + s_allowedList.push(OWNER); s_lockReleaseTokenPoolWithAllowList = new LockReleaseTokenPool(s_token, s_allowedList, address(s_mockRMN), true, address(s_sourceRouter)); diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol index f2408af0fca..d441c11c352 100644 --- a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol @@ -11,7 +11,7 @@ contract TokenPoolWithAllowListSetup is TokenPoolSetup { TokenPoolSetup.setUp(); s_allowedSenders.push(STRANGER); - s_allowedSenders.push(DUMMY_CONTRACT_ADDRESS); + s_allowedSenders.push(OWNER); s_tokenPool = new TokenPoolHelper(s_token, s_allowedSenders, address(s_mockRMN), address(s_sourceRouter)); } diff --git a/contracts/src/v0.8/ccip/test/router/Router.t.sol b/contracts/src/v0.8/ccip/test/router/Router.t.sol index a0fdfc3c2aa..9b2bb92b4e4 100644 --- a/contracts/src/v0.8/ccip/test/router/Router.t.sol +++ b/contracts/src/v0.8/ccip/test/router/Router.t.sol @@ -12,7 +12,7 @@ import {Internal} from "../../libraries/Internal.sol"; import {OnRamp} from "../../onRamp/OnRamp.sol"; import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {RouterSetup} from "../router/RouterSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";