diff --git a/contracts/gas-snapshots/llo-feeds.gas-snapshot b/contracts/gas-snapshots/llo-feeds.gas-snapshot index 553e00bc84..de8dbd6c2e 100644 --- a/contracts/gas-snapshots/llo-feeds.gas-snapshot +++ b/contracts/gas-snapshots/llo-feeds.gas-snapshot @@ -619,9 +619,9 @@ VerificationdeactivateConfigWhenThereAreMultipleDigestsTest:test_correctlyUnsets VerificationdeactivateConfigWhenThereAreMultipleDigestsTest:test_revertsIfCalledByNonOwner() (gas: 15016) VerificationdeactivateConfigWhenThereAreMultipleDigestsTest:test_revertsIfRemovingAnEmptyDigest() (gas: 10907) VerificationdeactivateConfigWhenThereAreMultipleDigestsTest:test_revertsIfRemovingAnNonExistentDigest() (gas: 13381) -VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfCalledByNonOwner() (gas: 14977) -VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfRemovingAnEmptyDigest() (gas: 10891) -VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfRemovingAnNonExistentDigest() (gas: 13195) +VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfCalledByNonOwner() (gas: 14955) +VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfRemovingAnEmptyDigest() (gas: 10869) +VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05:test_revertsIfRemovingAnNonExistentDigest() (gas: 13173) VerifierActivateConfigTest:test_revertsIfDigestIsEmpty() (gas: 10962) VerifierActivateConfigTest:test_revertsIfDigestNotSet() (gas: 13394) VerifierActivateConfigTest:test_revertsIfNotOwner() (gas: 17182) @@ -629,7 +629,7 @@ VerifierActivateConfigTestV05:test_revertsIfDigestIsEmpty() (gas: 10903) VerifierActivateConfigTestV05:test_revertsIfDigestNotSet() (gas: 13154) VerifierActivateConfigTestV05:test_revertsIfNotOwner() (gas: 17123) VerifierActivateConfigWithDeactivatedConfigTest:test_allowsVerification() (gas: 97157) -VerifierActivateConfigWithDeactivatedConfigTestV05:test_allowsVerification() (gas: 94182) +VerifierActivateConfigWithDeactivatedConfigTestV05:test_allowsVerification() (gas: 94328) VerifierActivateFeedTest:test_revertsIfNoFeedExistsActivate() (gas: 13179) VerifierActivateFeedTest:test_revertsIfNoFeedExistsDeactivate() (gas: 13202) VerifierActivateFeedTest:test_revertsIfNotOwnerActivateFeed() (gas: 17109) @@ -647,16 +647,16 @@ VerifierBulkVerifyBillingReport:test_verifyWithBulkLink() (gas: 557422) VerifierBulkVerifyBillingReport:test_verifyWithBulkNative() (gas: 560718) VerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrapped() (gas: 568541) VerifierBulkVerifyBillingReport:test_verifyWithBulkNativeUnwrappedReturnsChange() (gas: 575547) -VerifierBulkVerifyBillingReportV05:test_verifyMultiVersions() (gas: 473554) -VerifierBulkVerifyBillingReportV05:test_verifyMultiVersionsReturnsVerifiedReports() (gas: 471856) -VerifierBulkVerifyBillingReportV05:test_verifyWithBulkLink() (gas: 556446) -VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNative() (gas: 559742) -VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNativeUnwrapped() (gas: 567565) -VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNativeUnwrappedReturnsChange() (gas: 574667) +VerifierBulkVerifyBillingReportV05:test_verifyMultiVersions() (gas: 474042) +VerifierBulkVerifyBillingReportV05:test_verifyMultiVersionsReturnsVerifiedReports() (gas: 472344) +VerifierBulkVerifyBillingReportV05:test_verifyWithBulkLink() (gas: 557226) +VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNative() (gas: 560522) +VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNativeUnwrapped() (gas: 568345) +VerifierBulkVerifyBillingReportV05:test_verifyWithBulkNativeUnwrappedReturnsChange() (gas: 575447) VerifierConstructorTest:test_revertsIfInitializedWithEmptyVerifierProxy() (gas: 59960) VerifierConstructorTest:test_setsTheCorrectProperties() (gas: 1813269) -VerifierConstructorTestV05:test_revertsIfInitializedWithEmptyVerifierProxy() (gas: 59657) -VerifierConstructorTestV05:test_setsTheCorrectProperties() (gas: 1530840) +VerifierConstructorTestV05:test_revertsIfInitializedWithEmptyVerifierProxy() (gas: 59692) +VerifierConstructorTestV05:test_setsTheCorrectProperties() (gas: 1561892) VerifierDeactivateFeedWithVerifyTest:test_currentReportAllowsVerification() (gas: 192007) VerifierDeactivateFeedWithVerifyTest:test_currentReportFailsVerification() (gas: 113322) VerifierDeactivateFeedWithVerifyTest:test_previousReportAllowsVerification() (gas: 99651) @@ -664,14 +664,14 @@ VerifierDeactivateFeedWithVerifyTest:test_previousReportFailsVerification() (gas VerifierInterfacesTest:test_DestinationContractInterfaces() (gas: 628097) VerifierProxyAccessControlledVerificationTest:test_proxiesToTheVerifierIfHasAccess() (gas: 208441) VerifierProxyAccessControlledVerificationTest:test_revertsIfNoAccess() (gas: 112257) -VerifierProxyAccessControlledVerificationTestV05:test_proxiesToTheVerifierIfHasAccess() (gas: 205961) +VerifierProxyAccessControlledVerificationTestV05:test_proxiesToTheVerifierIfHasAccess() (gas: 206107) VerifierProxyAccessControlledVerificationTestV05:test_revertsIfNoAccess() (gas: 112257) -VerifierProxyConstructorTest:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1485337) -VerifierProxyConstructorTest:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1485337) -VerifierProxyConstructorTest:test_correctlySetsTheOwner() (gas: 1465483) -VerifierProxyConstructorTest:test_correctlySetsTheOwner() (gas: 1465483) -VerifierProxyConstructorTest:test_correctlySetsVersion() (gas: 9767) +VerifierProxyConstructorTest:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1715654) +VerifierProxyConstructorTest:test_correctlySetsTheOwner() (gas: 1695712) VerifierProxyConstructorTest:test_correctlySetsVersion() (gas: 9767) +VerifierProxyConstructorTestV03:test_correctlySetsTheCorrectAccessControllerInterface() (gas: 1485337) +VerifierProxyConstructorTestV03:test_correctlySetsTheOwner() (gas: 1465483) +VerifierProxyConstructorTestV03:test_correctlySetsVersion() (gas: 9767) VerifierProxyInitializeVerifierTest:test_revertsIfDigestAlreadySet() (gas: 54117) VerifierProxyInitializeVerifierTest:test_revertsIfNotCorrectVerifier() (gas: 13613) VerifierProxyInitializeVerifierTest:test_revertsIfNotOwner() (gas: 17168) @@ -681,34 +681,34 @@ VerifierProxyInitializeVerifierTest:test_setFeeManagerWhichDoesntHonourIERC165In VerifierProxyInitializeVerifierTest:test_setFeeManagerWhichDoesntHonourInterface() (gas: 16290) VerifierProxyInitializeVerifierTest:test_setFeeManagerZeroAddress() (gas: 10911) VerifierProxyInitializeVerifierTest:test_updatesVerifierIfVerifier() (gas: 54086) -VerifierProxyInitializeVerifierTestV05:test_revertsIfDigestAlreadySet() (gas: 48384) -VerifierProxyInitializeVerifierTestV05:test_revertsIfNotCorrectVerifier() (gas: 13613) -VerifierProxyInitializeVerifierTestV05:test_revertsIfNotOwner() (gas: 17168) -VerifierProxyInitializeVerifierTestV05:test_revertsIfVerifierAlreadyInitialized() (gas: 42053) -VerifierProxyInitializeVerifierTestV05:test_revertsIfZeroAddress() (gas: 10956) +VerifierProxyInitializeVerifierTestV05:test_revertsIfDigestAlreadySet() (gas: 48428) +VerifierProxyInitializeVerifierTestV05:test_revertsIfNotCorrectVerifier() (gas: 13635) +VerifierProxyInitializeVerifierTestV05:test_revertsIfNotOwner() (gas: 17190) +VerifierProxyInitializeVerifierTestV05:test_revertsIfVerifierAlreadyInitialized() (gas: 42097) +VerifierProxyInitializeVerifierTestV05:test_revertsIfZeroAddress() (gas: 10978) VerifierProxyInitializeVerifierTestV05:test_setFeeManagerWhichDoesntHonourIERC165Interface() (gas: 13884) VerifierProxyInitializeVerifierTestV05:test_setFeeManagerWhichDoesntHonourInterface() (gas: 16290) VerifierProxyInitializeVerifierTestV05:test_setFeeManagerZeroAddress() (gas: 10911) -VerifierProxyInitializeVerifierTestV05:test_updatesVerifierIfVerifier() (gas: 48356) -VerifierProxySetAccessControllerTest:test_emitsTheCorrectEvent() (gas: 35348) +VerifierProxyInitializeVerifierTestV05:test_updatesVerifierIfVerifier() (gas: 48422) VerifierProxySetAccessControllerTest:test_emitsTheCorrectEvent() (gas: 35348) VerifierProxySetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 15025) -VerifierProxySetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 15025) -VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessController() (gas: 34921) -VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessController() (gas: 34921) -VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15065) -VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15065) -VerifierProxyUnsetVerifierTest:test_revertsIfDigestDoesNotExist() (gas: 13149) -VerifierProxyUnsetVerifierTest:test_revertsIfDigestDoesNotExist() (gas: 13149) -VerifierProxyUnsetVerifierTest:test_revertsIfNotAdmin() (gas: 14973) -VerifierProxyUnsetVerifierTest:test_revertsIfNotAdmin() (gas: 14973) -VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_correctlyUnsetsVerifier() (gas: 15555) -VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_correctlyUnsetsVerifier() (gas: 15555) -VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_emitsAnEventAfterUnsettingVerifier() (gas: 17985) -VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_emitsAnEventAfterUnsettingVerifier() (gas: 17985) +VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessController() (gas: 34943) +VerifierProxySetAccessControllerTest:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15087) +VerifierProxySetAccessControllerTestV03:test_emitsTheCorrectEvent() (gas: 35348) +VerifierProxySetAccessControllerTestV03:test_revertsIfCalledByNonOwner() (gas: 15025) +VerifierProxySetAccessControllerTestV03:test_successfullySetsNewAccessController() (gas: 34921) +VerifierProxySetAccessControllerTestV03:test_successfullySetsNewAccessControllerIsEmpty() (gas: 15065) +VerifierProxyUnsetVerifierTest:test_revertsIfDigestDoesNotExist() (gas: 13171) +VerifierProxyUnsetVerifierTest:test_revertsIfNotAdmin() (gas: 14995) +VerifierProxyUnsetVerifierTestV03:test_revertsIfDigestDoesNotExist() (gas: 13149) +VerifierProxyUnsetVerifierTestV03:test_revertsIfNotAdmin() (gas: 14973) +VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_correctlyUnsetsVerifier() (gas: 15599) +VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest:test_emitsAnEventAfterUnsettingVerifier() (gas: 18007) +VerifierProxyUnsetVerifierWithPreviouslySetVerifierTestV03:test_correctlyUnsetsVerifier() (gas: 15555) +VerifierProxyUnsetVerifierWithPreviouslySetVerifierTestV03:test_emitsAnEventAfterUnsettingVerifier() (gas: 17985) VerifierProxyVerifyTest:test_proxiesToTheCorrectVerifier() (gas: 204276) VerifierProxyVerifyTest:test_revertsIfNoVerifierConfigured() (gas: 117265) -VerifierProxyVerifyTestV05:test_proxiesToTheCorrectVerifier() (gas: 201796) +VerifierProxyVerifyTestV05:test_proxiesToTheCorrectVerifier() (gas: 201942) VerifierProxyVerifyTestV05:test_revertsIfNoVerifierConfigured() (gas: 117265) VerifierSetAccessControllerTest:test_revertsIfCalledByNonOwner() (gas: 17152) VerifierSetAccessControllerTest:test_setFeeManagerWhichDoesntHonourInterface() (gas: 16272) @@ -724,19 +724,19 @@ VerifierSetConfigTest:test_revertsIfFaultToleranceIsZero() (gas: 176543) VerifierSetConfigTest:test_revertsIfNotEnoughSigners() (gas: 15842) VerifierSetConfigTest:test_revertsIfSetWithTooManySigners() (gas: 22182) VerifierSetConfigTest:test_revertsIfSignerContainsZeroAddress() (gas: 228025) -VerifierSetConfigTestV05:test_correctlyUpdatesTheConfig() (gas: 1022171) -VerifierSetConfigTestV05:test_revertsIfCalledByNonOwner() (gas: 213898) -VerifierSetConfigTestV05:test_revertsIfDuplicateSigners() (gas: 243789) -VerifierSetConfigTestV05:test_revertsIfFaultToleranceIsZero() (gas: 207455) -VerifierSetConfigTestV05:test_revertsIfNotEnoughSigners() (gas: 16407) -VerifierSetConfigTestV05:test_revertsIfSetWithTooManySigners() (gas: 41461) -VerifierSetConfigTestV05:test_revertsIfSignerContainsZeroAddress() (gas: 220308) +VerifierSetConfigTestV05:test_correctlyUpdatesTheConfig() (gas: 1022317) +VerifierSetConfigTestV05:test_revertsIfCalledByNonOwner() (gas: 213876) +VerifierSetConfigTestV05:test_revertsIfDuplicateSigners() (gas: 243775) +VerifierSetConfigTestV05:test_revertsIfFaultToleranceIsZero() (gas: 207433) +VerifierSetConfigTestV05:test_revertsIfNotEnoughSigners() (gas: 16385) +VerifierSetConfigTestV05:test_revertsIfSetWithTooManySigners() (gas: 41439) +VerifierSetConfigTestV05:test_revertsIfSignerContainsZeroAddress() (gas: 220290) VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_configDigestMatchesConfiguratorDigest() (gas: 584372) -VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlySetsConfigWhenDigestsAreRemoved() (gas: 527267) -VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() (gas: 961409) -VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlyUpdatesTheDigestInTheProxy() (gas: 519967) -VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_incrementalConfigUpdates() (gas: 1424440) -VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_revertsIfDuplicateConfigIsSet() (gas: 545708) +VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlySetsConfigWhenDigestsAreRemoved() (gas: 527305) +VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() (gas: 961573) +VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_correctlyUpdatesTheDigestInTheProxy() (gas: 520049) +VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_incrementalConfigUpdates() (gas: 1424620) +VerifierSetConfigWhenThereAreMultipleDigestsTest05:test_revertsIfDuplicateConfigIsSet() (gas: 545746) VerifierSetConfigWhenThereAreMultipleDigestsTest:test_correctlySetsConfigWhenDigestsAreRemoved() (gas: 542029) VerifierSetConfigWhenThereAreMultipleDigestsTest:test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() (gas: 967324) VerifierSetConfigWhenThereAreMultipleDigestsTest:test_correctlyUpdatesTheDigestInTheProxy() (gas: 522969) @@ -748,17 +748,17 @@ VerifierTestBillingReport:test_verifyWithLink() (gas: 275196) VerifierTestBillingReport:test_verifyWithNative() (gas: 316205) VerifierTestBillingReport:test_verifyWithNativeUnwrapped() (gas: 318520) VerifierTestBillingReport:test_verifyWithNativeUnwrappedReturnsChange() (gas: 325543) -VerifierTestBillingReportV05:test_verifyWithLink() (gas: 275000) -VerifierTestBillingReportV05:test_verifyWithNative() (gas: 316009) -VerifierTestBillingReportV05:test_verifyWithNativeUnwrapped() (gas: 318324) -VerifierTestBillingReportV05:test_verifyWithNativeUnwrappedReturnsChange() (gas: 325443) -VerifierUpdateConfigTest:test_updateConfig() (gas: 673580) -VerifierUpdateConfigTest:test_updateConfigRevertsIfCalledByNonOwner() (gas: 505312) -VerifierUpdateConfigTest:test_updateConfigRevertsIfDigestNotSet() (gas: 92340) -VerifierUpdateConfigTest:test_updateConfigRevertsIfFIsZero() (gas: 500500) -VerifierUpdateConfigTest:test_updateConfigRevertsIfFTooHigh() (gas: 501433) -VerifierUpdateConfigTest:test_updateConfigRevertsIfPrevSignersLengthMismatch() (gas: 526466) -VerifierUpdateConfigTest:test_updateConfigWithDifferentSigners() (gas: 803519) +VerifierTestBillingReportV05:test_verifyWithLink() (gas: 275146) +VerifierTestBillingReportV05:test_verifyWithNative() (gas: 316155) +VerifierTestBillingReportV05:test_verifyWithNativeUnwrapped() (gas: 318470) +VerifierTestBillingReportV05:test_verifyWithNativeUnwrappedReturnsChange() (gas: 325589) +VerifierUpdateConfigTest:test_updateConfig() (gas: 673808) +VerifierUpdateConfigTest:test_updateConfigRevertsIfCalledByNonOwner() (gas: 505417) +VerifierUpdateConfigTest:test_updateConfigRevertsIfDigestNotSet() (gas: 92385) +VerifierUpdateConfigTest:test_updateConfigRevertsIfFIsZero() (gas: 500605) +VerifierUpdateConfigTest:test_updateConfigRevertsIfFTooHigh() (gas: 501538) +VerifierUpdateConfigTest:test_updateConfigRevertsIfPrevSignersLengthMismatch() (gas: 526571) +VerifierUpdateConfigTest:test_updateConfigWithDifferentSigners() (gas: 803763) VerifierVerifyBulkTest:test_revertsVerifyBulkIfNoAccess() (gas: 112799) VerifierVerifyBulkTest:test_verifyBulkSingleCaseWithSingleConfig() (gas: 745046) VerifierVerifyBulkTest:test_verifyBulkWithSingleConfigOneVerifyFails() (gas: 698191) @@ -766,12 +766,12 @@ VerifierVerifyMultipleConfigDigestTest:test_canVerifyNewerReportsWithNewerConfig VerifierVerifyMultipleConfigDigestTest:test_canVerifyOlderReportsWithOlderConfigs() (gas: 189777) VerifierVerifyMultipleConfigDigestTest:test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() (gas: 88180) VerifierVerifyMultipleConfigDigestTest:test_revertsIfVerifyingWithAnUnsetDigest() (gas: 128007) -VerifierVerifyMultipleConfigDigestTestV05:test_canVerifyNewerReportsWithNewerConfigs() (gas: 131468) -VerifierVerifyMultipleConfigDigestTestV05:test_canVerifyOlderReportsWithOlderConfigs() (gas: 187297) -VerifierVerifyMultipleConfigDigestTestV05:test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() (gas: 85862) -VerifierVerifyMultipleConfigDigestTestV05:test_revertsIfVerifyingWithAnUnsetDigest() (gas: 123010) -VerifierVerifyMultipleConfigDigestTestV05:test_verifyAfterConfigUpdate() (gas: 906084) -VerifierVerifyMultipleConfigDigestTestV05:test_verifyAfterConfigUpdateWithExistingSigners() (gas: 745612) +VerifierVerifyMultipleConfigDigestTestV05:test_canVerifyNewerReportsWithNewerConfigs() (gas: 131614) +VerifierVerifyMultipleConfigDigestTestV05:test_canVerifyOlderReportsWithOlderConfigs() (gas: 187443) +VerifierVerifyMultipleConfigDigestTestV05:test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() (gas: 85978) +VerifierVerifyMultipleConfigDigestTestV05:test_revertsIfVerifyingWithAnUnsetDigest() (gas: 123104) +VerifierVerifyMultipleConfigDigestTestV05:test_verifyAfterConfigUpdate() (gas: 906538) +VerifierVerifyMultipleConfigDigestTestV05:test_verifyAfterConfigUpdateWithExistingSigners() (gas: 745956) VerifierVerifySingleConfigDigestTest:test_emitsAnEventIfReportVerified() (gas: 186868) VerifierVerifySingleConfigDigestTest:test_returnsThePriceAndBlockNumIfReportVerified() (gas: 189759) VerifierVerifySingleConfigDigestTest:test_revertsIfConfigDigestNotSet() (gas: 116096) @@ -782,15 +782,15 @@ VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedByNonProxy() (gas: 10 VerifierVerifySingleConfigDigestTest:test_revertsIfVerifiedWithIncorrectAddresses() (gas: 184034) VerifierVerifySingleConfigDigestTest:test_revertsIfWrongNumberOfSigners() (gas: 109982) VerifierVerifySingleConfigDigestTest:test_setsTheCorrectEpoch() (gas: 194549) -VerifierVerifySingleConfigDigestTestV05:test_emitsAnEventIfReportVerified() (gas: 184421) -VerifierVerifySingleConfigDigestTestV05:test_returnsThePriceAndBlockNumIfReportVerified() (gas: 187312) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfConfigDigestNotSet() (gas: 113634) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfDuplicateSignersHaveSigned() (gas: 179714) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfMismatchedSignatureLength() (gas: 50778) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfReportHasUnconfiguredConfigDigest() (gas: 104517) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfVerifiedByNonProxy() (gas: 100881) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfVerifiedWithIncorrectAddresses() (gas: 181443) -VerifierVerifySingleConfigDigestTestV05:test_revertsIfWrongNumberOfSigners() (gas: 107652) +VerifierVerifySingleConfigDigestTestV05:test_emitsAnEventIfReportVerified() (gas: 184567) +VerifierVerifySingleConfigDigestTestV05:test_returnsThePriceAndBlockNumIfReportVerified() (gas: 187458) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfConfigDigestNotSet() (gas: 113750) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfDuplicateSignersHaveSigned() (gas: 179830) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfMismatchedSignatureLength() (gas: 50894) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfReportHasUnconfiguredConfigDigest() (gas: 104633) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfVerifiedByNonProxy() (gas: 100997) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfVerifiedWithIncorrectAddresses() (gas: 181559) +VerifierVerifySingleConfigDigestTestV05:test_revertsIfWrongNumberOfSigners() (gas: 107768) VerifierVerifyTest:test_canVerifyNewerReportsWithNewerConfigs() (gas: 862929) VerifierVerifyTest:test_canVerifyOlderV3ReportsWithOlderConfigs() (gas: 815824) VerifierVerifyTest:test_failToVerifyReportIfDupSigners() (gas: 450716) @@ -804,18 +804,18 @@ VerifierVerifyTest:test_verifyFailsWhenReportIsOlderThanConfig() (gas: 2303123) VerifierVerifyTest:test_verifyReport() (gas: 1434593) VerifierVerifyTest:test_verifyTooglingActiveFlagsDonConfigs() (gas: 1918572) Verifier_accessControlledVerify:testVerifyWithAccessControl_gas() (gas: 212077) -Verifier_accessControlledVerifyV05:testVerifyWithAccessControl_gas() (gas: 209597) +Verifier_accessControlledVerifyV05:testVerifyWithAccessControl_gas() (gas: 209743) Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithLinkFeeSuccess_gas() (gas: 519313) Verifier_bulkVerifyWithFee:testBulkVerifyProxyWithNativeFeeSuccess_gas() (gas: 542709) -Verifier_bulkVerifyWithFeeV05:testBulkVerifyProxyWithLinkFeeSuccess_gas() (gas: 518345) -Verifier_bulkVerifyWithFeeV05:testBulkVerifyProxyWithNativeFeeSuccess_gas() (gas: 541741) +Verifier_bulkVerifyWithFeeV05:testBulkVerifyProxyWithLinkFeeSuccess_gas() (gas: 519125) +Verifier_bulkVerifyWithFeeV05:testBulkVerifyProxyWithNativeFeeSuccess_gas() (gas: 542521) Verifier_setConfig:testSetConfigSuccess_gas() (gas: 922625) -Verifier_setConfigV05:testSetConfigSuccess_gas() (gas: 895165) +Verifier_setConfigV05:testSetConfigSuccess_gas() (gas: 895289) Verifier_verify:testVerifyProxySuccess_gas() (gas: 198742) Verifier_verify:testVerifySuccess_gas() (gas: 186736) -Verifier_verifyV05:testVerifyProxySuccess_gas() (gas: 196262) -Verifier_verifyV05:testVerifySuccess_gas() (gas: 184256) +Verifier_verifyV05:testVerifyProxySuccess_gas() (gas: 196408) +Verifier_verifyV05:testVerifySuccess_gas() (gas: 184402) Verifier_verifyWithFee:testVerifyProxyWithLinkFeeSuccess_gas() (gas: 238823) Verifier_verifyWithFee:testVerifyProxyWithNativeFeeSuccess_gas() (gas: 257300) -Verifier_verifyWithFeeV05:testVerifyProxyWithLinkFeeSuccess_gas() (gas: 238627) -Verifier_verifyWithFeeV05:testVerifyProxyWithNativeFeeSuccess_gas() (gas: 257104) \ No newline at end of file +Verifier_verifyWithFeeV05:testVerifyProxyWithLinkFeeSuccess_gas() (gas: 238773) +Verifier_verifyWithFeeV05:testVerifyProxyWithNativeFeeSuccess_gas() (gas: 257250) \ No newline at end of file diff --git a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol index c168274f65..81ff0a0bc9 100644 --- a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyConstructorTest.t.sol @@ -5,7 +5,7 @@ import {AccessControllerInterface} from "../../../../shared/interfaces/AccessCon import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; import {BaseTest} from "./BaseVerifierTest.t.sol"; -contract VerifierProxyConstructorTest is BaseTest { +contract VerifierProxyConstructorTestV03 is BaseTest { function test_correctlySetsTheOwner() public { VerifierProxy proxy = new VerifierProxy(AccessControllerInterface(address(0))); assertEq(proxy.owner(), ADMIN); diff --git a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol index f06d54c991..c59b69b5ee 100644 --- a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxySetAccessControllerTest.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; import {BaseTest} from "./BaseVerifierTest.t.sol"; -contract VerifierProxySetAccessControllerTest is BaseTest { +contract VerifierProxySetAccessControllerTestV03 is BaseTest { event AccessControllerSet(address oldAccessController, address newAccessController); function test_revertsIfCalledByNonOwner() public { diff --git a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol index e4aef6beef..6f0cf7a6d5 100644 --- a/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.3.0/test/verifier/VerifierProxyUnsetVerifierTest.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {VerifierProxy} from "../../../v0.3.0/VerifierProxy.sol"; import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; -contract VerifierProxyUnsetVerifierTest is BaseTest { +contract VerifierProxyUnsetVerifierTestV03 is BaseTest { function test_revertsIfNotAdmin() public { vm.expectRevert("Only callable by owner"); @@ -18,7 +18,7 @@ contract VerifierProxyUnsetVerifierTest is BaseTest { } } -contract VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager { +contract VerifierProxyUnsetVerifierWithPreviouslySetVerifierTestV03 is BaseTestWithConfiguredVerifierAndFeeManager { bytes32 internal s_configDigest; event VerifierUnset(bytes32 configDigest, address verifierAddr); diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/NoOpFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/NoOpFeeManager.sol new file mode 100644 index 0000000000..2419898653 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/NoOpFeeManager.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {Common} from "../libraries/Common.sol"; +import {IFeeManager} from "./interfaces/IFeeManager.sol"; +import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +/** + * @title NoOpFeeManager + * @notice A no-op implementation of IFeeManager that does not collect fees. + * @dev All functions return successfully without performing any fee collection or state changes. + * Any ETH sent to payable functions is refunded to the subscriber. + */ +contract NoOpFeeManager is IFeeManager, ITypeAndVersion { + /// @notice Error thrown when ETH refund fails + error RefundFailed(); + + /// @notice The scalar representing 100% discount (1e18 = 100%) + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "NoOpFeeManager 0.5.0"; + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; + } + + /// @inheritdoc IVerifierFeeManager + function processFee(bytes calldata, bytes calldata, address subscriber) external payable override { + // Refund any ETH sent + _refund(subscriber); + } + + /// @inheritdoc IVerifierFeeManager + function processFeeBulk(bytes[] calldata, bytes calldata, address subscriber) external payable override { + // Refund any ETH sent + _refund(subscriber); + } + + /// @inheritdoc IVerifierFeeManager + function setFeeRecipients(bytes32, Common.AddressAndWeight[] calldata) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function getFeeAndReward( + address, + bytes memory, + address + ) external pure override returns (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) { + // Return zero fee, zero reward, 100% discount (1e18) to indicate no fees are charged + return (fee, reward, PERCENTAGE_SCALAR); + } + + /// @inheritdoc IFeeManager + function setNativeSurcharge( + uint64 + ) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function updateSubscriberDiscount(address, bytes32, address, uint64) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function withdraw(address, address, uint192) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function linkAvailableForPayment() external pure override returns (uint256) { + return 0; + } + + /// @inheritdoc IFeeManager + function payLinkDeficit( + bytes32 + ) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function updateSubscriberGlobalDiscount(address, address, uint64) external override { + // No-op + } + + /** + * @notice Returns 100% discount for any subscriber/feedId/token combination + * @dev Replicates public mapping getter signature from FeeManager for backwards compatibility + */ + // solhint-disable-next-line func-name-mixedcase + function s_subscriberDiscounts(address, bytes32, address) external pure returns (uint256) { + return PERCENTAGE_SCALAR; + } + + /** + * @notice Returns 100% discount for any subscriber/token combination + * @dev Replicates public mapping getter signature from FeeManager for backwards compatibility + */ + // solhint-disable-next-line func-name-mixedcase + function s_globalDiscounts(address, address) external pure returns (uint256) { + return PERCENTAGE_SCALAR; + } + + /** + * @notice Refunds any ETH sent to the contract + * @param recipient The address to refund ETH to + */ + function _refund( + address recipient + ) internal { + if (msg.value > 0) { + (bool success,) = payable(recipient).call{value: msg.value}(""); + if (!success) revert RefundFailed(); + } + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol index 5720ec9f5f..a17f4ed497 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/ChannelConfigStore.sol @@ -1,27 +1,144 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity 0.8.19; import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {EnumerableSet} from "@openzeppelin/contracts@4.8.3/utils/structs/EnumerableSet.sol"; import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; import {IChannelConfigStore} from "./interfaces/IChannelConfigStore.sol"; contract ChannelConfigStore is ConfirmedOwner, IChannelConfigStore, ITypeAndVersion { + // This contract uses uint32 for donIds when they are function arguments and + // uint256 elsewhere (e.g. in storage or event params). This inconsistency is + // ugly, but we maintain it for backwards compatibility. + + using EnumerableSet for EnumerableSet.UintSet; + event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + event ChannelDefinitionAdded(uint256 indexed donId, ChannelAdderId indexed channelAdderId, string url, bytes32 sha); + event ChannelAdderSet(uint256 indexed donId, ChannelAdderId indexed channelAdderId, bool allowed); + event ChannelAdderAddressSet(ChannelAdderId indexed channelAdderId, address adderAddress); + + /// @notice Thrown when a caller is not authorized to add channel definitions. + error UnauthorizedChannelAdder(); + + /// @notice Thrown when a ChannelAdderId is reserved. + error ReservedChannelAdderId(); + + // We reserve the ChannelAdderIds 0 through 999. 1 is used by the offchain code to internally + // represent the owner. The others are reserved for future use. + ChannelAdderId internal constant MIN_CHANNEL_ADDER_ID = ChannelAdderId.wrap(1000); constructor() ConfirmedOwner(msg.sender) {} - /// @notice The version of a channel definition keyed by DON ID - // Increments by 1 on every update + /// @notice The version of a channel definition keyed by DON ID. + // Increments by 1 on every update. mapping(uint256 => uint256) internal s_channelDefinitionVersions; + /// @notice Mapping from channel adder ID to its corresponding address + mapping(ChannelAdderId => address) internal s_channelAdderAddresses; + + /// @notice Mapping from DON ID to the set of allowed channel adder IDs + mapping(uint256 => EnumerableSet.UintSet) internal s_allowedChannelAdders; + + /// @notice Allows the owner to arbitrarily set channel definitions to the specified DON. + /// Unlike the channel adder, the owner can not only add, but also modify and delete + /// channel definitions. The DON enforces (in its consensus rules), that the channel + /// definitions provided by the owner are well-formed. + /// @param donId The DON ID + /// @param url The URL of the channel definition + /// @param sha The SHA hash of the channel definition function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external onlyOwner { uint32 newVersion = uint32(++s_channelDefinitionVersions[uint256(donId)]); emit NewChannelDefinition(donId, newVersion, url, sha); } + /// @notice Allows a channel adder to add channel definitions to the specified DON. + /// The DON enforces (in its consensus rules), that the channel definitions provided + /// by the channel adder are well-formed, purely additive, and do not overload the DON. + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @param url The URL of the channel definition + /// @param sha The SHA hash of the channel definition + function addChannelDefinitions( + uint32 donId, + ChannelAdderId channelAdderId, + string calldata url, + bytes32 sha + ) external { + if (msg.sender != s_channelAdderAddresses[channelAdderId]) { + revert UnauthorizedChannelAdder(); + } + if (!s_allowedChannelAdders[donId].contains(ChannelAdderId.unwrap(channelAdderId))) { + revert UnauthorizedChannelAdder(); + } + emit ChannelDefinitionAdded(donId, channelAdderId, url, sha); + } + + /// @notice Sets the address for a channel adder ID + /// @param channelAdderId The channel adder ID + /// @param adderAddress The address to associate with the channel adder ID. + /// Set this to the zero address (or some other address that cannot make + /// calls) to disable the channel adder. + function setChannelAdderAddress(ChannelAdderId channelAdderId, address adderAddress) external onlyOwner { + if (ChannelAdderId.unwrap(channelAdderId) < ChannelAdderId.unwrap(MIN_CHANNEL_ADDER_ID)) { + revert ReservedChannelAdderId(); + } + s_channelAdderAddresses[channelAdderId] = adderAddress; + emit ChannelAdderAddressSet(channelAdderId, adderAddress); + } + + /// @notice Sets whether a channel adder ID is allowed for a DON + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @param allowed Whether the channel adder should be allowed or removed + function setChannelAdder(uint32 donId, ChannelAdderId channelAdderId, bool allowed) external onlyOwner { + if (ChannelAdderId.unwrap(channelAdderId) < ChannelAdderId.unwrap(MIN_CHANNEL_ADDER_ID)) { + revert ReservedChannelAdderId(); + } + if (allowed) { + s_allowedChannelAdders[donId].add(ChannelAdderId.unwrap(channelAdderId)); + } else { + s_allowedChannelAdders[donId].remove(ChannelAdderId.unwrap(channelAdderId)); + } + emit ChannelAdderSet(donId, channelAdderId, allowed); + } + + /// @notice Gets the address associated with a channel adder ID + /// @param channelAdderId The channel adder ID + /// @return The address associated with the channel adder ID + function getChannelAdderAddress( + ChannelAdderId channelAdderId + ) external view returns (address) { + return s_channelAdderAddresses[channelAdderId]; + } + + /// @notice Checks if a channel adder is allowed for a DON + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @return True if the channel adder is allowed for the DON + function isChannelAdderAllowed(uint32 donId, ChannelAdderId channelAdderId) external view returns (bool) { + return s_allowedChannelAdders[donId].contains(ChannelAdderId.unwrap(channelAdderId)); + } + + /// @notice Gets all allowed channel adder IDs for a DON + /// @param donId The DON ID + /// @return allowedChannelAdderIds An array of allowed channel adder IDs + function getAllowedChannelAdders( + uint32 donId + ) external view returns (ChannelAdderId[] memory allowedChannelAdderIds) { + // Not very gas efficient, but we don't expect this function to be called + // from onchain anyways. + uint256[] memory values = s_allowedChannelAdders[donId].values(); + allowedChannelAdderIds = new ChannelAdderId[](values.length); + for (uint256 i = 0; i < values.length; i++) { + allowedChannelAdderIds[i] = ChannelAdderId.wrap(uint32(values[i])); + } + return allowedChannelAdderIds; + } + function typeAndVersion() external pure override returns (string memory) { - return "ChannelConfigStore 0.0.1"; + return "ChannelConfigStore 1.0.0"; } function supportsInterface( diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol index a8ae2dc5b8..31998f25a3 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/interfaces/IChannelConfigStore.sol @@ -4,5 +4,22 @@ pragma solidity 0.8.19; import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; interface IChannelConfigStore is IERC165 { + type ChannelAdderId is uint32; + function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external; + function addChannelDefinitions( + uint32 donId, + ChannelAdderId channelAdderId, + string calldata url, + bytes32 sha + ) external; + function setChannelAdderAddress(ChannelAdderId channelAdderId, address adderAddress) external; + function setChannelAdder(uint32 donId, ChannelAdderId channelAdderId, bool allowed) external; + function getChannelAdderAddress( + ChannelAdderId channelAdderId + ) external view returns (address); + function isChannelAdderAllowed(uint32 donId, ChannelAdderId channelAdderId) external view returns (bool); + function getAllowedChannelAdders( + uint32 donId + ) external view returns (ChannelAdderId[] memory); } diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol index 7e64b8b793..4442341317 100644 --- a/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/configuration/test/ChannelConfigStore.t.sol @@ -15,13 +15,25 @@ contract ChannelConfigStoreTest is Test { ExposedChannelConfigStore public channelConfigStore; event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + event ChannelDefinitionAdded( + uint256 indexed donId, IChannelConfigStore.ChannelAdderId indexed channelAdderId, string url, bytes32 sha + ); + event ChannelAdderSet(uint256 indexed donId, IChannelConfigStore.ChannelAdderId indexed channelAdderId, bool allowed); + event ChannelAdderAddressSet(IChannelConfigStore.ChannelAdderId indexed channelAdderId, address adderAddress); + + address public constant CHANNEL_ADDER_1 = address(0x1234); + address public constant CHANNEL_ADDER_2 = address(0x5678); + IChannelConfigStore.ChannelAdderId public constant CHANNEL_ADDER_ID_1 = IChannelConfigStore.ChannelAdderId.wrap(1000); + IChannelConfigStore.ChannelAdderId public constant CHANNEL_ADDER_ID_2 = IChannelConfigStore.ChannelAdderId.wrap(1001); + uint32 public constant DON_ID_1 = 42; + uint32 public constant DON_ID_2 = 99; function setUp() public virtual { channelConfigStore = new ExposedChannelConfigStore(); } function testTypeAndVersion() public view { - assertEq(channelConfigStore.typeAndVersion(), "ChannelConfigStore 0.0.1"); + assertEq(channelConfigStore.typeAndVersion(), "ChannelConfigStore 1.0.0"); } function testSupportsInterface() public view { @@ -47,3 +59,295 @@ contract ChannelConfigStoreTest is Test { assertEq(channelConfigStore.exposedReadChannelDefinitionStates(42), uint32(2)); } } + +/** + * @title ChannelConfigStoreChannelAdderTest + * @notice Test suite for channel adder functionality + */ +contract ChannelConfigStoreChannelAdderTest is ChannelConfigStoreTest { + function testSetChannelAdderAddress() public { + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_UpdatesExistingAddress() public { + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_2); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_2); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_2); + } + + function testSetChannelAdderAddress_CanSetToZeroAddress() public { + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, address(0)); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, address(0)); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), address(0)); + } + + function testSetChannelAdderAddress_RevertsWhenCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_RevertsForReservedChannelAdderId() public { + // Channel adder IDs 0-999 are reserved + IChannelConfigStore.ChannelAdderId reservedId = IChannelConfigStore.ChannelAdderId.wrap(0); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdderAddress(reservedId, CHANNEL_ADDER_1); + + reservedId = IChannelConfigStore.ChannelAdderId.wrap(999); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdderAddress(reservedId, CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_SucceedsAtMinimumAllowedId() public { + // Channel adder ID 1000 is the minimum allowed + IChannelConfigStore.ChannelAdderId minAllowedId = IChannelConfigStore.ChannelAdderId.wrap(1000); + channelConfigStore.setChannelAdderAddress(minAllowedId, CHANNEL_ADDER_1); + assertEq(channelConfigStore.getChannelAdderAddress(minAllowedId), CHANNEL_ADDER_1); + } + + function testSetChannelAdder_AllowsChannelAdder() public { + vm.expectEmit(); + emit ChannelAdderSet(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_RemovesChannelAdder() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + + vm.expectEmit(); + emit ChannelAdderSet(DON_ID_1, CHANNEL_ADDER_ID_1, false); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, false); + + assertFalse(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_AllowsMultipleAddersPerDon() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_2)); + + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 2); + + // Assert that allowedAdders contains both CHANNEL_ADDER_ID_1 and CHANNEL_ADDER_ID_2 (order agnostic) + assertTrue( + ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + ) + || ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + ) + ); + } + + function testSetChannelAdder_AdderCanBeAllowedOnMultipleDons() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_2, CHANNEL_ADDER_ID_1, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_2, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_RevertsWhenCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + } + + function testGetAllowedChannelAdders_ReturnsEmptyArrayWhenNone() public view { + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 0); + } + + function testGetAllowedChannelAdders_ReturnsAllAllowedAdders() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 2); + + // EnumerableSet doesn't guarantee order, so check both combinations + assertTrue( + ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + ) + || ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + ) + ); + } + + function testIsChannelAdderAllowed_ReturnsFalseForNonAllowedAdder() public view { + assertFalse(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testAddChannelDefinitions_SucceedsWithAuthorizedAdder() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenCallerNotMatchingChannelAdderId() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + // Try to call from wrong address + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_2); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenAdderNotAllowedForDon() public { + // Setup: set address but don't allow adder for this DON + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenNoAddressSetForChannelAdderId() public { + // Setup: allow adder but don't set address + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenAdderRemovedFromDon() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + // Verify it works + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + + // Remove adder + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, false); + + // Should now revert + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url2", keccak256("sha2")); + } + + function testAddChannelDefinitions_MultipleAddersCanAddToDon() public { + // Setup: set addresses and allow both adders + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_2, CHANNEL_ADDER_2); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + // Both should be able to add + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + + vm.prank(CHANNEL_ADDER_2); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + } + + function testAddChannelDefinitions_AdderCanAddToMultipleDons() public { + // Setup: set address and allow adder on multiple DONs + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_2, CHANNEL_ADDER_ID_1, true); + + // Should work for both DONs + vm.startPrank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + channelConfigStore.addChannelDefinitions(DON_ID_2, CHANNEL_ADDER_ID_1, "url2", keccak256("sha2")); + vm.stopPrank(); + } + + function testGetChannelAdderAddress_ReturnsZeroAddressForUnsetId() public view { + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), address(0)); + } + + function testAddChannelDefinitions_OneAddressCanControlMultipleChannelAdders() public { + // Setup: one address controls two different channel adder IDs + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_2, CHANNEL_ADDER_1); // same address + + // Allow both channel adder IDs for the DON + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + // Verify the same address can use both channel adder IDs + vm.startPrank(CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + + vm.stopPrank(); + + // Verify both channel adder IDs are allowed + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_2)); + } + + function testSetChannelAdder_RevertsForReservedChannelAdderId() public { + // Channel adder IDs 0-999 are reserved + IChannelConfigStore.ChannelAdderId reservedId = IChannelConfigStore.ChannelAdderId.wrap(0); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, true); + + reservedId = IChannelConfigStore.ChannelAdderId.wrap(999); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, true); + + // Should also revert when trying to remove a reserved ID + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, false); + } + + function testSetChannelAdder_SucceedsAtMinimumAllowedId() public { + // Channel adder ID 1000 is the minimum allowed + IChannelConfigStore.ChannelAdderId minAllowedId = IChannelConfigStore.ChannelAdderId.wrap(1000); + channelConfigStore.setChannelAdder(DON_ID_1, minAllowedId, true); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, minAllowedId)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.0/test/fee-manager/NoOpFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.0/test/fee-manager/NoOpFeeManager.t.sol new file mode 100644 index 0000000000..d97326bced --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.0/test/fee-manager/NoOpFeeManager.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; + +import {Common} from "../../../libraries/Common.sol"; +import {FeeManager} from "../../FeeManager.sol"; +import {NoOpFeeManager} from "../../NoOpFeeManager.sol"; +import {RewardManager} from "../../RewardManager.sol"; +import {IVerifierFeeManager} from "../../interfaces/IVerifierFeeManager.sol"; +import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +contract NoOpFeeManagerTest is Test { + NoOpFeeManager internal noOpFeeManager; + FeeManager internal feeManager; + RewardManager internal rewardManager; + FeeManagerProxy internal feeManagerProxy; + + ERC20Mock internal link; + WERC20Mock internal native; + + uint64 internal constant PERCENTAGE_SCALAR = 1e18; + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + address internal constant SUBSCRIBER = address(uint160(uint256(keccak256("SUBSCRIBER")))); + + function setUp() public { + vm.startPrank(ADMIN); + + link = new ERC20Mock(18); + native = new WERC20Mock(); + noOpFeeManager = new NoOpFeeManager(); + + // Deploy real FeeManager for comparison tests + feeManagerProxy = new FeeManagerProxy(); + rewardManager = new RewardManager(address(link)); + feeManager = new FeeManager(address(link), address(native), address(feeManagerProxy), address(rewardManager)); + feeManagerProxy.setFeeManager(feeManager); + rewardManager.setFeeManager(address(feeManager)); + + vm.stopPrank(); + } + + function test_typeAndVersion() public view { + assertEq(noOpFeeManager.typeAndVersion(), "NoOpFeeManager 0.5.0"); + } + + // VerifierProxy checks these interface support before accepting a fee manager + function test_supportsInterface() public view { + assertTrue(noOpFeeManager.supportsInterface(IVerifierFeeManager.processFee.selector)); + assertTrue(noOpFeeManager.supportsInterface(IVerifierFeeManager.processFeeBulk.selector)); + assertFalse(noOpFeeManager.supportsInterface(bytes4(0xdeadbeef))); + } + + // Some code queries these to check discount status - must always return 100% + function test_discountGettersReturn100Percent() public view { + bytes32 feedId = keccak256("ETH-USD"); + + assertEq(noOpFeeManager.s_globalDiscounts(SUBSCRIBER, address(link)), PERCENTAGE_SCALAR); + assertEq(noOpFeeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), PERCENTAGE_SCALAR); + } + + // Ensures our view functions match FeeManager's public mapping getter signatures + // so code can swap between implementations without breaking changes + function test_discountGettersMatchFeeManagerSignature() public view { + bytes32 feedId = keccak256("ETH-USD"); + + // FeeManager public mappings return 0 by default, NoOpFeeManager returns 100% + assertEq(feeManager.s_globalDiscounts(SUBSCRIBER, address(link)), 0); + assertEq(noOpFeeManager.s_globalDiscounts(SUBSCRIBER, address(link)), PERCENTAGE_SCALAR); + + assertEq(feeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), 0); + assertEq(noOpFeeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), PERCENTAGE_SCALAR); + } + + // Zero fees, zero rewards, 100% discount indicates no fees are charged + function test_getFeeAndReward() public view { + bytes memory report = abi.encode(bytes32(0), uint32(0), int192(0)); + (Common.Asset memory fee, Common.Asset memory reward, uint256 discount) = + noOpFeeManager.getFeeAndReward(SUBSCRIBER, report, address(link)); + + assertEq(fee.amount, 0); + assertEq(reward.amount, 0); + assertEq(discount, PERCENTAGE_SCALAR); + } + + function test_linkAvailableForPayment() public view { + assertEq(noOpFeeManager.linkAvailableForPayment(), 0); + } + + // Admin functions are no-ops but must not revert to maintain interface compatibility + function test_adminFunctionsDoNotRevert() public { + bytes32 feedId = keccak256("ETH-USD"); + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](0); + + noOpFeeManager.setNativeSurcharge(uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.updateSubscriberDiscount(SUBSCRIBER, feedId, address(link), uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.updateSubscriberGlobalDiscount(SUBSCRIBER, address(link), uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.setFeeRecipients(feedId, recipients); + noOpFeeManager.withdraw(address(link), ADMIN, 1000); + noOpFeeManager.payLinkDeficit(feedId); + } + + // ETH sent during verification must be refunded since no fees are collected + function test_processFeeRefundsETH() public { + uint256 ethAmount = 1 ether; + vm.deal(address(this), ethAmount); + + uint256 balanceBefore = SUBSCRIBER.balance; + noOpFeeManager.processFee{value: ethAmount}("", "", SUBSCRIBER); + + assertEq(SUBSCRIBER.balance - balanceBefore, ethAmount); + } + + function test_processFeeBulkRefundsETH() public { + uint256 ethAmount = 1 ether; + vm.deal(address(this), ethAmount); + bytes[] memory payloads = new bytes[](2); + + uint256 balanceBefore = SUBSCRIBER.balance; + noOpFeeManager.processFeeBulk{value: ethAmount}(payloads, "", SUBSCRIBER); + + assertEq(SUBSCRIBER.balance - balanceBefore, ethAmount); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/FeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/FeeManager.sol new file mode 100644 index 0000000000..cf875d19f8 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/FeeManager.sol @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; + +import {IWERC20} from "../../shared/interfaces/IWERC20.sol"; +import {Common} from "../libraries/Common.sol"; +import {IFeeManager} from "./interfaces/IFeeManager.sol"; +import {IRewardManager} from "./interfaces/IRewardManager.sol"; + +import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; +import {IERC20} from "@openzeppelin/contracts@4.8.3/interfaces/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts@4.8.3/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@openzeppelin/contracts@4.8.3/utils/math/Math.sol"; + +/** + * @title FeeManager + * @author Michael Fletcher + * @author Austin Born + * @notice This contract is used for the handling of fees required for users verifying reports. + */ +contract FeeManager is IFeeManager, ConfirmedOwner, ITypeAndVersion { + using SafeERC20 for IERC20; + + /// @notice list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token] + mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts; + + /// @notice map of global discounts + mapping(address => mapping(address => uint256)) public s_globalDiscounts; + + /// @notice keep track of any subsidised link that is owed to the reward manager. + mapping(bytes32 => uint256) public s_linkDeficit; + + /// @notice the total discount that can be applied to a fee, 1e18 = 100% discount + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + /// @notice the LINK token address + address public immutable i_linkAddress; + + /// @notice the native token address + address public immutable i_nativeAddress; + + /// @notice the proxy address + address public immutable i_proxyAddress; + + /// @notice the reward manager address + IRewardManager public immutable i_rewardManager; + + // @notice the mask to apply to get the report version + bytes32 private constant REPORT_VERSION_MASK = 0xffff000000000000000000000000000000000000000000000000000000000000; + + // @notice the different report versions + bytes32 private constant REPORT_V1 = 0x0001000000000000000000000000000000000000000000000000000000000000; + + /// @notice the surcharge fee to be paid if paying in native + uint256 public s_nativeSurcharge; + + /// @notice the error thrown if the discount or surcharge is invalid + error InvalidSurcharge(); + + /// @notice the error thrown if the discount is invalid + error InvalidDiscount(); + + /// @notice the error thrown if the address is invalid + error InvalidAddress(); + + /// @notice thrown if msg.value is supplied with a bad quote + error InvalidDeposit(); + + /// @notice thrown if a report has expired + error ExpiredReport(); + + /// @notice thrown if a report has no quote + error InvalidQuote(); + + // @notice thrown when the caller is not authorized + error Unauthorized(); + + // @notice thrown when trying to clear a zero deficit + error ZeroDeficit(); + + /// @notice thrown when trying to pay an address that cannot except funds + error InvalidReceivingAddress(); + + /// @notice Emitted whenever a subscriber's discount is updated + /// @param subscriber address of the subscriber to update discounts for + /// @param feedId Feed ID for the discount + /// @param token Token address for the discount + /// @param discount Discount to apply, in relation to the PERCENTAGE_SCALAR + event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); + + /// @notice Emitted when updating the native surcharge + /// @param newSurcharge Surcharge amount to apply relative to PERCENTAGE_SCALAR + event NativeSurchargeUpdated(uint64 newSurcharge); + + /// @notice Emits when this contract does not have enough LINK to send to the reward manager when paying in native + /// @param rewards Config digest and link fees which could not be subsidised + event InsufficientLink(IRewardManager.FeePayment[] rewards); + + /// @notice Emitted when funds are withdrawn + /// @param adminAddress Address of the admin + /// @param recipient Address of the recipient + /// @param assetAddress Address of the asset withdrawn + /// @param quantity Amount of the asset withdrawn + event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); + + /// @notice Emits when a deficit has been cleared for a particular config digest + /// @param configDigest Config digest of the deficit cleared + /// @param linkQuantity Amount of LINK required to pay the deficit + event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); + + /// @notice Emits when a fee has been processed + /// @param configDigest Config digest of the fee processed + /// @param subscriber Address of the subscriber who paid the fee + /// @param fee Fee paid + /// @param reward Reward paid + /// @param appliedDiscount Discount applied to the fee + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + Common.Asset fee, + Common.Asset reward, + uint256 appliedDiscount + ); + + /** + * @notice Construct the FeeManager contract + * @param _linkAddress The address of the LINK token + * @param _nativeAddress The address of the wrapped ERC-20 version of the native token (represents fee in native or + * wrapped) + * @param _proxyAddress The address of the proxy contract + * @param _rewardManagerAddress The address of the reward manager contract + */ + constructor( + address _linkAddress, + address _nativeAddress, + address _proxyAddress, + address _rewardManagerAddress + ) ConfirmedOwner(msg.sender) { + if ( + _linkAddress == address(0) || _nativeAddress == address(0) || _proxyAddress == address(0) + || _rewardManagerAddress == address(0) + ) revert InvalidAddress(); + + i_linkAddress = _linkAddress; + i_nativeAddress = _nativeAddress; + i_proxyAddress = _proxyAddress; + i_rewardManager = IRewardManager(_rewardManagerAddress); + + IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max); + } + + modifier onlyOwnerOrProxy() { + if (msg.sender != i_proxyAddress && msg.sender != owner()) revert Unauthorized(); + _; + } + + modifier onlyProxy() { + if (msg.sender != i_proxyAddress) revert Unauthorized(); + _; + } + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "FeeManager 2.1.0"; + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; + } + + /// @inheritdoc IVerifierFeeManager + function processFee( + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyProxy { + (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = + _processFee(payload, parameterPayload, subscriber); + + if (fee.amount == 0) { + _transfer(subscriber, msg.value); + return; + } + + IFeeManager.FeeAndReward[] memory feeAndReward = new IFeeManager.FeeAndReward[](1); + feeAndReward[0] = IFeeManager.FeeAndReward(bytes32(payload), fee, reward, appliedDiscount); + + if (fee.assetAddress == i_linkAddress) { + _handleFeesAndRewards(subscriber, feeAndReward, 1, 0); + } else { + _handleFeesAndRewards(subscriber, feeAndReward, 0, 1); + } + } + + /// @inheritdoc IVerifierFeeManager + function processFeeBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyProxy { + FeeAndReward[] memory feesAndRewards = new IFeeManager.FeeAndReward[](payloads.length); + + //keep track of the number of fees to prevent over initialising the FeePayment array within + // _convertToLinkAndNativeFees + uint256 numberOfLinkFees; + uint256 numberOfNativeFees; + + uint256 feesAndRewardsIndex; + for (uint256 i; i < payloads.length; ++i) { + (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = + _processFee(payloads[i], parameterPayload, subscriber); + + if (fee.amount != 0) { + feesAndRewards[feesAndRewardsIndex++] = + IFeeManager.FeeAndReward(bytes32(payloads[i]), fee, reward, appliedDiscount); + + unchecked { + //keep track of some tallys to make downstream calculations more efficient + if (fee.assetAddress == i_linkAddress) { + ++numberOfLinkFees; + } else { + ++numberOfNativeFees; + } + } + } + } + + if (numberOfLinkFees != 0 || numberOfNativeFees != 0) { + _handleFeesAndRewards(subscriber, feesAndRewards, numberOfLinkFees, numberOfNativeFees); + } else { + _transfer(subscriber, msg.value); + } + } + + /// @inheritdoc IFeeManager + function getFeeAndReward( + address subscriber, + bytes memory report, + address quoteAddress + ) public view returns (Common.Asset memory, Common.Asset memory, uint256) { + Common.Asset memory fee; + Common.Asset memory reward; + + //get the feedId from the report + bytes32 feedId = bytes32(report); + + //the report needs to be a support version + bytes32 reportVersion = _getReportVersion(feedId); + + //version 1 of the reports don't require quotes, so the fee will be 0 + if (reportVersion == REPORT_V1) { + fee.assetAddress = i_nativeAddress; + reward.assetAddress = i_linkAddress; + return (fee, reward, 0); + } + + //verify the quote payload is a supported token + if (quoteAddress != i_nativeAddress && quoteAddress != i_linkAddress) { + revert InvalidQuote(); + } + + //decode the report depending on the version + uint256 linkQuantity; + uint256 nativeQuantity; + uint256 expiresAt; + (,,, nativeQuantity, linkQuantity, expiresAt) = + abi.decode(report, (bytes32, uint32, uint32, uint192, uint192, uint32)); + + //read the timestamp bytes from the report data and verify it has not expired + if (expiresAt < block.timestamp) { + revert ExpiredReport(); + } + + //get the discount being applied + uint256 discount = s_subscriberDiscounts[subscriber][feedId][quoteAddress]; + + if (discount == 0) { + //check if a global discount has been applied + discount = s_globalDiscounts[subscriber][quoteAddress]; + } + + //the reward is always set in LINK + reward.assetAddress = i_linkAddress; + reward.amount = Math.ceilDiv(linkQuantity * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); + + //calculate either the LINK fee or native fee if it's within the report + if (quoteAddress == i_linkAddress) { + fee.assetAddress = i_linkAddress; + fee.amount = reward.amount; + } else { + uint256 surchargedFee = Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR); + + fee.assetAddress = i_nativeAddress; + fee.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR); + } + + //return the fee + return (fee, reward, discount); + } + + /// @inheritdoc IVerifierFeeManager + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external onlyOwnerOrProxy { + i_rewardManager.setRewardRecipients(configDigest, rewardRecipientAndWeights); + } + + /// @inheritdoc IFeeManager + function setNativeSurcharge( + uint64 surcharge + ) external onlyOwner { + if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge(); + + s_nativeSurcharge = surcharge; + + emit NativeSurchargeUpdated(surcharge); + } + + /// @inheritdoc IFeeManager + function updateSubscriberDiscount( + address subscriber, + bytes32 feedId, + address token, + uint64 discount + ) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_subscriberDiscounts[subscriber][feedId][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, feedId, token, discount); + } + + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_globalDiscounts[subscriber][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, bytes32(0), token, discount); + } + + /// @inheritdoc IFeeManager + function withdraw(address assetAddress, address recipient, uint192 quantity) external onlyOwner { + //address 0 is used to withdraw native in the context of withdrawing + if (assetAddress == address(0)) { + _transfer(recipient, quantity); + return; + } + + //withdraw the requested asset + IERC20(assetAddress).safeTransfer(recipient, quantity); + + //emit event when funds are withdrawn + emit Withdraw(msg.sender, recipient, assetAddress, uint192(quantity)); + } + + /// @inheritdoc IFeeManager + function linkAvailableForPayment() external view returns (uint256) { + //return the amount of LINK this contact has available to pay rewards + return IERC20(i_linkAddress).balanceOf(address(this)); + } + + /** + * @notice Gets the current version of the report that is encoded as the last two bytes of the feed + * @param feedId feed id to get the report version for + */ + function _getReportVersion( + bytes32 feedId + ) internal pure returns (bytes32) { + return REPORT_VERSION_MASK & feedId; + } + + function _processFee( + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) internal view returns (Common.Asset memory, Common.Asset memory, uint256) { + if (subscriber == address(this)) revert InvalidAddress(); + + //decode the report from the payload + (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes)); + + //get the feedId from the report + bytes32 feedId = bytes32(report); + + //v1 doesn't need a quote payload, so skip the decoding + address quote; + if (_getReportVersion(feedId) != REPORT_V1) { + //decode the quote from the bytes + (quote) = abi.decode(parameterPayload, (address)); + } + + //decode the fee, it will always be native or LINK + return getFeeAndReward(subscriber, report, quote); + } + + function _handleFeesAndRewards( + address subscriber, + FeeAndReward[] memory feesAndRewards, + uint256 numberOfLinkFees, + uint256 numberOfNativeFees + ) internal { + IRewardManager.FeePayment[] memory linkRewards = new IRewardManager.FeePayment[](numberOfLinkFees); + IRewardManager.FeePayment[] memory nativeFeeLinkRewards = new IRewardManager.FeePayment[](numberOfNativeFees); + + uint256 totalNativeFee; + uint256 totalNativeFeeLinkValue; + + uint256 linkRewardsIndex; + uint256 nativeFeeLinkRewardsIndex; + + uint256 totalNumberOfFees = numberOfLinkFees + numberOfNativeFees; + for (uint256 i; i < totalNumberOfFees; ++i) { + if (feesAndRewards[i].fee.assetAddress == i_linkAddress) { + linkRewards[linkRewardsIndex++] = + IRewardManager.FeePayment(feesAndRewards[i].configDigest, uint192(feesAndRewards[i].reward.amount)); + } else { + nativeFeeLinkRewards[nativeFeeLinkRewardsIndex++] = + IRewardManager.FeePayment(feesAndRewards[i].configDigest, uint192(feesAndRewards[i].reward.amount)); + totalNativeFee += feesAndRewards[i].fee.amount; + totalNativeFeeLinkValue += feesAndRewards[i].reward.amount; + } + + if (feesAndRewards[i].appliedDiscount != 0) { + emit DiscountApplied( + feesAndRewards[i].configDigest, + subscriber, + feesAndRewards[i].fee, + feesAndRewards[i].reward, + feesAndRewards[i].appliedDiscount + ); + } + } + + //keep track of change in case of any over payment + uint256 change; + + if (msg.value != 0) { + //there must be enough to cover the fee + if (totalNativeFee > msg.value) revert InvalidDeposit(); + + //wrap the amount required to pay the fee & approve as the subscriber paid in wrapped native + IWERC20(i_nativeAddress).deposit{value: totalNativeFee}(); + + unchecked { + //msg.value is always >= to fee.amount + change = msg.value - totalNativeFee; + } + } else { + if (totalNativeFee != 0) { + //subscriber has paid in wrapped native, so transfer the native to this contract + IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), totalNativeFee); + } + } + + if (linkRewards.length != 0) { + i_rewardManager.onFeePaid(linkRewards, subscriber); + } + + if (nativeFeeLinkRewards.length != 0) { + //distribute subsidised fees paid in Native + if (totalNativeFeeLinkValue > IERC20(i_linkAddress).balanceOf(address(this))) { + // If not enough LINK on this contract to forward for rewards, tally the deficit to be paid by out-of-band LINK + for (uint256 i; i < nativeFeeLinkRewards.length; ++i) { + unchecked { + //we have previously tallied the fees, any overflows would have already reverted + s_linkDeficit[nativeFeeLinkRewards[i].poolId] += nativeFeeLinkRewards[i].amount; + } + } + + emit InsufficientLink(nativeFeeLinkRewards); + } else { + //distribute the fees + i_rewardManager.onFeePaid(nativeFeeLinkRewards, address(this)); + } + } + + // a refund may be needed if the payee has paid in excess of the fee + _transfer(subscriber, change); + } + + function _transfer(address to, uint256 quantity) internal { + if (quantity != 0) { + (bool success,) = payable(to).call{value: quantity}(""); + if (!success) revert InvalidReceivingAddress(); + } + } + + /// @inheritdoc IFeeManager + function payLinkDeficit( + bytes32 configDigest + ) external onlyOwner { + uint256 deficit = s_linkDeficit[configDigest]; + + if (deficit == 0) revert ZeroDeficit(); + + delete s_linkDeficit[configDigest]; + + IRewardManager.FeePayment[] memory deficitFeePayment = new IRewardManager.FeePayment[](1); + + deficitFeePayment[0] = IRewardManager.FeePayment(configDigest, uint192(deficit)); + + i_rewardManager.onFeePaid(deficitFeePayment, address(this)); + + emit LinkDeficitCleared(configDigest, deficit); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/NoOpFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/NoOpFeeManager.sol new file mode 100644 index 0000000000..2419898653 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/NoOpFeeManager.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {Common} from "../libraries/Common.sol"; +import {IFeeManager} from "./interfaces/IFeeManager.sol"; +import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +/** + * @title NoOpFeeManager + * @notice A no-op implementation of IFeeManager that does not collect fees. + * @dev All functions return successfully without performing any fee collection or state changes. + * Any ETH sent to payable functions is refunded to the subscriber. + */ +contract NoOpFeeManager is IFeeManager, ITypeAndVersion { + /// @notice Error thrown when ETH refund fails + error RefundFailed(); + + /// @notice The scalar representing 100% discount (1e18 = 100%) + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "NoOpFeeManager 0.5.0"; + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; + } + + /// @inheritdoc IVerifierFeeManager + function processFee(bytes calldata, bytes calldata, address subscriber) external payable override { + // Refund any ETH sent + _refund(subscriber); + } + + /// @inheritdoc IVerifierFeeManager + function processFeeBulk(bytes[] calldata, bytes calldata, address subscriber) external payable override { + // Refund any ETH sent + _refund(subscriber); + } + + /// @inheritdoc IVerifierFeeManager + function setFeeRecipients(bytes32, Common.AddressAndWeight[] calldata) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function getFeeAndReward( + address, + bytes memory, + address + ) external pure override returns (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) { + // Return zero fee, zero reward, 100% discount (1e18) to indicate no fees are charged + return (fee, reward, PERCENTAGE_SCALAR); + } + + /// @inheritdoc IFeeManager + function setNativeSurcharge( + uint64 + ) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function updateSubscriberDiscount(address, bytes32, address, uint64) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function withdraw(address, address, uint192) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function linkAvailableForPayment() external pure override returns (uint256) { + return 0; + } + + /// @inheritdoc IFeeManager + function payLinkDeficit( + bytes32 + ) external override { + // No-op + } + + /// @inheritdoc IFeeManager + function updateSubscriberGlobalDiscount(address, address, uint64) external override { + // No-op + } + + /** + * @notice Returns 100% discount for any subscriber/feedId/token combination + * @dev Replicates public mapping getter signature from FeeManager for backwards compatibility + */ + // solhint-disable-next-line func-name-mixedcase + function s_subscriberDiscounts(address, bytes32, address) external pure returns (uint256) { + return PERCENTAGE_SCALAR; + } + + /** + * @notice Returns 100% discount for any subscriber/token combination + * @dev Replicates public mapping getter signature from FeeManager for backwards compatibility + */ + // solhint-disable-next-line func-name-mixedcase + function s_globalDiscounts(address, address) external pure returns (uint256) { + return PERCENTAGE_SCALAR; + } + + /** + * @notice Refunds any ETH sent to the contract + * @param recipient The address to refund ETH to + */ + function _refund( + address recipient + ) internal { + if (msg.value > 0) { + (bool success,) = payable(recipient).call{value: msg.value}(""); + if (!success) revert RefundFailed(); + } + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/RewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/RewardManager.sol new file mode 100644 index 0000000000..b48b1746e5 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/RewardManager.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {Common} from "../libraries/Common.sol"; +import {IRewardManager} from "./interfaces/IRewardManager.sol"; +import {IERC20} from "@openzeppelin/contracts@4.8.3/interfaces/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts@4.8.3/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title RewardManager + * @author Michael Fletcher + * @author Austin Born + * @notice This contract will be used to reward any configured recipients within a pool. Recipients will receive a share + * of their pool relative to their configured weight. + */ +contract RewardManager is IRewardManager, ConfirmedOwner, ITypeAndVersion { + using SafeERC20 for IERC20; + + // @dev The mapping of total fees collected for a particular pot: s_totalRewardRecipientFees[poolId] + mapping(bytes32 => uint256) public s_totalRewardRecipientFees; + + // @dev The mapping of fee balances for each pot last time the recipient claimed: + // s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] + mapping(bytes32 => mapping(address => uint256)) public s_totalRewardRecipientFeesLastClaimedAmounts; + + // @dev The mapping of RewardRecipient weights for a particular poolId: + // s_rewardRecipientWeights[poolId][rewardRecipient]. + mapping(bytes32 => mapping(address => uint256)) public s_rewardRecipientWeights; + + // @dev Keep track of the reward recipient weights that have been set to prevent duplicates + mapping(bytes32 => bool) public s_rewardRecipientWeightsSet; + + // @dev Store a list of pool ids that have been registered, to make off chain lookups easier + bytes32[] public s_registeredPoolIds; + + // @dev The address for the LINK contract + address public immutable i_linkAddress; + + // The total weight of all RewardRecipients. 1e18 = 100% of the pool fees + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + // The fee manager address + address public s_feeManagerAddress; + + // @notice Thrown whenever the RewardRecipient weights are invalid + error InvalidWeights(); + + // @notice Thrown when any given address is invalid + error InvalidAddress(); + + // @notice Thrown when the pool id is invalid + error InvalidPoolId(); + + // @notice Thrown when the calling contract is not within the authorized contracts + error Unauthorized(); + + // @notice Thrown when getAvailableRewardPoolIds parameters are incorrectly set + error InvalidPoolLength(); + + // Events emitted upon state change + event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients); + event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity); + event FeeManagerUpdated(address newFeeManagerAddress); + event FeePaid(FeePayment[] payments, address payer); + + /** + * @notice Constructor + * @param linkAddress address of the wrapped LINK token + */ + constructor( + address linkAddress + ) ConfirmedOwner(msg.sender) { + //ensure that the address ia not zero + if (linkAddress == address(0)) revert InvalidAddress(); + + i_linkAddress = linkAddress; + } + + // @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "RewardManager 1.1.0"; + } + + // @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == this.onFeePaid.selector; + } + + modifier onlyOwnerOrFeeManager() { + if (msg.sender != owner() && msg.sender != s_feeManagerAddress) revert Unauthorized(); + _; + } + + modifier onlyOwnerOrRecipientInPool( + bytes32 poolId + ) { + if (msg.sender != owner() && s_rewardRecipientWeights[poolId][msg.sender] == 0) revert Unauthorized(); + _; + } + + modifier onlyFeeManager() { + if (msg.sender != s_feeManagerAddress) revert Unauthorized(); + _; + } + + /// @inheritdoc IRewardManager + function onFeePaid(FeePayment[] calldata payments, address payer) external override onlyFeeManager { + uint256 totalFeeAmount; + for (uint256 i; i < payments.length; ++i) { + unchecked { + //the total amount for any ERC-20 asset cannot exceed 2^256 - 1 + //see + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/36bf1e46fa811f0f07d38eb9cfbc69a955f300ce/contracts/token/ERC20/ERC20.sol#L266 + //for example implementation. + s_totalRewardRecipientFees[payments[i].poolId] += payments[i].amount; + + //tally the total payable fees + totalFeeAmount += payments[i].amount; + } + } + + //transfer the fees to this contract + IERC20(i_linkAddress).safeTransferFrom(payer, address(this), totalFeeAmount); + + emit FeePaid(payments, payer); + } + + /// @inheritdoc IRewardManager + function claimRewards( + bytes32[] memory poolIds + ) external override { + _claimRewards(msg.sender, poolIds); + } + + // wrapper impl for claimRewards + function _claimRewards(address recipient, bytes32[] memory poolIds) internal returns (uint256) { + //get the total amount claimable for this recipient + uint256 claimAmount; + + //loop and claim all the rewards in the poolId pot + for (uint256 i; i < poolIds.length; ++i) { + //get the poolId to be claimed + bytes32 poolId = poolIds[i]; + + //get the total fees for the pot + uint256 totalFeesInPot = s_totalRewardRecipientFees[poolId]; + + unchecked { + //avoid unnecessary storage reads if there's no fees in the pot + if (totalFeesInPot == 0) continue; + + //get the claimable amount for this recipient, this calculation will never exceed the amount in the pot + uint256 claimableAmount = totalFeesInPot - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient]; + + //calculate the recipients share of the fees, which is their weighted share of the difference between the last + // amount they claimed and the current amount in the pot. This can never be more than the total amount in + // existence + uint256 recipientShare = (claimableAmount * s_rewardRecipientWeights[poolId][recipient]) / PERCENTAGE_SCALAR; + + //if there's no fees to claim, continue as there's nothing to update + if (recipientShare == 0) continue; + + //keep track of the total amount claimable, this can never be more than the total amount in existence + claimAmount += recipientShare; + + //set the current total amount of fees in the pot as it's used to calculate future claims + s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] = totalFeesInPot; + + //emit event if the recipient has rewards to claim + emit RewardsClaimed(poolIds[i], recipient, uint192(recipientShare)); + } + } + + //check if there's any rewards to claim in the given poolId + if (claimAmount != 0) { + //transfer the reward to the recipient + IERC20(i_linkAddress).safeTransfer(recipient, claimAmount); + } + + return claimAmount; + } + + /// @inheritdoc IRewardManager + function setRewardRecipients( + bytes32 poolId, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external override onlyOwnerOrFeeManager { + //revert if there are no recipients to set + if (rewardRecipientAndWeights.length == 0) revert InvalidAddress(); + + //check that the weights have not been previously set + if (s_rewardRecipientWeightsSet[poolId]) revert InvalidPoolId(); + + //keep track of the registered poolIds to make off chain lookups easier + s_registeredPoolIds.push(poolId); + + //keep track of which pools have had their reward recipients set + s_rewardRecipientWeightsSet[poolId] = true; + + //set the reward recipients, this will only be called once and contain the full set of RewardRecipients with a total + // weight of 100% + _setRewardRecipientWeights(poolId, rewardRecipientAndWeights, PERCENTAGE_SCALAR); + + emit RewardRecipientsUpdated(poolId, rewardRecipientAndWeights); + } + + function _setRewardRecipientWeights( + bytes32 poolId, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights, + uint256 expectedWeight + ) internal { + //we can't update the weights if it contains duplicates + if (Common._hasDuplicateAddresses(rewardRecipientAndWeights)) revert InvalidAddress(); + + //loop all the reward recipients and validate the weight and address + uint256 totalWeight; + for (uint256 i; i < rewardRecipientAndWeights.length; ++i) { + //get the weight + uint256 recipientWeight = rewardRecipientAndWeights[i].weight; + //get the address + address recipientAddress = rewardRecipientAndWeights[i].addr; + + //ensure the reward recipient address is not zero + if (recipientAddress == address(0)) revert InvalidAddress(); + + //save/overwrite the weight for the reward recipient + s_rewardRecipientWeights[poolId][recipientAddress] = recipientWeight; + + unchecked { + //keep track of the cumulative weight, this cannot overflow as the total weight is restricted at 1e18 + totalWeight += recipientWeight; + } + } + + //if total weight is not met, the fees will either be under or over distributed + if (totalWeight != expectedWeight) revert InvalidWeights(); + } + + /// @inheritdoc IRewardManager + function updateRewardRecipients( + bytes32 poolId, + Common.AddressAndWeight[] calldata newRewardRecipients + ) external override onlyOwner { + //create an array of poolIds to pass to _claimRewards if required + bytes32[] memory poolIds = new bytes32[](1); + poolIds[0] = poolId; + + //loop all the reward recipients and claim their rewards before updating their weights + uint256 existingTotalWeight; + for (uint256 i; i < newRewardRecipients.length; ++i) { + //get the address + address recipientAddress = newRewardRecipients[i].addr; + //get the existing weight + uint256 existingWeight = s_rewardRecipientWeights[poolId][recipientAddress]; + + //if a recipient is updated, the rewards must be claimed first as they can't claim previous fees at the new weight + _claimRewards(newRewardRecipients[i].addr, poolIds); + + unchecked { + //keep tally of the weights so that the expected collective weight is known + existingTotalWeight += existingWeight; + } + } + + //update the reward recipients, if the new collective weight isn't equal to the previous collective weight, the fees + // will either be under or over distributed + _setRewardRecipientWeights(poolId, newRewardRecipients, existingTotalWeight); + + //emit event + emit RewardRecipientsUpdated(poolId, newRewardRecipients); + } + + /// @inheritdoc IRewardManager + function payRecipients(bytes32 poolId, address[] calldata recipients) external onlyOwnerOrRecipientInPool(poolId) { + //convert poolIds to an array to match the interface of _claimRewards + bytes32[] memory poolIdsArray = new bytes32[](1); + poolIdsArray[0] = poolId; + + //loop each recipient and claim the rewards for each of the pools and assets + for (uint256 i; i < recipients.length; ++i) { + _claimRewards(recipients[i], poolIdsArray); + } + } + + /// @inheritdoc IRewardManager + function setFeeManager( + address newFeeManagerAddress + ) external onlyOwner { + if (newFeeManagerAddress == address(0)) revert InvalidAddress(); + + s_feeManagerAddress = newFeeManagerAddress; + + emit FeeManagerUpdated(newFeeManagerAddress); + } + + /// @inheritdoc IRewardManager + function getAvailableRewardPoolIds( + address recipient, + uint256 startIndex, + uint256 endIndex + ) external view returns (bytes32[] memory) { + //get the length of the pool ids which we will loop through and potentially return + uint256 registeredPoolIdsLength = s_registeredPoolIds.length; + + uint256 lastIndex = endIndex > registeredPoolIdsLength ? registeredPoolIdsLength : endIndex; + + if (startIndex > lastIndex) revert InvalidPoolLength(); + + //create a new array with the maximum amount of potential pool ids + bytes32[] memory claimablePoolIds = new bytes32[](lastIndex - startIndex); + //we want the pools which a recipient has funds for to be sequential, so we need to keep track of the index + uint256 poolIdArrayIndex; + + //loop all the pool ids, and check if the recipient has a registered weight and a claimable amount + for (uint256 i = startIndex; i < lastIndex; ++i) { + //get the poolId + bytes32 poolId = s_registeredPoolIds[i]; + + //if the recipient has a weight, they are a recipient of this poolId + if (s_rewardRecipientWeights[poolId][recipient] != 0) { + //get the total in this pool + uint256 totalPoolAmount = s_totalRewardRecipientFees[poolId]; + //if the recipient has any LINK, then add the poolId to the array + unchecked { + //s_totalRewardRecipientFeesLastClaimedAmounts can never exceed total pool amount, and the number of pools + // can't exceed the max array length + if (totalPoolAmount - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] != 0) { + claimablePoolIds[poolIdArrayIndex++] = poolId; + } + } + } + } + + return claimablePoolIds; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/Verifier.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/Verifier.sol new file mode 100644 index 0000000000..1cd70e10ea --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/Verifier.sol @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; + +import {Common} from "../libraries/Common.sol"; +import {IVerifier} from "./interfaces/IVerifier.sol"; +import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +// OCR2 standard +uint256 constant MAX_NUM_ORACLES = 31; + +/* + * The verifier contract is used to verify offchain reports signed + * by DONs. A report consists of a price, block number and feed Id. It + * represents the observed price of an asset at a specified block number for + * a feed. The verifier contract is used to verify that such reports have + * been signed by the correct signers. + **/ +contract Verifier is IVerifier, ConfirmedOwner, ITypeAndVersion { + // The first byte of the mask can be 0, because we only ever have 31 oracles + uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101; + + enum Role { + // Default role for an oracle address. This means that the oracle address + // is not a signer + Unset, + // Role given to an oracle address that is allowed to sign a report + Signer + } + + struct Signer { + // Index of oracle in a configuration + uint8 index; + // The oracle's role + Role role; + } + + struct VerifierState { + // The block number of the block the last time the configuration was updated. + uint32 latestConfigBlockNumber; + // Whether the config is deactivated + bool isActive; + // Fault tolerance + uint8 f; + // Number of signers + uint8 oracleCount; + // Map of signer addresses to oracles + mapping(address => Signer) oracles; + } + + /// @notice This event is emitted when a new report is verified. + /// It is used to keep a historical record of verified reports. + event ReportVerified(bytes32 indexed feedId, address requester); + + /// @notice This event is emitted whenever a new DON configuration is set. + event ConfigSet(bytes32 indexed configDigest, address[] signers, uint8 f); + + /// @notice This event is + event ConfigUpdated(bytes32 indexed configDigest, address[] prevSigners, address[] newSigners); + + /// @notice This event is emitted whenever a configuration is deactivated + event ConfigDeactivated(bytes32 indexed configDigest); + + /// @notice This event is emitted whenever a configuration is activated + event ConfigActivated(bytes32 indexed configDigest); + + /// @notice This error is thrown whenever an address tries + /// to exeecute a transaction that it is not authorized to do so + error AccessForbidden(); + + /// @notice This error is thrown whenever a zero address is passed + error ZeroAddress(); + + /// @notice This error is thrown whenever the config digest + /// is empty + error DigestEmpty(); + + /// @notice This error is thrown whenever the config digest + /// passed in has not been set in this verifier + /// @param configDigest The config digest that has not been set + error DigestNotSet(bytes32 configDigest); + + /// @notice This error is thrown whenever the config digest + /// has been deactivated + /// @param configDigest The config digest that is inactive + error DigestInactive(bytes32 configDigest); + + /// @notice This error is thrown whenever trying to set a config + /// with a fault tolerance of 0 + error FaultToleranceMustBePositive(); + + /// @notice This error is thrown whenever a report is signed + /// with more than the max number of signers + /// @param numSigners The number of signers who have signed the report + /// @param maxSigners The maximum number of signers that can sign a report + error ExcessSigners(uint256 numSigners, uint256 maxSigners); + + /// @notice This error is thrown whenever a report is signed + /// with less than the minimum number of signers + /// @param numSigners The number of signers who have signed the report + /// @param minSigners The minimum number of signers that need to sign a report + error InsufficientSigners(uint256 numSigners, uint256 minSigners); + + /// @notice This error is thrown whenever a report is signed + /// with an incorrect number of signers + /// @param numSigners The number of signers who have signed the report + /// @param expectedNumSigners The expected number of signers that need to sign + /// a report + error IncorrectSignatureCount(uint256 numSigners, uint256 expectedNumSigners); + + /// @notice This error is thrown whenever the R and S signer components + /// have different lengths + /// @param rsLength The number of r signature components + /// @param ssLength The number of s signature components + error MismatchedSignatures(uint256 rsLength, uint256 ssLength); + + /// @notice This error is thrown whenever setting a config with duplicate signatures + error NonUniqueSignatures(); + + /// @notice This error is thrown whenever a report fails to verify due to bad or duplicate signatures + error BadVerification(); + + /// @notice This error is thrown whenever a config digest is already set when setting the configuration + error ConfigDigestAlreadySet(); + + /// @notice The address of the verifier proxy + address private immutable i_verifierProxyAddr; + + /// @notice Verifier states keyed on config digest + mapping(bytes32 => VerifierState) internal s_verifierStates; + + /// @param verifierProxyAddr The address of the VerifierProxy contract + constructor( + address verifierProxyAddr + ) ConfirmedOwner(msg.sender) { + if (verifierProxyAddr == address(0)) revert ZeroAddress(); + i_verifierProxyAddr = verifierProxyAddr; + } + + modifier checkConfigValid(uint256 numSigners, uint256 f) { + if (f == 0) revert FaultToleranceMustBePositive(); + if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES); + if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1); + _; + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool isVerifier) { + return interfaceId == this.verify.selector; + } + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "Verifier 2.0.1"; + } + + /// @inheritdoc IVerifier + function verify( + bytes calldata signedReport, + address sender + ) external override returns (bytes memory verifierResponse) { + bytes memory reportData = _verify(signedReport); + emit ReportVerified(bytes32(reportData), sender); + return reportData; + } + + /// @inheritdoc IVerifier + function verifyView( + bytes calldata signedReport + ) external view override returns (bytes memory verifierResponse) { + return _verify(signedReport); + } + + /// @notice Internal verification logic shared by verify() and verifyView() + /// @param signedReport The signed report to verify + /// @return verifierResponse The verified report data + function _verify( + bytes calldata signedReport + ) internal view returns (bytes memory verifierResponse) { + if (msg.sender != i_verifierProxyAddr) revert AccessForbidden(); + (bytes32[3] memory reportContext, bytes memory reportData, bytes32[] memory rs, bytes32[] memory ss, bytes32 rawVs) + = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32)); + + // reportContext consists of: + // reportContext[0]: ConfigDigest + // reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round + // reportContext[2]: ExtraHash + bytes32 configDigest = reportContext[0]; + + VerifierState storage verifierState = s_verifierStates[configDigest]; + + _validateReport(configDigest, rs, ss, verifierState); + + bytes32 hashedReport = keccak256(reportData); + + _verifySignatures(hashedReport, reportContext, rs, ss, rawVs, verifierState); + + return reportData; + } + + /// @notice Validates parameters of the report + /// @param configDigest Config digest from the report + /// @param rs R components from the report + /// @param ss S components from the report + /// @param config Config for the given digest + function _validateReport( + bytes32 configDigest, + bytes32[] memory rs, + bytes32[] memory ss, + VerifierState storage config + ) private view { + uint8 expectedNumSignatures = config.f + 1; + + if (!config.isActive) revert DigestInactive(configDigest); + if (rs.length != expectedNumSignatures) revert IncorrectSignatureCount(rs.length, expectedNumSignatures); + if (rs.length != ss.length) revert MismatchedSignatures(rs.length, ss.length); + } + + /// @notice Verifies that a report has been signed by the correct + /// signers and that enough signers have signed the reports. + /// @param hashedReport The keccak256 hash of the raw report's bytes + /// @param reportContext The context the report was signed in + /// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES + /// entries + /// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES + /// entries + /// @param rawVs ith element is the the V component of the ith signature + /// @param config The config digest the report was signed for + function _verifySignatures( + bytes32 hashedReport, + bytes32[3] memory reportContext, + bytes32[] memory rs, + bytes32[] memory ss, + bytes32 rawVs, + VerifierState storage config + ) private view { + bytes32 h = keccak256(abi.encodePacked(hashedReport, reportContext)); + // i-th byte counts number of sigs made by i-th signer + uint256 signedCount; + + Signer memory o; + address signerAddress; + uint256 numSigners = rs.length; + for (uint256 i; i < numSigners; ++i) { + signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); + o = config.oracles[signerAddress]; + if (o.role != Role.Signer) revert BadVerification(); + unchecked { + signedCount += 1 << (8 * o.index); + } + } + + if (signedCount & ORACLE_MASK != signedCount) revert BadVerification(); + } + + /// @inheritdoc IVerifier + function updateConfig( + bytes32 configDigest, + address[] calldata prevSigners, + address[] calldata newSigners, + uint8 f + ) external override checkConfigValid(newSigners.length, f) onlyOwner { + VerifierState storage config = s_verifierStates[configDigest]; + + if (config.f == 0) revert DigestNotSet(configDigest); + + // We must be removing the number of signers that were originally set + if (config.oracleCount != prevSigners.length) { + revert NonUniqueSignatures(); + } + + for (uint256 i; i < prevSigners.length; ++i) { + // Check the signers being removed are not zero address or duplicates + if (config.oracles[prevSigners[i]].role == Role.Unset) { + revert NonUniqueSignatures(); + } + + delete config.oracles[prevSigners[i]]; + } + + // Once signers have been cleared we can set the new signers + _setConfig(configDigest, newSigners, f, new Common.AddressAndWeight[](0), true); + + emit ConfigUpdated(configDigest, prevSigners, newSigners); + } + + /// @inheritdoc IVerifier + function setConfig( + bytes32 configDigest, + address[] calldata signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights + ) external override checkConfigValid(signers.length, f) onlyOwner { + _setConfig(configDigest, signers, f, recipientAddressesAndWeights, false); + } + + function _setConfig( + bytes32 configDigest, + address[] calldata signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights, + bool _updateConfig + ) internal { + VerifierState storage verifierState = s_verifierStates[configDigest]; + + if (verifierState.f > 0 && !_updateConfig) { + revert ConfigDigestAlreadySet(); + } + + verifierState.latestConfigBlockNumber = uint32(block.number); + verifierState.f = f; + verifierState.isActive = true; + verifierState.oracleCount = uint8(signers.length); + + for (uint8 i; i < signers.length; ++i) { + address signerAddr = signers[i]; + if (signerAddr == address(0)) revert ZeroAddress(); + + // All signer roles are unset by default for a new config digest. + // Here the contract checks to see if a signer's address has already + // been set to ensure that the group of signer addresses that will + // sign reports with the config digest are unique. + bool isSignerAlreadySet = verifierState.oracles[signerAddr].role != Role.Unset; + if (isSignerAlreadySet) revert NonUniqueSignatures(); + verifierState.oracles[signerAddr] = Signer({role: Role.Signer, index: i}); + } + + if (!_updateConfig) { + IVerifierProxy(i_verifierProxyAddr).setVerifier(bytes32(0), configDigest, recipientAddressesAndWeights); + + emit ConfigSet(configDigest, signers, f); + } + } + + /// @inheritdoc IVerifier + function activateConfig( + bytes32 configDigest + ) external onlyOwner { + VerifierState storage verifierState = s_verifierStates[configDigest]; + + if (configDigest == bytes32("")) revert DigestEmpty(); + if (verifierState.f == 0) revert DigestNotSet(configDigest); + verifierState.isActive = true; + emit ConfigActivated(configDigest); + } + + /// @inheritdoc IVerifier + function deactivateConfig( + bytes32 configDigest + ) external onlyOwner { + VerifierState storage verifierState = s_verifierStates[configDigest]; + + if (configDigest == bytes32("")) revert DigestEmpty(); + if (verifierState.f == 0) revert DigestNotSet(configDigest); + verifierState.isActive = false; + emit ConfigDeactivated(configDigest); + } + + /// @inheritdoc IVerifier + function latestConfigDetails( + bytes32 configDigest + ) external view override returns (uint32 blockNumber) { + VerifierState storage verifierState = s_verifierStates[configDigest]; + return (verifierState.latestConfigBlockNumber); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/VerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/VerifierProxy.sol new file mode 100644 index 0000000000..cc462a3d38 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/VerifierProxy.sol @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; + +import {Common} from "../libraries/Common.sol"; +import {IVerifier} from "./interfaces/IVerifier.sol"; +import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol"; +import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +/** + * The verifier proxy contract is the gateway for all report verification requests + * on a chain. It is responsible for taking in a verification request and routing + * it to the correct verifier contract. + */ +contract VerifierProxy is IVerifierProxy, ConfirmedOwner, ITypeAndVersion { + /// @notice This event is emitted whenever a new verifier contract is set + /// @param oldConfigDigest The config digest that was previously the latest config + /// digest of the verifier contract at the verifier address. + /// @param oldConfigDigest The latest config digest of the verifier contract + /// at the verifier address. + /// @param verifierAddress The address of the verifier contract that verifies reports for + /// a given digest + event VerifierSet(bytes32 oldConfigDigest, bytes32 newConfigDigest, address verifierAddress); + + /// @notice This event is emitted whenever a new verifier contract is initialized + /// @param verifierAddress The address of the verifier contract that verifies reports + event VerifierInitialized(address verifierAddress); + + /// @notice This event is emitted whenever a verifier is unset + /// @param configDigest The config digest that was unset + /// @param verifierAddress The Verifier contract address unset + event VerifierUnset(bytes32 configDigest, address verifierAddress); + + /// @notice This event is emitted when a new access controller is set + /// @param oldAccessController The old access controller address + /// @param newAccessController The new access controller address + event AccessControllerSet(address oldAccessController, address newAccessController); + + /// @notice This event is emitted when a new fee manager is set + /// @param oldFeeManager The old fee manager address + /// @param newFeeManager The new fee manager address + event FeeManagerSet(address oldFeeManager, address newFeeManager); + + /// @notice This error is thrown whenever an address tries + /// to exeecute a transaction that it is not authorized to do so + error AccessForbidden(); + + /// @notice This error is thrown whenever a zero address is passed + error ZeroAddress(); + + /// @notice This error is thrown when trying to set a verifier address + /// for a digest that has already been initialized + /// @param configDigest The digest for the verifier that has + /// already been set + /// @param verifier The address of the verifier the digest was set for + error ConfigDigestAlreadySet(bytes32 configDigest, address verifier); + + /// @notice This error is thrown when trying to set a verifier address that has already been initialized + error VerifierAlreadyInitialized(address verifier); + + /// @notice This error is thrown when the verifier at an address does + /// not conform to the verifier interface + error VerifierInvalid(); + + /// @notice This error is thrown when the fee manager at an address does + /// not conform to the fee manager interface + error FeeManagerInvalid(); + + /// @notice This error is thrown whenever a verifier is not found + /// @param configDigest The digest for which a verifier is not found + error VerifierNotFound(bytes32 configDigest); + + /// @notice This error is thrown whenever billing fails. + error BadVerification(); + + /// @notice Mapping of authorized verifiers + mapping(address => bool) private s_initializedVerifiers; + + /// @notice Mapping between config digests and verifiers + mapping(bytes32 => address) private s_verifiersByConfig; + + /// @notice The contract to control addresses that are allowed to verify reports + AccessControllerInterface public s_accessController; + + /// @notice The contract to control fees for report verification + IVerifierFeeManager public s_feeManager; + + constructor( + AccessControllerInterface accessController + ) ConfirmedOwner(msg.sender) { + s_accessController = accessController; + } + + modifier checkAccess() { + AccessControllerInterface ac = s_accessController; + if (address(ac) != address(0) && !ac.hasAccess(msg.sender, msg.data)) revert AccessForbidden(); + _; + } + + modifier onlyInitializedVerifier() { + if (!s_initializedVerifiers[msg.sender]) revert AccessForbidden(); + _; + } + + modifier onlyValidVerifier( + address verifierAddress + ) { + if (verifierAddress == address(0)) revert ZeroAddress(); + if (!IERC165(verifierAddress).supportsInterface(IVerifier.verify.selector)) revert VerifierInvalid(); + _; + } + + modifier onlyUnsetConfigDigest( + bytes32 configDigest + ) { + address configDigestVerifier = s_verifiersByConfig[configDigest]; + if (configDigestVerifier != address(0)) revert ConfigDigestAlreadySet(configDigest, configDigestVerifier); + _; + } + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "VerifierProxy 2.0.1"; + } + + /// @inheritdoc IVerifierProxy + function verify( + bytes calldata payload, + bytes calldata parameterPayload + ) external payable checkAccess returns (bytes memory) { + IVerifierFeeManager feeManager = s_feeManager; + + // Bill the verifier + if (address(feeManager) != address(0)) { + feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender); + } + + return _verify(payload); + } + + /// @inheritdoc IVerifierProxy + function verifyView( + bytes calldata payload + ) external view checkAccess returns (bytes memory) { + return _verifyView(payload); + } + + /// @inheritdoc IVerifierProxy + function verifyBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload + ) external payable checkAccess returns (bytes[] memory verifiedReports) { + IVerifierFeeManager feeManager = s_feeManager; + + // Bill the verifier + if (address(feeManager) != address(0)) { + feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender); + } + + //verify the reports + verifiedReports = new bytes[](payloads.length); + for (uint256 i; i < payloads.length; ++i) { + verifiedReports[i] = _verify(payloads[i]); + } + + return verifiedReports; + } + + /// @inheritdoc IVerifierProxy + function verifyBulkView( + bytes[] calldata payloads + ) external view checkAccess returns (bytes[] memory verifiedReports) { + //verify the reports + verifiedReports = new bytes[](payloads.length); + for (uint256 i; i < payloads.length; ++i) { + verifiedReports[i] = _verifyView(payloads[i]); + } + + return verifiedReports; + } + + function _verify( + bytes calldata payload + ) internal returns (bytes memory verifiedReport) { + // First 32 bytes of the signed report is the config digest + bytes32 configDigest = bytes32(payload); + address verifierAddress = s_verifiersByConfig[configDigest]; + if (verifierAddress == address(0)) revert VerifierNotFound(configDigest); + + return IVerifier(verifierAddress).verify(payload, msg.sender); + } + + function _verifyView( + bytes calldata payload + ) internal view returns (bytes memory) { + // First 32 bytes of the signed report is the config digest + bytes32 configDigest = bytes32(payload); + address verifierAddress = s_verifiersByConfig[configDigest]; + if (verifierAddress == address(0)) revert VerifierNotFound(configDigest); + + return IVerifier(verifierAddress).verifyView(payload); + } + + /// @inheritdoc IVerifierProxy + function initializeVerifier( + address verifierAddress + ) external override onlyOwner onlyValidVerifier(verifierAddress) { + if (s_initializedVerifiers[verifierAddress]) revert VerifierAlreadyInitialized(verifierAddress); + + s_initializedVerifiers[verifierAddress] = true; + emit VerifierInitialized(verifierAddress); + } + + /// @inheritdoc IVerifierProxy + function setVerifier( + bytes32 currentConfigDigest, + bytes32 newConfigDigest, + Common.AddressAndWeight[] calldata addressesAndWeights + ) external override onlyUnsetConfigDigest(newConfigDigest) onlyInitializedVerifier { + s_verifiersByConfig[newConfigDigest] = msg.sender; + + // Empty recipients array will be ignored and must be set off chain + if (addressesAndWeights.length > 0) { + if (address(s_feeManager) == address(0)) { + revert ZeroAddress(); + } + + s_feeManager.setFeeRecipients(newConfigDigest, addressesAndWeights); + } + + emit VerifierSet(currentConfigDigest, newConfigDigest, msg.sender); + } + + /// @inheritdoc IVerifierProxy + function unsetVerifier( + bytes32 configDigest + ) external override onlyOwner { + address verifierAddress = s_verifiersByConfig[configDigest]; + if (verifierAddress == address(0)) revert VerifierNotFound(configDigest); + delete s_verifiersByConfig[configDigest]; + emit VerifierUnset(configDigest, verifierAddress); + } + + /// @inheritdoc IVerifierProxy + function getVerifier( + bytes32 configDigest + ) external view override returns (address) { + return s_verifiersByConfig[configDigest]; + } + + /// @inheritdoc IVerifierProxy + function setAccessController( + AccessControllerInterface accessController + ) external onlyOwner { + address oldAccessController = address(s_accessController); + s_accessController = accessController; + emit AccessControllerSet(oldAccessController, address(accessController)); + } + + /// @inheritdoc IVerifierProxy + function setFeeManager( + IVerifierFeeManager feeManager + ) external onlyOwner { + if (address(feeManager) == address(0)) revert ZeroAddress(); + + if ( + !IERC165(feeManager).supportsInterface(IVerifierFeeManager.processFee.selector) + || !IERC165(feeManager).supportsInterface(IVerifierFeeManager.processFeeBulk.selector) + ) revert FeeManagerInvalid(); + + address oldFeeManager = address(s_feeManager); + s_feeManager = IVerifierFeeManager(feeManager); + emit FeeManagerSet(oldFeeManager, address(feeManager)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/ChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/ChannelConfigStore.sol new file mode 100644 index 0000000000..a17f4ed497 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/ChannelConfigStore.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {EnumerableSet} from "@openzeppelin/contracts@4.8.3/utils/structs/EnumerableSet.sol"; + +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {IChannelConfigStore} from "./interfaces/IChannelConfigStore.sol"; + +contract ChannelConfigStore is ConfirmedOwner, IChannelConfigStore, ITypeAndVersion { + // This contract uses uint32 for donIds when they are function arguments and + // uint256 elsewhere (e.g. in storage or event params). This inconsistency is + // ugly, but we maintain it for backwards compatibility. + + using EnumerableSet for EnumerableSet.UintSet; + + event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + event ChannelDefinitionAdded(uint256 indexed donId, ChannelAdderId indexed channelAdderId, string url, bytes32 sha); + event ChannelAdderSet(uint256 indexed donId, ChannelAdderId indexed channelAdderId, bool allowed); + event ChannelAdderAddressSet(ChannelAdderId indexed channelAdderId, address adderAddress); + + /// @notice Thrown when a caller is not authorized to add channel definitions. + error UnauthorizedChannelAdder(); + + /// @notice Thrown when a ChannelAdderId is reserved. + error ReservedChannelAdderId(); + + // We reserve the ChannelAdderIds 0 through 999. 1 is used by the offchain code to internally + // represent the owner. The others are reserved for future use. + ChannelAdderId internal constant MIN_CHANNEL_ADDER_ID = ChannelAdderId.wrap(1000); + + constructor() ConfirmedOwner(msg.sender) {} + + /// @notice The version of a channel definition keyed by DON ID. + // Increments by 1 on every update. + mapping(uint256 => uint256) internal s_channelDefinitionVersions; + + /// @notice Mapping from channel adder ID to its corresponding address + mapping(ChannelAdderId => address) internal s_channelAdderAddresses; + + /// @notice Mapping from DON ID to the set of allowed channel adder IDs + mapping(uint256 => EnumerableSet.UintSet) internal s_allowedChannelAdders; + + /// @notice Allows the owner to arbitrarily set channel definitions to the specified DON. + /// Unlike the channel adder, the owner can not only add, but also modify and delete + /// channel definitions. The DON enforces (in its consensus rules), that the channel + /// definitions provided by the owner are well-formed. + /// @param donId The DON ID + /// @param url The URL of the channel definition + /// @param sha The SHA hash of the channel definition + function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external onlyOwner { + uint32 newVersion = uint32(++s_channelDefinitionVersions[uint256(donId)]); + emit NewChannelDefinition(donId, newVersion, url, sha); + } + + /// @notice Allows a channel adder to add channel definitions to the specified DON. + /// The DON enforces (in its consensus rules), that the channel definitions provided + /// by the channel adder are well-formed, purely additive, and do not overload the DON. + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @param url The URL of the channel definition + /// @param sha The SHA hash of the channel definition + function addChannelDefinitions( + uint32 donId, + ChannelAdderId channelAdderId, + string calldata url, + bytes32 sha + ) external { + if (msg.sender != s_channelAdderAddresses[channelAdderId]) { + revert UnauthorizedChannelAdder(); + } + if (!s_allowedChannelAdders[donId].contains(ChannelAdderId.unwrap(channelAdderId))) { + revert UnauthorizedChannelAdder(); + } + emit ChannelDefinitionAdded(donId, channelAdderId, url, sha); + } + + /// @notice Sets the address for a channel adder ID + /// @param channelAdderId The channel adder ID + /// @param adderAddress The address to associate with the channel adder ID. + /// Set this to the zero address (or some other address that cannot make + /// calls) to disable the channel adder. + function setChannelAdderAddress(ChannelAdderId channelAdderId, address adderAddress) external onlyOwner { + if (ChannelAdderId.unwrap(channelAdderId) < ChannelAdderId.unwrap(MIN_CHANNEL_ADDER_ID)) { + revert ReservedChannelAdderId(); + } + s_channelAdderAddresses[channelAdderId] = adderAddress; + emit ChannelAdderAddressSet(channelAdderId, adderAddress); + } + + /// @notice Sets whether a channel adder ID is allowed for a DON + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @param allowed Whether the channel adder should be allowed or removed + function setChannelAdder(uint32 donId, ChannelAdderId channelAdderId, bool allowed) external onlyOwner { + if (ChannelAdderId.unwrap(channelAdderId) < ChannelAdderId.unwrap(MIN_CHANNEL_ADDER_ID)) { + revert ReservedChannelAdderId(); + } + if (allowed) { + s_allowedChannelAdders[donId].add(ChannelAdderId.unwrap(channelAdderId)); + } else { + s_allowedChannelAdders[donId].remove(ChannelAdderId.unwrap(channelAdderId)); + } + emit ChannelAdderSet(donId, channelAdderId, allowed); + } + + /// @notice Gets the address associated with a channel adder ID + /// @param channelAdderId The channel adder ID + /// @return The address associated with the channel adder ID + function getChannelAdderAddress( + ChannelAdderId channelAdderId + ) external view returns (address) { + return s_channelAdderAddresses[channelAdderId]; + } + + /// @notice Checks if a channel adder is allowed for a DON + /// @param donId The DON ID + /// @param channelAdderId The channel adder ID + /// @return True if the channel adder is allowed for the DON + function isChannelAdderAllowed(uint32 donId, ChannelAdderId channelAdderId) external view returns (bool) { + return s_allowedChannelAdders[donId].contains(ChannelAdderId.unwrap(channelAdderId)); + } + + /// @notice Gets all allowed channel adder IDs for a DON + /// @param donId The DON ID + /// @return allowedChannelAdderIds An array of allowed channel adder IDs + function getAllowedChannelAdders( + uint32 donId + ) external view returns (ChannelAdderId[] memory allowedChannelAdderIds) { + // Not very gas efficient, but we don't expect this function to be called + // from onchain anyways. + uint256[] memory values = s_allowedChannelAdders[donId].values(); + allowedChannelAdderIds = new ChannelAdderId[](values.length); + for (uint256 i = 0; i < values.length; i++) { + allowedChannelAdderIds[i] = ChannelAdderId.wrap(uint32(values[i])); + } + return allowedChannelAdderIds; + } + + function typeAndVersion() external pure override returns (string memory) { + return "ChannelConfigStore 1.0.0"; + } + + function supportsInterface( + bytes4 interfaceId + ) external pure returns (bool) { + return interfaceId == type(IChannelConfigStore).interfaceId; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/Configurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/Configurator.sol new file mode 100644 index 0000000000..7705002eca --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/Configurator.sol @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol"; +import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; + +import {IConfigurator} from "./interfaces/IConfigurator.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +// OCR2 standard +uint256 constant MAX_NUM_ORACLES = 31; + +// Subsequent versions of the onchain config must be backwards compatible and only append fields +uint256 constant MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION = 1; + +/** + * @title Configurator + * @author samsondav + * @notice This contract is intended to be deployed on the source chain and acts as a OCR3 configurator for LLO/Mercury + * + */ +contract Configurator is IConfigurator, ConfirmedOwner, ITypeAndVersion, IERC165 { + /// @notice This error is thrown whenever trying to set a config + /// with a fault tolerance of 0 + error FaultToleranceMustBePositive(); + + /// @notice This error is thrown whenever a report is signed + /// with more than the max number of signers + /// @param numSigners The number of signers who have signed the report + /// @param maxSigners The maximum number of signers that can sign a report + error ExcessSigners(uint256 numSigners, uint256 maxSigners); + + /// @notice This error is thrown whenever a report is signed + /// with less than the minimum number of signers + /// @param numSigners The number of signers who have signed the report + /// @param minSigners The minimum number of signers that need to sign a report + error InsufficientSigners(uint256 numSigners, uint256 minSigners); + + /// @notice This error is thrown whenever the onchainConfig length is invalid + /// (must be at least 64 bytes) + /// @param onchainConfigLength The (wrong) length of the onchainConfig + error InvalidOnchainLength(uint256 onchainConfigLength); + + /// @notice This error is thrown if the onchainConfig version is too old. + /// @param version The version of the onchainConfig + error UnsupportedOnchainConfigVersion(uint256 version); + + /// @notice This event is emitted when a production config is set with a non-zero predecessor config digest in the + /// on-chain config. + /// @param predecessorConfigDigest The predecessor config digest + error NonZeroPredecessorConfigDigest(bytes32 predecessorConfigDigest); + + /// @notice This event is emitted when a staging config is set with a predecessor config digest that does not match + /// the current production config digest. + /// @param predecessorConfigDigest The predecessor config digest + error InvalidPredecessorConfigDigest(bytes32 predecessorConfigDigest); + + /// @notice This event is emitted during promoteStagingConfig if the isGreenProduction flag does not match the + /// contract state + /// @param configId The configId + /// @param isGreenProductionContractState The current (correct) isGreenProduction state according to the contract + error IsGreenProductionMustMatchContractState(bytes32 configId, bool isGreenProductionContractState); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set + /// @param configId The configId that has never been set + error ConfigUnset(bytes32 configId); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set as a staging config + /// @param configId The configId that has never been set as a staging config + /// @param isGreenProduction The isGreenProduction flag + error ConfigUnsetStaging(bytes32 configId, bool isGreenProduction); + + /// @notice This event is emitted during promoteStagingConfig if the configId has never been set as a production + /// config + /// @param configId The configId that has never been set as a production config + /// @param isGreenProduction The isGreenProduction flag + error ConfigUnsetProduction(bytes32 configId, bool isGreenProduction); + + struct ConfigurationState { + // The number of times a configuration (either staging or production) has + // been set for this configId + uint64 configCount; + // The block number of the block the last time + // the configuration was updated. + uint32 latestConfigBlockNumber; + // isGreenProduction is a bit flip that indicates whether blue is production + // exactly one of blue/green must be production at all times. + // 0 -> blue is production + // 1 -> green is production + // + // So, to clarify, if isGreenProduction is false (initial state) then: + // [0](blue) is production and [1](green) is staging/retired + // + // and if isGreenProduction is true then: + // [0](blue) is staging/retired and [1](green) is production + // State is swapped every time a staging config is promoted to production. + bool isGreenProduction; + // The digest of the current configurations (0 is always blue, 1 is always green) + bytes32[2] configDigest; + } + + constructor() ConfirmedOwner(msg.sender) {} + + /// @notice Configuration states keyed on config ID + /// @dev The first element is the blue configuration state + /// and the second element is the green configuration state + mapping(bytes32 => ConfigurationState) internal s_configurationStates; + + /// @inheritdoc IConfigurator + function setProductionConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override checkConfigValid(signers.length, f) onlyOwner { + if (onchainConfig.length < 64) revert InvalidOnchainLength(onchainConfig.length); + + // Ensure that predecessorConfigDigest is unset and version is correct + uint256 version; + bytes32 predecessorConfigDigest; + assembly { + version := mload(add(onchainConfig, 32)) + predecessorConfigDigest := mload(add(onchainConfig, 64)) + } + if (version < MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION) revert UnsupportedOnchainConfigVersion(version); + if (predecessorConfigDigest != 0) revert NonZeroPredecessorConfigDigest(predecessorConfigDigest); + + _setConfig( + configId, + block.chainid, + address(this), + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + true + ); + } + + /// @inheritdoc IConfigurator + function setStagingConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override checkConfigValid(signers.length, f) onlyOwner { + if (onchainConfig.length < 64) revert InvalidOnchainLength(onchainConfig.length); + + // Ensure that predecessorConfigDigest is set and corresponds to an + // existing production instance + uint256 version; + bytes32 predecessorConfigDigest; + assembly { + version := mload(add(onchainConfig, 32)) + predecessorConfigDigest := mload(add(onchainConfig, 64)) + } + if (version < MIN_SUPPORTED_ONCHAIN_CONFIG_VERSION) revert UnsupportedOnchainConfigVersion(version); + + ConfigurationState memory configurationState = s_configurationStates[configId]; + if ( + predecessorConfigDigest == bytes32(0) + || predecessorConfigDigest + != s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 1 : 0] + ) revert InvalidPredecessorConfigDigest(predecessorConfigDigest); + + _setConfig( + configId, + block.chainid, + address(this), + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + false + ); + } + + /// @inheritdoc IConfigurator + // This will trigger the following: + // - Offchain ShouldRetireCache will start returning true for the old (production) + // protocol instance + // - Once the old production instance retires it will generate a handover + // retirement report + // - The staging instance will become the new production instance once + // any honest oracle that is on both instances forward the retirement + // report from the old instance to the new instance via the + // PredecessorRetirementReportCache + // + // Note: the promotion flow only works if the previous production instance + // is working correctly & generating reports. If that's not the case, the + // owner is expected to "setProductionConfig" directly instead. This will + // cause "gaps" to be created, but that seems unavoidable in such a scenario. + function promoteStagingConfig(bytes32 configId, bool isGreenProduction) external onlyOwner { + ConfigurationState storage configurationState = s_configurationStates[configId]; + if (isGreenProduction != configurationState.isGreenProduction) { + revert IsGreenProductionMustMatchContractState(configId, !isGreenProduction); + } + if (configurationState.configCount == 0) revert ConfigUnset(configId); + if (configurationState.configDigest[isGreenProduction ? 0 : 1] == bytes32(0)) { + revert ConfigUnsetStaging(configId, isGreenProduction); + } + bytes32 retiredConfigDigest = configurationState.configDigest[isGreenProduction ? 1 : 0]; + if (retiredConfigDigest == bytes32(0)) revert ConfigUnsetProduction(configId, isGreenProduction); + + configurationState.isGreenProduction = !isGreenProduction; // flip blue<->green + emit PromoteStagingConfig(configId, retiredConfigDigest, !isGreenProduction); + } + + /// @notice Sets config based on the given arguments + /// @param configId config ID to set config for + /// @param sourceChainId Chain ID of source config + /// @param sourceAddress Address of source config Verifier + /// @param signers addresses with which oracles sign the reports + /// @param offchainTransmitters CSA key for the ith Oracle + /// @param f number of faulty oracles the system can tolerate + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version number for offchainEncoding schema + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the + /// contract + function _setConfig( + bytes32 configId, + uint256 sourceChainId, + address sourceAddress, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig, + bool isProduction + ) internal { + ConfigurationState storage configurationState = s_configurationStates[configId]; + + uint64 newConfigCount = ++configurationState.configCount; + + bytes32 configDigest = _configDigestFromConfigData( + configId, + sourceChainId, + sourceAddress, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + + if (isProduction) { + emit ProductionConfigSet( + configId, + configurationState.latestConfigBlockNumber, + configDigest, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + configurationState.isGreenProduction + ); + s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 1 : 0] = configDigest; + } else { + emit StagingConfigSet( + configId, + configurationState.latestConfigBlockNumber, + configDigest, + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig, + configurationState.isGreenProduction + ); + s_configurationStates[configId].configDigest[configurationState.isGreenProduction ? 0 : 1] = configDigest; + } + + configurationState.latestConfigBlockNumber = uint32(block.number); + } + + /// @notice Generates the config digest from config data + /// @param configId config ID to set config for + /// @param sourceChainId Chain ID of configurator contract + /// @param sourceAddress Address of configurator contract + /// @param configCount ordinal number of this config setting among all config settings over the life of this contract + /// @param signers ith element is address ith oracle uses to sign a report + /// @param offchainTransmitters ith element is address ith oracle used to transmit reports (in this case used for + /// flexible additional field, such as CSA pub keys) + /// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly + /// @param onchainConfig serialized configuration used by the contract (and possibly oracles) + /// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter + /// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the + /// contract + /// @dev This function is a modified version of the method from OCR2Abstract + function _configDigestFromConfigData( + bytes32 configId, + uint256 sourceChainId, + address sourceAddress, + uint64 configCount, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + configId, + sourceChainId, + sourceAddress, + configCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + // 0x0009 corresponds to ConfigDigestPrefixLLO in libocr + uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool isVerifier) { + return interfaceId == type(IConfigurator).interfaceId; + } + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "Configurator 0.5.0"; + } + + modifier checkConfigValid(uint256 numSigners, uint256 f) { + if (f == 0) revert FaultToleranceMustBePositive(); + if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES); + if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1); + _; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IChannelConfigStore.sol new file mode 100644 index 0000000000..31998f25a3 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IChannelConfigStore.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IChannelConfigStore is IERC165 { + type ChannelAdderId is uint32; + + function setChannelDefinitions(uint32 donId, string calldata url, bytes32 sha) external; + function addChannelDefinitions( + uint32 donId, + ChannelAdderId channelAdderId, + string calldata url, + bytes32 sha + ) external; + function setChannelAdderAddress(ChannelAdderId channelAdderId, address adderAddress) external; + function setChannelAdder(uint32 donId, ChannelAdderId channelAdderId, bool allowed) external; + function getChannelAdderAddress( + ChannelAdderId channelAdderId + ) external view returns (address); + function isChannelAdderAllowed(uint32 donId, ChannelAdderId channelAdderId) external view returns (bool); + function getAllowedChannelAdders( + uint32 donId + ) external view returns (ChannelAdderId[] memory); +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IConfigurator.sol new file mode 100644 index 0000000000..e90f83b1f0 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/interfaces/IConfigurator.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +interface IConfigurator { + /// @notice This event is emitted whenever a new production configuration is set for a feed. It triggers a new run of + /// the offchain reporting protocol. + event ProductionConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + + /// @notice This event is emitted whenever a new staging configuration is set for a feed. It triggers a new run of the + /// offchain reporting protocol. + event StagingConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + + event PromoteStagingConfig(bytes32 indexed configId, bytes32 indexed retiredConfigDigest, bool isGreenProduction); + + /// @notice Promotes the staging configuration to production + // currentState must match the current state for the given configId (prevents + // accidentally double-flipping if same transaction is sent twice) + function promoteStagingConfig(bytes32 configId, bool currentState) external; + + function setProductionConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; + + function setStagingConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/ChannelConfigStore.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/ChannelConfigStore.t.sol new file mode 100644 index 0000000000..4442341317 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/ChannelConfigStore.t.sol @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ChannelConfigStore} from "../ChannelConfigStore.sol"; +import {IChannelConfigStore} from "../interfaces/IChannelConfigStore.sol"; +import {ExposedChannelConfigStore} from "./mocks/ExposedChannelConfigStore.sol"; +import {Test} from "forge-std/Test.sol"; + +/** + * @title ChannelConfigStoreTest + * @author samsondav + * @notice Base class for ChannelConfigStore tests + */ +contract ChannelConfigStoreTest is Test { + ExposedChannelConfigStore public channelConfigStore; + + event NewChannelDefinition(uint256 indexed donId, uint32 version, string url, bytes32 sha); + event ChannelDefinitionAdded( + uint256 indexed donId, IChannelConfigStore.ChannelAdderId indexed channelAdderId, string url, bytes32 sha + ); + event ChannelAdderSet(uint256 indexed donId, IChannelConfigStore.ChannelAdderId indexed channelAdderId, bool allowed); + event ChannelAdderAddressSet(IChannelConfigStore.ChannelAdderId indexed channelAdderId, address adderAddress); + + address public constant CHANNEL_ADDER_1 = address(0x1234); + address public constant CHANNEL_ADDER_2 = address(0x5678); + IChannelConfigStore.ChannelAdderId public constant CHANNEL_ADDER_ID_1 = IChannelConfigStore.ChannelAdderId.wrap(1000); + IChannelConfigStore.ChannelAdderId public constant CHANNEL_ADDER_ID_2 = IChannelConfigStore.ChannelAdderId.wrap(1001); + uint32 public constant DON_ID_1 = 42; + uint32 public constant DON_ID_2 = 99; + + function setUp() public virtual { + channelConfigStore = new ExposedChannelConfigStore(); + } + + function testTypeAndVersion() public view { + assertEq(channelConfigStore.typeAndVersion(), "ChannelConfigStore 1.0.0"); + } + + function testSupportsInterface() public view { + assertTrue(channelConfigStore.supportsInterface(type(IChannelConfigStore).interfaceId)); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + + vm.startPrank(address(2)); + channelConfigStore.setChannelDefinitions(42, "url", keccak256("sha")); + } + + function testSetChannelDefinitions() public { + vm.expectEmit(); + emit NewChannelDefinition(42, 1, "url", keccak256("sha")); + channelConfigStore.setChannelDefinitions(42, "url", keccak256("sha")); + + vm.expectEmit(); + emit NewChannelDefinition(42, 2, "url2", keccak256("sha2")); + channelConfigStore.setChannelDefinitions(42, "url2", keccak256("sha2")); + + assertEq(channelConfigStore.exposedReadChannelDefinitionStates(42), uint32(2)); + } +} + +/** + * @title ChannelConfigStoreChannelAdderTest + * @notice Test suite for channel adder functionality + */ +contract ChannelConfigStoreChannelAdderTest is ChannelConfigStoreTest { + function testSetChannelAdderAddress() public { + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_UpdatesExistingAddress() public { + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_2); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_2); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), CHANNEL_ADDER_2); + } + + function testSetChannelAdderAddress_CanSetToZeroAddress() public { + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelAdderAddressSet(CHANNEL_ADDER_ID_1, address(0)); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, address(0)); + + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), address(0)); + } + + function testSetChannelAdderAddress_RevertsWhenCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_RevertsForReservedChannelAdderId() public { + // Channel adder IDs 0-999 are reserved + IChannelConfigStore.ChannelAdderId reservedId = IChannelConfigStore.ChannelAdderId.wrap(0); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdderAddress(reservedId, CHANNEL_ADDER_1); + + reservedId = IChannelConfigStore.ChannelAdderId.wrap(999); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdderAddress(reservedId, CHANNEL_ADDER_1); + } + + function testSetChannelAdderAddress_SucceedsAtMinimumAllowedId() public { + // Channel adder ID 1000 is the minimum allowed + IChannelConfigStore.ChannelAdderId minAllowedId = IChannelConfigStore.ChannelAdderId.wrap(1000); + channelConfigStore.setChannelAdderAddress(minAllowedId, CHANNEL_ADDER_1); + assertEq(channelConfigStore.getChannelAdderAddress(minAllowedId), CHANNEL_ADDER_1); + } + + function testSetChannelAdder_AllowsChannelAdder() public { + vm.expectEmit(); + emit ChannelAdderSet(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_RemovesChannelAdder() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + + vm.expectEmit(); + emit ChannelAdderSet(DON_ID_1, CHANNEL_ADDER_ID_1, false); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, false); + + assertFalse(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_AllowsMultipleAddersPerDon() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_2)); + + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 2); + + // Assert that allowedAdders contains both CHANNEL_ADDER_ID_1 and CHANNEL_ADDER_ID_2 (order agnostic) + assertTrue( + ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + ) + || ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + ) + ); + } + + function testSetChannelAdder_AdderCanBeAllowedOnMultipleDons() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_2, CHANNEL_ADDER_ID_1, true); + + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_2, CHANNEL_ADDER_ID_1)); + } + + function testSetChannelAdder_RevertsWhenCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + } + + function testGetAllowedChannelAdders_ReturnsEmptyArrayWhenNone() public view { + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 0); + } + + function testGetAllowedChannelAdders_ReturnsAllAllowedAdders() public { + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + IChannelConfigStore.ChannelAdderId[] memory allowedAdders = channelConfigStore.getAllowedChannelAdders(DON_ID_1); + assertEq(allowedAdders.length, 2); + + // EnumerableSet doesn't guarantee order, so check both combinations + assertTrue( + ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + ) + || ( + IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[0]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_2) + && IChannelConfigStore.ChannelAdderId.unwrap(allowedAdders[1]) + == IChannelConfigStore.ChannelAdderId.unwrap(CHANNEL_ADDER_ID_1) + ) + ); + } + + function testIsChannelAdderAllowed_ReturnsFalseForNonAllowedAdder() public view { + assertFalse(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + } + + function testAddChannelDefinitions_SucceedsWithAuthorizedAdder() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenCallerNotMatchingChannelAdderId() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + // Try to call from wrong address + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_2); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenAdderNotAllowedForDon() public { + // Setup: set address but don't allow adder for this DON + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenNoAddressSetForChannelAdderId() public { + // Setup: allow adder but don't set address + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + } + + function testAddChannelDefinitions_RevertsWhenAdderRemovedFromDon() public { + // Setup: set address and allow adder + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + + // Verify it works + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url", keccak256("sha")); + + // Remove adder + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, false); + + // Should now revert + vm.expectRevert(ChannelConfigStore.UnauthorizedChannelAdder.selector); + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url2", keccak256("sha2")); + } + + function testAddChannelDefinitions_MultipleAddersCanAddToDon() public { + // Setup: set addresses and allow both adders + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_2, CHANNEL_ADDER_2); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + // Both should be able to add + vm.prank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + + vm.prank(CHANNEL_ADDER_2); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + } + + function testAddChannelDefinitions_AdderCanAddToMultipleDons() public { + // Setup: set address and allow adder on multiple DONs + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_2, CHANNEL_ADDER_ID_1, true); + + // Should work for both DONs + vm.startPrank(CHANNEL_ADDER_1); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + channelConfigStore.addChannelDefinitions(DON_ID_2, CHANNEL_ADDER_ID_1, "url2", keccak256("sha2")); + vm.stopPrank(); + } + + function testGetChannelAdderAddress_ReturnsZeroAddressForUnsetId() public view { + assertEq(channelConfigStore.getChannelAdderAddress(CHANNEL_ADDER_ID_1), address(0)); + } + + function testAddChannelDefinitions_OneAddressCanControlMultipleChannelAdders() public { + // Setup: one address controls two different channel adder IDs + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_1, CHANNEL_ADDER_1); + channelConfigStore.setChannelAdderAddress(CHANNEL_ADDER_ID_2, CHANNEL_ADDER_1); // same address + + // Allow both channel adder IDs for the DON + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_1, true); + channelConfigStore.setChannelAdder(DON_ID_1, CHANNEL_ADDER_ID_2, true); + + // Verify the same address can use both channel adder IDs + vm.startPrank(CHANNEL_ADDER_1); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_1, "url1", keccak256("sha1")); + + vm.expectEmit(); + emit ChannelDefinitionAdded(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + channelConfigStore.addChannelDefinitions(DON_ID_1, CHANNEL_ADDER_ID_2, "url2", keccak256("sha2")); + + vm.stopPrank(); + + // Verify both channel adder IDs are allowed + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_1)); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, CHANNEL_ADDER_ID_2)); + } + + function testSetChannelAdder_RevertsForReservedChannelAdderId() public { + // Channel adder IDs 0-999 are reserved + IChannelConfigStore.ChannelAdderId reservedId = IChannelConfigStore.ChannelAdderId.wrap(0); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, true); + + reservedId = IChannelConfigStore.ChannelAdderId.wrap(999); + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, true); + + // Should also revert when trying to remove a reserved ID + vm.expectRevert(ChannelConfigStore.ReservedChannelAdderId.selector); + channelConfigStore.setChannelAdder(DON_ID_1, reservedId, false); + } + + function testSetChannelAdder_SucceedsAtMinimumAllowedId() public { + // Channel adder ID 1000 is the minimum allowed + IChannelConfigStore.ChannelAdderId minAllowedId = IChannelConfigStore.ChannelAdderId.wrap(1000); + channelConfigStore.setChannelAdder(DON_ID_1, minAllowedId, true); + assertTrue(channelConfigStore.isChannelAdderAllowed(DON_ID_1, minAllowedId)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/BaseConfiguratorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/BaseConfiguratorTest.t.sol new file mode 100644 index 0000000000..ab0cde98e0 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/BaseConfiguratorTest.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; +import {IConfigurator} from "../../interfaces/IConfigurator.sol"; + +import {ExposedChannelConfigStore} from "../mocks/ExposedChannelConfigStore.sol"; +import {ExposedConfigurator} from "../mocks/ExposedConfigurator.sol"; +import {Test} from "forge-std/Test.sol"; + +/** + * @title ConfiguratorTest + * @author samsondav + * @notice Base class for Configurator tests + */ +contract BaseTest is Test { + uint256 internal constant MAX_ORACLES = 31; + address internal constant USER = address(2); + bytes32 internal constant CONFIG_ID_1 = (keccak256("CONFIG_ID_1")); + uint8 internal constant FAULT_TOLERANCE = 10; + uint64 internal constant OFFCHAIN_CONFIG_VERSION = 1; + + bytes32[] internal s_offchaintransmitters; + bool private s_baseTestInitialized; + + Configurator internal s_configurator; + ExposedConfigurator internal s_exposedConfigurator; + + event ProductionConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + event StagingConfigSet( + bytes32 indexed configId, + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + bytes[] signers, + bytes32[] offchainTransmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig, + bool isGreenProduction + ); + event PromoteStagingConfig(bytes32 indexed configId, bytes32 indexed retiredConfigDigest, bool isGreenProduction); + + bytes[MAX_ORACLES] internal s_signers; + + function setUp() public virtual { + // BaseTest.setUp may be called multiple times from tests' setUp due to inheritance. + if (s_baseTestInitialized) return; + s_baseTestInitialized = true; + + s_configurator = new Configurator(); + s_exposedConfigurator = new ExposedConfigurator(); + + for (uint256 i; i < MAX_ORACLES; i++) { + bytes memory mockSigner = abi.encodePacked(i + 1); + s_signers[i] = mockSigner; + } + + for (uint256 i; i < MAX_ORACLES; i++) { + s_offchaintransmitters.push(bytes32(i + 1)); + } + } + + function _getSigners( + uint256 numSigners + ) internal view returns (bytes[] memory) { + bytes[] memory signers = new bytes[](numSigners); + for (uint256 i; i < numSigners; i++) { + signers[i] = s_signers[i]; + } + return signers; + } + + function _getOffchainTransmitters( + uint256 numTransmitters + ) internal pure returns (bytes32[] memory) { + bytes32[] memory transmitters = new bytes32[](numTransmitters); + for (uint256 i; i < numTransmitters; i++) { + transmitters[i] = bytes32(101 + i); + } + return transmitters; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol new file mode 100644 index 0000000000..1954bec92a --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorPromoteStagingConfigTest.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; + +contract ConfiguratorPromoteStagingConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.startPrank(USER); + + vm.expectRevert("Only callable by owner"); + + s_configurator.promoteStagingConfig(CONFIG_ID_1, false); + } + + function test_revertsIfIsGreenProductionDoesNotMatchContractState() public { + vm.expectRevert( + abi.encodeWithSelector(Configurator.IsGreenProductionMustMatchContractState.selector, CONFIG_ID_1, false) + ); + s_configurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_revertsIfNoConfigHasEverBeenSetWithThisConfigId() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnset.selector, keccak256("nonExistentConfigId"))); + s_configurator.promoteStagingConfig(keccak256("nonExistentConfigId"), false); + } + + function test_revertsIfStagingConfigDigestIsZero() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, Configurator.ConfigurationState(1, uint32(block.number), false, [bytes32(0), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetStaging.selector, CONFIG_ID_1, false)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + + // isGreenProduction = true + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, Configurator.ConfigurationState(1, uint32(block.number), true, [bytes32(0), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetStaging.selector, CONFIG_ID_1, true)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_revertsIfProductionConfigDigestIsZero() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), false, [bytes32(0), keccak256("stagingConfigDigest")]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetProduction.selector, CONFIG_ID_1, false)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + + // isGreenProduction = true + + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState(1, uint32(block.number), true, [keccak256("stagingConfigDigest"), bytes32(0)]) + ); + + vm.expectRevert(abi.encodeWithSelector(Configurator.ConfigUnsetProduction.selector, CONFIG_ID_1, true)); + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + } + + function test_promotesStagingConfig() public { + // isGreenProduction = false + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState( + 1, uint32(block.number), false, [keccak256("productionConfigDigest"), keccak256("stagingConfigDigest")] + ) + ); + + vm.expectEmit(); + emit PromoteStagingConfig(CONFIG_ID_1, keccak256("productionConfigDigest"), true); + + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, false); + assertEq(s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1).isGreenProduction, true); + + // isGreenProduction = true + + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, + Configurator.ConfigurationState( + 1, uint32(block.number), true, [keccak256("stagingConfigDigest"), keccak256("productionConfigDigest")] + ) + ); + + vm.expectEmit(); + emit PromoteStagingConfig(CONFIG_ID_1, keccak256("productionConfigDigest"), false); + + s_exposedConfigurator.promoteStagingConfig(CONFIG_ID_1, true); + assertEq(s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1).isGreenProduction, false); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol new file mode 100644 index 0000000000..701a3fdb36 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetProductionConfigTest.t.sol @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; + +contract ConfiguratorSetProductionConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + bytes[] memory signers = _getSigners(MAX_ORACLES); + + vm.startPrank(USER); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfSetWithTooManySigners() public { + bytes[] memory signers = new bytes[](MAX_ORACLES + 1); + vm.expectRevert(abi.encodeWithSelector(Configurator.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfFaultToleranceIsZero() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.FaultToleranceMustBePositive.selector)); + bytes[] memory signers = _getSigners(MAX_ORACLES); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, 0, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfNotEnoughSigners() public { + bytes[] memory signers = _getSigners(2); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfOnchainConfigIsInvalid() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + bytes memory onchainConfig = bytes(""); + uint8 f = 1; + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.InvalidOnchainLength.selector, onchainConfig.length)); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + onchainConfig = abi.encode(uint256(0), bytes32(0)); + + vm.expectRevert(abi.encodeWithSelector(Configurator.UnsupportedOnchainConfigVersion.selector, uint256(0))); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + onchainConfig = + abi.encode(uint256(1), keccak256("any non-zero predecessor config digest is invalid for production")); + + vm.expectRevert( + abi.encodeWithSelector( + Configurator.NonZeroPredecessorConfigDigest.selector, + keccak256("any non-zero predecessor config digest is invalid for production") + ) + ); + s_configurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + } + + function test_supportsHigherVersionsIgnoringExcessOnchainConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + bytes memory onchainConfig = abi.encodePacked(uint256(2), bytes32(0), keccak256("some rubbish")); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + // initial block number + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 0, + cd1, + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + } + + function test_correctlyUpdatesTheConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + bytes memory onchainConfig = abi.encodePacked(uint256(1), bytes32(0)); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + // initial block number + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 0, + cd1, + 1, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + Configurator.ConfigurationState memory configurationState = + s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd1); + assertEq(configurationState.configDigest[1], 0); // no staging config yet + assertEq(configurationState.configCount, 1); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // go to new block + vm.roll(10); + + // set it again, configCount=2 + + bytes32 cd2 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 5, + cd2, + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd2); + assertEq(configurationState.configDigest[1], 0); // no staging config yet + assertEq(configurationState.configCount, 2); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // when isGreenProduction=true + s_exposedConfigurator.exposedSetIsGreenProduction(CONFIG_ID_1, true); + + // go to new block + vm.roll(15); + + // set it again, configCount=3 + bytes32 cd3 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit ProductionConfigSet( + CONFIG_ID_1, + 10, + cd3, + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + true + ); + + s_exposedConfigurator.setProductionConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd2); // the previous config left unchanged + assertEq(configurationState.configDigest[1], cd3); // new config is on green now because green is production due to + // isGreenProduction=true + assertEq(configurationState.configCount, 3); + assertEq(configurationState.isGreenProduction, true); + assertEq(configurationState.latestConfigBlockNumber, block.number); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol new file mode 100644 index 0000000000..c7ac50a61b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorSetStagingConfigTest.t.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; + +contract ConfiguratorSetStagingConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + bytes[] memory signers = _getSigners(MAX_ORACLES); + + vm.startPrank(USER); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfSetWithTooManySigners() public { + bytes[] memory signers = new bytes[](MAX_ORACLES + 1); + vm.expectRevert(abi.encodeWithSelector(Configurator.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfFaultToleranceIsZero() public { + vm.expectRevert(abi.encodeWithSelector(Configurator.FaultToleranceMustBePositive.selector)); + bytes[] memory signers = _getSigners(MAX_ORACLES); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, 0, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfNotEnoughSigners() public { + bytes[] memory signers = _getSigners(2); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, s_offchaintransmitters, FAULT_TOLERANCE, bytes(""), OFFCHAIN_CONFIG_VERSION, bytes("") + ); + } + + function test_revertsIfOnchainConfigIsInvalid() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + bytes memory onchainConfig = bytes(""); + uint8 f = 1; + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.InvalidOnchainLength.selector, onchainConfig.length)); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + onchainConfig = abi.encode(uint256(0), keccak256("previousConfigDigest")); + + vm.expectRevert(abi.encodeWithSelector(Configurator.UnsupportedOnchainConfigVersion.selector, uint256(0))); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + onchainConfig = abi.encode(uint256(1), keccak256("previousConfigDigest")); + + vm.expectRevert( + abi.encodeWithSelector(Configurator.InvalidPredecessorConfigDigest.selector, keccak256("previousConfigDigest")) + ); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + onchainConfig = abi.encode(uint256(1), uint256(0)); + + vm.expectRevert(abi.encodeWithSelector(Configurator.InvalidPredecessorConfigDigest.selector, uint256(0))); + s_configurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + } + + function test_correctlyUpdatesTheConfig() public { + bytes[] memory signers = _getSigners(4); + bytes32[] memory offchainTransmitters = _getOffchainTransmitters(4); + uint8 f = 1; + + // initial block number + vm.roll(2); + + bytes32 productionConfigDigest = keccak256("productionConfigDigest"); + s_exposedConfigurator.exposedSetConfigurationState( + CONFIG_ID_1, Configurator.ConfigurationState(1, uint32(block.number), false, [productionConfigDigest, bytes32(0)]) + ); + bytes memory onchainConfig = abi.encodePacked(uint256(1), productionConfigDigest); + bytes memory offchainConfig = abi.encodePacked(keccak256("offchainConfig")); + + vm.roll(5); + + bytes32 cd1 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + // when isGreenProduction=false + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 2, + cd1, + 2, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + Configurator.ConfigurationState memory configurationState = + s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], productionConfigDigest); + assertEq(configurationState.configDigest[1], cd1); + assertEq(configurationState.configCount, 2); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // go to new block + vm.roll(10); + + // set it again, configCount=2 + + bytes32 cd2 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 5, + cd2, + 3, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + false + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], productionConfigDigest); + assertEq(configurationState.configDigest[1], cd2); + assertEq(configurationState.configCount, 3); + assertEq(configurationState.isGreenProduction, false); + assertEq(configurationState.latestConfigBlockNumber, block.number); + + // when isGreenProduction=true + s_exposedConfigurator.exposedSetIsGreenProduction(CONFIG_ID_1, true); + onchainConfig = abi.encodePacked(uint256(1), cd2); // predecessorConfigDigest the production digest is now the green + // digest + + // go to new block + vm.roll(15); + + // set it again, configCount=3 + bytes32 cd3 = s_exposedConfigurator.exposedConfigDigestFromConfigData( + CONFIG_ID_1, + block.chainid, + address(s_exposedConfigurator), + 4, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig + ); + + vm.expectEmit(); + emit StagingConfigSet( + CONFIG_ID_1, + 10, + cd3, + 4, + signers, + offchainTransmitters, + f, + onchainConfig, + OFFCHAIN_CONFIG_VERSION, + offchainConfig, + true + ); + + s_exposedConfigurator.setStagingConfig( + CONFIG_ID_1, signers, offchainTransmitters, f, onchainConfig, OFFCHAIN_CONFIG_VERSION, offchainConfig + ); + + configurationState = s_exposedConfigurator.exposedReadConfigurationStates(CONFIG_ID_1); + assertEq(configurationState.configDigest[0], cd3); // new config is on blue now because blue is staging due to + // isGreenProduction=true + assertEq(configurationState.configDigest[1], cd2); // the previous config left unchanged + assertEq(configurationState.configCount, 4); + assertEq(configurationState.isGreenProduction, true); + assertEq(configurationState.latestConfigBlockNumber, block.number); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorTest.t.sol new file mode 100644 index 0000000000..1b5ebb4ea1 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/configurator/ConfiguratorTest.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; +import {IConfigurator} from "../../interfaces/IConfigurator.sol"; +import {BaseTest} from "./BaseConfiguratorTest.t.sol"; + +contract ConfiguratorTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function testTypeAndVersion() public view { + assertEq(s_configurator.typeAndVersion(), "Configurator 0.5.0"); + } + + function testSupportsInterface() public view { + assertTrue(s_configurator.supportsInterface(type(IConfigurator).interfaceId)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedChannelConfigStore.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedChannelConfigStore.sol new file mode 100644 index 0000000000..417ebbfd3b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedChannelConfigStore.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ChannelConfigStore} from "../../ChannelConfigStore.sol"; + +// Exposed ChannelConfigStore exposes certain internal ChannelConfigStore +// methods/structures so that golang code can access them, and we get +// reliable type checking on their usage +contract ExposedChannelConfigStore is ChannelConfigStore { + constructor() {} + + function exposedReadChannelDefinitionStates( + uint256 donId + ) public view returns (uint256) { + return s_channelDefinitionVersions[donId]; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedConfigurator.sol new file mode 100644 index 0000000000..5d8cd5fa4e --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/configuration/test/mocks/ExposedConfigurator.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {Configurator} from "../../Configurator.sol"; + +// Exposed ChannelConfigStore exposes certain internal ChannelConfigStore +// methods/structures so that golang code can access them, and we get +// reliable type checking on their usage +contract ExposedConfigurator is Configurator { + constructor() {} + + function exposedReadConfigurationStates( + bytes32 configId + ) public view returns (ConfigurationState memory) { + return s_configurationStates[configId]; + } + + function exposedSetIsGreenProduction(bytes32 configId, bool isGreenProduction) public { + s_configurationStates[configId].isGreenProduction = isGreenProduction; + } + + function exposedSetConfigurationState(bytes32 configId, ConfigurationState memory state) public { + s_configurationStates[configId] = state; + } + + function exposedConfigDigestFromConfigData( + bytes32 _configId, + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + bytes[] memory _signers, + bytes32[] memory _offchainTransmitters, + uint8 _f, + bytes calldata _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) public pure returns (bytes32) { + return _configDigestFromConfigData( + _configId, + _chainId, + _contractAddress, + _configCount, + _signers, + _offchainTransmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IFeeManager.sol new file mode 100644 index 0000000000..2e8379ac6b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IFeeManager.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IFeeManager is IERC165, IVerifierFeeManager { + /** + * @notice Calculate the applied fee and the reward from a report. If the sender is a subscriber, they will receive a + * discount. + * @param subscriber address trying to verify + * @param report report to calculate the fee for + * @param quoteAddress address of the quote payment token + * @return (fee, reward, totalDiscount) fee and the reward data with the discount applied + */ + function getFeeAndReward( + address subscriber, + bytes memory report, + address quoteAddress + ) external returns (Common.Asset memory, Common.Asset memory, uint256); + + /** + * @notice Sets the native surcharge + * @param surcharge surcharge to be paid if paying in native + */ + function setNativeSurcharge( + uint64 surcharge + ) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param feedId feed id to apply the discount to + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external; + + /** + * @notice Withdraws any native or LINK rewards to the owner address + * @param assetAddress address of the asset to withdraw + * @param recipientAddress address to withdraw to + * @param quantity quantity to withdraw + */ + function withdraw(address assetAddress, address recipientAddress, uint192 quantity) external; + + /** + * @notice Returns the link balance of the fee manager + * @return link balance of the fee manager + */ + function linkAvailableForPayment() external returns (uint256); + + /** + * @notice Admin function to pay the LINK deficit for a given config digest + * @param configDigest the config digest to pay the deficit for + */ + function payLinkDeficit( + bytes32 configDigest + ) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external; + + /** + * @notice The structure to hold a fee and reward to verify a report + * @param digest the digest linked to the fee and reward + * @param fee the fee paid to verify the report + * @param reward the reward paid upon verification + * & @param appliedDiscount the discount applied to the reward + */ + struct FeeAndReward { + bytes32 configDigest; + Common.Asset fee; + Common.Asset reward; + uint256 appliedDiscount; + } + + /** + * @notice The structure to hold quote metadata + * @param quoteAddress the address of the quote + */ + struct Quote { + address quoteAddress; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IRewardManager.sol new file mode 100644 index 0000000000..7ae18969cc --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IRewardManager.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IRewardManager is IERC165 { + /** + * @notice Record the fee received for a particular pool + * @param payments array of structs containing pool id and amount + * @param payee the user the funds should be retrieved from + */ + function onFeePaid(FeePayment[] calldata payments, address payee) external; + + /** + * @notice Claims the rewards in a specific pool + * @param poolIds array of poolIds to claim rewards for + */ + function claimRewards( + bytes32[] calldata poolIds + ) external; + + /** + * @notice Set the RewardRecipients and weights for a specific pool. This should only be called once per pool Id. Else + * updateRewardRecipients should be used. + * @param poolId poolId to set RewardRecipients and weights for + * @param rewardRecipientAndWeights array of each RewardRecipient and associated weight + */ + function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata rewardRecipientAndWeights) external; + + /** + * @notice Updates a subset the reward recipients for a specific poolId. The collective weight of the recipients + * should add up to the recipients existing weights. Any recipients with a weight of 0 will be removed. + * @param poolId the poolId to update + * @param newRewardRecipients array of new reward recipients + */ + function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata newRewardRecipients) external; + + /** + * @notice Pays all the recipients for each of the pool ids + * @param poolId the pool id to pay recipients for + * @param recipients array of recipients to pay within the pool + */ + function payRecipients(bytes32 poolId, address[] calldata recipients) external; + + /** + * @notice Sets the fee manager. This needs to be done post construction to prevent a circular dependency. + * @param newFeeManager address of the new verifier proxy + */ + function setFeeManager( + address newFeeManager + ) external; + + /** + * @notice Gets a list of pool ids which have reward for a specific recipient. + * @param recipient address of the recipient to get pool ids for + * @param startIndex the index to start from + * @param endIndex the index to stop at + */ + function getAvailableRewardPoolIds( + address recipient, + uint256 startIndex, + uint256 endIndex + ) external view returns (bytes32[] memory); + + /** + * @notice The structure to hold a fee payment notice + * @param poolId the poolId receiving the payment + * @param amount the amount being paid + */ + struct FeePayment { + bytes32 poolId; + uint192 amount; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IVerifierFeeManager.sol new file mode 100644 index 0000000000..5f2b0c68fd --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/IVerifierFeeManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IVerifierFeeManager is IERC165 { + /** + * @notice Handles fees for a report from the subscriber and manages rewards + * @param payload report to process the fee for + * @param parameterPayload fee payload + * @param subscriber address of the fee will be applied + */ + function processFee(bytes calldata payload, bytes calldata parameterPayload, address subscriber) external payable; + + /** + * @notice Processes the fees for each report in the payload, billing the subscriber and paying the reward manager + * @param payloads reports to process + * @param parameterPayload fee payload + * @param subscriber address of the user to process fee for + */ + function processFeeBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable; + + /** + * @notice Sets the fee recipients according to the fee manager + * @param configDigest digest of the configuration + * @param rewardRecipientAndWeights the address and weights of all the recipients to receive rewards + */ + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IFeeManager.sol new file mode 100644 index 0000000000..2e8379ac6b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IFeeManager.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IFeeManager is IERC165, IVerifierFeeManager { + /** + * @notice Calculate the applied fee and the reward from a report. If the sender is a subscriber, they will receive a + * discount. + * @param subscriber address trying to verify + * @param report report to calculate the fee for + * @param quoteAddress address of the quote payment token + * @return (fee, reward, totalDiscount) fee and the reward data with the discount applied + */ + function getFeeAndReward( + address subscriber, + bytes memory report, + address quoteAddress + ) external returns (Common.Asset memory, Common.Asset memory, uint256); + + /** + * @notice Sets the native surcharge + * @param surcharge surcharge to be paid if paying in native + */ + function setNativeSurcharge( + uint64 surcharge + ) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param feedId feed id to apply the discount to + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external; + + /** + * @notice Withdraws any native or LINK rewards to the owner address + * @param assetAddress address of the asset to withdraw + * @param recipientAddress address to withdraw to + * @param quantity quantity to withdraw + */ + function withdraw(address assetAddress, address recipientAddress, uint192 quantity) external; + + /** + * @notice Returns the link balance of the fee manager + * @return link balance of the fee manager + */ + function linkAvailableForPayment() external returns (uint256); + + /** + * @notice Admin function to pay the LINK deficit for a given config digest + * @param configDigest the config digest to pay the deficit for + */ + function payLinkDeficit( + bytes32 configDigest + ) external; + + /** + * @notice Adds a subscriber to the fee manager + * @param subscriber address of the subscriber + * @param token token to apply the discount to + * @param discount discount to be applied to the fee + */ + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external; + + /** + * @notice The structure to hold a fee and reward to verify a report + * @param digest the digest linked to the fee and reward + * @param fee the fee paid to verify the report + * @param reward the reward paid upon verification + * & @param appliedDiscount the discount applied to the reward + */ + struct FeeAndReward { + bytes32 configDigest; + Common.Asset fee; + Common.Asset reward; + uint256 appliedDiscount; + } + + /** + * @notice The structure to hold quote metadata + * @param quoteAddress the address of the quote + */ + struct Quote { + address quoteAddress; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IRewardManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IRewardManager.sol new file mode 100644 index 0000000000..7ae18969cc --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IRewardManager.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IRewardManager is IERC165 { + /** + * @notice Record the fee received for a particular pool + * @param payments array of structs containing pool id and amount + * @param payee the user the funds should be retrieved from + */ + function onFeePaid(FeePayment[] calldata payments, address payee) external; + + /** + * @notice Claims the rewards in a specific pool + * @param poolIds array of poolIds to claim rewards for + */ + function claimRewards( + bytes32[] calldata poolIds + ) external; + + /** + * @notice Set the RewardRecipients and weights for a specific pool. This should only be called once per pool Id. Else + * updateRewardRecipients should be used. + * @param poolId poolId to set RewardRecipients and weights for + * @param rewardRecipientAndWeights array of each RewardRecipient and associated weight + */ + function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata rewardRecipientAndWeights) external; + + /** + * @notice Updates a subset the reward recipients for a specific poolId. The collective weight of the recipients + * should add up to the recipients existing weights. Any recipients with a weight of 0 will be removed. + * @param poolId the poolId to update + * @param newRewardRecipients array of new reward recipients + */ + function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata newRewardRecipients) external; + + /** + * @notice Pays all the recipients for each of the pool ids + * @param poolId the pool id to pay recipients for + * @param recipients array of recipients to pay within the pool + */ + function payRecipients(bytes32 poolId, address[] calldata recipients) external; + + /** + * @notice Sets the fee manager. This needs to be done post construction to prevent a circular dependency. + * @param newFeeManager address of the new verifier proxy + */ + function setFeeManager( + address newFeeManager + ) external; + + /** + * @notice Gets a list of pool ids which have reward for a specific recipient. + * @param recipient address of the recipient to get pool ids for + * @param startIndex the index to start from + * @param endIndex the index to stop at + */ + function getAvailableRewardPoolIds( + address recipient, + uint256 startIndex, + uint256 endIndex + ) external view returns (bytes32[] memory); + + /** + * @notice The structure to hold a fee payment notice + * @param poolId the poolId receiving the payment + * @param amount the amount being paid + */ + struct FeePayment { + bytes32 poolId; + uint192 amount; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifier.sol new file mode 100644 index 0000000000..449d3d3b22 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifier.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IVerifier is IERC165 { + /** + * @notice Verifies that the data encoded has been signed + * correctly by routing to the correct verifier. + * @param signedReport The encoded data to be verified. + * @param sender The address that requested to verify the contract. + * This is only used for logging purposes. + * @dev Verification is typically only done through the proxy contract so + * we can't just use msg.sender to log the requester as the msg.sender + * contract will always be the proxy. + * @return verifierResponse The encoded verified response. + */ + function verify(bytes calldata signedReport, address sender) external returns (bytes memory verifierResponse); + + /** + * @notice Verifies that the data encoded has been signed correctly (view version) + * @param signedReport The encoded data to be verified. + * @return verifierResponse The encoded verified response. + * @dev Same as verify() but does not emit events (view-compatible) + */ + function verifyView( + bytes calldata signedReport + ) external view returns (bytes memory verifierResponse); + + /** + * @notice sets a configuration and its associated keys and f + * @param configDigest The digest of the configuration we're setting + * @param signers addresses with which oracles sign the reports + * @param f number of faulty oracles the system can tolerate + * @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards + */ + function setConfig( + bytes32 configDigest, + address[] calldata signers, + uint8 f, + Common.AddressAndWeight[] memory recipientAddressesAndWeights + ) external; + + /** + * @notice updates a configuration that has been set + * @param configDigest The digest of the configuration we're updating + * @param prevSigners the existing signers that need to be removed + * @param newSigners the signers to be added + * @param f the newnumber of faulty oracles the system can tolerate + */ + function updateConfig( + bytes32 configDigest, + address[] calldata prevSigners, + address[] calldata newSigners, + uint8 f + ) external; + + /** + * @notice Activates the configuration for a config digest + * @param configDigest The config digest to activate + * @dev This function can be called by the contract admin to activate a configuration. + */ + function activateConfig( + bytes32 configDigest + ) external; + + /** + * @notice Deactivates the configuration for a config digest + * @param configDigest The config digest to deactivate + * @dev This function can be called by the contract admin to deactivate an incorrect configuration. + */ + function deactivateConfig( + bytes32 configDigest + ) external; + + /** + * @notice information about current offchain reporting protocol configuration + * @param configDigest Config Digest to fetch data for + * @return blockNumber block at which this config was set + */ + function latestConfigDetails( + bytes32 configDigest + ) external view returns (uint32 blockNumber); +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierFeeManager.sol new file mode 100644 index 0000000000..5f2b0c68fd --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierFeeManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Common} from "../../libraries/Common.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +interface IVerifierFeeManager is IERC165 { + /** + * @notice Handles fees for a report from the subscriber and manages rewards + * @param payload report to process the fee for + * @param parameterPayload fee payload + * @param subscriber address of the fee will be applied + */ + function processFee(bytes calldata payload, bytes calldata parameterPayload, address subscriber) external payable; + + /** + * @notice Processes the fees for each report in the payload, billing the subscriber and paying the reward manager + * @param payloads reports to process + * @param parameterPayload fee payload + * @param subscriber address of the user to process fee for + */ + function processFeeBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable; + + /** + * @notice Sets the fee recipients according to the fee manager + * @param configDigest digest of the configuration + * @param rewardRecipientAndWeights the address and weights of all the recipients to receive rewards + */ + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierProxy.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierProxy.sol new file mode 100644 index 0000000000..5f0e8ffa19 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/interfaces/interfaces/IVerifierProxy.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../libraries/Common.sol"; +import {IVerifierFeeManager} from "./IVerifierFeeManager.sol"; + +interface IVerifierProxy { + /** + * @notice Verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payload The encoded data to be verified, including the signed + * report. + * @param parameterPayload fee metadata for billing + * @return verifierResponse The encoded report from the verifier. + */ + function verify( + bytes calldata payload, + bytes calldata parameterPayload + ) external payable returns (bytes memory verifierResponse); + + /** + * @notice Verifies that the data encoded has been signed correctly (view version) + * @param payload The encoded data to be verified, including the signed report. + * @return verifierResponse The encoded report from the verifier. + * @dev Same as verify() but does not emit events or bill the user (view-compatible) + */ + function verifyView( + bytes calldata payload + ) external view returns (bytes memory verifierResponse); + + /** + * @notice Bulk verifies that the data encoded has been signed + * correctly by routing to the correct verifier, and bills the user if applicable. + * @param payloads The encoded payloads to be verified, including the signed + * report. + * @param parameterPayload fee metadata for billing + * @return verifiedReports The encoded reports from the verifier. + */ + function verifyBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload + ) external payable returns (bytes[] memory verifiedReports); + + /** + * @notice Bulk verifies that the data encoded has been signed correctly (view version) + * @param payloads The encoded payloads to be verified, including the signed reports. + * @return verifiedReports The encoded reports from the verifier. + * @dev Same as verifyBulk() but does not emit events or bill the user (view-compatible) + */ + function verifyBulkView( + bytes[] calldata payloads + ) external view returns (bytes[] memory verifiedReports); + + /** + * @notice Sets the verifier address initially, allowing `setVerifier` to be set by this Verifier in the future + * @param verifierAddress The address of the verifier contract to initialize + */ + function initializeVerifier( + address verifierAddress + ) external; + + /** + * @notice Sets a new verifier for a config digest + * @param currentConfigDigest The current config digest + * @param newConfigDigest The config digest to set + * @param addressesAndWeights The addresses and weights of reward recipients + * reports for a given config digest. + */ + function setVerifier( + bytes32 currentConfigDigest, + bytes32 newConfigDigest, + Common.AddressAndWeight[] memory addressesAndWeights + ) external; + + /** + * @notice Removes a verifier for a given config digest + * @param configDigest The config digest of the verifier to remove + */ + function unsetVerifier( + bytes32 configDigest + ) external; + + /** + * @notice Retrieves the verifier address that verifies reports + * for a config digest. + * @param configDigest The config digest to query for + * @return verifierAddress The address of the verifier contract that verifies + * reports for a given config digest. + */ + function getVerifier( + bytes32 configDigest + ) external view returns (address verifierAddress); + + /** + * @notice Called by the admin to set an access controller contract + * @param accessController The new access controller to set + */ + function setAccessController( + AccessControllerInterface accessController + ) external; + + /** + * @notice Updates the fee manager + * @param feeManager The new fee manager + */ + function setFeeManager( + IVerifierFeeManager feeManager + ) external; +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/BaseFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/BaseFeeManager.t.sol new file mode 100644 index 0000000000..1a5eb41551 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/BaseFeeManager.t.sol @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {FeeManager} from "../../FeeManager.sol"; +import {RewardManager} from "../../RewardManager.sol"; + +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice Base class for all feeManager tests + * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup the + * feeManager + */ +contract BaseFeeManagerTest is Test { + //contracts + FeeManager internal feeManager; + RewardManager internal rewardManager; + FeeManagerProxy internal feeManagerProxy; + + ERC20Mock internal link; + WERC20Mock internal native; + + //erc20 config + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + //contract owner + address internal constant INVALID_ADDRESS = address(0); + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + address internal constant USER = address(uint160(uint256(keccak256("USER")))); + address internal constant PROXY = address(uint160(uint256(keccak256("PROXY")))); + + //version masks + bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000; + + //feed ids & config digests + bytes32 internal constant DEFAULT_FEED_1_V1 = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant DEFAULT_FEED_1_V2 = (keccak256("ETH-USD") & V_MASK) | V2_BITMASK; + bytes32 internal constant DEFAULT_FEED_1_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK; + + bytes32 internal constant DEFAULT_FEED_2_V3 = (keccak256("LINK-USD") & V_MASK) | V3_BITMASK; + bytes32 internal constant DEFAULT_CONFIG_DIGEST = keccak256("DEFAULT_CONFIG_DIGEST"); + + //report + uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10; + uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12; + + //rewards + uint64 internal constant FEE_SCALAR = 1e18; + + address internal constant NATIVE_WITHDRAW_ADDRESS = address(0); + + //the selector for each error + bytes4 internal immutable INVALID_DISCOUNT_ERROR = FeeManager.InvalidDiscount.selector; + bytes4 internal immutable INVALID_ADDRESS_ERROR = FeeManager.InvalidAddress.selector; + bytes4 internal immutable INVALID_SURCHARGE_ERROR = FeeManager.InvalidSurcharge.selector; + bytes4 internal immutable EXPIRED_REPORT_ERROR = FeeManager.ExpiredReport.selector; + bytes4 internal immutable INVALID_DEPOSIT_ERROR = FeeManager.InvalidDeposit.selector; + bytes4 internal immutable INVALID_QUOTE_ERROR = FeeManager.InvalidQuote.selector; + bytes4 internal immutable UNAUTHORIZED_ERROR = FeeManager.Unauthorized.selector; + bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; + bytes internal constant INSUFFICIENT_ALLOWANCE_ERROR = "ERC20: insufficient allowance"; + bytes4 internal immutable ZERO_DEFICIT = FeeManager.ZeroDeficit.selector; + + //events emitted + event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); + event NativeSurchargeUpdated(uint64 newSurcharge); + event InsufficientLink(IRewardManager.FeePayment[] feesAndRewards); + event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); + event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + Common.Asset fee, + Common.Asset reward, + uint256 appliedDiscountQuantity + ); + + function setUp() public virtual { + //change to admin user + vm.startPrank(ADMIN); + + //init required contracts + _initializeContracts(); + } + + function _initializeContracts() internal { + link = new ERC20Mock(18); + native = new WERC20Mock(); + + feeManagerProxy = new FeeManagerProxy(); + rewardManager = new RewardManager(address(link)); + feeManager = new FeeManager(address(link), address(native), address(feeManagerProxy), address(rewardManager)); + + //link the feeManager to the proxy + feeManagerProxy.setFeeManager(feeManager); + + //link the feeManager to the reward manager + rewardManager.setFeeManager(address(feeManager)); + + //mint some tokens to the admin + link.mint(ADMIN, DEFAULT_LINK_MINT_QUANTITY); + native.mint(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(ADMIN, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some tokens to the proxy + link.mint(PROXY, DEFAULT_LINK_MINT_QUANTITY); + native.mint(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(PROXY, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function setSubscriberDiscount( + address subscriber, + bytes32 feedId, + address token, + uint256 discount, + address sender + ) internal { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the discount + feeManager.updateSubscriberDiscount(subscriber, feedId, token, uint64(discount)); + + //change back to the original address + changePrank(originalAddr); + } + + function setSubscriberGlobalDiscount(address subscriber, address token, uint256 discount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the discount + feeManager.updateSubscriberGlobalDiscount(subscriber, token, uint64(discount)); + + //change back to the original address + changePrank(originalAddr); + } + + function setNativeSurcharge(uint256 surcharge, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the surcharge + feeManager.setNativeSurcharge(uint64(surcharge)); + + //change back to the original address + changePrank(originalAddr); + } + + // solium-disable-next-line no-unused-vars + function getFee(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { + //get the fee + (Common.Asset memory fee,,) = feeManager.getFeeAndReward(subscriber, report, quote); + + return fee; + } + + function getReward(bytes memory report, address quote, address subscriber) public view returns (Common.Asset memory) { + //get the reward + (, Common.Asset memory reward,) = feeManager.getFeeAndReward(subscriber, report, quote); + + return reward; + } + + function getAppliedDiscount(bytes memory report, address quote, address subscriber) public view returns (uint256) { + //get the reward + (,, uint256 appliedDiscount) = feeManager.getFeeAndReward(subscriber, report, quote); + + return appliedDiscount; + } + + function getV1Report( + bytes32 feedId + ) public pure returns (bytes memory) { + return abi.encode(feedId, uint32(0), int192(0), int192(0), int192(0), uint64(0), bytes32(0), uint64(0), uint64(0)); + } + + function getV2Report( + bytes32 feedId + ) public view returns (bytes memory) { + return abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(DEFAULT_REPORT_NATIVE_FEE), + uint192(DEFAULT_REPORT_LINK_FEE), + uint32(block.timestamp), + int192(0) + ); + } + + function getV3Report( + bytes32 feedId + ) public view returns (bytes memory) { + return abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(DEFAULT_REPORT_NATIVE_FEE), + uint192(DEFAULT_REPORT_LINK_FEE), + uint32(block.timestamp), + int192(0), + int192(0), + int192(0) + ); + } + + function getV3ReportWithCustomExpiryAndFee( + bytes32 feedId, + uint256 expiry, + uint256 linkFee, + uint256 nativeFee + ) public pure returns (bytes memory) { + return abi.encode( + feedId, + uint32(0), + uint32(0), + uint192(nativeFee), + uint192(linkFee), + uint32(expiry), + int192(0), + int192(0), + int192(0) + ); + } + + function getLinkQuote() public view returns (address) { + return address(link); + } + + function getNativeQuote() public view returns (address) { + return address(native); + } + + function withdraw(address assetAddress, address recipient, uint256 amount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //set the surcharge + feeManager.withdraw(assetAddress, recipient, uint192(amount)); + + //change back to the original address + changePrank(originalAddr); + } + + function getLinkBalance( + address balanceAddress + ) public view returns (uint256) { + return link.balanceOf(balanceAddress); + } + + function getNativeBalance( + address balanceAddress + ) public view returns (uint256) { + return native.balanceOf(balanceAddress); + } + + function getNativeUnwrappedBalance( + address balanceAddress + ) public view returns (uint256) { + return balanceAddress.balance; + } + + function mintLink(address recipient, uint256 amount) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(ADMIN); + + //mint the link to the recipient + link.mint(recipient, amount); + + //change back to the original address + changePrank(originalAddr); + } + + function mintNative(address recipient, uint256 amount, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //mint the native to the recipient + native.mint(recipient, amount); + + //change back to the original address + changePrank(originalAddr); + } + + function issueUnwrappedNative(address recipient, uint256 quantity) public { + vm.deal(recipient, quantity); + } + + function ProcessFeeAsUser( + bytes memory payload, + address subscriber, + address tokenAddress, + uint256 wrappedNativeValue, + address sender + ) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //process the fee + feeManager.processFee{value: wrappedNativeValue}(payload, abi.encode(tokenAddress), subscriber); + + //change ProcessFeeAsUserback to the original address + changePrank(originalAddr); + } + + function processFee(bytes memory payload, address subscriber, address feeAddress, uint256 wrappedNativeValue) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(subscriber); + + //process the fee + feeManagerProxy.processFee{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + //change back to the original address + changePrank(originalAddr); + } + + function processFee( + bytes[] memory payloads, + address subscriber, + address feeAddress, + uint256 wrappedNativeValue + ) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(subscriber); + + //process the fee + feeManagerProxy.processFeeBulk{value: wrappedNativeValue}(payloads, abi.encode(feeAddress)); + + //change back to the original address + changePrank(originalAddr); + } + + function getPayload( + bytes memory reportPayload + ) public pure returns (bytes memory) { + return abi.encode([DEFAULT_CONFIG_DIGEST, 0, 0], reportPayload, new bytes32[](1), new bytes32[](1), bytes32("")); + } + + function approveLink(address spender, uint256 quantity, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + link.approve(spender, quantity); + + //change back to the original address + changePrank(originalAddr); + } + + function approveNative(address spender, uint256 quantity, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + native.approve(spender, quantity); + + //change back to the original address + changePrank(originalAddr); + } + + function payLinkDeficit(bytes32 configDigest, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //approve the link to be transferred + feeManager.payLinkDeficit(configDigest); + + //change back to the original address + changePrank(originalAddr); + } + + function getLinkDeficit( + bytes32 configDigest + ) public view returns (uint256) { + return feeManager.s_linkDeficit(configDigest); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.general.t.sol new file mode 100644 index 0000000000..5abd496edd --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.general.t.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import "./BaseFeeManager.t.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the setup functionality of the feemanager + */ +contract FeeManagerProcessFeeTestV05 is BaseFeeManagerTest { + function setUp() public override { + super.setUp(); + } + + function test_WithdrawERC20() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //get the balances to ne used for comparison + uint256 contractBalance = getLinkBalance(address(feeManager)); + uint256 adminBalance = getLinkBalance(ADMIN); + + //the amount to withdraw + uint256 withdrawAmount = contractBalance / 2; + + //withdraw some balance + withdraw(address(link), ADMIN, withdrawAmount, ADMIN); + + //check the balance has been reduced + uint256 newContractBalance = getLinkBalance(address(feeManager)); + uint256 newAdminBalance = getLinkBalance(ADMIN); + + //check the balance is greater than zero + assertGt(newContractBalance, 0); + //check the balance has been reduced by the correct amount + assertEq(newContractBalance, contractBalance - withdrawAmount); + //check the admin balance has increased by the correct amount + assertEq(newAdminBalance, adminBalance + withdrawAmount); + } + + function test_WithdrawUnwrappedNative() public { + //issue funds straight to the contract to bypass the lack of fallback function + issueUnwrappedNative(address(feeManager), DEFAULT_NATIVE_MINT_QUANTITY); + + //get the balances to be used for comparison + uint256 contractBalance = getNativeUnwrappedBalance(address(feeManager)); + uint256 adminBalance = getNativeUnwrappedBalance(ADMIN); + + //the amount to withdraw + uint256 withdrawAmount = contractBalance / 2; + + //withdraw some balance + withdraw(NATIVE_WITHDRAW_ADDRESS, ADMIN, withdrawAmount, ADMIN); + + //check the balance has been reduced + uint256 newContractBalance = getNativeUnwrappedBalance(address(feeManager)); + uint256 newAdminBalance = getNativeUnwrappedBalance(ADMIN); + + //check the balance is greater than zero + assertGt(newContractBalance, 0); + //check the balance has been reduced by the correct amount + assertEq(newContractBalance, contractBalance - withdrawAmount); + //check the admin balance has increased by the correct amount + assertEq(newAdminBalance, adminBalance + withdrawAmount); + } + + function test_WithdrawNonAdminAddr() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //should revert if not admin + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //withdraw some balance + withdraw(address(link), ADMIN, DEFAULT_LINK_MINT_QUANTITY, USER); + } + + function test_eventIsEmittedAfterSurchargeIsSet() public { + //native surcharge + uint64 nativeSurcharge = FEE_SCALAR / 5; + + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit NativeSurchargeUpdated(nativeSurcharge); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + } + + function test_subscriberDiscountEventIsEmittedOnUpdate() public { + //native surcharge + uint64 discount = FEE_SCALAR / 3; + + //an event should be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit SubscriberDiscountUpdated(USER, DEFAULT_FEED_1_V3, address(native), discount); + + //set the surcharge + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN); + } + + function test_eventIsEmittedUponWithdraw() public { + //simulate a fee + mintLink(address(feeManager), DEFAULT_LINK_MINT_QUANTITY); + + //the amount to withdraw + uint192 withdrawAmount = 1; + + //expect an emit + vm.expectEmit(); + + //the event to be emitted + emit Withdraw(ADMIN, ADMIN, address(link), withdrawAmount); + + //withdraw some balance + withdraw(address(link), ADMIN, withdrawAmount, ADMIN); + } + + function test_linkAvailableForPaymentReturnsLinkBalance() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //check there's a balance + assertGt(getLinkBalance(address(feeManager)), 0); + + //check the link available for payment is the link balance + assertEq(feeManager.linkAvailableForPayment(), getLinkBalance(address(feeManager))); + } + + function test_payLinkDeficit() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //not enough funds in the reward pool should trigger an insufficient link event + vm.expectEmit(); + + IRewardManager.FeePayment[] memory contractFees = new IRewardManager.FeePayment[](1); + contractFees[0] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + emit InsufficientLink(contractFees); + + //process the fee + processFee(payload, USER, address(native), 0); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + } + + function test_payLinkDeficitTwice() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //not enough funds in the reward pool should trigger an insufficient link event + vm.expectEmit(); + + IRewardManager.FeePayment[] memory contractFees = new IRewardManager.FeePayment[](1); + contractFees[0] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + //emit the event that is expected to be emitted + emit InsufficientLink(contractFees); + + //process the fee + processFee(payload, USER, address(native), 0); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //paying again should revert with 0 + vm.expectRevert(ZERO_DEFICIT); + + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + } + + function test_payLinkDeficitPaysAllFeesProcessed() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 2, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(payload, USER, address(native), 0); + processFee(payload, USER, address(native), 0); + + //check the deficit has been increased twice + assertEq(getLinkDeficit(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE * 2); + + //double check the rewardManager balance is 0 + assertEq(getLinkBalance(address(rewardManager)), 0); + + //simulate a deposit of link to cover the deficit + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 2); + + vm.expectEmit(); + emit LinkDeficitCleared(DEFAULT_CONFIG_DIGEST, DEFAULT_REPORT_LINK_FEE * 2); + + //pay the deficit which will transfer link from the rewardManager to the rewardManager + payLinkDeficit(DEFAULT_CONFIG_DIGEST, ADMIN); + + //check the rewardManager received the link + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 2); + } + + function test_payLinkDeficitOnlyCallableByAdmin() public { + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + payLinkDeficit(DEFAULT_CONFIG_DIGEST, USER); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.getFeeAndReward.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.getFeeAndReward.t.sol new file mode 100644 index 0000000000..c3b90209ac --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.getFeeAndReward.t.sol @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import "./BaseFeeManager.t.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager's getFeeAndReward + */ +contract FeeManagerProcessFeeTestV05 is BaseFeeManagerTest { + function test_baseFeeIsAppliedForNative() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_baseFeeIsAppliedForLink() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_discountAIsNotAppliedWhenSetForOtherUsers() public { + //set the subscriber discount for another user + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), INVALID_ADDRESS); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_discountIsNotAppliedForInvalidTokenAddress() public { + //should revert with invalid address as it's not a configured token + vm.expectRevert(INVALID_ADDRESS_ERROR); + + //set the subscriber discount for another user + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, INVALID_ADDRESS, FEE_SCALAR / 2, ADMIN); + } + + function test_discountIsAppliedForLink() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_DiscountIsAppliedForNative() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE / 2); + } + + function test_discountIsNoLongerAppliedAfterRemoving() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE / 2); + + //remove the discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), 0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_surchargeIsApplied() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + } + + function test_surchargeIsNotAppliedForLinkFee() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_surchargeIsNoLongerAppliedAfterRemoving() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should be the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + + //remove the surcharge + setNativeSurcharge(0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the default + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_feeIsUpdatedAfterNewSurchargeIsApplied() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + + //change the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge + expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge); + } + + function test_surchargeIsAppliedForNativeFeeWithDiscount() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge quantity + uint256 expectedSurcharge = ((DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR); + + //calculate the expected discount quantity + uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge) / 2); + + //expected fee should the base fee offset by the surcharge and discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge - expectedDiscount); + } + + function test_emptyQuoteRevertsWithError() public { + //expect a revert + vm.expectRevert(INVALID_QUOTE_ERROR); + + //get the fee required by the feeManager + getFee(getV3Report(DEFAULT_FEED_1_V3), address(0), USER); + } + + function test_nativeSurcharge100Percent() public { + //set the surcharge + setNativeSurcharge(FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be twice the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2); + } + + function test_nativeSurcharge0Percent() public { + //set the surcharge + setNativeSurcharge(0, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_nativeSurchargeCannotExceed100Percent() public { + //should revert if surcharge is greater than 100% + vm.expectRevert(INVALID_SURCHARGE_ERROR); + + //set the surcharge above the max + setNativeSurcharge(FEE_SCALAR + 1, ADMIN); + } + + function test_discountIsAppliedWith100PercentSurcharge() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE; + + //fee should be twice the surcharge minus the discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE * 2 - expectedDiscount); + } + + function test_feeIsZeroWith100PercentDiscount() public { + //set the subscriber discount to 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_feeIsUpdatedAfterDiscountIsRemoved() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2; + + //fee should be 50% of the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + + //remove the discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), 0, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_feeIsUpdatedAfterNewDiscountIsApplied() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity + uint256 expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 2; + + //fee should be 50% of the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + + //change the discount to 25% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 4, ADMIN); + + //get the fee required by the feeManager + fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //expected discount is now 25% + expectedDiscount = DEFAULT_REPORT_NATIVE_FEE / 4; + + //fee should be the base fee minus the expected discount + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + } + + function test_setDiscountOver100Percent() public { + //should revert with invalid discount + vm.expectRevert(INVALID_DISCOUNT_ERROR); + + //set the subscriber discount to over 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR + 1, ADMIN); + } + + function test_surchargeIsNotAppliedWith100PercentDiscount() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 5; + + //set the subscriber discount to 100% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, ADMIN); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_nonAdminUserCanNotSetDiscount() public { + //should revert with unauthorized + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR, USER); + } + + function test_surchargeFeeRoundsUpWhenUneven() public { + //native surcharge + uint256 nativeSurcharge = FEE_SCALAR / 3; + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected surcharge quantity + uint256 expectedSurcharge = (DEFAULT_REPORT_NATIVE_FEE * nativeSurcharge) / FEE_SCALAR; + + //expected fee should the base fee offset by the expected surcharge + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE + expectedSurcharge + 1); + } + + function test_discountFeeRoundsDownWhenUneven() public { + //native surcharge + uint256 discount = FEE_SCALAR / 3; + + //set the subscriber discount to 33.333% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), discount, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected quantity + uint256 expectedDiscount = ((DEFAULT_REPORT_NATIVE_FEE * discount) / FEE_SCALAR); + + //expected fee should the base fee offset by the expected surcharge + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscount); + } + + function test_reportWithNoExpiryOrFeeReturnsZero() public view { + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV1Report(DEFAULT_FEED_1_V1), getNativeQuote(), USER); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_correctDiscountIsAppliedWhenBothTokensAreDiscounted() public { + //set the subscriber and native discounts + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 4, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager for both tokens + Common.Asset memory linkFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + Common.Asset memory nativeFee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //calculate the expected discount quantity for each token + uint256 expectedDiscountLink = (DEFAULT_REPORT_LINK_FEE * FEE_SCALAR) / 4 / FEE_SCALAR; + uint256 expectedDiscountNative = (DEFAULT_REPORT_NATIVE_FEE * FEE_SCALAR) / 2 / FEE_SCALAR; + + //check the fee calculation for each token + assertEq(linkFee.amount, DEFAULT_REPORT_LINK_FEE - expectedDiscountLink); + assertEq(nativeFee.amount, DEFAULT_REPORT_NATIVE_FEE - expectedDiscountNative); + } + + function test_discountIsNotAppliedToOtherFeeds() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_2_V3), getNativeQuote(), USER); + + //fee should be the base fee + assertEq(fee.amount, DEFAULT_REPORT_NATIVE_FEE); + } + + function test_noFeeIsAppliedWhenReportHasZeroFee() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0), getNativeQuote(), USER + ); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_noFeeIsAppliedWhenReportHasZeroFeeAndDiscountAndSurchargeIsSet() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee( + getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, uint32(block.timestamp), 0, 0), getNativeQuote(), USER + ); + + //fee should be zero + assertEq(fee.amount, 0); + } + + function test_nativeSurchargeEventIsEmittedOnUpdate() public { + //native surcharge + uint64 nativeSurcharge = FEE_SCALAR / 3; + + //an event should be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit NativeSurchargeUpdated(nativeSurcharge); + + //set the surcharge + setNativeSurcharge(nativeSurcharge, ADMIN); + } + + function test_getBaseRewardWithLinkQuote() public view { + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithLinkQuoteAndLinkDiscount() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the discounted base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_getRewardWithNativeQuote() public view { + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithNativeQuoteAndSurcharge() public { + //set the native surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link regardless of the surcharge + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_getRewardWithLinkDiscount() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal the discounted base fee + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE / 2); + } + + function test_getLinkFeeIsRoundedUp() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //the reward should equal .66% + 1 of the base fee due to a 33% discount rounded up + assertEq(fee.amount, (DEFAULT_REPORT_LINK_FEE * 2) / 3 + 1); + } + + function test_getLinkRewardIsSameAsFee() public { + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //check the reward is in link + assertEq(fee.assetAddress, address(link)); + + //the reward should equal .66% of the base fee due to a 33% discount rounded down + assertEq(reward.amount, fee.amount); + } + + function test_getLinkRewardWithNativeQuoteAndSurchargeWithLinkDiscount() public { + //set the native surcharge + setNativeSurcharge(FEE_SCALAR / 2, ADMIN); + + //set the link discount + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 3, ADMIN); + + //get the fee required by the feeManager + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //the reward should equal the base fee in link regardless of the surcharge + assertEq(reward.amount, DEFAULT_REPORT_LINK_FEE); + } + + function test_testRevertIfReportHasExpired() public { + //expect a revert + vm.expectRevert(EXPIRED_REPORT_ERROR); + + //get the fee required by the feeManager + getFee( + getV3ReportWithCustomExpiryAndFee( + DEFAULT_FEED_1_V3, block.timestamp - 1, DEFAULT_REPORT_LINK_FEE, DEFAULT_REPORT_NATIVE_FEE + ), + getNativeQuote(), + USER + ); + } + + function test_discountIsReturnedForLink() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 2, ADMIN); + + //get the fee applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_DiscountIsReturnedForNative() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_DiscountIsReturnedForNativeWithSurcharge() public { + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //set the surcharge + setNativeSurcharge(FEE_SCALAR / 5, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountWithNativeAndLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + } + + function test_GlobalDiscountIsOverridenByIndividualDiscountNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 4, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 4); + } + + function test_GlobalDiscountIsOverridenByIndividualDiscountLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(link), FEE_SCALAR / 4, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 4); + } + + function test_GlobalDiscountIsUpdatedAfterBeingSetToZeroLink() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(link), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + + //set the global discount to zero + setSubscriberGlobalDiscount(USER, address(link), 0, ADMIN); + + //get the discount applied + discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getLinkQuote(), USER); + + //fee should be zero + assertEq(discount, 0); + } + + function test_GlobalDiscountIsUpdatedAfterBeingSetToZeroNative() public { + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, ADMIN); + + //get the discount applied + uint256 discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be half the default + assertEq(discount, FEE_SCALAR / 2); + + //set the global discount to zero + setSubscriberGlobalDiscount(USER, address(native), 0, ADMIN); + + //get the discount applied + discount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + //fee should be zero + assertEq(discount, 0); + } + + function test_GlobalDiscountCantBeSetToMoreThanMaximum() public { + //should revert with invalid discount + vm.expectRevert(INVALID_DISCOUNT_ERROR); + + //set the global discount to 101% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR + 1, ADMIN); + } + + function test_onlyOwnerCanSetGlobalDiscount() public { + //should revert with unauthorized + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //set the global discount to 50% + setSubscriberGlobalDiscount(USER, address(native), FEE_SCALAR / 2, USER); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFee.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFee.t.sol new file mode 100644 index 0000000000..34083bac60 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFee.t.sol @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; + +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import "./BaseFeeManager.t.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager processFee + */ +contract FeeManagerProcessFeeTestV05 is BaseFeeManagerTest { + function setUp() public override { + super.setUp(); + } + + function test_nonAdminProxyUserCannotProcessFee() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //should revert as the user is not the owner + vm.expectRevert(UNAUTHORIZED_ERROR); + + //process the fee + ProcessFeeAsUser(payload, USER, address(link), 0, USER); + } + + function test_processFeeAsProxy() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_processFeeIfSubscriberIsSelf() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert due to the feeManager being the subscriber + vm.expectRevert(INVALID_ADDRESS_ERROR); + + //process the fee will fail due to assertion + processFee(payload, address(feeManager), address(native), 0); + } + + function test_processFeeWithWithEmptyQuotePayload() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link by default + processFee(payload, USER, address(0), 0); + } + + function test_processFeeWithWithZeroQuotePayload() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as the quote is invalid + vm.expectRevert(INVALID_QUOTE_ERROR); + + //processing the fee will transfer the link by default + processFee(payload, USER, INVALID_ADDRESS, 0); + } + + function test_processFeeWithWithCorruptQuotePayload() public { + //get the default payload + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], getV3Report(DEFAULT_FEED_1_V3), new bytes32[](1), new bytes32[](1), bytes32("") + ); + + //expect an evm revert as the quote is corrupt + vm.expectRevert(); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(payload, USER, address(link), 0); + } + + function test_processFeeDefaultReportsStillVerifiesWithEmptyQuote() public { + //get the default payload + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(0), 0); + } + + function test_processFeeWithDefaultReportPayloadAndQuoteStillVerifies() public { + //get the default payload + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(payload, USER, address(link), 0); + } + + function test_processFeeNative() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeEmitsEventIfNotEnoughLink() public { + //simulate a deposit of half the link required for the fee + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE / 2); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //expect an emit as there's not enough link + vm.expectEmit(); + + IRewardManager.FeePayment[] memory contractFees = new IRewardManager.FeePayment[](1); + contractFees[0] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + //emit the event that is expected to be emitted + emit InsufficientLink(contractFees); + + //processing the fee will transfer the native from the user to the feeManager + processFee(payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check no link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), 0); + assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE / 2); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithUnwrappedNative() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //only the proxy or admin can call processFee, they will pass in the native value on the users behalf + processFee(payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE); + + //check the native has been transferred and converted to wrapped native + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeUnwrappedBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithUnwrappedNativeShortFunds() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as not enough funds + vm.expectRevert(INVALID_DEPOSIT_ERROR); + + //only the proxy or admin can call processFee, they will pass in the native value on the users behalf + processFee(payload, USER, address(native), DEFAULT_REPORT_NATIVE_FEE - 1); + } + + function test_processFeeWithUnwrappedNativeLinkAddress() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //expect a revert as not enough funds + vm.expectRevert(INSUFFICIENT_ALLOWANCE_ERROR); + + //the change will be returned and the user will attempted to be billed in LINK + processFee(payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE - 1); + } + + function test_processFeeWithUnwrappedNativeLinkAddressExcessiveFee() public { + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, PROXY); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds + // would be returned to the caller of the proxy. + processFee(payload, PROXY, address(link), DEFAULT_REPORT_NATIVE_FEE); + + //check the native unwrapped is no longer in the account + assertEq(getNativeBalance(address(feeManager)), 0); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //native should not be deducted + assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processFeeWithUnwrappedNativeWithExcessiveFee() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee from the proxy to test whether the funds are returned to the subscriber. In reality, the funds + // would be returned to the caller of the proxy. + processFee(payload, PROXY, address(native), DEFAULT_REPORT_NATIVE_FEE * 2); + + //check the native has been transferred and converted to wrapped native + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + assertEq(getNativeUnwrappedBalance(address(feeManager)), 0); + + //check the link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the feeManager has had the link deducted, the remaining balance should be 0 + assertEq(getLinkBalance(address(feeManager)), 0); + + //check the subscriber has had the native deducted + assertEq(getNativeUnwrappedBalance(PROXY), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeUsesCorrectDigest() public { + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + + //check funds have been paid to the reward manager + assertEq(rewardManager.s_totalRewardRecipientFees(DEFAULT_CONFIG_DIGEST), DEFAULT_REPORT_LINK_FEE); + } + + function test_V1PayloadVerifies() public { + //replicate a default payload + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], getV2Report(DEFAULT_FEED_1_V1), new bytes32[](1), new bytes32[](1), bytes32("") + ); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(0), 0); + } + + function test_V2PayloadVerifies() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_V2PayloadWithoutQuoteFails() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(0), 0); + } + + function test_V2PayloadWithoutZeroFee() public { + //get the default payload + bytes memory payload = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + + //expect a revert as the quote is invalid + vm.expectRevert(); + + //processing the fee will transfer the link from the user to the rewardManager + processFee(payload, USER, address(link), 0); + } + + function test_processFeeWithInvalidReportVersionFailsToDecode() public { + bytes memory data = abi.encode(0x0000100000000000000000000000000000000000000000000000000000000000); + + //get the default payload + bytes memory payload = getPayload(data); + + //serialization will fail as there is no report to decode + vm.expectRevert(); + + //processing the fee will not withdraw anything as there is no fee to collect + processFee(payload, USER, address(link), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkWithNativeQuote() public { + //get the default payload + bytes memory payload = + getPayload(getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0)); + + //call processFee should not revert as the fee is 0 + processFee(payload, PROXY, address(native), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkWithLinkQuote() public { + //get the default payload + bytes memory payload = + getPayload(getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, DEFAULT_REPORT_LINK_FEE, 0)); + + //approve the link to be transferred from the from the subscriber to the rewardManager + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + //processing the fee will transfer the link to the rewardManager from the user + processFee(payload, USER, address(link), 0); + + //check the link has been transferred + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + + //check the user has had the link fee deducted + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_processFeeWithZeroLinkNonZeroNativeWithNativeQuote() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //get the default payload + bytes memory payload = + getPayload(getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //processing the fee will transfer the native from the user to the feeManager + processFee(payload, USER, address(native), 0); + + //check the native has been transferred + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE); + + //check no link has been transferred to the rewardManager + assertEq(getLinkBalance(address(rewardManager)), 0); + + //check the feeManager has had no link deducted + assertEq(getLinkBalance(address(feeManager)), DEFAULT_REPORT_LINK_FEE); + + //check the subscriber has had the native deducted + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + } + + function test_processFeeWithZeroLinkNonZeroNativeWithLinkQuote() public { + //get the default payload + bytes memory payload = + getPayload(getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)); + + //call processFee should not revert as the fee is 0 + processFee(payload, USER, address(link), 0); + } + + function test_processFeeWithZeroNativeNonZeroLinkReturnsChange() public { + //get the default payload + bytes memory payload = + getPayload(getV3ReportWithCustomExpiryAndFee(DEFAULT_FEED_1_V3, block.timestamp, 0, DEFAULT_REPORT_NATIVE_FEE)); + + //call processFee should not revert as the fee is 0 + processFee(payload, USER, address(link), DEFAULT_REPORT_NATIVE_FEE); + + //check the change has been returned + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_V1PayloadVerifiesAndReturnsChange() public { + //emulate a V1 payload with no quote + bytes memory payload = getPayload(getV1Report(DEFAULT_FEED_1_V1)); + + processFee(payload, USER, address(0), DEFAULT_REPORT_NATIVE_FEE); + + //Fee manager should not contain any native + assertEq(address(feeManager).balance, 0); + assertEq(getNativeBalance(address(feeManager)), 0); + + //check the unused native passed in is returned + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processFeeWithDiscountEmitsEvent() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //set the subscriber discount to 50% + setSubscriberDiscount(USER, DEFAULT_FEED_1_V3, address(native), FEE_SCALAR / 2, ADMIN); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE / 2, USER); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + Common.Asset memory fee = getFee(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + Common.Asset memory reward = getReward(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + uint256 appliedDiscount = getAppliedDiscount(getV3Report(DEFAULT_FEED_1_V3), getNativeQuote(), USER); + + vm.expectEmit(); + + emit DiscountApplied(DEFAULT_CONFIG_DIGEST, USER, fee, reward, appliedDiscount); + + //call processFee should not revert as the fee is 0 + processFee(payload, USER, address(native), 0); + } + + function test_processFeeWithNoDiscountDoesNotEmitEvent() public { + //simulate a deposit of link for the conversion pool + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //approve the native to be transferred from the user + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + //get the default payload + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + //call processFee should not revert as the fee is 0 + processFee(payload, USER, address(native), 0); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFeeBulk.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFeeBulk.t.sol new file mode 100644 index 0000000000..72a1ed66cd --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/FeeManager.processFeeBulk.t.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import "./BaseFeeManager.t.sol"; + +/** + * @title BaseFeeManagerTest + * @author Michael Fletcher + * @notice This contract will test the functionality of the feeManager processFee + */ +contract FeeManagerProcessFeeTestV05 is BaseFeeManagerTest { + uint256 internal constant NUMBER_OF_REPORTS = 5; + + function setUp() public override { + super.setUp(); + } + + function test_processMultipleLinkReports() public { + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS, USER); + + processFee(payloads, USER, address(link), DEFAULT_NATIVE_MINT_QUANTITY); + + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 0); + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + + //the subscriber (user) should receive funds back and not the proxy, although when live the proxy will forward the + // funds sent and not cover it seen here + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processMultipleWrappedNativeReports() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS, USER); + + processFee(payloads, USER, address(native), 0); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 1); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + } + + function test_processMultipleUnwrappedNativeReports() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS + 1); + + bytes memory payload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + processFee(payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS * 2); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS); + } + + function test_processV1V2V3Reports() public { + mintLink(address(feeManager), 1); + + bytes memory payloadV1 = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], getV1Report(DEFAULT_FEED_1_V1), new bytes32[](1), new bytes32[](1), bytes32("") + ); + + bytes memory linkPayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + bytes memory linkPayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = payloadV1; + payloads[1] = linkPayloadV2; + payloads[2] = linkPayloadV2; + payloads[3] = linkPayloadV3; + payloads[4] = linkPayloadV3; + + approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * 4, USER); + + processFee(payloads, USER, address(link), 0); + + assertEq(getNativeBalance(address(feeManager)), 0); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - 0); + } + + function test_processV1V2V3ReportsWithUnwrapped() public { + mintLink(address(feeManager), DEFAULT_REPORT_LINK_FEE * 4 + 1); + + bytes memory payloadV1 = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], getV1Report(DEFAULT_FEED_1_V1), new bytes32[](1), new bytes32[](1), bytes32("") + ); + + bytes memory nativePayloadV2 = getPayload(getV2Report(DEFAULT_FEED_1_V2)); + bytes memory nativePayloadV3 = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = payloadV1; + payloads[1] = nativePayloadV2; + payloads[2] = nativePayloadV2; + payloads[3] = nativePayloadV3; + payloads[4] = nativePayloadV3; + + processFee(payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 4); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 4); + assertEq(getLinkBalance(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 4); + assertEq(getLinkBalance(address(feeManager)), 1); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 4); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_processMultipleV1Reports() public { + bytes memory payload = abi.encode( + [DEFAULT_CONFIG_DIGEST, 0, 0], getV1Report(DEFAULT_FEED_1_V1), new bytes32[](1), new bytes32[](1), bytes32("") + ); + + bytes[] memory payloads = new bytes[](NUMBER_OF_REPORTS); + for (uint256 i = 0; i < NUMBER_OF_REPORTS; ++i) { + payloads[i] = payload; + } + + processFee(payloads, USER, address(native), DEFAULT_REPORT_NATIVE_FEE * 5); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(PROXY.balance, DEFAULT_NATIVE_MINT_QUANTITY); + } + + function test_eventIsEmittedIfNotEnoughLink() public { + bytes memory nativePayload = getPayload(getV3Report(DEFAULT_FEED_1_V3)); + + bytes[] memory payloads = new bytes[](5); + payloads[0] = nativePayload; + payloads[1] = nativePayload; + payloads[2] = nativePayload; + payloads[3] = nativePayload; + payloads[4] = nativePayload; + + approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * 5, USER); + + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](5); + payments[0] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[1] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[2] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[3] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + payments[4] = IRewardManager.FeePayment(DEFAULT_CONFIG_DIGEST, uint192(DEFAULT_REPORT_LINK_FEE)); + + vm.expectEmit(); + + emit InsufficientLink(payments); + + processFee(payloads, USER, address(native), 0); + + assertEq(getNativeBalance(address(feeManager)), DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(getNativeBalance(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(getLinkBalance(USER), DEFAULT_LINK_MINT_QUANTITY); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/NoOpFeeManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/NoOpFeeManager.t.sol new file mode 100644 index 0000000000..d97326bced --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/fee-manager/NoOpFeeManager.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; + +import {Common} from "../../../libraries/Common.sol"; +import {FeeManager} from "../../FeeManager.sol"; +import {NoOpFeeManager} from "../../NoOpFeeManager.sol"; +import {RewardManager} from "../../RewardManager.sol"; +import {IVerifierFeeManager} from "../../interfaces/IVerifierFeeManager.sol"; +import {FeeManagerProxy} from "../mocks/FeeManagerProxy.sol"; +import {Test} from "forge-std/Test.sol"; + +contract NoOpFeeManagerTest is Test { + NoOpFeeManager internal noOpFeeManager; + FeeManager internal feeManager; + RewardManager internal rewardManager; + FeeManagerProxy internal feeManagerProxy; + + ERC20Mock internal link; + WERC20Mock internal native; + + uint64 internal constant PERCENTAGE_SCALAR = 1e18; + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + address internal constant SUBSCRIBER = address(uint160(uint256(keccak256("SUBSCRIBER")))); + + function setUp() public { + vm.startPrank(ADMIN); + + link = new ERC20Mock(18); + native = new WERC20Mock(); + noOpFeeManager = new NoOpFeeManager(); + + // Deploy real FeeManager for comparison tests + feeManagerProxy = new FeeManagerProxy(); + rewardManager = new RewardManager(address(link)); + feeManager = new FeeManager(address(link), address(native), address(feeManagerProxy), address(rewardManager)); + feeManagerProxy.setFeeManager(feeManager); + rewardManager.setFeeManager(address(feeManager)); + + vm.stopPrank(); + } + + function test_typeAndVersion() public view { + assertEq(noOpFeeManager.typeAndVersion(), "NoOpFeeManager 0.5.0"); + } + + // VerifierProxy checks these interface support before accepting a fee manager + function test_supportsInterface() public view { + assertTrue(noOpFeeManager.supportsInterface(IVerifierFeeManager.processFee.selector)); + assertTrue(noOpFeeManager.supportsInterface(IVerifierFeeManager.processFeeBulk.selector)); + assertFalse(noOpFeeManager.supportsInterface(bytes4(0xdeadbeef))); + } + + // Some code queries these to check discount status - must always return 100% + function test_discountGettersReturn100Percent() public view { + bytes32 feedId = keccak256("ETH-USD"); + + assertEq(noOpFeeManager.s_globalDiscounts(SUBSCRIBER, address(link)), PERCENTAGE_SCALAR); + assertEq(noOpFeeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), PERCENTAGE_SCALAR); + } + + // Ensures our view functions match FeeManager's public mapping getter signatures + // so code can swap between implementations without breaking changes + function test_discountGettersMatchFeeManagerSignature() public view { + bytes32 feedId = keccak256("ETH-USD"); + + // FeeManager public mappings return 0 by default, NoOpFeeManager returns 100% + assertEq(feeManager.s_globalDiscounts(SUBSCRIBER, address(link)), 0); + assertEq(noOpFeeManager.s_globalDiscounts(SUBSCRIBER, address(link)), PERCENTAGE_SCALAR); + + assertEq(feeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), 0); + assertEq(noOpFeeManager.s_subscriberDiscounts(SUBSCRIBER, feedId, address(link)), PERCENTAGE_SCALAR); + } + + // Zero fees, zero rewards, 100% discount indicates no fees are charged + function test_getFeeAndReward() public view { + bytes memory report = abi.encode(bytes32(0), uint32(0), int192(0)); + (Common.Asset memory fee, Common.Asset memory reward, uint256 discount) = + noOpFeeManager.getFeeAndReward(SUBSCRIBER, report, address(link)); + + assertEq(fee.amount, 0); + assertEq(reward.amount, 0); + assertEq(discount, PERCENTAGE_SCALAR); + } + + function test_linkAvailableForPayment() public view { + assertEq(noOpFeeManager.linkAvailableForPayment(), 0); + } + + // Admin functions are no-ops but must not revert to maintain interface compatibility + function test_adminFunctionsDoNotRevert() public { + bytes32 feedId = keccak256("ETH-USD"); + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](0); + + noOpFeeManager.setNativeSurcharge(uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.updateSubscriberDiscount(SUBSCRIBER, feedId, address(link), uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.updateSubscriberGlobalDiscount(SUBSCRIBER, address(link), uint64(PERCENTAGE_SCALAR)); + noOpFeeManager.setFeeRecipients(feedId, recipients); + noOpFeeManager.withdraw(address(link), ADMIN, 1000); + noOpFeeManager.payLinkDeficit(feedId); + } + + // ETH sent during verification must be refunded since no fees are collected + function test_processFeeRefundsETH() public { + uint256 ethAmount = 1 ether; + vm.deal(address(this), ethAmount); + + uint256 balanceBefore = SUBSCRIBER.balance; + noOpFeeManager.processFee{value: ethAmount}("", "", SUBSCRIBER); + + assertEq(SUBSCRIBER.balance - balanceBefore, ethAmount); + } + + function test_processFeeBulkRefundsETH() public { + uint256 ethAmount = 1 ether; + vm.deal(address(this), ethAmount); + bytes[] memory payloads = new bytes[](2); + + uint256 balanceBefore = SUBSCRIBER.balance; + noOpFeeManager.processFeeBulk{value: ethAmount}(payloads, "", SUBSCRIBER); + + assertEq(SUBSCRIBER.balance - balanceBefore, ethAmount); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/gas/Gas_VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/gas/Gas_VerifierTest.t.sol new file mode 100644 index 0000000000..4c966b8025 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/gas/Gas_VerifierTest.t.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "../verifier/BaseVerifierTest.t.sol"; + +contract Verifier_setConfigV05 is BaseTest { + address[] internal s_signerAddrs; + + function setUp() public override { + BaseTest.setUp(); + Signer[] memory signers = _getSigners(MAX_ORACLES); + s_signerAddrs = _getSignerAddresses(signers); + s_verifierProxy.initializeVerifier(address(s_verifier)); + } + + function testSetConfigSuccess_gas() public { + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + s_signerAddrs, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + 1, + bytes("") + ); + + s_verifier.setConfig(configDigest, s_signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } +} + +contract Verifier_verifyWithFeeV05 is BaseTestWithConfiguredVerifierAndFeeManager { + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + function setUp() public virtual override { + super.setUp(); + + //mint some link and eth to warm the storage + link.mint(address(rewardManager), DEFAULT_LINK_MINT_QUANTITY); + native.mint(address(feeManager), DEFAULT_NATIVE_MINT_QUANTITY); + + //warm the rewardManager + link.mint(address(this), DEFAULT_NATIVE_MINT_QUANTITY); + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, address(this)); + bytes32 latestConfigDigest = v1ConfigDigest; + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some link tokens to the feeManager pool + link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE); + + //approve funds prior to test + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); + payments[0] = IRewardManager.FeePayment(latestConfigDigest, uint192(DEFAULT_REPORT_LINK_FEE)); + + changePrank(address(feeManager)); + rewardManager.onFeePaid(payments, address(this)); + + changePrank(USER); + } + + function testVerifyProxyWithLinkFeeSuccess_gas() public { + bytes memory signedLinkPayload = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + s_verifierProxy.verify(signedLinkPayload, abi.encode(link)); + } + + function testVerifyProxyWithNativeFeeSuccess_gas() public { + bytes memory signedNativePayload = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + s_verifierProxy.verify(signedNativePayload, abi.encode(native)); + } +} + +contract Verifier_bulkVerifyWithFeeV05 is BaseTestWithConfiguredVerifierAndFeeManager { + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + uint256 internal constant NUMBER_OF_REPORTS_TO_VERIFY = 5; + + function setUp() public virtual override { + super.setUp(); + + //mint some link and eth to warm the storage + link.mint(address(rewardManager), DEFAULT_LINK_MINT_QUANTITY); + native.mint(address(feeManager), DEFAULT_NATIVE_MINT_QUANTITY); + + //warm the rewardManager + link.mint(address(this), DEFAULT_NATIVE_MINT_QUANTITY); + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, address(this)); + bytes32 latestConfigDigest = v1ConfigDigest; + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some link tokens to the feeManager pool + link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS_TO_VERIFY); + + //approve funds prior to test + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBER_OF_REPORTS_TO_VERIFY, USER); + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBER_OF_REPORTS_TO_VERIFY, USER); + + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); + payments[0] = IRewardManager.FeePayment(latestConfigDigest, uint192(DEFAULT_REPORT_LINK_FEE)); + + changePrank(address(feeManager)); + rewardManager.onFeePaid(payments, address(this)); + + changePrank(USER); + } + + function testBulkVerifyProxyWithLinkFeeSuccess_gas() public { + bytes memory signedLinkPayload = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedLinkPayloads = new bytes[](NUMBER_OF_REPORTS_TO_VERIFY); + for (uint256 i = 0; i < NUMBER_OF_REPORTS_TO_VERIFY; i++) { + signedLinkPayloads[i] = signedLinkPayload; + } + + s_verifierProxy.verifyBulk(signedLinkPayloads, abi.encode(link)); + } + + function testBulkVerifyProxyWithNativeFeeSuccess_gas() public { + bytes memory signedNativePayload = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedNativePayloads = new bytes[](NUMBER_OF_REPORTS_TO_VERIFY); + for (uint256 i = 0; i < NUMBER_OF_REPORTS_TO_VERIFY; i++) { + signedNativePayloads[i] = signedNativePayload; + } + + s_verifierProxy.verifyBulk(signedNativePayloads, abi.encode(native)); + } +} + +contract Verifier_verifyV05 is BaseTestWithConfiguredVerifierAndFeeManager { + bytes internal s_signedReport; + bytes32 internal s_configDigest; + + function setUp() public override { + BaseTestWithConfiguredVerifierAndFeeManager.setUp(); + BaseTest.V1Report memory s_testReportOne = _createV1Report( + FEED_ID, + OBSERVATIONS_TIMESTAMP, + MEDIAN, + BID, + ASK, + BLOCKNUMBER_UPPER_BOUND, + blockhash(BLOCKNUMBER_UPPER_BOUND), + BLOCKNUMBER_LOWER_BOUND, + uint32(block.timestamp) + ); + s_configDigest = v1ConfigDigest; + bytes32[3] memory reportContext; + reportContext[0] = s_configDigest; + reportContext[1] = bytes32(abi.encode(uint32(5), uint8(1))); + s_signedReport = _generateV1EncodedBlob(s_testReportOne, reportContext, _getSigners(FAULT_TOLERANCE + 1)); + } + + function testVerifySuccess_gas() public { + changePrank(address(s_verifierProxy)); + + s_verifier.verify(s_signedReport, msg.sender); + } + + function testVerifyProxySuccess_gas() public { + s_verifierProxy.verify(s_signedReport, abi.encode(native)); + } +} + +contract Verifier_accessControlledVerifyV05 is BaseTestWithConfiguredVerifierAndFeeManager { + bytes internal s_signedReport; + bytes32 internal s_configDigest; + SimpleWriteAccessController s_accessController; + + address internal constant CLIENT = address(9000); + address internal constant ACCESS_CONTROLLER_ADDR = address(10_000); + + function setUp() public override { + BaseTestWithConfiguredVerifierAndFeeManager.setUp(); + BaseTest.V1Report memory s_testReportOne = _createV1Report( + FEED_ID, + OBSERVATIONS_TIMESTAMP, + MEDIAN, + BID, + ASK, + BLOCKNUMBER_UPPER_BOUND, + blockhash(BLOCKNUMBER_UPPER_BOUND), + BLOCKNUMBER_LOWER_BOUND, + uint32(block.timestamp) + ); + s_configDigest = v1ConfigDigest; + bytes32[3] memory reportContext; + reportContext[0] = s_configDigest; + reportContext[1] = bytes32(abi.encode(uint32(5), uint8(1))); + s_signedReport = _generateV1EncodedBlob(s_testReportOne, reportContext, _getSigners(FAULT_TOLERANCE + 1)); + s_accessController = new SimpleWriteAccessController(); + s_verifierProxy.setAccessController(s_accessController); + s_accessController.addAccess(CLIENT); + } + + function testVerifyWithAccessControl_gas() public { + changePrank(CLIENT); + s_verifierProxy.verify(s_signedReport, abi.encode(native)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ErroredVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ErroredVerifier.sol new file mode 100644 index 0000000000..4da1b417ba --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ErroredVerifier.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {IVerifier} from "../../interfaces/IVerifier.sol"; + +contract ErroredVerifier is IVerifier { + function supportsInterface( + bytes4 interfaceId + ) public pure override returns (bool) { + return interfaceId == this.verify.selector; + } + + //define each of the errors thrown in the revert below + + error FailedToVerify(); + error FailedToSetConfig(); + error FailedToUnsetConfig(); + error FailedToActivateConfig(); + error FailedToDeactivateConfig(); + error FailedToActivateFeed(); + error FailedToDeactivateFeed(); + error FailedToGetLatestConfigDigestAndEpoch(); + error FailedToGetLatestConfigDetails(); + + function verify( + bytes memory, + /** + * signedReport* + */ + address + ) + external + pure + override + returns ( + /** + * sender* + */ + bytes memory + ) + { + revert FailedToVerify(); + } + + function updateConfig(bytes32, address[] calldata, address[] calldata, uint8) external pure { + revert FailedToUnsetConfig(); + } + + function setConfig(bytes32, address[] calldata, uint8, Common.AddressAndWeight[] calldata) external pure override { + revert FailedToSetConfig(); + } + + function activateConfig( + bytes32 + ) external pure { + revert FailedToActivateConfig(); + } + + function deactivateConfig( + bytes32 + ) external pure { + revert FailedToDeactivateConfig(); + } + + function latestConfigDetails( + bytes32 + ) external pure override returns (uint32) { + revert FailedToGetLatestConfigDetails(); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ExposedVerifier.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ExposedVerifier.sol new file mode 100644 index 0000000000..6eb6bb3d94 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/ExposedVerifier.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +// ExposedVerifier exposes certain internal Verifier +// methods/structures so that golang code can access them, and we get +// reliable type checking on their usage +contract ExposedVerifier { + constructor() {} + + function _configDigestFromConfigData( + bytes32 feedId, + uint256 chainId, + address contractAddress, + uint64 configCount, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + feedId, + chainId, + contractAddress, + configCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x0006 << (256 - 16); // 0x000600..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + function exposedConfigDigestFromConfigData( + bytes32 _feedId, + uint256 _chainId, + address _contractAddress, + uint64 _configCount, + address[] memory _signers, + bytes32[] memory _offchainTransmitters, + uint8 _f, + bytes calldata _onchainConfig, + uint64 _encodedConfigVersion, + bytes memory _encodedConfig + ) public pure returns (bytes32) { + return _configDigestFromConfigData( + _feedId, + _chainId, + _contractAddress, + _configCount, + _signers, + _offchainTransmitters, + _f, + _onchainConfig, + _encodedConfigVersion, + _encodedConfig + ); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/FeeManagerProxy.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/FeeManagerProxy.sol new file mode 100644 index 0000000000..a00631fdab --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/FeeManagerProxy.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {IFeeManager} from "../../interfaces/IFeeManager.sol"; + +contract FeeManagerProxy { + IFeeManager internal s_feeManager; + + function processFee(bytes calldata payload, bytes calldata parameterPayload) public payable { + s_feeManager.processFee{value: msg.value}(payload, parameterPayload, msg.sender); + } + + function processFeeBulk(bytes[] calldata payloads, bytes calldata parameterPayload) public payable { + s_feeManager.processFeeBulk{value: msg.value}(payloads, parameterPayload, msg.sender); + } + + function setFeeManager( + IFeeManager feeManager + ) public { + s_feeManager = feeManager; + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockConfigurator.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockConfigurator.sol new file mode 100644 index 0000000000..85c41194db --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockConfigurator.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +// Derived from v0.5/Configurator.sol +contract MockConfigurator { + struct ConfigurationState { + uint64 configCount; + uint32 latestConfigBlockNumber; + bytes32 configDigest; + } + + mapping(bytes32 => ConfigurationState) public s_configurationStates; + + function setStagingConfig( + bytes32 configId, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external { + ConfigurationState storage configurationState = s_configurationStates[configId]; + + uint64 newConfigCount = ++configurationState.configCount; + + bytes32 configDigest = _configDigestFromConfigData( + configId, + block.chainid, + address(this), + newConfigCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ); + + s_configurationStates[configId].configDigest = configDigest; + configurationState.latestConfigBlockNumber = uint32(block.number); + } + + function _configDigestFromConfigData( + bytes32 configId, + uint256 sourceChainId, + address sourceAddress, + uint64 configCount, + bytes[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + configId, + sourceChainId, + sourceAddress, + configCount, + signers, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); + uint256 prefix = 0x0009 << (256 - 16); + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockFeeManager.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockFeeManager.sol new file mode 100644 index 0000000000..9ef1708ca3 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/mocks/MockFeeManager.sol @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {ConfirmedOwner} from "../../../../shared/access/ConfirmedOwner.sol"; + +import {ITypeAndVersion} from "../../../../shared/interfaces/ITypeAndVersion.sol"; + +import {Common} from "../../../libraries/Common.sol"; +import {IFeeManager} from "../../interfaces/IFeeManager.sol"; +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; + +import {IVerifierFeeManager} from "../../interfaces/IVerifierFeeManager.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; +import {IERC20} from "@openzeppelin/contracts@4.8.3/interfaces/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts@4.8.3/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title MockFeeManager + */ +contract MockFeeManager is IFeeManager, ConfirmedOwner, ITypeAndVersion { + using SafeERC20 for IERC20; + + /// @notice list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token] + mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts; + + /// @notice map of global discounts + mapping(address => mapping(address => uint256)) public s_globalDiscounts; + + /// @notice keep track of any subsidised link that is owed to the reward manager. + mapping(bytes32 => uint256) public s_linkDeficit; + + /// @notice the total discount that can be applied to a fee, 1e18 = 100% discount + uint64 private constant PERCENTAGE_SCALAR = 1e18; + + /// @notice the LINK token address + address public immutable i_linkAddress; + + /// @notice the native token address + address public immutable i_nativeAddress; + + /// @notice the proxy address + address public immutable i_proxyAddress; + + /// @notice the reward manager address + IRewardManager public immutable i_rewardManager; + + // @notice the mask to apply to get the report version + bytes32 private constant REPORT_VERSION_MASK = 0xffff000000000000000000000000000000000000000000000000000000000000; + + // @notice the different report versions + bytes32 private constant REPORT_V1 = 0x0001000000000000000000000000000000000000000000000000000000000000; + + /// @notice the surcharge fee to be paid if paying in native + uint256 public s_nativeSurcharge; + + /// @notice the error thrown if the discount or surcharge is invalid + error InvalidSurcharge(); + + /// @notice the error thrown if the discount is invalid + error InvalidDiscount(); + + /// @notice the error thrown if the address is invalid + error InvalidAddress(); + + /// @notice thrown if msg.value is supplied with a bad quote + error InvalidDeposit(); + + /// @notice thrown if a report has expired + error ExpiredReport(); + + /// @notice thrown if a report has no quote + error InvalidQuote(); + + // @notice thrown when the caller is not authorized + error Unauthorized(); + + // @notice thrown when trying to clear a zero deficit + error ZeroDeficit(); + + /// @notice thrown when trying to pay an address that cannot except funds + error InvalidReceivingAddress(); + + /// @notice Emitted whenever a subscriber's discount is updated + /// @param subscriber address of the subscriber to update discounts for + /// @param feedId Feed ID for the discount + /// @param token Token address for the discount + /// @param discount Discount to apply, in relation to the PERCENTAGE_SCALAR + event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount); + + /// @notice Emitted when updating the native surcharge + /// @param newSurcharge Surcharge amount to apply relative to PERCENTAGE_SCALAR + event NativeSurchargeUpdated(uint64 newSurcharge); + + /// @notice Emits when this contract does not have enough LINK to send to the reward manager when paying in native + /// @param rewards Config digest and link fees which could not be subsidised + event InsufficientLink(IRewardManager.FeePayment[] rewards); + + /// @notice Emitted when funds are withdrawn + /// @param adminAddress Address of the admin + /// @param recipient Address of the recipient + /// @param assetAddress Address of the asset withdrawn + /// @param quantity Amount of the asset withdrawn + event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity); + + /// @notice Emits when a deficit has been cleared for a particular config digest + /// @param configDigest Config digest of the deficit cleared + /// @param linkQuantity Amount of LINK required to pay the deficit + event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity); + + /// @notice Emits when a fee has been processed + /// @param configDigest Config digest of the fee processed + /// @param subscriber Address of the subscriber who paid the fee + /// @param fee Fee paid + /// @param reward Reward paid + /// @param appliedDiscount Discount applied to the fee + event DiscountApplied( + bytes32 indexed configDigest, + address indexed subscriber, + Common.Asset fee, + Common.Asset reward, + uint256 appliedDiscount + ); + + /** + * @notice Construct the FeeManager contract + * @param _linkAddress The address of the LINK token + * @param _nativeAddress The address of the wrapped ERC-20 version of the native token (represents fee in native or + * wrapped) + * @param _proxyAddress The address of the proxy contract + * @param _rewardManagerAddress The address of the reward manager contract + */ + constructor( + address _linkAddress, + address _nativeAddress, + address _proxyAddress, + address _rewardManagerAddress + ) ConfirmedOwner(msg.sender) { + if ( + _linkAddress == address(0) || _nativeAddress == address(0) || _proxyAddress == address(0) + || _rewardManagerAddress == address(0) + ) revert InvalidAddress(); + + i_linkAddress = _linkAddress; + i_nativeAddress = _nativeAddress; + i_proxyAddress = _proxyAddress; + i_rewardManager = IRewardManager(_rewardManagerAddress); + + IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max); + } + + modifier onlyOwnerOrProxy() { + if (msg.sender != i_proxyAddress && msg.sender != owner()) revert Unauthorized(); + _; + } + + modifier onlyProxy() { + if (msg.sender != i_proxyAddress) revert Unauthorized(); + _; + } + + /// @inheritdoc ITypeAndVersion + function typeAndVersion() external pure override returns (string memory) { + return "FeeManager 2.1.0"; + } + + /// @inheritdoc IERC165 + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector; + } + + /// @inheritdoc IVerifierFeeManager + function processFee( + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyProxy { + _processFee(payload, parameterPayload, subscriber); + } + + /// @inheritdoc IVerifierFeeManager + function processFeeBulk( + bytes[] calldata payloads, + bytes calldata parameterPayload, + address subscriber + ) external payable override onlyProxy { + FeeAndReward[] memory feesAndRewards = new IFeeManager.FeeAndReward[](payloads.length); + + //keep track of the number of fees to prevent over initialising the FeePayment array within + // _convertToLinkAndNativeFees + uint256 numberOfLinkFees; + uint256 numberOfNativeFees; + + uint256 feesAndRewardsIndex; + for (uint256 i; i < payloads.length; ++i) { + (Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = + _processFee(payloads[i], parameterPayload, subscriber); + + if (fee.amount != 0) { + feesAndRewards[feesAndRewardsIndex++] = + IFeeManager.FeeAndReward(bytes32(payloads[i]), fee, reward, appliedDiscount); + + unchecked { + //keep track of some tallys to make downstream calculations more efficient + if (fee.assetAddress == i_linkAddress) { + ++numberOfLinkFees; + } else { + ++numberOfNativeFees; + } + } + } + } + + if (numberOfLinkFees != 0 || numberOfNativeFees != 0) { + _handleFeesAndRewards(subscriber, feesAndRewards, numberOfLinkFees, numberOfNativeFees); + } + } + + /// @inheritdoc IFeeManager + function getFeeAndReward( + address, // subscriber + bytes memory, // report, + address quoteAddress + ) public view returns (Common.Asset memory, Common.Asset memory, uint256) { + Common.Asset memory fee; + Common.Asset memory reward; + + //verify the quote payload is a supported token + if (quoteAddress != i_nativeAddress && quoteAddress != i_linkAddress) { + revert InvalidQuote(); + } + + return (fee, reward, 0); + } + + /// @inheritdoc IVerifierFeeManager + function setFeeRecipients( + bytes32 configDigest, + Common.AddressAndWeight[] calldata rewardRecipientAndWeights + ) external onlyOwnerOrProxy { + i_rewardManager.setRewardRecipients(configDigest, rewardRecipientAndWeights); + } + + /// @inheritdoc IFeeManager + function setNativeSurcharge( + uint64 surcharge + ) external onlyOwner { + if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge(); + + s_nativeSurcharge = surcharge; + + emit NativeSurchargeUpdated(surcharge); + } + + /// @inheritdoc IFeeManager + function updateSubscriberDiscount( + address subscriber, + bytes32 feedId, + address token, + uint64 discount + ) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_subscriberDiscounts[subscriber][feedId][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, feedId, token, discount); + } + + function updateSubscriberGlobalDiscount(address subscriber, address token, uint64 discount) external onlyOwner { + //make sure the discount is not greater than the total discount that can be applied + if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount(); + //make sure the token is either LINK or native + if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress(); + + s_globalDiscounts[subscriber][token] = discount; + + emit SubscriberDiscountUpdated(subscriber, bytes32(0), token, discount); + } + + /// @inheritdoc IFeeManager + function withdraw(address assetAddress, address recipient, uint192 quantity) external onlyOwner { + //withdraw the requested asset + IERC20(assetAddress).safeTransfer(recipient, quantity); + + //emit event when funds are withdrawn + emit Withdraw(msg.sender, recipient, assetAddress, uint192(quantity)); + } + + /// @inheritdoc IFeeManager + function linkAvailableForPayment() external view returns (uint256) { + //return the amount of LINK this contact has available to pay rewards + return IERC20(i_linkAddress).balanceOf(address(this)); + } + + /** + * @notice Gets the current version of the report that is encoded as the last two bytes of the feed + * @param feedId feed id to get the report version for + */ + function _getReportVersion( + bytes32 feedId + ) internal pure returns (bytes32) { + return REPORT_VERSION_MASK & feedId; + } + + function _processFee( + bytes calldata payload, + bytes calldata parameterPayload, + address subscriber + ) internal view returns (Common.Asset memory, Common.Asset memory, uint256) { + if (subscriber == address(this)) revert InvalidAddress(); + + //decode the report from the payload + (, bytes memory report) = abi.decode(payload, (bytes32[3], bytes)); + + //get the feedId from the report + bytes32 feedId = bytes32(report); + + //v1 doesn't need a quote payload, so skip the decoding + address quote; + if (_getReportVersion(feedId) != REPORT_V1) { + //decode the quote from the bytes + (quote) = abi.decode(parameterPayload, (address)); + } + + //decode the fee, it will always be native or LINK + return getFeeAndReward(subscriber, report, quote); + } + + function _handleFeesAndRewards( + address subscriber, + FeeAndReward[] memory feesAndRewards, + uint256 numberOfLinkFees, + uint256 numberOfNativeFees + ) internal {} + + /// @inheritdoc IFeeManager + function payLinkDeficit( + bytes32 configDigest + ) external onlyOwner { + uint256 deficit = s_linkDeficit[configDigest]; + + emit LinkDeficitCleared(configDigest, deficit); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/BaseRewardManager.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/BaseRewardManager.t.sol new file mode 100644 index 0000000000..0203b17278 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/BaseRewardManager.t.sol @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; + +import {Common} from "../../../libraries/Common.sol"; +import {RewardManager} from "../../RewardManager.sol"; +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import {Test} from "forge-std/Test.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice Base class for all reward manager tests + * @dev This contract is intended to be inherited from and not used directly. It contains functionality to setup a + * primary and secondary pool + */ +contract BaseRewardManagerTest is Test { + //contracts + ERC20Mock internal asset; + ERC20Mock internal unsupported; + RewardManager internal rewardManager; + + //default address for unregistered recipient + address internal constant INVALID_ADDRESS = address(0); + //contract owner + address internal constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); + //address to represent verifier contract + address internal constant FEE_MANAGER = address(uint160(uint256(keccak256("FEE_MANAGER")))); + //a general user + address internal constant USER = address(uint160(uint256(keccak256("USER")))); + + //default recipients configured in reward manager + address internal constant DEFAULT_RECIPIENT_1 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_1")))); + address internal constant DEFAULT_RECIPIENT_2 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_2")))); + address internal constant DEFAULT_RECIPIENT_3 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_3")))); + address internal constant DEFAULT_RECIPIENT_4 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_4")))); + address internal constant DEFAULT_RECIPIENT_5 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_5")))); + address internal constant DEFAULT_RECIPIENT_6 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_6")))); + address internal constant DEFAULT_RECIPIENT_7 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_7")))); + + //additional recipients not in the reward manager + address internal constant DEFAULT_RECIPIENT_8 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_8")))); + address internal constant DEFAULT_RECIPIENT_9 = address(uint160(uint256(keccak256("DEFAULT_RECIPIENT_9")))); + + //two pools should be enough to test all edge cases + bytes32 internal constant PRIMARY_POOL_ID = keccak256("primary_pool"); + bytes32 internal constant SECONDARY_POOL_ID = keccak256("secondary_pool"); + bytes32 internal constant INVALID_POOL_ID = keccak256("invalid_pool"); + bytes32 internal constant ZERO_POOL_ID = bytes32(0); + + //convenience arrays of all pool combinations used for testing + bytes32[] internal PRIMARY_POOL_ARRAY = [PRIMARY_POOL_ID]; + bytes32[] internal SECONDARY_POOL_ARRAY = [SECONDARY_POOL_ID]; + bytes32[] internal ALL_POOLS = [PRIMARY_POOL_ID, SECONDARY_POOL_ID]; + + //erc20 config + uint256 internal constant DEFAULT_MINT_QUANTITY = 100 ether; + + //reward scalar (this should match the const in the contract) + uint64 internal constant POOL_SCALAR = 1e18; + uint64 internal constant ONE_PERCENT = POOL_SCALAR / 100; + uint64 internal constant FIFTY_PERCENT = POOL_SCALAR / 2; + uint64 internal constant TEN_PERCENT = POOL_SCALAR / 10; + + //the selector for each error + bytes4 internal immutable UNAUTHORIZED_ERROR_SELECTOR = RewardManager.Unauthorized.selector; + bytes4 internal immutable INVALID_ADDRESS_ERROR_SELECTOR = RewardManager.InvalidAddress.selector; + bytes4 internal immutable INVALID_WEIGHT_ERROR_SELECTOR = RewardManager.InvalidWeights.selector; + bytes4 internal immutable INVALID_POOL_ID_ERROR_SELECTOR = RewardManager.InvalidPoolId.selector; + bytes internal constant ONLY_CALLABLE_BY_OWNER_ERROR = "Only callable by owner"; + bytes4 internal immutable INVALID_POOL_LENGTH_SELECTOR = RewardManager.InvalidPoolLength.selector; + + // Events emitted within the reward manager + event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients); + event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity); + event FeeManagerUpdated(address newProxyAddress); + event FeePaid(IRewardManager.FeePayment[] payments, address payee); + + function setUp() public virtual { + //change to admin user + vm.startPrank(ADMIN); + + //init required contracts + _initializeERC20Contracts(); + _initializeRewardManager(); + } + + function _initializeERC20Contracts() internal { + //create the contracts + asset = new ERC20Mock(18); + unsupported = new ERC20Mock(18); + + //mint some tokens to the admin + asset.mint(ADMIN, DEFAULT_MINT_QUANTITY); + unsupported.mint(ADMIN, DEFAULT_MINT_QUANTITY); + + //mint some tokens to the user + asset.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); + unsupported.mint(FEE_MANAGER, DEFAULT_MINT_QUANTITY); + } + + function _initializeRewardManager() internal { + //create the contract + rewardManager = new RewardManager(address(asset)); + + rewardManager.setFeeManager(FEE_MANAGER); + } + + function createPrimaryPool() public { + rewardManager.setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients()); + } + + function createSecondaryPool() public { + rewardManager.setRewardRecipients(SECONDARY_POOL_ID, getSecondaryRecipients()); + } + + //override this to test variations of different recipients. changing this function will require existing tests to be + // updated as constants are hardcoded to be explicit + function getPrimaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights. 2500 = 25% of pool + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, POOL_SCALAR / 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, POOL_SCALAR / 4); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR / 4); + + return recipients; + } + + function getPrimaryRecipientAddresses() public pure returns (address[] memory) { + //array of recipients + address[] memory recipients = new address[](4); + + recipients[0] = DEFAULT_RECIPIENT_1; + recipients[1] = DEFAULT_RECIPIENT_2; + recipients[2] = DEFAULT_RECIPIENT_3; + recipients[3] = DEFAULT_RECIPIENT_4; + + return recipients; + } + + //override this to test variations of different recipients. + function getSecondaryRecipients() public virtual returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights. 2500 = 25% of pool + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR / 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, POOL_SCALAR / 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, POOL_SCALAR / 4); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, POOL_SCALAR / 4); + + return recipients; + } + + function getSecondaryRecipientAddresses() public pure returns (address[] memory) { + //array of recipients + address[] memory recipients = new address[](4); + + recipients[0] = DEFAULT_RECIPIENT_1; + recipients[1] = DEFAULT_RECIPIENT_5; + recipients[2] = DEFAULT_RECIPIENT_6; + recipients[3] = DEFAULT_RECIPIENT_7; + + return recipients; + } + + function addFundsToPool(bytes32 poolId, Common.Asset memory amount, address sender) public { + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); + payments[0] = IRewardManager.FeePayment(poolId, uint192(amount.amount)); + + addFundsToPool(payments, sender); + } + + function addFundsToPool(IRewardManager.FeePayment[] memory payments, address sender) public { + //record the current address and switch to the sender + address originalAddr = msg.sender; + changePrank(sender); + + uint256 totalPayment; + for (uint256 i; i < payments.length; ++i) { + totalPayment += payments[i].amount; + } + + //approve the amount being paid into the pool + ERC20Mock(address(asset)).approve(address(rewardManager), totalPayment); + + //this represents the verifier adding some funds to the pool + rewardManager.onFeePaid(payments, sender); + + //change back to the original address + changePrank(originalAddr); + } + + function getAsset( + uint256 quantity + ) public view returns (Common.Asset memory) { + return Common.Asset(address(asset), quantity); + } + + function getAssetBalance( + address addr + ) public view returns (uint256) { + return asset.balanceOf(addr); + } + + function claimRewards(bytes32[] memory poolIds, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //claim the rewards + rewardManager.claimRewards(poolIds); + + //change back to the original address + changePrank(originalAddr); + } + + function payRecipients(bytes32 poolId, address[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.payRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.setRewardRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } + + function setFeeManager(address feeManager, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //update the proxy + rewardManager.setFeeManager(feeManager); + + //change back to the original address + changePrank(originalAddr); + } + + function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] memory recipients, address sender) public { + //record the current address and switch to the recipient + address originalAddr = msg.sender; + changePrank(sender); + + //pay the recipients + rewardManager.updateRewardRecipients(poolId, recipients); + + //change back to the original address + changePrank(originalAddr); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.claim.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.claim.t.sol new file mode 100644 index 0000000000..f77dab0328 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.claim.t.sol @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice This contract will test the claim functionality of the RewardManager contract. + */ +contract RewardManagerClaimTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipients() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } + + function test_claimRewardsWithDuplicatePoolIdsDoesNotPayoutTwice() public { + //add funds to a different pool to ensure they're not claimed + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create an array containing duplicate poolIds + bytes32[] memory poolIds = new bytes32[](2); + poolIds[0] = PRIMARY_POOL_ID; + poolIds[1] = PRIMARY_POOL_ID; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(poolIds, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the pool should still have the remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimSingleRecipient() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount); + } + + function test_claimMultipleRecipients() public { + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - (expectedRecipientAmount * 2)); + } + + function test_claimUnregisteredRecipient() public { + //claim the rewards for a recipient who isn't in this pool + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //check the recipients didn't receive any fees from this pool + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimUnevenAmountRoundsDown() public { + //adding 1 to the pool should leave 1 wei worth of dust, which the contract doesn't handle due to it being + // economically infeasible + addFundsToPool(PRIMARY_POOL_ID, getAsset(1), FEE_MANAGER); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //check the rewardManager has the remaining quantity equals 1 wei + assertEq(getAssetBalance(address(rewardManager)), 1); + } + + function test_claimUnregisteredPoolId() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the recipients balance is still 0 as there's no pool to receive fees from + assertEq(getAssetBalance(recipient.addr), 0); + + //check the rewardManager has the full amount + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_singleRecipientClaimMultipleDeposits() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2); + + //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2)); + } + + function test_recipientsClaimMultipleDeposits() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should be 0 as all of the funds have been claimed + assertEq(getAssetBalance(address(rewardManager)), 0); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //expected recipient amount is 1/4 of the pool deposit + expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should again be 0 as all of the funds have been claimed + assertEq(getAssetBalance(address(rewardManager)), 0); + } + + function test_eventIsEmittedUponClaim() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardsClaimed(PRIMARY_POOL_ID, recipient.addr, uint192(POOL_DEPOSIT_AMOUNT / 4)); + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + } + + function test_eventIsNotEmittedUponUnsuccessfulClaim() public { + //record logs to check no events were emitted + vm.recordLogs(); + + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } +} + +contract RewardManagerRecipientClaimMultiplePoolsTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a two pools + createPrimaryPool(); + createSecondaryPool(); + + //add funds to each of the pools to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipientsSinglePool() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //check the pool balance is still equal to DEPOSIT_AMOUNT as the test only claims for one of the pools + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimMultipleRecipientsSinglePool() public { + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - (expectedRecipientAmount * 2)); + } + + function test_claimMultipleRecipientsMultiplePools() public { + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received. The first recipient is shared + // across both pools so should receive 1/4 of each pool + assertEq(getAssetBalance(getPrimaryRecipients()[0].addr), expectedRecipientAmount * 2); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), expectedRecipientAmount); + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimAllRecipientsMultiplePools() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i = 1; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //claim funds for each recipient within the pool + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory secondaryRecipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, secondaryRecipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(secondaryRecipient.addr), expectedRecipientAmount); + } + + //special case to handle the first recipient of each pool as they're the same address + Common.AddressAndWeight memory commonRecipient = getPrimaryRecipients()[0]; + + //claim the individual rewards for each pool + claimRewards(PRIMARY_POOL_ARRAY, commonRecipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, commonRecipient.addr); + + //check the balance matches the ratio the recipient should have received, which is 1/4 of each deposit for each pool + assertEq(getAssetBalance(commonRecipient.addr), expectedRecipientAmount * 2); + } + + function test_claimSingleUniqueRecipient() public { + //the first recipient of the secondary pool is in both pools, so take the second recipient which is unique + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[1]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount + uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4; + + //the recipient should have received 1/4 of the deposit amount + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount); + } + + function test_claimSingleRecipientMultiplePools() public { + //the first recipient of the secondary pool is in both pools + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount for each pool + uint256 recipientExpectedAmount = (POOL_DEPOSIT_AMOUNT / 4) * 2; + + //this recipient belongs in both pools so should have received 1/4 of each + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - recipientExpectedAmount); + } + + function test_claimUnregisteredRecipient() public { + //claim the individual rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[1].addr); + claimRewards(SECONDARY_POOL_ARRAY, getPrimaryRecipients()[1].addr); + + //check the recipients didn't receive any fees from this pool + assertEq(getAssetBalance(getSecondaryRecipients()[1].addr), 0); + assertEq(getAssetBalance(getPrimaryRecipients()[1].addr), 0); + + //check the rewardManager has the remaining quantity + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2); + } + + function test_claimUnevenAmountRoundsDown() public { + //adding an uneven amount of dust to each pool, this should round down to the nearest whole number with 4 remaining + // in the contract + addFundsToPool(PRIMARY_POOL_ID, getAsset(3), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(1), FEE_MANAGER); + + //the recipient should have received 1/4 of the deposit amount for each pool + uint256 recipientExpectedAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + } + + //special case to handle the first recipient of each pool as they're the same address + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), recipientExpectedAmount * 2); + + //claim funds for each recipient of the secondary pool except the first + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), recipientExpectedAmount); + } + + //contract should have 4 remaining + assertEq(getAssetBalance(address(rewardManager)), 4); + } + + function test_singleRecipientClaimMultipleDeposits() public { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[0]; + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is 3/4 of the initial deposit plus the deposit from the + // second pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 2 - expectedRecipientAmount); + + //add funds to the pool to be split among the recipients + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the individual rewards for this recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received 1/4 of the next deposit amount + expectedRecipientAmount += POOL_DEPOSIT_AMOUNT / 4; + + //check the recipients balance matches the ratio the recipient should have received, which is 1/4 of each deposit + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + + //check the rewardManager has the remaining quantity, which is now 3/4 of both deposits + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT * 3 - expectedRecipientAmount); + } + + function test_recipientsClaimMultipleDeposits() public { + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim funds for each recipient within the pool + for (uint256 i; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //the reward manager balance should contain only the funds of the secondary pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + + //add funds to the pool to be split among the recipients + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //special case to handle the first recipient of each pool as they're the same address + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount * 2); + + //claim funds for each recipient within the pool except the first + for (uint256 i = 1; i < getSecondaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getSecondaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(SECONDARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount * 2); + } + + //the reward manager balance should again be the balance of the secondary pool as the primary pool has been emptied + // twice + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_claimEmptyPoolWhenSecondPoolContainsFunds() public { + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //claim all rewards for each recipient in the primary pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //claim all the rewards again for the first recipient as that address is a member of both pools + claimRewards(PRIMARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //check the balance + assertEq(getAssetBalance(getSecondaryRecipients()[0].addr), expectedRecipientAmount); + } + + function test_getRewardsAvailableToRecipientInBothPools() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = + rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, type(uint256).max); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], SECONDARY_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInSinglePool() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = + rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[1].addr, 0, type(uint256).max); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInNoPools() public view { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, 0, type(uint256).max); + + //check the recipient is in neither pool + assertEq(poolIds[0], ZERO_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getRewardsAvailableToRecipientInBothPoolsWhereAlreadyClaimed() public { + //get index 0 as this recipient is in both default pools + bytes32[] memory poolIds = + rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, type(uint256).max); + + //check the recipient is in both pools + assertEq(poolIds[0], PRIMARY_POOL_ID); + assertEq(poolIds[1], SECONDARY_POOL_ID); + + //claim the rewards for each pool + claimRewards(PRIMARY_POOL_ARRAY, getPrimaryRecipients()[0].addr); + claimRewards(SECONDARY_POOL_ARRAY, getSecondaryRecipients()[0].addr); + + //get the available pools again + poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, type(uint256).max); + + //user should not be in any pool + assertEq(poolIds[0], ZERO_POOL_ID); + assertEq(poolIds[1], ZERO_POOL_ID); + } + + function test_getAvailableRewardsCursorCannotBeGreaterThanTotalPools() public { + vm.expectRevert(INVALID_POOL_LENGTH_SELECTOR); + + rewardManager.getAvailableRewardPoolIds(FEE_MANAGER, type(uint256).max, 0); + } + + function test_getAvailableRewardsCursorAndTotalPoolsEqual() public { + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 2, 2); + + assertEq(poolIds.length, 0); + } + + function test_getAvailableRewardsCursorSingleResult() public { + bytes32[] memory poolIds = rewardManager.getAvailableRewardPoolIds(getPrimaryRecipients()[0].addr, 0, 1); + + assertEq(poolIds[0], PRIMARY_POOL_ID); + } +} + +contract RewardManagerRecipientClaimDifferentWeightsTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with uneven weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 8); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 6); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 4); + + return recipients; + } + + function test_allRecipientsClaimingReceiveExpectedAmount() public { + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } +} + +contract RewardManagerRecipientClaimUnevenWeightTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + } + + function getPrimaryRecipients() public virtual override returns (Common.AddressAndWeight[] memory) { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + + uint64 oneThird = POOL_SCALAR / 3; + + //init each recipient with even weights. + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, oneThird); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 2 * oneThird + 1); + + return recipients; + } + + function test_allRecipientsClaimingReceiveExpectedAmountWithSmallDeposit() public { + //add a smaller amount of funds to the pool + uint256 smallDeposit = 1e8; + + //add a smaller amount of funds to the pool + addFundsToPool(PRIMARY_POOL_ID, getAsset(smallDeposit), FEE_MANAGER); + + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (smallDeposit * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //smaller deposits will consequently have less precision and will not be able to be split as evenly, the remaining 1 + // will be lost due to 333...|... being paid out instead of 333...4| + assertEq(getAssetBalance(address(rewardManager)), 1); + } + + function test_allRecipientsClaimingReceiveExpectedAmount() public { + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop all the recipients and claim their expected amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //claim the individual rewards for each recipient + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //the recipient should have received a share proportional to their weight + uint256 expectedRecipientAmount = (POOL_DEPOSIT_AMOUNT * recipient.weight) / POOL_SCALAR; + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + + //their should be 0 wei left over indicating a successful split + assertEq(getAssetBalance(address(rewardManager)), 0); + } +} + +contract RewardManagerNoRecipientSet is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //add funds to the pool to be split among the recipients once registered + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_claimAllRecipientsAfterRecipientsSet() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //try and claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //there should be no rewards claimed as the recipient is not registered + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the recipient received nothing + assertEq(getAssetBalance(recipient.addr), 0); + } + + //Set the recipients after the rewards have been paid into the pool + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //claim funds for each recipient within the pool + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //get the recipient that is claiming + Common.AddressAndWeight memory recipient = getPrimaryRecipients()[i]; + + //there should be no rewards claimed as the recipient is registered + claimRewards(PRIMARY_POOL_ARRAY, recipient.addr); + + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipient.addr), expectedRecipientAmount); + } + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.general.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.general.t.sol new file mode 100644 index 0000000000..6dd88b79f2 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.general.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; +import {RewardManager} from "../../RewardManager.sol"; +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice This contract will test the core functionality of the RewardManager contract + */ +contract RewardManagerSetupTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + } + + function test_rejectsZeroLinkAddressOnConstruction() public { + //should revert if the contract is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //create a rewardManager with a zero link address + new RewardManager(address(0)); + } + + function test_eventEmittedUponFeeManagerUpdate() public { + //expect the event to be emitted + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit FeeManagerUpdated(FEE_MANAGER); + + //set the verifier proxy + setFeeManager(FEE_MANAGER, ADMIN); + } + + function test_eventEmittedUponFeePaid() public { + //create pool and add funds + createPrimaryPool(); + + //change to the feeManager who is the one who will be paying the fees + changePrank(FEE_MANAGER); + + //approve the amount being paid into the pool + ERC20Mock(getAsset(POOL_DEPOSIT_AMOUNT).assetAddress).approve(address(rewardManager), POOL_DEPOSIT_AMOUNT); + + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); + payments[0] = IRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT)); + + //event is emitted when funds are added + vm.expectEmit(); + emit FeePaid(payments, FEE_MANAGER); + + //this represents the verifier adding some funds to the pool + rewardManager.onFeePaid(payments, FEE_MANAGER); + } + + function test_setFeeManagerZeroAddress() public { + //should revert if the contract is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the verifier proxy + setFeeManager(address(0), ADMIN); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.payRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.payRecipients.t.sol new file mode 100644 index 0000000000..644dd8a9a2 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.payRecipients.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {IRewardManager} from "../../interfaces/IRewardManager.sol"; +import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice This contract will test the payRecipients functionality of the RewardManager contract + */ +contract RewardManagerPayRecipientsTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_payAllRecipients() public { + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), ADMIN); + + //each recipient should receive 1/4 of the pool + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_paySingleRecipient() public { + //get the first individual recipient + address recipient = getPrimaryRecipientAddresses()[0]; + + //get a single recipient as an array + address[] memory recipients = new address[](1); + recipients[0] = recipient; + + //pay a single recipient + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //the recipient should have received 1/4 of the deposit amount + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + assertEq(getAssetBalance(recipient), expectedRecipientAmount); + } + + function test_payRecipientWithInvalidPool() public { + //get the first individual recipient + address recipient = getPrimaryRecipientAddresses()[0]; + + //get a single recipient as an array + address[] memory recipients = new address[](1); + recipients[0] = recipient; + + //pay a single recipient + payRecipients(SECONDARY_POOL_ID, recipients, ADMIN); + + //the recipient should have received nothing + assertEq(getAssetBalance(recipient), 0); + } + + function test_payRecipientsEmptyRecipientList() public { + //get a single recipient + address[] memory recipients = new address[](0); + + //pay a single recipient + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //rewardManager should have the full balance + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_payAllRecipientsWithAdditionalUnregisteredRecipient() public { + //load all the recipients and add an additional one who is not in the pool + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1); + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + recipients[recipients.length - 1] = DEFAULT_RECIPIENT_5; + + //pay the recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + + //the unregistered recipient should receive nothing + assertEq(getAssetBalance(DEFAULT_RECIPIENT_5), 0); + } + + function test_payAllRecipientsWithAdditionalInvalidRecipient() public { + //load all the recipients and add an additional one which is invalid, that should receive nothing + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length + 1); + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + recipients[recipients.length - 1] = INVALID_ADDRESS; + + //pay the recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_paySubsetOfRecipientsInPool() public { + //load a subset of the recipients into an array + address[] memory recipients = new address[](getPrimaryRecipientAddresses().length - 1); + for (uint256 i = 0; i < recipients.length; i++) { + recipients[i] = getPrimaryRecipientAddresses()[i]; + } + + //pay the subset of recipients + payRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //each recipient should receive 1/4 of the pool except the last + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each subset of recipients received the correct amount + for (uint256 i = 0; i < recipients.length - 1; i++) { + assertEq(getAssetBalance(recipients[i]), expectedRecipientAmount); + } + + //check the pool has the remaining balance + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT - expectedRecipientAmount * recipients.length); + } + + function test_payAllRecipientsFromNonAdminUser() public { + //should revert if the caller isn't an admin or recipient within the pool + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), FEE_MANAGER); + } + + function test_payAllRecipientsFromRecipientInPool() public { + //pay all the recipients in the pool + payRecipients(PRIMARY_POOL_ID, getPrimaryRecipientAddresses(), DEFAULT_RECIPIENT_1); + + //each recipient should receive 1/4 of the pool + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //check each recipient received the correct amount + for (uint256 i = 0; i < getPrimaryRecipientAddresses().length; i++) { + assertEq(getAssetBalance(getPrimaryRecipientAddresses()[i]), expectedRecipientAmount); + } + } + + function test_payRecipientsWithInvalidPoolId() public { + //pay all the recipients in the pool + payRecipients(INVALID_POOL_ID, getPrimaryRecipientAddresses(), ADMIN); + + //pool should still contain the full balance + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_addFundsToPoolAsOwner() public { + //add funds to the pool + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_addFundsToPoolAsNonOwnerOrFeeManager() public { + //should revert if the caller isn't an admin or recipient within the pool + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + IRewardManager.FeePayment[] memory payments = new IRewardManager.FeePayment[](1); + payments[0] = IRewardManager.FeePayment(PRIMARY_POOL_ID, uint192(POOL_DEPOSIT_AMOUNT)); + + //add funds to the pool + rewardManager.onFeePaid(payments, USER); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.setRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.setRecipients.t.sol new file mode 100644 index 0000000000..5b675db04b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.setRecipients.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice This contract will test the setRecipient functionality of the RewardManager contract + */ +contract RewardManagerSetRecipientsTest is BaseRewardManagerTest { + function setUp() public override { + //setup contracts + super.setUp(); + } + + function test_setRewardRecipients() public { + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRewardRecipientsIsEmpty() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWithZeroWeight() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, ONE_PERCENT * 25); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, ONE_PERCENT * 25); + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, 0); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWithZeroAddress() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = getPrimaryRecipients(); + + //override the first recipient with a zero address + recipients[0].addr = address(0); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientWeights() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 25); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, 25); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, 25); + + //should revert if the recipients array is empty + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //set the recipients with a recipient with a weight of 100% + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setSingleRewardRecipient() public { + //array of recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](1); + + //init each recipient with even weights + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, POOL_SCALAR); + + //set the recipients with a recipient with a weight of 100% + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_setRewardRecipientTwice() public { + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //should revert if recipients for this pool have already been set + vm.expectRevert(INVALID_POOL_ID_ERROR_SELECTOR); + + //set the recipients again + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRewardRecipientFromNonOwnerOrFeeManagerAddress() public { + //should revert if the sender is not the owner or proxy + vm.expectRevert(UNAUTHORIZED_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), USER); + } + + function test_setRewardRecipientFromManagerAddress() public { + //update the proxy address + setFeeManager(FEE_MANAGER, ADMIN); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER); + } + + function test_eventIsEmittedUponSetRecipients() public { + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients()); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + } + + function test_setRecipientContainsDuplicateRecipients() public { + //create a new array to hold the existing recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add all the existing recipients again + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i]; + } + + //should revert as the list contains a duplicate + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //set the recipients + setRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.updateRewardRecipients.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.updateRewardRecipients.t.sol new file mode 100644 index 0000000000..4e47d96d34 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/reward-manager/RewardManager.updateRewardRecipients.t.sol @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {BaseRewardManagerTest} from "./BaseRewardManager.t.sol"; + +/** + * @title BaseRewardManagerTest + * @author Michael Fletcher + * @notice This contract will test the updateRecipient functionality of the RewardManager contract + */ +contract RewardManagerUpdateRewardRecipientsTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function test_onlyAdminCanUpdateRecipients() public { + //should revert if the caller is not the admin + vm.expectRevert(ONLY_CALLABLE_BY_OWNER_ERROR); + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), FEE_MANAGER); + } + + function test_updateAllRecipientsWithSameAddressAndWeight() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount); + } + } + + function test_updatePartialRecipientsWithSameAddressAndWeight() public { + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //get a subset of the recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, ONE_PERCENT * 25); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, ONE_PERCENT * 25); + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should still have half remaining funds + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2); + } + + function test_updateRecipientWithNewZeroAddress() public { + //create a new array to hold the existing recipients plus a new zero address + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 1); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add a new address to the primary recipients + recipients[recipients.length - 1] = Common.AddressAndWeight(address(0), 0); + + //should revert if the recipient is a zero address + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //update the recipients with invalid address + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsContainsDuplicateRecipients() public { + //create a new array to hold the existing recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length * 2); + + //add all the existing recipients + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i] = getPrimaryRecipients()[i]; + } + //add all the existing recipients again + for (uint256 i; i < getPrimaryRecipients().length; i++) { + recipients[i + getPrimaryRecipients().length] = getPrimaryRecipients()[i]; + } + + //should revert as the list contains a duplicate + vm.expectRevert(INVALID_ADDRESS_ERROR_SELECTOR); + + //update the recipients with the duplicate addresses + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 4); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, ONE_PERCENT * 25); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, ONE_PERCENT * 25); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, ONE_PERCENT * 25); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, ONE_PERCENT * 25); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentPartialSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, FIFTY_PERCENT); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, FIFTY_PERCENT); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentLargerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 5); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 2); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT * 2); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT * 2); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT * 2); + recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT * 2); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsUpdateAndRemoveExistingForLargerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](9); + + //update the existing recipients + recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0); + recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0); + recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 3); + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT); + recipients[6] = Common.AddressAndWeight(DEFAULT_RECIPIENT_7, TEN_PERCENT); + recipients[7] = Common.AddressAndWeight(DEFAULT_RECIPIENT_8, TEN_PERCENT); + recipients[8] = Common.AddressAndWeight(DEFAULT_RECIPIENT_9, TEN_PERCENT); + + //should revert as the weight does not equal 100% + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsUpdateAndRemoveExistingForSmallerSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](5); + + //update the existing recipients + recipients[0] = Common.AddressAndWeight(getPrimaryRecipients()[0].addr, 0); + recipients[1] = Common.AddressAndWeight(getPrimaryRecipients()[1].addr, 0); + recipients[2] = Common.AddressAndWeight(getPrimaryRecipients()[2].addr, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(getPrimaryRecipients()[3].addr, TEN_PERCENT * 2); + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientsToDifferentSetWithInvalidWeights() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](getPrimaryRecipients().length + 2); + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //copy the recipient and set the weight to 0 which implies the recipient is being replaced + recipients[i] = Common.AddressAndWeight(getPrimaryRecipients()[i].addr, 0); + } + + //add the new recipients individually + recipients[4] = Common.AddressAndWeight(DEFAULT_RECIPIENT_5, TEN_PERCENT * 5); + recipients[5] = Common.AddressAndWeight(DEFAULT_RECIPIENT_6, TEN_PERCENT); + + //should revert as the weight will not equal 100% + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsToSubset() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, 0); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, 0); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 5); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsWithUnderWeightSet() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT); + + //should revert as the new weights exceed the previous weights being replaced + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updatePartialRecipientsWithExcessiveWeight() public { + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, POOL_SCALAR); + + //should revert as the new weights exceed the previous weights being replaced + vm.expectRevert(INVALID_WEIGHT_ERROR_SELECTOR); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + } + + function test_updateRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set with their new weights + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT * 3); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT * 5); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should have no funds remaining + assertEq(getAssetBalance(address(rewardManager)), 0); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop each user and claim the rewards + for (uint256 i; i < recipients.length; i++) { + //claim the rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr); + } + + //manually check the balance of each recipient + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_1), (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_2), (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_3), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 3) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_4), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 5) / POOL_SCALAR + expectedRecipientAmount + ); + } + + function test_partialUpdateRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set with their new weights + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](2); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should have half the funds remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT / 2); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //loop each user and claim the rewards + for (uint256 i; i < recipients.length; i++) { + //claim the rewards for this recipient + claimRewards(PRIMARY_POOL_ARRAY, recipients[i].addr); + } + + //manually check the balance of each recipient + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_1), (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(DEFAULT_RECIPIENT_2), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + + //the reward manager should have half the funds remaining + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + } + + function test_eventIsEmittedUponUpdateRecipients() public { + //expect an emit + vm.expectEmit(); + + //emit the event that is expected to be emitted + emit RewardRecipientsUpdated(PRIMARY_POOL_ID, getPrimaryRecipients()); + + //expected recipient amount is 1/4 of the pool deposit + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //updating a recipient should force the funds to be paid out + updateRewardRecipients(PRIMARY_POOL_ID, getPrimaryRecipients(), ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < getPrimaryRecipients().length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(getPrimaryRecipients()[i].addr), expectedRecipientAmount); + } + } +} + +contract RewardManagerUpdateRewardRecipientsMultiplePoolsTest is BaseRewardManagerTest { + uint256 internal constant POOL_DEPOSIT_AMOUNT = 10e18; + + function setUp() public override { + //setup contracts + super.setUp(); + + //create a single pool for these tests + createPrimaryPool(); + createSecondaryPool(); + + //add funds to the pool to be split among the recipients + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + addFundsToPool(SECONDARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + } + + function getSecondaryRecipients() public override returns (Common.AddressAndWeight[] memory) { + //for testing purposes, the primary and secondary pool to contain the same recipients + return getPrimaryRecipients(); + } + + function test_updatePrimaryRecipientWeights() public { + //expected recipient amount is 1/4 of the pool deposit for original recipients + uint256 expectedRecipientAmount = POOL_DEPOSIT_AMOUNT / 4; + + //create a list of containing recipients from the primary configured set, and new recipients + Common.AddressAndWeight[] memory recipients = new Common.AddressAndWeight[](4); + recipients[0] = Common.AddressAndWeight(DEFAULT_RECIPIENT_1, TEN_PERCENT * 4); + recipients[1] = Common.AddressAndWeight(DEFAULT_RECIPIENT_2, TEN_PERCENT * 4); + recipients[2] = Common.AddressAndWeight(DEFAULT_RECIPIENT_3, TEN_PERCENT); + recipients[3] = Common.AddressAndWeight(DEFAULT_RECIPIENT_4, TEN_PERCENT); + + //updating a recipient should force the funds to be paid out for the primary recipients + updateRewardRecipients(PRIMARY_POOL_ID, recipients, ADMIN); + + //check each recipient received the correct amount + for (uint256 i; i < recipients.length; i++) { + //check the balance matches the ratio the recipient should have received + assertEq(getAssetBalance(recipients[i].addr), expectedRecipientAmount); + } + + //the reward manager should still have the funds for the secondary pool + assertEq(getAssetBalance(address(rewardManager)), POOL_DEPOSIT_AMOUNT); + + //add more funds to the pool to check new distribution + addFundsToPool(PRIMARY_POOL_ID, getAsset(POOL_DEPOSIT_AMOUNT), FEE_MANAGER); + + //claim the rewards for the updated recipients manually + claimRewards(PRIMARY_POOL_ARRAY, recipients[0].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[1].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[2].addr); + claimRewards(PRIMARY_POOL_ARRAY, recipients[3].addr); + + //check the balance matches the ratio the recipient who were updated should have received + assertEq( + getAssetBalance(recipients[0].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[1].addr), + (POOL_DEPOSIT_AMOUNT * TEN_PERCENT * 4) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[2].addr), (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + assertEq( + getAssetBalance(recipients[3].addr), (POOL_DEPOSIT_AMOUNT * TEN_PERCENT) / POOL_SCALAR + expectedRecipientAmount + ); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/BaseVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/BaseVerifierTest.t.sol new file mode 100644 index 0000000000..b87310eb4c --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/BaseVerifierTest.t.sol @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; + +import {ERC20Mock} from "../../../../shared/mocks/ERC20Mock.sol"; +import {WERC20Mock} from "../../../../shared/mocks/WERC20Mock.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {FeeManager} from "../../FeeManager.sol"; + +import {RewardManager} from "../../RewardManager.sol"; +import {Verifier} from "../../Verifier.sol"; +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {IVerifier} from "../../interfaces/IVerifier.sol"; +import {ErroredVerifier} from "../mocks/ErroredVerifier.sol"; +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +import {Strings} from "@openzeppelin/contracts@4.9.6/utils/Strings.sol"; +import {Test} from "forge-std/Test.sol"; + +contract BaseTest is Test { + uint256 internal constant MAX_ORACLES = 31; + address internal constant ADMIN = address(1); + address internal constant USER = address(2); + address internal constant MOCK_VERIFIER_ADDRESS = address(100); + address internal constant MOCK_VERIFIER_ADDRESS_TWO = address(200); + address internal constant ACCESS_CONTROLLER_ADDRESS = address(300); + + bytes32 internal constant V_MASK = 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + bytes32 internal constant V1_BITMASK = 0x0001000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V2_BITMASK = 0x0002000000000000000000000000000000000000000000000000000000000000; + bytes32 internal constant V3_BITMASK = 0x0003000000000000000000000000000000000000000000000000000000000000; + + uint256 internal constant SOURCE_CHAIN_ID = 0x1234; + address internal constant SOURCE_ADDRESS = address(0x1234); + + //version 0 feeds + bytes32 internal constant FEED_ID = (keccak256("ETH-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant FEED_ID_2 = (keccak256("LINK-USD") & V_MASK) | V1_BITMASK; + bytes32 internal constant FEED_ID_3 = (keccak256("BTC-USD") & V_MASK) | V1_BITMASK; + + //version 3 feeds + bytes32 internal constant FEED_ID_V3 = (keccak256("ETH-USD") & V_MASK) | V3_BITMASK; + + bytes32 internal constant INVALID_FEED = keccak256("INVALID"); + uint32 internal constant OBSERVATIONS_TIMESTAMP = 1000; + uint64 internal constant BLOCKNUMBER_LOWER_BOUND = 1000; + uint64 internal constant BLOCKNUMBER_UPPER_BOUND = BLOCKNUMBER_LOWER_BOUND + 5; + int192 internal constant MEDIAN = 1 ether; + int192 internal constant BID = 500_000_000 gwei; + int192 internal constant ASK = 2 ether; + + bytes32 internal constant EMPTY_BYTES = bytes32(""); + + uint8 internal constant FAULT_TOLERANCE = 10; + uint64 internal constant VERIFIER_VERSION = 1; + + string internal constant SERVER_URL = "https://mercury.server/client/"; + uint8 internal constant MAX_COMMITMENT_DELAY = 5; + + VerifierProxy internal s_verifierProxy; + Verifier internal s_verifier; + Verifier internal s_verifier_2; + ErroredVerifier internal s_erroredVerifier; + + struct Signer { + uint256 mockPrivateKey; + address signerAddress; + } + + struct V1Report { + // The feed ID the report has data for + bytes32 feedId; + // The time the median value was observed on + uint32 observationsTimestamp; + // The median value agreed in an OCR round + int192 median; + // The best bid value agreed in an OCR round + int192 bid; + // The best ask value agreed in an OCR round + int192 ask; + // The upper bound of the block range the median value was observed within + uint64 blocknumberUpperBound; + // The blockhash for the upper bound of block range (ensures correct blockchain) + bytes32 upperBlockhash; + // The lower bound of the block range the median value was observed within + uint64 blocknumberLowerBound; + // The current block timestamp + uint64 currentBlockTimestamp; + } + + Signer[MAX_ORACLES] internal s_signers; + bytes32[] internal s_offchaintransmitters; + bool private s_baseTestInitialized; + + function setUp() public virtual { + // BaseTest.setUp is often called multiple times from tests' setUp due to inheritance. + if (s_baseTestInitialized) return; + s_baseTestInitialized = true; + + vm.startPrank(ADMIN); + vm.mockCall( + MOCK_VERIFIER_ADDRESS, + abi.encodeWithSelector(IERC165.supportsInterface.selector, IVerifier.verify.selector), + abi.encode(true) + ); + s_verifierProxy = new VerifierProxy(AccessControllerInterface(address(0))); + + s_verifier = new Verifier(address(s_verifierProxy)); + s_verifier_2 = new Verifier(address(s_verifierProxy)); + s_erroredVerifier = new ErroredVerifier(); + + for (uint256 i; i < MAX_ORACLES; i++) { + uint256 mockPK = i + 1; + s_signers[i].mockPrivateKey = mockPK; + s_signers[i].signerAddress = vm.addr(mockPK); + } + } + + function _getSigners( + uint256 numSigners + ) internal view returns (Signer[] memory) { + Signer[] memory signers = new Signer[](numSigners); + for (uint256 i; i < numSigners; i++) { + signers[i] = s_signers[i]; + } + return signers; + } + + function _getSignerAddresses( + Signer[] memory signers + ) internal view returns (address[] memory) { + address[] memory signerAddrs = new address[](signers.length); + for (uint256 i = 0; i < signerAddrs.length; i++) { + signerAddrs[i] = s_signers[i].signerAddress; + } + return signerAddrs; + } + + function _generateSignerSignatures( + bytes memory report, + bytes32[3] memory reportContext, + Signer[] memory signers + ) internal pure returns (bytes32[] memory rawRs, bytes32[] memory rawSs, bytes32 rawVs) { + bytes32[] memory rs = new bytes32[](signers.length); + bytes32[] memory ss = new bytes32[](signers.length); + bytes memory vs = new bytes(signers.length); + + bytes32 hash = keccak256(abi.encodePacked(keccak256(report), reportContext)); + + for (uint256 i = 0; i < signers.length; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signers[i].mockPrivateKey, hash); + rs[i] = r; + ss[i] = s; + vs[i] = bytes1(v - 27); + } + return (rs, ss, bytes32(vs)); + } + + function _encodeReport( + V1Report memory report + ) internal pure returns (bytes memory) { + return abi.encode( + report.feedId, + report.observationsTimestamp, + report.median, + report.bid, + report.ask, + report.blocknumberUpperBound, + report.upperBlockhash, + report.blocknumberLowerBound, + report.currentBlockTimestamp + ); + } + + function _generateV1EncodedBlob( + V1Report memory report, + bytes32[3] memory reportContext, + Signer[] memory signers + ) internal pure returns (bytes memory) { + bytes memory reportBytes = _encodeReport(report); + (bytes32[] memory rs, bytes32[] memory ss, bytes32 rawVs) = + _generateSignerSignatures(reportBytes, reportContext, signers); + return abi.encode(reportContext, reportBytes, rs, ss, rawVs); + } + + function _configDigestFromConfigData( + bytes32 feedId, + uint256 chainId, + address verifierAddr, + uint64 configCount, + address[] memory signers, + bytes32[] memory offchainTransmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + // Convert addresses to bytes array to match configurator + bytes[] memory signersAsBytes = new bytes[](signers.length); + for (uint256 i; i < signers.length; ++i) { + signersAsBytes[i] = abi.encodePacked(signers[i]); + } + + uint256 h = uint256( + keccak256( + abi.encode( + feedId, + chainId, + verifierAddr, + configCount, + signersAsBytes, + offchainTransmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x0009 << (256 - 16); // 0x000900..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } + + function _createV1Report( + bytes32 feedId, + uint32 observationsTimestamp, + int192 median, + int192 bid, + int192 ask, + uint64 blocknumberUpperBound, + bytes32 upperBlockhash, + uint64 blocknumberLowerBound, + uint32 currentBlockTimestamp + ) internal pure returns (V1Report memory) { + return V1Report({ + feedId: feedId, + observationsTimestamp: observationsTimestamp, + median: median, + bid: bid, + ask: ask, + blocknumberUpperBound: blocknumberUpperBound, + upperBlockhash: upperBlockhash, + blocknumberLowerBound: blocknumberLowerBound, + currentBlockTimestamp: currentBlockTimestamp + }); + } + + function _ccipReadURL(bytes32 feedId, uint256 commitmentBlock) internal pure returns (string memory url) { + return string( + abi.encodePacked( + SERVER_URL, + "?feedIDHex=", + Strings.toHexString(uint256(feedId)), + "&L2Blocknumber=", + Strings.toString(commitmentBlock) + ) + ); + } +} + +contract BaseTestWithConfiguredVerifierAndFeeManager is BaseTest { + FeeManager internal feeManager; + RewardManager internal rewardManager; + ERC20Mock internal link; + WERC20Mock internal native; + + uint256 internal constant DEFAULT_REPORT_LINK_FEE = 1e10; + uint256 internal constant DEFAULT_REPORT_NATIVE_FEE = 1e12; + + bytes32 internal v1ConfigDigest; + bytes32 internal v3ConfigDigest; + + struct V3Report { + // The feed ID the report has data for + bytes32 feedId; + // The time the median value was observed on + uint32 observationsTimestamp; + // The timestamp the report is valid from + uint32 validFromTimestamp; + // The link fee + uint192 linkFee; + // The native fee + uint192 nativeFee; + // The expiry of the report + uint32 expiresAt; + // The median value agreed in an OCR round + int192 benchmarkPrice; + // The best bid value agreed in an OCR round + int192 bid; + // The best ask value agreed in an OCR round + int192 ask; + } + + function setUp() public virtual override { + BaseTest.setUp(); + Signer[] memory signers = _getSigners(MAX_ORACLES); + + s_verifierProxy.initializeVerifier(address(s_verifier)); + + v1ConfigDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig( + v1ConfigDigest, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0) + ); + + v3ConfigDigest = _configDigestFromConfigData( + FEED_ID_V3, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig( + v3ConfigDigest, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0) + ); + + link = new ERC20Mock(18); + native = new WERC20Mock(); + + rewardManager = new RewardManager(address(link)); + feeManager = new FeeManager(address(link), address(native), address(s_verifierProxy), address(rewardManager)); + + s_verifierProxy.setFeeManager(feeManager); + rewardManager.setFeeManager(address(feeManager)); + } + + function _encodeReport( + V3Report memory report + ) internal pure returns (bytes memory) { + return abi.encode( + report.feedId, + report.observationsTimestamp, + report.validFromTimestamp, + report.nativeFee, + report.linkFee, + report.expiresAt, + report.benchmarkPrice, + report.bid, + report.ask + ); + } + + function _generateV3EncodedBlob( + V3Report memory report, + bytes32[3] memory reportContext, + Signer[] memory signers + ) internal pure returns (bytes memory) { + bytes memory reportBytes = _encodeReport(report); + (bytes32[] memory rs, bytes32[] memory ss, bytes32 rawVs) = + _generateSignerSignatures(reportBytes, reportContext, signers); + return abi.encode(reportContext, reportBytes, rs, ss, rawVs); + } + + function _generateV1Report() internal view returns (V1Report memory) { + return _createV1Report( + FEED_ID, + OBSERVATIONS_TIMESTAMP, + MEDIAN, + BID, + ASK, + BLOCKNUMBER_UPPER_BOUND, + bytes32(blockhash(BLOCKNUMBER_UPPER_BOUND)), + BLOCKNUMBER_LOWER_BOUND, + uint32(block.timestamp) + ); + } + + function _generateV3Report() internal view returns (V3Report memory) { + return V3Report({ + feedId: FEED_ID_V3, + observationsTimestamp: OBSERVATIONS_TIMESTAMP, + validFromTimestamp: uint32(block.timestamp), + nativeFee: uint192(DEFAULT_REPORT_NATIVE_FEE), + linkFee: uint192(DEFAULT_REPORT_LINK_FEE), + expiresAt: uint32(block.timestamp), + benchmarkPrice: MEDIAN, + bid: BID, + ask: ASK + }); + } + + function _generateReportContext( + bytes32 configDigest + ) internal pure returns (bytes32[3] memory) { + bytes32[3] memory reportContext; + reportContext[0] = configDigest; + reportContext[1] = bytes32(abi.encode(uint32(5), uint8(1))); + return reportContext; + } + + function _approveLink(address spender, uint256 quantity, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + link.approve(spender, quantity); + changePrank(originalAddr); + } + + function _approveNative(address spender, uint256 quantity, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + native.approve(spender, quantity); + changePrank(originalAddr); + } + + function _verify(bytes memory payload, address feeAddress, uint256 wrappedNativeValue, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + s_verifierProxy.verify{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + changePrank(originalAddr); + } + + function _verifyBulk(bytes[] memory payload, address feeAddress, uint256 wrappedNativeValue, address sender) internal { + address originalAddr = msg.sender; + changePrank(sender); + + s_verifierProxy.verifyBulk{value: wrappedNativeValue}(payload, abi.encode(feeAddress)); + + changePrank(originalAddr); + } +} + +contract BaseTestWithMultipleConfiguredDigests is BaseTestWithConfiguredVerifierAndFeeManager { + bytes32 internal s_configDigestOne; + bytes32 internal s_configDigestTwo; + bytes32 internal s_configDigestThree; + bytes32 internal s_configDigestFour; + bytes32 internal s_configDigestFive; + + uint32 internal s_numConfigsSet; + + uint8 internal constant FAULT_TOLERANCE_TWO = 2; + uint8 internal constant FAULT_TOLERANCE_THREE = 1; + + function setUp() public virtual override { + BaseTestWithConfiguredVerifierAndFeeManager.setUp(); + Signer[] memory signers = _getSigners(MAX_ORACLES); + + s_configDigestOne = v1ConfigDigest; + + // Verifier 1, Feed 1, Config 2 + Signer[] memory secondSetOfSigners = _getSigners(8); + s_configDigestTwo = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 2, + _getSignerAddresses(secondSetOfSigners), + s_offchaintransmitters, + FAULT_TOLERANCE_TWO, + bytes(""), + 2, + bytes("") + ); + s_verifier.setConfig( + s_configDigestTwo, _getSignerAddresses(secondSetOfSigners), FAULT_TOLERANCE_TWO, new Common.AddressAndWeight[](0) + ); + + // Verifier 1, Feed 1, Config 3 + Signer[] memory thirdSetOfSigners = _getSigners(5); + s_configDigestThree = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 3, + _getSignerAddresses(thirdSetOfSigners), + s_offchaintransmitters, + FAULT_TOLERANCE_THREE, + bytes(""), + 3, + bytes("") + ); + s_verifier.setConfig( + s_configDigestThree, + _getSignerAddresses(thirdSetOfSigners), + FAULT_TOLERANCE_THREE, + new Common.AddressAndWeight[](0) + ); + + // Verifier 1, Feed 2, Config 1 + s_configDigestFour = _configDigestFromConfigData( + FEED_ID_2, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + 4, + bytes("") + ); + s_verifier.setConfig( + s_configDigestFour, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0) + ); + + // Verifier 2, Feed 3, Config 1 + s_verifierProxy.initializeVerifier(address(s_verifier_2)); + s_configDigestFive = _configDigestFromConfigData( + FEED_ID_3, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + 5, + bytes("") + ); + s_verifier_2.setConfig( + s_configDigestFive, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0) + ); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierActivateConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierActivateConfigTest.t.sol new file mode 100644 index 0000000000..9dd200807f --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierActivateConfigTest.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Verifier} from "../../Verifier.sol"; +import { + BaseTestWithConfiguredVerifierAndFeeManager, BaseTestWithMultipleConfiguredDigests +} from "./BaseVerifierTest.t.sol"; + +contract VerifierActivateConfigTestV05 is BaseTestWithConfiguredVerifierAndFeeManager { + function test_revertsIfNotOwner() public { + vm.expectRevert("Only callable by owner"); + + changePrank(address(s_verifierProxy)); + s_verifier.activateConfig(bytes32("mock")); + } + + function test_revertsIfDigestIsEmpty() public { + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestEmpty.selector)); + s_verifier.activateConfig(bytes32("")); + } + + function test_revertsIfDigestNotSet() public { + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestNotSet.selector, bytes32("non-existent-digest"))); + s_verifier.activateConfig(bytes32("non-existent-digest")); + } +} + +contract VerifierActivateConfigWithDeactivatedConfigTestV05 is BaseTestWithMultipleConfiguredDigests { + bytes32[3] internal s_reportContext; + + event ConfigActivated(bytes32 configDigest); + + V1Report internal s_testReportOne; + + function setUp() public override { + BaseTestWithMultipleConfiguredDigests.setUp(); + s_reportContext[0] = s_configDigestTwo; + s_reportContext[1] = bytes32(abi.encode(uint32(5), uint8(1))); + s_testReportOne = _createV1Report( + FEED_ID, + uint32(block.timestamp), + MEDIAN, + BID, + ASK, + uint64(block.number), + blockhash(block.number + 3), + uint64(block.number + 3), + uint32(block.timestamp) + ); + + s_verifier.deactivateConfig(s_configDigestTwo); + } + + function test_allowsVerification() public { + s_verifier.activateConfig(s_configDigestTwo); + changePrank(address(s_verifierProxy)); + + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE_TWO + 1)); + s_verifier.verify(signedReport, msg.sender); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyConstructorTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyConstructorTest.t.sol new file mode 100644 index 0000000000..b75309e5ac --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyConstructorTest.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {BaseTest} from "./BaseVerifierTest.t.sol"; + +contract VerifierProxyConstructorTest is BaseTest { + function test_correctlySetsTheOwner() public { + VerifierProxy proxy = new VerifierProxy(AccessControllerInterface(address(0))); + assertEq(proxy.owner(), ADMIN); + } + + function test_correctlySetsTheCorrectAccessControllerInterface() public { + address accessControllerAddr = address(1234); + VerifierProxy proxy = new VerifierProxy(AccessControllerInterface(accessControllerAddr)); + assertEq(address(proxy.s_accessController()), accessControllerAddr); + } + + function test_correctlySetsVersion() public view { + string memory version = s_verifierProxy.typeAndVersion(); + assertEq(version, "VerifierProxy 2.0.0"); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyInitializeVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyInitializeVerifierTest.t.sol new file mode 100644 index 0000000000..04346f3f19 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyInitializeVerifierTest.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {BaseTest} from "./BaseVerifierTest.t.sol"; + +contract VerifierProxyInitializeVerifierTestV05 is BaseTest { + bytes32 latestDigest; + + function setUp() public override { + BaseTest.setUp(); + } + + function test_revertsIfNotOwner() public { + changePrank(USER); + vm.expectRevert("Only callable by owner"); + s_verifierProxy.initializeVerifier(address(s_verifier)); + } + + function test_revertsIfZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.ZeroAddress.selector)); + s_verifierProxy.initializeVerifier(address(0)); + } + + function test_revertsIfVerifierAlreadyInitialized() public { + s_verifierProxy.initializeVerifier(address(s_verifier)); + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.VerifierAlreadyInitialized.selector, address(s_verifier))); + s_verifierProxy.initializeVerifier(address(s_verifier)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetAccessControllerTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetAccessControllerTest.t.sol new file mode 100644 index 0000000000..f06d54c991 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetAccessControllerTest.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {BaseTest} from "./BaseVerifierTest.t.sol"; + +contract VerifierProxySetAccessControllerTest is BaseTest { + event AccessControllerSet(address oldAccessController, address newAccessController); + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + + changePrank(USER); + s_verifierProxy.setAccessController(AccessControllerInterface(ACCESS_CONTROLLER_ADDRESS)); + } + + function test_successfullySetsNewAccessController() public { + s_verifierProxy.setAccessController(AccessControllerInterface(ACCESS_CONTROLLER_ADDRESS)); + AccessControllerInterface ac = s_verifierProxy.s_accessController(); + assertEq(address(ac), ACCESS_CONTROLLER_ADDRESS); + } + + function test_successfullySetsNewAccessControllerIsEmpty() public { + s_verifierProxy.setAccessController(AccessControllerInterface(address(0))); + AccessControllerInterface ac = s_verifierProxy.s_accessController(); + assertEq(address(ac), address(0)); + } + + function test_emitsTheCorrectEvent() public { + vm.expectEmit(true, false, false, false); + emit AccessControllerSet(address(0), ACCESS_CONTROLLER_ADDRESS); + s_verifierProxy.setAccessController(AccessControllerInterface(ACCESS_CONTROLLER_ADDRESS)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetVerifierTest.t.sol new file mode 100644 index 0000000000..45547a3733 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxySetVerifierTest.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {IVerifier} from "../../interfaces/IVerifier.sol"; +import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; + +import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; + +contract VerifierProxyInitializeVerifierTestV05 is BaseTestWithConfiguredVerifierAndFeeManager { + function test_revertsIfNotCorrectVerifier() public { + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.AccessForbidden.selector)); + s_verifierProxy.setVerifier(bytes32("prev-config"), bytes32("new-config"), new Common.AddressAndWeight[](0)); + } + + function test_revertsIfDigestAlreadySet() public { + bytes32 takenDigest = v1ConfigDigest; + + address maliciousVerifier = address(666); + bytes32 maliciousDigest = bytes32("malicious-digest"); + vm.mockCall( + maliciousVerifier, + abi.encodeWithSelector(IERC165.supportsInterface.selector, IVerifier.verify.selector), + abi.encode(true) + ); + s_verifierProxy.initializeVerifier(maliciousVerifier); + vm.expectRevert( + abi.encodeWithSelector(VerifierProxy.ConfigDigestAlreadySet.selector, takenDigest, address(s_verifier)) + ); + changePrank(address(maliciousVerifier)); + s_verifierProxy.setVerifier(maliciousDigest, takenDigest, new Common.AddressAndWeight[](0)); + } + + function test_updatesVerifierIfVerifier() public { + bytes32 prevDigest = v1ConfigDigest; + changePrank(address(s_verifier)); + s_verifierProxy.setVerifier(prevDigest, bytes32("new-config"), new Common.AddressAndWeight[](0)); + assertEq(s_verifierProxy.getVerifier(bytes32("new-config")), address(s_verifier)); + assertEq(s_verifierProxy.getVerifier(prevDigest), address(s_verifier)); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyTest.t.sol new file mode 100644 index 0000000000..713f308ac8 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyTest.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {FeeManager} from "../../FeeManager.sol"; +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; + +contract VerifierProxyInitializeVerifierTestV05 is BaseTestWithConfiguredVerifierAndFeeManager { + function test_setFeeManagerZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.ZeroAddress.selector)); + s_verifierProxy.setFeeManager(FeeManager(address(0))); + } + + function test_setFeeManagerWhichDoesntHonourInterface() public { + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.FeeManagerInvalid.selector)); + s_verifierProxy.setFeeManager(FeeManager(address(s_verifier))); + } + + function test_setFeeManagerWhichDoesntHonourIERC165Interface() public { + vm.expectRevert(); + s_verifierProxy.setFeeManager(FeeManager(address(1))); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyUnsetVerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyUnsetVerifierTest.t.sol new file mode 100644 index 0000000000..58a239739b --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierProxyUnsetVerifierTest.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {BaseTest, BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; + +contract VerifierProxyUnsetVerifierTest is BaseTest { + function test_revertsIfNotAdmin() public { + vm.expectRevert("Only callable by owner"); + + changePrank(USER); + s_verifierProxy.unsetVerifier(bytes32("")); + } + + function test_revertsIfDigestDoesNotExist() public { + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.VerifierNotFound.selector, bytes32(""))); + s_verifierProxy.unsetVerifier(bytes32("")); + } +} + +contract VerifierProxyUnsetVerifierWithPreviouslySetVerifierTest is BaseTestWithConfiguredVerifierAndFeeManager { + bytes32 internal s_configDigest; + + event VerifierUnset(bytes32 configDigest, address verifierAddr); + + function setUp() public override { + BaseTestWithConfiguredVerifierAndFeeManager.setUp(); + s_configDigest = v3ConfigDigest; + } + + function test_correctlyUnsetsVerifier() public { + s_verifierProxy.unsetVerifier(s_configDigest); + address verifierAddr = s_verifierProxy.getVerifier(s_configDigest); + assertEq(verifierAddr, address(0)); + } + + function test_emitsAnEventAfterUnsettingVerifier() public { + vm.expectEmit(true, false, false, false); + emit VerifierUnset(s_configDigest, address(s_verifier)); + s_verifierProxy.unsetVerifier(s_configDigest); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierSetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierSetConfigTest.t.sol new file mode 100644 index 0000000000..7d86289364 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierSetConfigTest.t.sol @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Common} from "../../../libraries/Common.sol"; +import {Verifier} from "../../Verifier.sol"; +import {MockConfigurator} from "../mocks/MockConfigurator.sol"; +import {BaseTest, BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; + +contract VerifierSetConfigTestV05 is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + } + + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + Signer[] memory signers = _getSigners(MAX_ORACLES); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + changePrank(USER); + s_verifier.setConfig(configDigest, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfSetWithTooManySigners() public { + address[] memory signers = new address[](MAX_ORACLES + 1); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + vm.expectRevert(abi.encodeWithSelector(Verifier.ExcessSigners.selector, signers.length, MAX_ORACLES)); + s_verifier.setConfig(configDigest, signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfFaultToleranceIsZero() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + 0, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + vm.expectRevert(abi.encodeWithSelector(Verifier.FaultToleranceMustBePositive.selector)); + s_verifier.setConfig(configDigest, _getSignerAddresses(signers), 0, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfNotEnoughSigners() public { + address[] memory signers = new address[](2); + signers[0] = address(1000); + signers[1] = address(1001); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + signers, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + vm.expectRevert( + abi.encodeWithSelector(Verifier.InsufficientSigners.selector, signers.length, FAULT_TOLERANCE * 3 + 1) + ); + s_verifier.setConfig(configDigest, signers, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfDuplicateSigners() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + signerAddrs[0] = signerAddrs[1]; + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + signerAddrs, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + vm.expectRevert(abi.encodeWithSelector(Verifier.NonUniqueSignatures.selector)); + s_verifier.setConfig(configDigest, signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_revertsIfSignerContainsZeroAddress() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + address[] memory signerAddrs = _getSignerAddresses(signers); + signerAddrs[0] = address(0); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + signerAddrs, + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + vm.expectRevert(abi.encodeWithSelector(Verifier.ZeroAddress.selector)); + s_verifier.setConfig(configDigest, signerAddrs, FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + } + + function test_correctlyUpdatesTheConfig() public { + Signer[] memory signers = _getSigners(MAX_ORACLES); + + s_verifierProxy.initializeVerifier(address(s_verifier)); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + FAULT_TOLERANCE, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest, _getSignerAddresses(signers), FAULT_TOLERANCE, new Common.AddressAndWeight[](0)); + + uint32 blockNumber = s_verifier.latestConfigDetails(configDigest); + assertEq(blockNumber, block.number); + } +} + +contract VerifierUpdateConfigTest is BaseTest { + function setUp() public virtual override { + BaseTest.setUp(); + + s_verifierProxy.initializeVerifier(address(s_verifier)); + } + + function test_updateConfig() public { + // Get initial signers and config digest + address[] memory signerAddresses = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test222"); + + // Set initial config + s_verifier.setConfig(configDigest, signerAddresses, 4, new Common.AddressAndWeight[](0)); + + // Unset the config + s_verifier.updateConfig(configDigest, signerAddresses, signerAddresses, 4); + } + + function test_updateConfigRevertsIfFIsZero() public { + // Get initial signers and config digest + address[] memory signerAddresses = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + // Set initial config + s_verifier.setConfig(configDigest, signerAddresses, 4, new Common.AddressAndWeight[](0)); + + // Try to update with f=0 + vm.expectRevert(Verifier.FaultToleranceMustBePositive.selector); + s_verifier.updateConfig(configDigest, signerAddresses, signerAddresses, 0); + } + + function test_updateConfigRevertsIfFTooHigh() public { + // Get initial signers and config digest + address[] memory signerAddresses = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + // Set initial config + s_verifier.setConfig(configDigest, signerAddresses, 4, new Common.AddressAndWeight[](0)); + + // Try to update with f too high + vm.expectRevert(abi.encodeWithSelector(Verifier.InsufficientSigners.selector, signerAddresses.length, 46)); + s_verifier.updateConfig(configDigest, signerAddresses, signerAddresses, 15); + } + + function test_updateConfigWithDifferentSigners() public { + // Get initial signers and config digest + address[] memory initialSigners = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + // Set initial config + s_verifier.setConfig(configDigest, initialSigners, 4, new Common.AddressAndWeight[](0)); + + // Get new signers + address[] memory newSigners = _getSignerAddresses(_getSigners(20)); + + // Update config with new signers + s_verifier.updateConfig(configDigest, initialSigners, newSigners, 6); + + // Verify config was updated + uint32 blockNumber = s_verifier.latestConfigDetails(configDigest); + assertEq(blockNumber, block.number); + } + + function test_updateConfigRevertsIfDigestNotSet() public { + address[] memory signerAddresses = _getSignerAddresses(_getSigners(15)); + bytes32 nonExistentDigest = keccak256("nonexistent"); + + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestNotSet.selector, nonExistentDigest)); + s_verifier.updateConfig(nonExistentDigest, signerAddresses, signerAddresses, 4); + } + + function test_updateConfigRevertsIfPrevSignersLengthMismatch() public { + // Get initial signers and config digest + address[] memory initialSigners = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + // Set initial config + s_verifier.setConfig(configDigest, initialSigners, 4, new Common.AddressAndWeight[](0)); + + // Try to update with wrong number of previous signers + address[] memory wrongPrevSigners = _getSignerAddresses(_getSigners(10)); + address[] memory newSigners = _getSignerAddresses(_getSigners(15)); + + vm.expectRevert(Verifier.NonUniqueSignatures.selector); + s_verifier.updateConfig(configDigest, wrongPrevSigners, newSigners, 4); + } + + function test_updateConfigRevertsIfCalledByNonOwner() public { + address[] memory signerAddresses = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + // Set initial config + s_verifier.setConfig(configDigest, signerAddresses, 4, new Common.AddressAndWeight[](0)); + + // Try to update as non-owner + changePrank(USER); + vm.expectRevert("Only callable by owner"); + s_verifier.updateConfig(configDigest, signerAddresses, signerAddresses, 4); + } +} + +contract VerifierSetConfigWhenThereAreMultipleDigestsTest05 is BaseTestWithMultipleConfiguredDigests { + function test_correctlyUpdatesTheDigestInTheProxy() public { + Signer[] memory newSigners = _getSigners(15); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(newSigners), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest, _getSignerAddresses(newSigners), 4, new Common.AddressAndWeight[](0)); + + address verifierAddr = s_verifierProxy.getVerifier(configDigest); + assertEq(verifierAddr, address(s_verifier)); + } + + function test_correctlyUpdatesDigestsOnMultipleVerifiersInTheProxy() public { + Signer[] memory newSigners = _getSigners(15); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID_2, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(newSigners), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest, _getSignerAddresses(newSigners), 4, new Common.AddressAndWeight[](0)); + + address verifierAddr = s_verifierProxy.getVerifier(configDigest); + assertEq(verifierAddr, address(s_verifier)); + + bytes32 configDigest2 = _configDigestFromConfigData( + FEED_ID_3, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(newSigners), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier_2.setConfig(configDigest2, _getSignerAddresses(newSigners), 4, new Common.AddressAndWeight[](0)); + + address verifierAddr2 = s_verifierProxy.getVerifier(configDigest2); + assertEq(verifierAddr2, address(s_verifier_2)); + } + + function test_correctlySetsConfigWhenDigestsAreRemoved() public { + s_verifier.deactivateConfig(s_configDigestTwo); + + Signer[] memory newSigners = _getSigners(15); + + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(newSigners), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest, _getSignerAddresses(newSigners), 4, new Common.AddressAndWeight[](0)); + + uint32 blockNumber = s_verifier.latestConfigDetails(configDigest); + + assertEq(blockNumber, block.number); + } + + function test_revertsIfDuplicateConfigIsSet() public { + // Set initial config + bytes32 configDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(_getSigners(15)), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest, _getSignerAddresses(_getSigners(15)), 4, new Common.AddressAndWeight[](0)); + + // Try to set same config again + vm.expectRevert(abi.encodeWithSelector(Verifier.ConfigDigestAlreadySet.selector)); + s_verifier.setConfig(configDigest, _getSignerAddresses(_getSigners(15)), 4, new Common.AddressAndWeight[](0)); + } + + function test_incrementalConfigUpdates() public { + // Set initial config + bytes32 configDigest1 = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 1, + _getSignerAddresses(_getSigners(15)), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest1, _getSignerAddresses(_getSigners(15)), 4, new Common.AddressAndWeight[](0)); + + // Set second config + bytes32 configDigest2 = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 2, + _getSignerAddresses(_getSigners(15)), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest2, _getSignerAddresses(_getSigners(15)), 4, new Common.AddressAndWeight[](0)); + + // Set third config + bytes32 configDigest3 = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 3, + _getSignerAddresses(_getSigners(15)), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig(configDigest3, _getSignerAddresses(_getSigners(15)), 4, new Common.AddressAndWeight[](0)); + } + + function test_configDigestMatchesConfiguratorDigest() public { + MockConfigurator configurator = new MockConfigurator(); + + // Convert addresses to bytes array + Signer[] memory signers = _getSigners(15); + bytes[] memory signersAsBytes = new bytes[](signers.length); + for (uint256 i; i < signers.length; ++i) { + signersAsBytes[i] = abi.encodePacked(signers[i].signerAddress); + } + + configurator.setStagingConfig( + FEED_ID, signersAsBytes, s_offchaintransmitters, 4, bytes(""), VERIFIER_VERSION, bytes("") + ); + + bytes32 expectedConfigDigest = _configDigestFromConfigData( + FEED_ID, + block.chainid, + address(configurator), + 1, + _getSignerAddresses(signers), + s_offchaintransmitters, + 4, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + (,, bytes32 configDigest) = configurator.s_configurationStates(FEED_ID); + + assertEq(configDigest, expectedConfigDigest); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTest.t.sol new file mode 100644 index 0000000000..e5a84db120 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTest.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Verifier} from "../../Verifier.sol"; +import {BaseTest} from "./BaseVerifierTest.t.sol"; + +contract VerifierConstructorTestV05 is BaseTest { + function test_revertsIfInitializedWithEmptyVerifierProxy() public { + vm.expectRevert(abi.encodeWithSelector(Verifier.ZeroAddress.selector)); + new Verifier(address(0)); + } + + function test_setsTheCorrectProperties() public { + Verifier v = new Verifier(address(s_verifierProxy)); + assertEq(v.owner(), ADMIN); + + uint32 blockNumber = v.latestConfigDetails(FEED_ID); + assertEq(blockNumber, 0); + + string memory typeAndVersion = s_verifier.typeAndVersion(); + assertEq(typeAndVersion, "Verifier 2.0.0"); + } +} + +contract VerifierSupportsInterfaceTest is BaseTest { + function test_falseIfIsNotCorrectInterface() public view { + bool isInterface = s_verifier.supportsInterface(bytes4("abcd")); + assertEq(isInterface, false); + } + + function test_trueIfIsCorrectInterface() public view { + bool isInterface = s_verifier.supportsInterface(Verifier.verify.selector); + assertEq(isInterface, true); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTestBillingReport.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTestBillingReport.t.sol new file mode 100644 index 0000000000..5173f27f58 --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierTestBillingReport.t.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; + +contract VerifierTestWithConfiguredVerifierAndFeeManager is BaseTestWithConfiguredVerifierAndFeeManager { + uint256 internal constant DEFAULT_LINK_MINT_QUANTITY = 100 ether; + uint256 internal constant DEFAULT_NATIVE_MINT_QUANTITY = 100 ether; + + function setUp() public virtual override { + super.setUp(); + + //mint some tokens to the user + link.mint(USER, DEFAULT_LINK_MINT_QUANTITY); + native.mint(USER, DEFAULT_NATIVE_MINT_QUANTITY); + vm.deal(USER, DEFAULT_NATIVE_MINT_QUANTITY); + + //mint some link tokens to the feeManager pool + link.mint(address(feeManager), DEFAULT_REPORT_LINK_FEE); + } +} + +contract VerifierTestBillingReportV05 is VerifierTestWithConfiguredVerifierAndFeeManager { + function test_verifyWithLink() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE, USER); + + _verify(signedReport, address(link), 0, USER); + + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE); + } + + function test_verifyWithNative() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE, USER); + + _verify(signedReport, address(native), 0, USER); + + assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE); + } + + function test_verifyWithNativeUnwrapped() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE, USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + assertEq(address(feeManager).balance, 0); + } + + function test_verifyWithNativeUnwrappedReturnsChange() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + _verify(signedReport, address(native), DEFAULT_REPORT_NATIVE_FEE * 2, USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE); + assertEq(address(feeManager).balance, 0); + } +} + +contract VerifierBulkVerifyBillingReportV05 is VerifierTestWithConfiguredVerifierAndFeeManager { + uint256 internal constant NUMBERS_OF_REPORTS = 5; + + function test_verifyWithBulkLink() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _approveLink(address(rewardManager), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS, USER); + + _verifyBulk(signedReports, address(link), 0, USER); + + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * NUMBERS_OF_REPORTS); + } + + function test_verifyWithBulkNative() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _approveNative(address(feeManager), DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER); + + _verifyBulk(signedReports, address(native), 0, USER); + + assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS); + } + + function test_verifyWithBulkNativeUnwrapped() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _verifyBulk(signedReports, address(native), DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS, USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * 5); + assertEq(address(feeManager).balance, 0); + } + + function test_verifyWithBulkNativeUnwrappedReturnsChange() public { + bytes memory signedReport = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](NUMBERS_OF_REPORTS); + for (uint256 i = 0; i < NUMBERS_OF_REPORTS; i++) { + signedReports[i] = signedReport; + } + + _verifyBulk(signedReports, address(native), DEFAULT_REPORT_NATIVE_FEE * (NUMBERS_OF_REPORTS * 2), USER); + + assertEq(USER.balance, DEFAULT_NATIVE_MINT_QUANTITY - DEFAULT_REPORT_NATIVE_FEE * NUMBERS_OF_REPORTS); + assertEq(address(feeManager).balance, 0); + } + + function test_verifyMultiVersions() public { + bytes memory signedReportV1 = _generateV1EncodedBlob( + _generateV1Report(), _generateReportContext(v1ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes memory signedReportV3 = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](3); + + signedReports[0] = signedReportV1; + signedReports[1] = signedReportV3; + signedReports[2] = signedReportV3; + + _approveLink(address(rewardManager), 2 * DEFAULT_REPORT_LINK_FEE, USER); + + _verifyBulk(signedReports, address(link), 0, USER); + + assertEq(link.balanceOf(USER), DEFAULT_LINK_MINT_QUANTITY - 2 * DEFAULT_REPORT_LINK_FEE); + assertEq(native.balanceOf(USER), DEFAULT_NATIVE_MINT_QUANTITY); + assertEq(link.balanceOf(address(rewardManager)), DEFAULT_REPORT_LINK_FEE * 2); + } + + function test_verifyMultiVersionsReturnsVerifiedReports() public { + bytes memory signedReportV1 = _generateV1EncodedBlob( + _generateV1Report(), _generateReportContext(v1ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes memory signedReportV3 = _generateV3EncodedBlob( + _generateV3Report(), _generateReportContext(v3ConfigDigest), _getSigners(FAULT_TOLERANCE + 1) + ); + + bytes[] memory signedReports = new bytes[](3); + + signedReports[0] = signedReportV1; + signedReports[1] = signedReportV3; + signedReports[2] = signedReportV3; + + _approveLink(address(rewardManager), 2 * DEFAULT_REPORT_LINK_FEE, USER); + + address originalAddr = msg.sender; + changePrank(USER); + + bytes[] memory verifierReports = s_verifierProxy.verifyBulk{value: 0}(signedReports, abi.encode(link)); + + changePrank(originalAddr); + + assertEq(verifierReports[0], _encodeReport(_generateV1Report())); + assertEq(verifierReports[1], _encodeReport(_generateV3Report())); + assertEq(verifierReports[2], _encodeReport(_generateV3Report())); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierUnsetConfigTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierUnsetConfigTest.t.sol new file mode 100644 index 0000000000..c2884e36cd --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierUnsetConfigTest.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {Verifier} from "../../Verifier.sol"; +import {BaseTestWithMultipleConfiguredDigests} from "./BaseVerifierTest.t.sol"; + +contract VerificationdeactivateConfigWhenThereAreMultipleDigestsTestV05 is BaseTestWithMultipleConfiguredDigests { + function test_revertsIfCalledByNonOwner() public { + vm.expectRevert("Only callable by owner"); + + changePrank(USER); + s_verifier.deactivateConfig(bytes32("")); + } + + function test_revertsIfRemovingAnEmptyDigest() public { + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestEmpty.selector)); + s_verifier.deactivateConfig(bytes32("")); + } + + function test_revertsIfRemovingAnNonExistentDigest() public { + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestNotSet.selector, bytes32("mock-digest"))); + s_verifier.deactivateConfig(bytes32("mock-digest")); + } +} diff --git a/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierVerifyTest.t.sol b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierVerifyTest.t.sol new file mode 100644 index 0000000000..822f80e45a --- /dev/null +++ b/contracts/src/v0.8/llo-feeds/v0.5.1/test/verifier/VerifierVerifyTest.t.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; +import {Common} from "../../../libraries/Common.sol"; +import {Verifier} from "../../Verifier.sol"; +import {VerifierProxy} from "../../VerifierProxy.sol"; +import {BaseTestWithConfiguredVerifierAndFeeManager} from "./BaseVerifierTest.t.sol"; + +contract VerifierVerifyTest is BaseTestWithConfiguredVerifierAndFeeManager { + bytes32[3] internal s_reportContext; + + event ReportVerified(bytes32 indexed feedId, address requester); + + V1Report internal s_testReportOne; + + function setUp() public virtual override { + BaseTestWithConfiguredVerifierAndFeeManager.setUp(); + s_reportContext[0] = v1ConfigDigest; + s_reportContext[1] = bytes32(abi.encode(uint32(5), uint8(1))); + s_testReportOne = _createV1Report( + FEED_ID, + OBSERVATIONS_TIMESTAMP, + MEDIAN, + BID, + ASK, + BLOCKNUMBER_UPPER_BOUND, + blockhash(BLOCKNUMBER_UPPER_BOUND), + BLOCKNUMBER_LOWER_BOUND, + uint32(block.timestamp) + ); + } + + function assertReportsEqual(bytes memory response, V1Report memory testReport) public pure { + ( + bytes32 feedId, + uint32 timestamp, + int192 median, + int192 bid, + int192 ask, + uint64 blockNumUB, + bytes32 upperBlockhash, + uint64 blockNumLB + ) = abi.decode(response, (bytes32, uint32, int192, int192, int192, uint64, bytes32, uint64)); + assertEq(feedId, testReport.feedId); + assertEq(timestamp, testReport.observationsTimestamp); + assertEq(median, testReport.median); + assertEq(bid, testReport.bid); + assertEq(ask, testReport.ask); + assertEq(blockNumLB, testReport.blocknumberLowerBound); + assertEq(blockNumUB, testReport.blocknumberUpperBound); + assertEq(upperBlockhash, testReport.upperBlockhash); + } +} + +contract VerifierProxyVerifyTestV05 is VerifierVerifyTest { + function test_revertsIfNoVerifierConfigured() public { + s_reportContext[0] = bytes32("corrupt-digest"); + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.VerifierNotFound.selector, bytes32("corrupt-digest"))); + s_verifierProxy.verify(signedReport, bytes("")); + } + + function test_proxiesToTheCorrectVerifier() public { + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + + bytes memory response = s_verifierProxy.verify(signedReport, abi.encode(native)); + assertReportsEqual(response, s_testReportOne); + } +} + +contract VerifierProxyAccessControlledVerificationTestV05 is VerifierVerifyTest { + function setUp() public override { + VerifierVerifyTest.setUp(); + AccessControllerInterface accessController = AccessControllerInterface(ACCESS_CONTROLLER_ADDRESS); + + s_verifierProxy.setAccessController(accessController); + } + + function test_revertsIfNoAccess() public { + vm.mockCall( + ACCESS_CONTROLLER_ADDRESS, + abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER), + abi.encode(false) + ); + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectRevert(abi.encodeWithSelector(VerifierProxy.AccessForbidden.selector)); + + changePrank(USER); + s_verifierProxy.verify(signedReport, abi.encode(native)); + } + + function test_proxiesToTheVerifierIfHasAccess() public { + vm.mockCall( + ACCESS_CONTROLLER_ADDRESS, + abi.encodeWithSelector(AccessControllerInterface.hasAccess.selector, USER), + abi.encode(true) + ); + + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + + changePrank(USER); + bytes memory response = s_verifierProxy.verify(signedReport, bytes("")); + assertReportsEqual(response, s_testReportOne); + } +} + +contract VerifierVerifySingleConfigDigestTestV05 is VerifierVerifyTest { + function test_revertsIfVerifiedByNonProxy() public { + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectRevert(abi.encodeWithSelector(Verifier.AccessForbidden.selector)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfVerifiedWithIncorrectAddresses() public { + Signer[] memory signers = _getSigners(FAULT_TOLERANCE + 1); + signers[10].mockPrivateKey = 1234; + bytes memory signedReport = _generateV1EncodedBlob(s_testReportOne, s_reportContext, signers); + changePrank(address(s_verifierProxy)); + vm.expectRevert(abi.encodeWithSelector(Verifier.BadVerification.selector)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfMismatchedSignatureLength() public { + bytes32[] memory rs = new bytes32[](FAULT_TOLERANCE + 1); + bytes32[] memory ss = new bytes32[](FAULT_TOLERANCE + 3); + bytes32 rawVs = bytes32(""); + bytes memory signedReport = abi.encode(s_reportContext, abi.encode(s_testReportOne), rs, ss, rawVs); + changePrank(address(s_verifierProxy)); + vm.expectRevert(abi.encodeWithSelector(Verifier.MismatchedSignatures.selector, rs.length, ss.length)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfConfigDigestNotSet() public { + bytes32[3] memory reportContext = s_reportContext; + reportContext[0] = bytes32("wrong-context-digest"); + bytes memory signedReport = _generateV1EncodedBlob(s_testReportOne, reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestInactive.selector, reportContext[0])); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfReportHasUnconfiguredConfigDigest() public { + V1Report memory report = _createV1Report( + FEED_ID_2, + OBSERVATIONS_TIMESTAMP, + MEDIAN, + BID, + ASK, + BLOCKNUMBER_UPPER_BOUND, + blockhash(BLOCKNUMBER_UPPER_BOUND), + BLOCKNUMBER_LOWER_BOUND, + uint32(block.timestamp) + ); + s_reportContext[0] = keccak256("unconfigured-digesty"); + bytes memory signedReport = _generateV1EncodedBlob(report, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestInactive.selector, s_reportContext[0])); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfWrongNumberOfSigners() public { + bytes memory signedReport = _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(10)); + vm.expectRevert(abi.encodeWithSelector(Verifier.IncorrectSignatureCount.selector, 10, FAULT_TOLERANCE + 1)); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_revertsIfDuplicateSignersHaveSigned() public { + Signer[] memory signers = _getSigners(FAULT_TOLERANCE + 1); + // Duplicate signer at index 1 + signers[0] = signers[1]; + bytes memory signedReport = _generateV1EncodedBlob(s_testReportOne, s_reportContext, signers); + vm.expectRevert(abi.encodeWithSelector(Verifier.BadVerification.selector)); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_returnsThePriceAndBlockNumIfReportVerified() public { + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + changePrank(address(s_verifierProxy)); + bytes memory response = s_verifier.verify(signedReport, msg.sender); + + assertReportsEqual(response, s_testReportOne); + } + + function test_emitsAnEventIfReportVerified() public { + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + vm.expectEmit(true, true, true, true, address(s_verifier)); + emit ReportVerified(s_testReportOne.feedId, msg.sender); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } +} + +contract VerifierVerifyMultipleConfigDigestTestV05 is VerifierVerifyTest { + bytes32 internal s_oldConfigDigest; + bytes32 internal s_newConfigDigest; + + uint8 internal constant FAULT_TOLERANCE_TWO = 5; + + function setUp() public override { + VerifierVerifyTest.setUp(); + s_oldConfigDigest = v1ConfigDigest; + + s_newConfigDigest = _configDigestFromConfigData( + FEED_ID, + SOURCE_CHAIN_ID, + SOURCE_ADDRESS, + 2, + _getSignerAddresses(_getSigners(20)), + s_offchaintransmitters, + FAULT_TOLERANCE_TWO, + bytes(""), + VERIFIER_VERSION, + bytes("") + ); + + s_verifier.setConfig( + s_newConfigDigest, _getSignerAddresses(_getSigners(20)), FAULT_TOLERANCE_TWO, new Common.AddressAndWeight[](0) + ); + } + + function test_revertsIfVerifyingWithAnUnsetDigest() public { + s_verifier.deactivateConfig(s_oldConfigDigest); + + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + changePrank(address(s_verifierProxy)); + vm.expectRevert(abi.encodeWithSelector(Verifier.DigestInactive.selector, s_reportContext[0])); + s_verifier.verify(signedReport, msg.sender); + } + + function test_canVerifyOlderReportsWithOlderConfigs() public { + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE + 1)); + changePrank(address(s_verifierProxy)); + bytes memory response = s_verifier.verify(signedReport, msg.sender); + assertReportsEqual(response, s_testReportOne); + } + + function test_canVerifyNewerReportsWithNewerConfigs() public { + s_reportContext[0] = s_newConfigDigest; + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE_TWO + 1)); + changePrank(address(s_verifierProxy)); + bytes memory response = s_verifier.verify(signedReport, msg.sender); + assertReportsEqual(response, s_testReportOne); + } + + function test_revertsIfAReportIsVerifiedWithAnExistingButIncorrectDigest() public { + // Try sending the older digest signed with the new set of signers + s_reportContext[0] = s_oldConfigDigest; + bytes memory signedReport = + _generateV1EncodedBlob(s_testReportOne, s_reportContext, _getSigners(FAULT_TOLERANCE_TWO + 1)); + vm.expectRevert( + abi.encodeWithSelector(Verifier.IncorrectSignatureCount.selector, FAULT_TOLERANCE_TWO + 1, FAULT_TOLERANCE + 1) + ); + changePrank(address(s_verifierProxy)); + s_verifier.verify(signedReport, msg.sender); + } + + function test_verifyAfterConfigUpdate() public { + // Get initial signers and set initial config + address[] memory initialSigners = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + s_verifier.setConfig(configDigest, initialSigners, 4, new Common.AddressAndWeight[](0)); + + // Get new signers and update config + address[] memory newSigners = _getSignerAddresses(_getSigners(20)); + s_verifier.updateConfig(configDigest, initialSigners, newSigners, 6); + + // Verify report with new signers should pass + s_reportContext[0] = configDigest; + bytes memory signedReportNewSigners = _generateV1EncodedBlob( + s_testReportOne, + s_reportContext, + _getSigners(7) // More than f=6 signers + ); + + bytes memory response = s_verifierProxy.verify(signedReportNewSigners, bytes("")); + assertReportsEqual(response, s_testReportOne); + + // Verify report with old signers should fail + bytes memory signedReportOldSigners = _generateV1EncodedBlob( + s_testReportOne, + s_reportContext, + _getSigners(5) // Old number of signers + ); + vm.expectRevert(abi.encodeWithSelector(Verifier.IncorrectSignatureCount.selector, 5, 7)); + + s_verifierProxy.verify(signedReportOldSigners, bytes("")); + } + + function test_verifyAfterConfigUpdateWithExistingSigners() public { + // Get initial signers and set initial config + address[] memory signers = _getSignerAddresses(_getSigners(15)); + bytes32 configDigest = keccak256("test"); + + s_verifier.setConfig(configDigest, signers, 4, new Common.AddressAndWeight[](0)); + + // Update config with same signers and f + s_verifier.updateConfig(configDigest, signers, signers, 4); + + // Verify report should pass + s_reportContext[0] = configDigest; + bytes memory signedReport = _generateV1EncodedBlob( + s_testReportOne, + s_reportContext, + _getSigners(5) // More than f=4 signers + ); + + bytes memory response = s_verifierProxy.verify(signedReport, bytes("")); + assertReportsEqual(response, s_testReportOne); + } +} diff --git a/gethwrappers/llo-feeds/generated/verifier_proxy_v0_5_0/verifier_proxy_v0_5_0.go b/gethwrappers/llo-feeds/generated/verifier_proxy_v0_5_0/verifier_proxy_v0_5_0.go index 0d84b24fba..a9adb3633a 100644 --- a/gethwrappers/llo-feeds/generated/verifier_proxy_v0_5_0/verifier_proxy_v0_5_0.go +++ b/gethwrappers/llo-feeds/generated/verifier_proxy_v0_5_0/verifier_proxy_v0_5_0.go @@ -36,8 +36,8 @@ type CommonAddressAndWeight struct { } var VerifierProxyMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"accessController\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getVerifier\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initializeVerifier\",\"inputs\":[{\"name\":\"verifierAddress\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"s_accessController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"s_feeManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIVerifierFeeManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setAccessController\",\"inputs\":[{\"name\":\"accessController\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFeeManager\",\"inputs\":[{\"name\":\"feeManager\",\"type\":\"address\",\"internalType\":\"contractIVerifierFeeManager\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setVerifier\",\"inputs\":[{\"name\":\"currentConfigDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"newConfigDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"addressesAndWeights\",\"type\":\"tuple[]\",\"internalType\":\"structCommon.AddressAndWeight[]\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"weight\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"unsetVerifier\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verify\",\"inputs\":[{\"name\":\"payload\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"parameterPayload\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"verifyBulk\",\"inputs\":[{\"name\":\"payloads\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"parameterPayload\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"verifiedReports\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"stateMutability\":\"payable\"},{\"type\":\"event\",\"name\":\"AccessControllerSet\",\"inputs\":[{\"name\":\"oldAccessController\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newAccessController\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeManagerSet\",\"inputs\":[{\"name\":\"oldFeeManager\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newFeeManager\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierInitialized\",\"inputs\":[{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierSet\",\"inputs\":[{\"name\":\"oldConfigDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"newConfigDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierUnset\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessForbidden\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"BadVerification\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ConfigDigestAlreadySet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"verifier\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"FeeManagerInvalid\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"VerifierAlreadyInitialized\",\"inputs\":[{\"name\":\"verifier\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"VerifierInvalid\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"VerifierNotFound\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", - Bin: "0x60806040523480156200001157600080fd5b5060405162001d3638038062001d36833981016040819052620000349162000193565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000e8565b5050600480546001600160a01b0319166001600160a01b03939093169290921790915550620001c5565b336001600160a01b03821603620001425760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001a657600080fd5b81516001600160a01b0381168114620001be57600080fd5b9392505050565b611b6180620001d56000396000f3fe6080604052600436106100dd5760003560e01c806394ba28461161007f578063f08391d811610059578063f08391d8146102be578063f2fde38b146102de578063f7e83aee146102fe578063f873a61c1461031157600080fd5b806394ba28461461022e578063b011b2471461025b578063eeb7b2481461027b57600080fd5b80636e914094116100bb5780636e914094146101ae57806379ba5097146101ce5780638c2a4d53146101e35780638da5cb5b1461020357600080fd5b8063181f5a77146100e257806338416b5b1461013a578063472d35b91461018c575b600080fd5b3480156100ee57600080fd5b5060408051808201909152601381527f566572696669657250726f787920322e302e300000000000000000000000000060208201525b60405161013191906113c3565b60405180910390f35b34801561014657600080fd5b506005546101679073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610131565b34801561019857600080fd5b506101ac6101a73660046113ff565b610331565b005b3480156101ba57600080fd5b506101ac6101c936600461141c565b6105a9565b3480156101da57600080fd5b506101ac61069a565b3480156101ef57600080fd5b506101ac6101fe3660046113ff565b610797565b34801561020f57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610167565b34801561023a57600080fd5b506004546101679073ffffffffffffffffffffffffffffffffffffffff1681565b34801561026757600080fd5b506101ac610276366004611435565b6109c8565b34801561028757600080fd5b5061016761029636600461141c565b60009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b3480156102ca57600080fd5b506101ac6102d93660046113ff565b610bee565b3480156102ea57600080fd5b506101ac6102f93660046113ff565b610c75565b61012461030c366004611501565b610c89565b61032461031f36600461156d565b610e43565b60405161013191906115ee565b6103396110a7565b73ffffffffffffffffffffffffffffffffffffffff8116610386576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527fdba45fe000000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610410573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610434919061166e565b15806104eb57506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f6c2f1a1700000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa1580156104c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e9919061166e565b155b15610522576040517f8238941900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f0391015b60405180910390a15050565b6105b16110a7565b60008181526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1680610615576040517fb151802b000000000000000000000000000000000000000000000000000000008152600481018390526024015b60405180910390fd5b6000828152600360205260409081902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055517f11dc15c4b8ac2b183166cc8427e5385a5ece8308217a4217338c6a7614845c4c9061059d908490849091825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b60015473ffffffffffffffffffffffffffffffffffffffff16331461071b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015260640161060c565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b61079f6110a7565b8073ffffffffffffffffffffffffffffffffffffffff81166107ed576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f3d3ac1b500000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610877573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061089b919061166e565b6108d1576040517f75b0527a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff1615610949576040517f4e01ccfd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8316600482015260240161060c565b73ffffffffffffffffffffffffffffffffffffffff821660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590519182527f1f2cd7c97f4d801b5efe26cc409617c1fd6c5ef786e79aacb90af40923e4e8e9910161059d565b600083815260036020526040902054839073ffffffffffffffffffffffffffffffffffffffff168015610a46576040517f375d1fe60000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff8216602482015260440161060c565b3360009081526002602052604090205460ff16610a8f576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260036020526040902080547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790558215610ba75760055473ffffffffffffffffffffffffffffffffffffffff16610b1a576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546040517ff65df96200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063f65df96290610b7490889088908890600401611690565b600060405180830381600087803b158015610b8e57600080fd5b505af1158015610ba2573d6000803e3d6000fd5b505050505b6040805187815260208101879052338183015290517fbeb513e532542a562ac35699e7cd9ae7d198dcd3eee15bada6c857d28ceaddcf9181900360600190a1505050505050565b610bf66110a7565b6004805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b6910161059d565b610c7d6110a7565b610c868161112a565b50565b60045460609073ffffffffffffffffffffffffffffffffffffffff168015801590610d4957506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf890610d069033906000903690600401611762565b602060405180830381865afa158015610d23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d47919061166e565b155b15610d80576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60055473ffffffffffffffffffffffffffffffffffffffff168015610e2e576040517fdba45fe000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063dba45fe0903490610dfb908b908b908b908b90339060040161179b565b6000604051808303818588803b158015610e1457600080fd5b505af1158015610e28573d6000803e3d6000fd5b50505050505b610e38878761121f565b979650505050505050565b60045460609073ffffffffffffffffffffffffffffffffffffffff168015801590610f0357506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf890610ec09033906000903690600401611762565b602060405180830381865afa158015610edd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f01919061166e565b155b15610f3a576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60055473ffffffffffffffffffffffffffffffffffffffff168015610fe8576040517f6c2f1a1700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636c2f1a17903490610fb5908b908b908b908b9033906004016117eb565b6000604051808303818588803b158015610fce57600080fd5b505af1158015610fe2573d6000803e3d6000fd5b50505050505b8567ffffffffffffffff811115611001576110016118fc565b60405190808252806020026020018201604052801561103457816020015b606081526020019060019003908161101f5790505b50925060005b8681101561109c5761106e8888838181106110575761105761192b565b9050602002810190611069919061195a565b61121f565b8482815181106110805761108061192b565b602002602001018190525080611095906119bf565b905061103a565b505050949350505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314611128576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161060c565b565b3373ffffffffffffffffffffffffffffffffffffffff8216036111a9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161060c565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6060600061122d8385611a1e565b60008181526003602052604090205490915073ffffffffffffffffffffffffffffffffffffffff168061128f576040517fb151802b0000000000000000000000000000000000000000000000000000000081526004810183905260240161060c565b6040517f3d3ac1b500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690633d3ac1b5906112e590889088903390600401611a5a565b6000604051808303816000875af1158015611304573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261134a9190810190611a94565b925050505b92915050565b60005b83811015611370578181015183820152602001611358565b50506000910152565b60008151808452611391816020860160208601611355565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006113d66020830184611379565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610c8657600080fd5b60006020828403121561141157600080fd5b81356113d6816113dd565b60006020828403121561142e57600080fd5b5035919050565b6000806000806060858703121561144b57600080fd5b8435935060208501359250604085013567ffffffffffffffff8082111561147157600080fd5b818701915087601f83011261148557600080fd5b81358181111561149457600080fd5b8860208260061b85010111156114a957600080fd5b95989497505060200194505050565b60008083601f8401126114ca57600080fd5b50813567ffffffffffffffff8111156114e257600080fd5b6020830191508360208285010111156114fa57600080fd5b9250929050565b6000806000806040858703121561151757600080fd5b843567ffffffffffffffff8082111561152f57600080fd5b61153b888389016114b8565b9096509450602087013591508082111561155457600080fd5b50611561878288016114b8565b95989497509550505050565b6000806000806040858703121561158357600080fd5b843567ffffffffffffffff8082111561159b57600080fd5b818701915087601f8301126115af57600080fd5b8135818111156115be57600080fd5b8860208260051b85010111156115d357600080fd5b60209283019650945090860135908082111561155457600080fd5b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015611661577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261164f858351611379565b94509285019290850190600101611615565b5092979650505050505050565b60006020828403121561168057600080fd5b815180151581146113d657600080fd5b838152604060208083018290528282018490526000919085906060850184805b8881101561170a5784356116c3816113dd565b73ffffffffffffffffffffffffffffffffffffffff1683528484013567ffffffffffffffff81168082146116f5578384fd5b848601525093850193918501916001016116b0565b50909998505050505050505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff84168152604060208201526000611792604083018486611719565b95945050505050565b6060815260006117af606083018789611719565b82810360208401526117c2818688611719565b91505073ffffffffffffffffffffffffffffffffffffffff831660408301529695505050505050565b6060808252810185905260006080600587901b8301810190830188835b898110156118b7577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8086850301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18c360301811261186957600080fd5b8b01602081810191359067ffffffffffffffff82111561188857600080fd5b81360383131561189757600080fd5b6118a2878385611719565b96509485019493909301925050600101611808565b50505082810360208401526118cd818688611719565b9150506118f2604083018473ffffffffffffffffffffffffffffffffffffffff169052565b9695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261198f57600080fd5b83018035915067ffffffffffffffff8211156119aa57600080fd5b6020019150368190038213156114fa57600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611a17577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b8035602083101561134f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b604081526000611a6e604083018587611719565b905073ffffffffffffffffffffffffffffffffffffffff83166020830152949350505050565b600060208284031215611aa657600080fd5b815167ffffffffffffffff80821115611abe57600080fd5b818401915084601f830112611ad257600080fd5b815181811115611ae457611ae46118fc565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611b2a57611b2a6118fc565b81604052828152876020848701011115611b4357600080fd5b610e3883602083016020880161135556fea164736f6c6343000813000a", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"accessController\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getVerifier\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initializeVerifier\",\"inputs\":[{\"name\":\"verifierAddress\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"s_accessController\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"s_feeManager\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIVerifierFeeManager\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setAccessController\",\"inputs\":[{\"name\":\"accessController\",\"type\":\"address\",\"internalType\":\"contractAccessControllerInterface\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFeeManager\",\"inputs\":[{\"name\":\"feeManager\",\"type\":\"address\",\"internalType\":\"contractIVerifierFeeManager\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setVerifier\",\"inputs\":[{\"name\":\"currentConfigDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"newConfigDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"addressesAndWeights\",\"type\":\"tuple[]\",\"internalType\":\"structCommon.AddressAndWeight[]\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"weight\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"unsetVerifier\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verify\",\"inputs\":[{\"name\":\"payload\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"parameterPayload\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"verifyBulk\",\"inputs\":[{\"name\":\"payloads\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"parameterPayload\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"verifiedReports\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"verifyBulkView\",\"inputs\":[{\"name\":\"payloads\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"verifiedReports\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"verifyView\",\"inputs\":[{\"name\":\"payload\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"AccessControllerSet\",\"inputs\":[{\"name\":\"oldAccessController\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newAccessController\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeManagerSet\",\"inputs\":[{\"name\":\"oldFeeManager\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newFeeManager\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierInitialized\",\"inputs\":[{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierSet\",\"inputs\":[{\"name\":\"oldConfigDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"newConfigDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"VerifierUnset\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"},{\"name\":\"verifierAddress\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessForbidden\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"BadVerification\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ConfigDigestAlreadySet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"verifier\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"FeeManagerInvalid\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"VerifierAlreadyInitialized\",\"inputs\":[{\"name\":\"verifier\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"VerifierInvalid\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"VerifierNotFound\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", + Bin: "0x60806040523480156200001157600080fd5b50604051620021b3380380620021b3833981016040819052620000349162000193565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000e8565b5050600480546001600160a01b0319166001600160a01b03939093169290921790915550620001c5565b336001600160a01b03821603620001425760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001a657600080fd5b81516001600160a01b0381168114620001be57600080fd5b9392505050565b611fde80620001d56000396000f3fe6080604052600436106100f35760003560e01c80638da5cb5b1161008a578063f08391d811610059578063f08391d814610321578063f2fde38b14610341578063f7e83aee14610361578063f873a61c1461037457600080fd5b80638da5cb5b1461026657806394ba284614610291578063b011b247146102be578063eeb7b248146102de57600080fd5b8063665bc7a3116100c6578063665bc7a3146101e45780636e9140941461021157806379ba5097146102315780638c2a4d531461024657600080fd5b8063181f5a77146100f857806338416b5b14610150578063472d35b9146101a257806351b34c90146101c4575b600080fd5b34801561010457600080fd5b5060408051808201909152601381527f566572696669657250726f787920322e302e310000000000000000000000000060208201525b60405161014791906117b6565b60405180910390f35b34801561015c57600080fd5b5060055461017d9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610147565b3480156101ae57600080fd5b506101c26101bd3660046117f2565b610387565b005b3480156101d057600080fd5b5061013a6101df366004611858565b6105ff565b3480156101f057600080fd5b506102046101ff3660046118df565b610708565b6040516101479190611915565b34801561021d57600080fd5b506101c261022c366004611995565b6108bb565b34801561023d57600080fd5b506101c26109ac565b34801561025257600080fd5b506101c26102613660046117f2565b610aa9565b34801561027257600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff1661017d565b34801561029d57600080fd5b5060045461017d9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156102ca57600080fd5b506101c26102d93660046119ae565b610cda565b3480156102ea57600080fd5b5061017d6102f9366004611995565b60009081526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b34801561032d57600080fd5b506101c261033c3660046117f2565b610f00565b34801561034d57600080fd5b506101c261035c3660046117f2565b610f87565b61013a61036f366004611a31565b610f9b565b610204610382366004611a9d565b611155565b61038f6113b9565b73ffffffffffffffffffffffffffffffffffffffff81166103dc576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527fdba45fe000000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610466573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061048a9190611ad7565b158061054157506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f6c2f1a1700000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa15801561051b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061053f9190611ad7565b155b15610578576040517f8238941900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f04628abcaa6b1674651352125cb94b65b289145bc2bc4d67720bb7d966372f0391015b60405180910390a15050565b60045460609073ffffffffffffffffffffffffffffffffffffffff1680158015906106bf57506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf89061067c9033906000903690600401611b42565b602060405180830381865afa158015610699573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106bd9190611ad7565b155b156106f6576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610700848461143c565b949350505050565b60045460609073ffffffffffffffffffffffffffffffffffffffff1680158015906107c857506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf8906107859033906000903690600401611b42565b602060405180830381865afa1580156107a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107c69190611ad7565b155b156107ff576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8267ffffffffffffffff81111561081857610818611b7b565b60405190808252806020026020018201604052801561084b57816020015b60608152602001906001900390816108365790505b50915060005b838110156108b35761088585858381811061086e5761086e611baa565b90506020028101906108809190611bd9565b61143c565b83828151811061089757610897611baa565b6020026020010181905250806108ac90611c3e565b9050610851565b505092915050565b6108c36113b9565b60008181526003602052604090205473ffffffffffffffffffffffffffffffffffffffff1680610927576040517fb151802b000000000000000000000000000000000000000000000000000000008152600481018390526024015b60405180910390fd5b6000828152600360205260409081902080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055517f11dc15c4b8ac2b183166cc8427e5385a5ece8308217a4217338c6a7614845c4c906105f3908490849091825273ffffffffffffffffffffffffffffffffffffffff16602082015260400190565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a2d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015260640161091e565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610ab16113b9565b8073ffffffffffffffffffffffffffffffffffffffff8116610aff576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527f3d3ac1b500000000000000000000000000000000000000000000000000000000600482015273ffffffffffffffffffffffffffffffffffffffff8216906301ffc9a790602401602060405180830381865afa158015610b89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bad9190611ad7565b610be3576040517f75b0527a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff1615610c5b576040517f4e01ccfd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8316600482015260240161091e565b73ffffffffffffffffffffffffffffffffffffffff821660008181526002602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590519182527f1f2cd7c97f4d801b5efe26cc409617c1fd6c5ef786e79aacb90af40923e4e8e991016105f3565b600083815260036020526040902054839073ffffffffffffffffffffffffffffffffffffffff168015610d58576040517f375d1fe60000000000000000000000000000000000000000000000000000000081526004810183905273ffffffffffffffffffffffffffffffffffffffff8216602482015260440161091e565b3360009081526002602052604090205460ff16610da1576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600085815260036020526040902080547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790558215610eb95760055473ffffffffffffffffffffffffffffffffffffffff16610e2c576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6005546040517ff65df96200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9091169063f65df96290610e8690889088908890600401611c9d565b600060405180830381600087803b158015610ea057600080fd5b505af1158015610eb4573d6000803e3d6000fd5b505050505b6040805187815260208101879052338183015290517fbeb513e532542a562ac35699e7cd9ae7d198dcd3eee15bada6c857d28ceaddcf9181900360600190a1505050505050565b610f086113b9565b6004805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff000000000000000000000000000000000000000083168117909355604080519190921680825260208201939093527f953e92b1a6442e9c3242531154a3f6f6eb00b4e9c719ba8118fa6235e4ce89b691016105f3565b610f8f6113b9565b610f988161156e565b50565b60045460609073ffffffffffffffffffffffffffffffffffffffff16801580159061105b57506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf8906110189033906000903690600401611b42565b602060405180830381865afa158015611035573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110599190611ad7565b155b15611092576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60055473ffffffffffffffffffffffffffffffffffffffff168015611140576040517fdba45fe000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063dba45fe090349061110d908b908b908b908b903390600401611d26565b6000604051808303818588803b15801561112657600080fd5b505af115801561113a573d6000803e3d6000fd5b50505050505b61114a8787611663565b979650505050505050565b60045460609073ffffffffffffffffffffffffffffffffffffffff16801580159061121557506040517f6b14daf800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636b14daf8906111d29033906000903690600401611b42565b602060405180830381865afa1580156111ef573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112139190611ad7565b155b1561124c576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60055473ffffffffffffffffffffffffffffffffffffffff1680156112fa576040517f6c2f1a1700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690636c2f1a179034906112c7908b908b908b908b903390600401611d76565b6000604051808303818588803b1580156112e057600080fd5b505af11580156112f4573d6000803e3d6000fd5b50505050505b8567ffffffffffffffff81111561131357611313611b7b565b60405190808252806020026020018201604052801561134657816020015b60608152602001906001900390816113315790505b50925060005b868110156113ae5761138088888381811061136957611369611baa565b905060200281019061137b9190611bd9565b611663565b84828151811061139257611392611baa565b6020026020010181905250806113a790611c3e565b905061134c565b505050949350505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461143a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161091e565b565b6060600061144a8385611e87565b60008181526003602052604090205490915073ffffffffffffffffffffffffffffffffffffffff16806114ac576040517fb151802b0000000000000000000000000000000000000000000000000000000081526004810183905260240161091e565b6040517f51b34c9000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8216906351b34c90906115009088908890600401611ec3565b600060405180830381865afa15801561151d573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682016040526115639190810190611ed7565b925050505b92915050565b3373ffffffffffffffffffffffffffffffffffffffff8216036115ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161091e565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b606060006116718385611e87565b60008181526003602052604090205490915073ffffffffffffffffffffffffffffffffffffffff16806116d3576040517fb151802b0000000000000000000000000000000000000000000000000000000081526004810183905260240161091e565b6040517f3d3ac1b500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff821690633d3ac1b59061172990889088903390600401611f97565b6000604051808303816000875af115801561151d573d6000803e3d6000fd5b60005b8381101561176357818101518382015260200161174b565b50506000910152565b60008151808452611784816020860160208601611748565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006117c9602083018461176c565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610f9857600080fd5b60006020828403121561180457600080fd5b81356117c9816117d0565b60008083601f84011261182157600080fd5b50813567ffffffffffffffff81111561183957600080fd5b60208301915083602082850101111561185157600080fd5b9250929050565b6000806020838503121561186b57600080fd5b823567ffffffffffffffff81111561188257600080fd5b61188e8582860161180f565b90969095509350505050565b60008083601f8401126118ac57600080fd5b50813567ffffffffffffffff8111156118c457600080fd5b6020830191508360208260051b850101111561185157600080fd5b600080602083850312156118f257600080fd5b823567ffffffffffffffff81111561190957600080fd5b61188e8582860161189a565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015611988577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc088860301845261197685835161176c565b9450928501929085019060010161193c565b5092979650505050505050565b6000602082840312156119a757600080fd5b5035919050565b600080600080606085870312156119c457600080fd5b8435935060208501359250604085013567ffffffffffffffff808211156119ea57600080fd5b818701915087601f8301126119fe57600080fd5b813581811115611a0d57600080fd5b8860208260061b8501011115611a2257600080fd5b95989497505060200194505050565b60008060008060408587031215611a4757600080fd5b843567ffffffffffffffff80821115611a5f57600080fd5b611a6b8883890161180f565b90965094506020870135915080821115611a8457600080fd5b50611a918782880161180f565b95989497509550505050565b60008060008060408587031215611ab357600080fd5b843567ffffffffffffffff80821115611acb57600080fd5b611a6b8883890161189a565b600060208284031215611ae957600080fd5b815180151581146117c957600080fd5b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff84168152604060208201526000611b72604083018486611af9565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112611c0e57600080fd5b83018035915067ffffffffffffffff821115611c2957600080fd5b60200191503681900382131561185157600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c96577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b838152604060208083018290528282018490526000919085906060850184805b88811015611d17578435611cd0816117d0565b73ffffffffffffffffffffffffffffffffffffffff1683528484013567ffffffffffffffff8116808214611d02578384fd5b84860152509385019391850191600101611cbd565b50909998505050505050505050565b606081526000611d3a606083018789611af9565b8281036020840152611d4d818688611af9565b91505073ffffffffffffffffffffffffffffffffffffffff831660408301529695505050505050565b6060808252810185905260006080600587901b8301810190830188835b89811015611e42577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8086850301835281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18c3603018112611df457600080fd5b8b01602081810191359067ffffffffffffffff821115611e1357600080fd5b813603831315611e2257600080fd5b611e2d878385611af9565b96509485019493909301925050600101611d93565b5050508281036020840152611e58818688611af9565b915050611e7d604083018473ffffffffffffffffffffffffffffffffffffffff169052565b9695505050505050565b80356020831015611568577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b602081526000610700602083018486611af9565b600060208284031215611ee957600080fd5b815167ffffffffffffffff80821115611f0157600080fd5b818401915084601f830112611f1557600080fd5b815181811115611f2757611f27611b7b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611f6d57611f6d611b7b565b81604052828152876020848701011115611f8657600080fd5b61114a836020830160208801611748565b604081526000611fab604083018587611af9565b905073ffffffffffffffffffffffffffffffffffffffff8316602083015294935050505056fea164736f6c6343000813000a", } var VerifierProxyABI = VerifierProxyMetaData.ABI @@ -286,6 +286,50 @@ func (_VerifierProxy *VerifierProxyCallerSession) TypeAndVersion() (string, erro return _VerifierProxy.Contract.TypeAndVersion(&_VerifierProxy.CallOpts) } +func (_VerifierProxy *VerifierProxyCaller) VerifyBulkView(opts *bind.CallOpts, payloads [][]byte) ([][]byte, error) { + var out []interface{} + err := _VerifierProxy.contract.Call(opts, &out, "verifyBulkView", payloads) + + if err != nil { + return *new([][]byte), err + } + + out0 := *abi.ConvertType(out[0], new([][]byte)).(*[][]byte) + + return out0, err + +} + +func (_VerifierProxy *VerifierProxySession) VerifyBulkView(payloads [][]byte) ([][]byte, error) { + return _VerifierProxy.Contract.VerifyBulkView(&_VerifierProxy.CallOpts, payloads) +} + +func (_VerifierProxy *VerifierProxyCallerSession) VerifyBulkView(payloads [][]byte) ([][]byte, error) { + return _VerifierProxy.Contract.VerifyBulkView(&_VerifierProxy.CallOpts, payloads) +} + +func (_VerifierProxy *VerifierProxyCaller) VerifyView(opts *bind.CallOpts, payload []byte) ([]byte, error) { + var out []interface{} + err := _VerifierProxy.contract.Call(opts, &out, "verifyView", payload) + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +func (_VerifierProxy *VerifierProxySession) VerifyView(payload []byte) ([]byte, error) { + return _VerifierProxy.Contract.VerifyView(&_VerifierProxy.CallOpts, payload) +} + +func (_VerifierProxy *VerifierProxyCallerSession) VerifyView(payload []byte) ([]byte, error) { + return _VerifierProxy.Contract.VerifyView(&_VerifierProxy.CallOpts, payload) +} + func (_VerifierProxy *VerifierProxyTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { return _VerifierProxy.contract.Transact(opts, "acceptOwnership") } @@ -1321,6 +1365,10 @@ type VerifierProxyInterface interface { TypeAndVersion(opts *bind.CallOpts) (string, error) + VerifyBulkView(opts *bind.CallOpts, payloads [][]byte) ([][]byte, error) + + VerifyView(opts *bind.CallOpts, payload []byte) ([]byte, error) + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) InitializeVerifier(opts *bind.TransactOpts, verifierAddress common.Address) (*types.Transaction, error) diff --git a/gethwrappers/llo-feeds/generated/verifier_v0_5_0/verifier_v0_5_0.go b/gethwrappers/llo-feeds/generated/verifier_v0_5_0/verifier_v0_5_0.go index bea7ab72f4..e464cfb442 100644 --- a/gethwrappers/llo-feeds/generated/verifier_v0_5_0/verifier_v0_5_0.go +++ b/gethwrappers/llo-feeds/generated/verifier_v0_5_0/verifier_v0_5_0.go @@ -36,8 +36,8 @@ type CommonAddressAndWeight struct { } var VerifierMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"verifierProxyAddr\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"activateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deactivateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"latestConfigDetails\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"blockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"signers\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\",\"internalType\":\"structCommon.AddressAndWeight[]\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"weight\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"isVerifier\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"updateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"prevSigners\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"newSigners\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verify\",\"inputs\":[{\"name\":\"signedReport\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"verifierResponse\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"ConfigActivated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigDeactivated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigSet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"signers\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigUpdated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"prevSigners\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"},{\"name\":\"newSigners\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ReportVerified\",\"inputs\":[{\"name\":\"feedId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"requester\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessForbidden\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"BadVerification\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ConfigDigestAlreadySet\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DigestEmpty\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DigestInactive\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"DigestNotSet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ExcessSigners\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"FaultToleranceMustBePositive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectSignatureCount\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"expectedNumSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InsufficientSigners\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"minSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MismatchedSignatures\",\"inputs\":[{\"name\":\"rsLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"ssLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NonUniqueSignatures\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", - Bin: "0x60a06040523480156200001157600080fd5b5060405162001e7038038062001e708339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b608051611c75620001fb600039600081816107c20152610e110152611c756000f3fe608060405234801561001057600080fd5b50600436106100c95760003560e01c80633d3ac1b5116100815780638da5cb5b1161005b5780638da5cb5b146101e3578063e84f128e1461020b578063f2fde38b1461024657600080fd5b80633d3ac1b5146101b557806341e3df58146101c857806379ba5097146101db57600080fd5b80630e112e54116100b25780630e112e541461014d5780630f672ef414610160578063181f5a771461017357600080fd5b806301ffc9a7146100ce5780630d1d79af14610138575b600080fd5b6101236100dc366004611333565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b61014b61014636600461137c565b610259565b005b61014b61015b3660046113f7565b610351565b61014b61016e36600461137c565b6106bc565b60408051808201909152600e81527f566572696669657220322e302e3000000000000000000000000000000000000060208201525b60405161012f91906114e4565b6101a86101c336600461151b565b6107a8565b61014b6101d636600461168a565b6108cc565b61014b61098e565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161012f565b61023161021936600461137c565b60009081526002602052604090205463ffffffff1690565b60405163ffffffff909116815260200161012f565b61014b6102543660046117a3565b610a8b565b610261610a9f565b6000818152600260205260409020816102a6576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805465010000000000900460ff166000036102f5576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018390526024015b60405180910390fd5b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffff1664010000000017815560405182907fa543797a0501218bba8a3daf75a71c8df8d1a7f791f4e44d40e43b6450183cea90600090a25050565b8160ff82166000819003610391576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156103d6576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044016102ec565b6103e18160036117ed565b821161043957816103f38260036117ed565b6103fe90600161180a565b6040517f9dd9e6d8000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016102ec565b610441610a9f565b6000888152600260205260408120805490916501000000000090910460ff16900361049b576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018a90526024016102ec565b80546601000000000000900460ff1687146104e2576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8781101561061c5760008260010160008b8b858181106105075761050761184c565b905060200201602081019061051c91906117a3565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002054610100900460ff16600181111561055c5761055c61181d565b03610593576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010160008a8a848181106105ab576105ab61184c565b90506020020160208101906105c091906117a3565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001690556106158161187b565b90506104e5565b5061067389878787600060405190808252806020026020018201604052801561066b57816020015b60408051808201909152600080825260208201528152602001906001900390816106445790505b506001610b22565b887fb0b75a854fab801413da6202fc07e875c54eaf371a1e3909fb2645364ba58616898989896040516106a99493929190611907565b60405180910390a2505050505050505050565b6106c4610a9f565b600081815260026020526040902081610709576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805465010000000000900460ff16600003610753576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018390526024016102ec565b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffff16815560405182907f5bfaab86edc1b932e3c334327a591c9ded067cb521abae19b95ca927d607657990600090a25050565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610819576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008080808061082b888a018a611a2d565b84516000818152600260205260409020959a50939850919650945092509061085582868684610ec2565b8551602087012061086a818988888887610fc2565b61087387611b08565b60405173ffffffffffffffffffffffffffffffffffffffff8c1681527f58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc59060200160405180910390a250949a9950505050505050505050565b8260ff8316600081900361090c576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f821115610951576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f60248201526044016102ec565b61095c8160036117ed565b821161096e57816103f38260036117ed565b610976610a9f565b61098587878787876000610b22565b50505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610a0f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064016102ec565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610a93610a9f565b610a9c8161123e565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314610b20576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e65720000000000000000000060448201526064016102ec565b565b6000868152600260205260409020805465010000000000900460ff1615801590610b4a575081155b15610b81576040517f961dba8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805460ff8681166601000000000000027fffffffffffffffffffffffffffffffffffffffffffffffffff00ff00ffffffff91871665010000000000027fffffffffffffffffffffffffffffffffffffffffffffffffffff00ff0000000090931663ffffffff43161792909217161764010000000017815560005b60ff8116861115610dce57600087878360ff16818110610c1d57610c1d61184c565b9050602002016020810190610c3291906117a3565b905073ffffffffffffffffffffffffffffffffffffffff8116610c81576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073ffffffffffffffffffffffffffffffffffffffff831660009081526001868101602052604090912054610100900460ff1690811115610cc657610cc661181d565b1480159150610d01576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051808201825260ff85811682526001602080840182815273ffffffffffffffffffffffffffffffffffffffff881660009081528a84019092529490208351815493167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008416811782559451939490939284927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090911690911790610100908490811115610db357610db361181d565b0217905550905050505080610dc790611b4d565b9050610bfb565b5081610985576040517fb011b24700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063b011b24790610e4b906000908b908890600401611b6c565b600060405180830381600087803b158015610e6557600080fd5b505af1158015610e79573d6000803e3d6000fd5b50505050867f5b1f376eb2bda670fa39339616d0a73f45b61bec8faeba8ca834f2ebb49676e0878787604051610eb193929190611bec565b60405180910390a250505050505050565b8054600090610ede9065010000000000900460ff166001611c13565b8254909150640100000000900460ff16610f27576040517fd990d621000000000000000000000000000000000000000000000000000000008152600481018690526024016102ec565b8060ff16845114610f735783516040517f5348a282000000000000000000000000000000000000000000000000000000008152600481019190915260ff821660248201526044016102ec565b8251845114610fbb57835183516040517ff0d31408000000000000000000000000000000000000000000000000000000008152600481019290925260248201526044016102ec565b5050505050565b60008686604051602001610fd7929190611c2c565b604051602081830303815290604052805190602001209050600061100b604080518082019091526000808252602082015290565b8651600090815b818110156111d65760018689836020811061102f5761102f61184c565b61103c91901a601b611c13565b8c848151811061104e5761104e61184c565b60200260200101518c85815181106110685761106861184c565b6020026020010151604051600081526020016040526040516110a6949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa1580156110c8573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526001808d01602090815291859020848601909552845460ff80821686529399509395509085019261010090049091169081111561114d5761114d61181d565b600181111561115e5761115e61181d565b905250935060018460200151600181111561117b5761117b61181d565b146111b2576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b836000015160080260ff166001901b85019450806111cf9061187b565b9050611012565b50837e01010101010101010101010101010101010101010101010101010101010101851614611231576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036112bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c6600000000000000000060448201526064016102ec565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006020828403121561134557600080fd5b81357fffffffff000000000000000000000000000000000000000000000000000000008116811461137557600080fd5b9392505050565b60006020828403121561138e57600080fd5b5035919050565b60008083601f8401126113a757600080fd5b50813567ffffffffffffffff8111156113bf57600080fd5b6020830191508360208260051b85010111156113da57600080fd5b9250929050565b803560ff811681146113f257600080fd5b919050565b6000806000806000806080878903121561141057600080fd5b86359550602087013567ffffffffffffffff8082111561142f57600080fd5b61143b8a838b01611395565b9097509550604089013591508082111561145457600080fd5b5061146189828a01611395565b90945092506114749050606088016113e1565b90509295509295509295565b6000815180845260005b818110156114a65760208185018101518683018201520161148a565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006113756020830184611480565b803573ffffffffffffffffffffffffffffffffffffffff811681146113f257600080fd5b60008060006040848603121561153057600080fd5b833567ffffffffffffffff8082111561154857600080fd5b818601915086601f83011261155c57600080fd5b81358181111561156b57600080fd5b87602082850101111561157d57600080fd5b60209283019550935061159391860190506114f7565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156115ee576115ee61159c565b60405290565b6040516060810167ffffffffffffffff811182821017156115ee576115ee61159c565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff8111828210171561165e5761165e61159c565b604052919050565b600067ffffffffffffffff8211156116805761168061159c565b5060051b60200190565b6000806000806000608086880312156116a257600080fd5b8535945060208087013567ffffffffffffffff808211156116c257600080fd5b6116ce8a838b01611395565b9097509550604091506116e28983016113e1565b94506060890135818111156116f657600080fd5b8901601f81018b1361170757600080fd5b803561171a61171582611666565b611617565b81815260069190911b8201850190858101908d83111561173957600080fd5b928601925b8284101561178f5785848f0312156117565760008081fd5b61175e6115cb565b611767856114f7565b815287850135868116811461177c5760008081fd5b818901528252928501929086019061173e565b809750505050505050509295509295909350565b6000602082840312156117b557600080fd5b611375826114f7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8082028115828204841417611804576118046117be565b92915050565b80820180821115611804576118046117be565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036118ac576118ac6117be565b5060010190565b8183526000602080850194508260005b858110156118fc5773ffffffffffffffffffffffffffffffffffffffff6118e9836114f7565b16875295820195908201906001016118c3565b509495945050505050565b60408152600061191b6040830186886118b3565b828103602084015261192e8185876118b3565b979650505050505050565b600082601f83011261194a57600080fd5b813567ffffffffffffffff8111156119645761196461159c565b61199560207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611617565b8181528460208386010111156119aa57600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f8301126119d857600080fd5b813560206119e861171583611666565b82815260059290921b84018101918181019086841115611a0757600080fd5b8286015b84811015611a225780358352918301918301611a0b565b509695505050505050565b600080600080600060e08688031215611a4557600080fd5b86601f870112611a5457600080fd5b611a5c6115f4565b806060880189811115611a6e57600080fd5b885b81811015611a88578035845260209384019301611a70565b5090965035905067ffffffffffffffff80821115611aa557600080fd5b611ab189838a01611939565b95506080880135915080821115611ac757600080fd5b611ad389838a016119c7565b945060a0880135915080821115611ae957600080fd5b50611af6888289016119c7565b9598949750929560c001359392505050565b80516020808301519190811015611b47577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b600060ff821660ff8103611b6357611b636117be565b60010192915050565b600060608201858352602085818501526040606081860152828651808552608087019150838801945060005b81811015611bdd578551805173ffffffffffffffffffffffffffffffffffffffff16845285015167ffffffffffffffff16858401529484019491830191600101611b98565b50909998505050505050505050565b604081526000611c006040830185876118b3565b905060ff83166020830152949350505050565b60ff8181168382160190811115611804576118046117be565b828152600060208083018460005b6003811015611c5757815183529183019190830190600101611c3a565b50505050608082019050939250505056fea164736f6c6343000813000a", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"verifierProxyAddr\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"activateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deactivateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"latestConfigDetails\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"blockNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"signers\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"recipientAddressesAndWeights\",\"type\":\"tuple[]\",\"internalType\":\"structCommon.AddressAndWeight[]\",\"components\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"weight\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"isVerifier\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"updateConfig\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"prevSigners\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"newSigners\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verify\",\"inputs\":[{\"name\":\"signedReport\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"verifierResponse\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"verifyView\",\"inputs\":[{\"name\":\"signedReport\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"verifierResponse\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"ConfigActivated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigDeactivated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigSet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"signers\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"},{\"name\":\"f\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ConfigUpdated\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"prevSigners\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"},{\"name\":\"newSigners\",\"type\":\"address[]\",\"indexed\":false,\"internalType\":\"address[]\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ReportVerified\",\"inputs\":[{\"name\":\"feedId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"requester\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessForbidden\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"BadVerification\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ConfigDigestAlreadySet\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DigestEmpty\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DigestInactive\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"DigestNotSet\",\"inputs\":[{\"name\":\"configDigest\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ExcessSigners\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"FaultToleranceMustBePositive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IncorrectSignatureCount\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"expectedNumSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InsufficientSigners\",\"inputs\":[{\"name\":\"numSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"minSigners\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MismatchedSignatures\",\"inputs\":[{\"name\":\"rsLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"ssLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NonUniqueSignatures\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAddress\",\"inputs\":[]}]", + Bin: "0x60a06040523480156200001157600080fd5b5060405162001f0b38038062001f0b8339810160408190526200003491620001a6565b33806000816200008b5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000be57620000be81620000fb565b5050506001600160a01b038116620000e95760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0316608052620001d8565b336001600160a01b03821603620001555760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000082565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600060208284031215620001b957600080fd5b81516001600160a01b0381168114620001d157600080fd5b9392505050565b608051611d10620001fb60003960008181610d8a0152610e550152611d106000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c806341e3df58116100815780638da5cb5b1161005b5780638da5cb5b14610201578063e84f128e14610229578063f2fde38b1461026457600080fd5b806341e3df58146101d357806351b34c90146101e657806379ba5097146101f957600080fd5b80630f672ef4116100b25780630f672ef41461016b578063181f5a771461017e5780633d3ac1b5146101c057600080fd5b806301ffc9a7146100d95780630d1d79af146101435780630e112e5414610158575b600080fd5b61012e6100e736600461137d565b7fffffffff00000000000000000000000000000000000000000000000000000000167f3d3ac1b5000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6101566101513660046113c6565b610277565b005b610156610166366004611441565b61036f565b6101566101793660046113c6565b6106da565b60408051808201909152600e81527f566572696669657220322e302e3100000000000000000000000000000000000060208201525b60405161013a919061152e565b6101b36101ce3660046115a7565b6107c6565b6101566101e13660046116e9565b610830565b6101b36101f4366004611802565b6108f2565b610156610907565b60005460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161013a565b61024f6102373660046113c6565b60009081526002602052604090205463ffffffff1690565b60405163ffffffff909116815260200161013a565b610156610272366004611844565b610a04565b61027f610a18565b6000818152600260205260409020816102c4576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805465010000000000900460ff16600003610313576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018390526024015b60405180910390fd5b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffff1664010000000017815560405182907fa543797a0501218bba8a3daf75a71c8df8d1a7f791f4e44d40e43b6450183cea90600090a25050565b8160ff821660008190036103af576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156103f4576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f602482015260440161030a565b6103ff81600361188e565b8211610457578161041182600361188e565b61041c9060016118a5565b6040517f9dd9e6d80000000000000000000000000000000000000000000000000000000081526004810192909252602482015260440161030a565b61045f610a18565b6000888152600260205260408120805490916501000000000090910460ff1690036104b9576040517f74eb4b93000000000000000000000000000000000000000000000000000000008152600481018a905260240161030a565b80546601000000000000900460ff168714610500576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8781101561063a5760008260010160008b8b85818110610525576105256118e7565b905060200201602081019061053a9190611844565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002054610100900460ff16600181111561057a5761057a6118b8565b036105b1576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010160008a8a848181106105c9576105c96118e7565b90506020020160208101906105de9190611844565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016905561063381611916565b9050610503565b5061069189878787600060405190808252806020026020018201604052801561068957816020015b60408051808201909152600080825260208201528152602001906001900390816106625790505b506001610a9b565b887fb0b75a854fab801413da6202fc07e875c54eaf371a1e3909fb2645364ba58616898989896040516106c794939291906119a2565b60405180910390a2505050505050505050565b6106e2610a18565b600081815260026020526040902081610727576040517fe332262700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805465010000000000900460ff16600003610771576040517f74eb4b930000000000000000000000000000000000000000000000000000000081526004810183905260240161030a565b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffffffff16815560405182907f5bfaab86edc1b932e3c334327a591c9ded067cb521abae19b95ca927d607657990600090a25050565b606060006107d48585610e3b565b90506107df816119d4565b60405173ffffffffffffffffffffffffffffffffffffffff851681527f58ca9502e98a536e06e72d680fcc251e5d10b72291a281665a2c2dc0ac30fcc59060200160405180910390a2949350505050565b8260ff83166000819003610870576040517f0743bae600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b601f8211156108b5576040517f61750f4000000000000000000000000000000000000000000000000000000000815260048101839052601f602482015260440161030a565b6108c081600361188e565b82116108d2578161041182600361188e565b6108da610a18565b6108e987878787876000610a9b565b50505050505050565b60606108fe8383610e3b565b90505b92915050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e657200000000000000000000604482015260640161030a565b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610a0c610a18565b610a1581610f0c565b50565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a99576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640161030a565b565b6000868152600260205260409020805465010000000000900460ff1615801590610ac3575081155b15610afa576040517f961dba8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805460ff8681166601000000000000027fffffffffffffffffffffffffffffffffffffffffffffffffff00ff00ffffffff91871665010000000000027fffffffffffffffffffffffffffffffffffffffffffffffffffff00ff0000000090931663ffffffff43161792909217161764010000000017815560005b60ff8116861115610d4757600087878360ff16818110610b9657610b966118e7565b9050602002016020810190610bab9190611844565b905073ffffffffffffffffffffffffffffffffffffffff8116610bfa576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008073ffffffffffffffffffffffffffffffffffffffff831660009081526001868101602052604090912054610100900460ff1690811115610c3f57610c3f6118b8565b1480159150610c7a576040517ff67bc7c400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60408051808201825260ff85811682526001602080840182815273ffffffffffffffffffffffffffffffffffffffff881660009081528a84019092529490208351815493167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008416811782559451939490939284927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090911690911790610100908490811115610d2c57610d2c6118b8565b0217905550905050505080610d4090611a19565b9050610b74565b50816108e9576040517fb011b24700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063b011b24790610dc4906000908b908890600401611a38565b600060405180830381600087803b158015610dde57600080fd5b505af1158015610df2573d6000803e3d6000fd5b50505050867f5b1f376eb2bda670fa39339616d0a73f45b61bec8faeba8ca834f2ebb49676e0878787604051610e2a93929190611ab8565b60405180910390a250505050505050565b60603373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610eac576040517fef67f5d800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080808080610ebe87890189611bd3565b84516000818152600260205260409020959a509398509196509450925090610ee882868684611001565b85516020870120610efd818988888887611101565b50949998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603610f8b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640161030a565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b805460009061101d9065010000000000900460ff166001611cae565b8254909150640100000000900460ff16611066576040517fd990d6210000000000000000000000000000000000000000000000000000000081526004810186905260240161030a565b8060ff168451146110b25783516040517f5348a282000000000000000000000000000000000000000000000000000000008152600481019190915260ff8216602482015260440161030a565b82518451146110fa57835183516040517ff0d314080000000000000000000000000000000000000000000000000000000081526004810192909252602482015260440161030a565b5050505050565b60008686604051602001611116929190611cc7565b604051602081830303815290604052805190602001209050600061114a604080518082019091526000808252602082015290565b8651600090815b818110156113155760018689836020811061116e5761116e6118e7565b61117b91901a601b611cae565b8c848151811061118d5761118d6118e7565b60200260200101518c85815181106111a7576111a76118e7565b6020026020010151604051600081526020016040526040516111e5949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611207573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526001808d01602090815291859020848601909552845460ff80821686529399509395509085019261010090049091169081111561128c5761128c6118b8565b600181111561129d5761129d6118b8565b90525093506001846020015160018111156112ba576112ba6118b8565b146112f1576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b836000015160080260ff166001901b850194508061130e90611916565b9050611151565b50837e01010101010101010101010101010101010101010101010101010101010101851614611370576040517f4df18f0700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505050505050505050565b60006020828403121561138f57600080fd5b81357fffffffff00000000000000000000000000000000000000000000000000000000811681146113bf57600080fd5b9392505050565b6000602082840312156113d857600080fd5b5035919050565b60008083601f8401126113f157600080fd5b50813567ffffffffffffffff81111561140957600080fd5b6020830191508360208260051b850101111561142457600080fd5b9250929050565b803560ff8116811461143c57600080fd5b919050565b6000806000806000806080878903121561145a57600080fd5b86359550602087013567ffffffffffffffff8082111561147957600080fd5b6114858a838b016113df565b9097509550604089013591508082111561149e57600080fd5b506114ab89828a016113df565b90945092506114be90506060880161142b565b90509295509295509295565b6000815180845260005b818110156114f0576020818501810151868301820152016114d4565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006108fe60208301846114ca565b60008083601f84011261155357600080fd5b50813567ffffffffffffffff81111561156b57600080fd5b60208301915083602082850101111561142457600080fd5b803573ffffffffffffffffffffffffffffffffffffffff8116811461143c57600080fd5b6000806000604084860312156115bc57600080fd5b833567ffffffffffffffff8111156115d357600080fd5b6115df86828701611541565b90945092506115f2905060208501611583565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff8111828210171561164d5761164d6115fb565b60405290565b6040516060810167ffffffffffffffff8111828210171561164d5761164d6115fb565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156116bd576116bd6115fb565b604052919050565b600067ffffffffffffffff8211156116df576116df6115fb565b5060051b60200190565b60008060008060006080868803121561170157600080fd5b8535945060208087013567ffffffffffffffff8082111561172157600080fd5b61172d8a838b016113df565b90975095506040915061174189830161142b565b945060608901358181111561175557600080fd5b8901601f81018b1361176657600080fd5b8035611779611774826116c5565b611676565b81815260069190911b8201850190858101908d83111561179857600080fd5b928601925b828410156117ee5785848f0312156117b55760008081fd5b6117bd61162a565b6117c685611583565b81528785013586811681146117db5760008081fd5b818901528252928501929086019061179d565b809750505050505050509295509295909350565b6000806020838503121561181557600080fd5b823567ffffffffffffffff81111561182c57600080fd5b61183885828601611541565b90969095509350505050565b60006020828403121561185657600080fd5b6108fe82611583565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176109015761090161185f565b808201808211156109015761090161185f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036119475761194761185f565b5060010190565b8183526000602080850194508260005b858110156119975773ffffffffffffffffffffffffffffffffffffffff61198483611583565b168752958201959082019060010161195e565b509495945050505050565b6040815260006119b660408301868861194e565b82810360208401526119c981858761194e565b979650505050505050565b80516020808301519190811015611a13577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b600060ff821660ff8103611a2f57611a2f61185f565b60010192915050565b600060608201858352602085818501526040606081860152828651808552608087019150838801945060005b81811015611aa9578551805173ffffffffffffffffffffffffffffffffffffffff16845285015167ffffffffffffffff16858401529484019491830191600101611a64565b50909998505050505050505050565b604081526000611acc60408301858761194e565b905060ff83166020830152949350505050565b600082601f830112611af057600080fd5b813567ffffffffffffffff811115611b0a57611b0a6115fb565b611b3b60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601611676565b818152846020838601011115611b5057600080fd5b816020850160208301376000918101602001919091529392505050565b600082601f830112611b7e57600080fd5b81356020611b8e611774836116c5565b82815260059290921b84018101918181019086841115611bad57600080fd5b8286015b84811015611bc85780358352918301918301611bb1565b509695505050505050565b600080600080600060e08688031215611beb57600080fd5b86601f870112611bfa57600080fd5b611c02611653565b806060880189811115611c1457600080fd5b885b81811015611c2e578035845260209384019301611c16565b5090965035905067ffffffffffffffff80821115611c4b57600080fd5b611c5789838a01611adf565b95506080880135915080821115611c6d57600080fd5b611c7989838a01611b6d565b945060a0880135915080821115611c8f57600080fd5b50611c9c88828901611b6d565b9598949750929560c001359392505050565b60ff81811683821601908111156109015761090161185f565b828152600060208083018460005b6003811015611cf257815183529183019190830190600101611cd5565b50505050608082019050939250505056fea164736f6c6343000813000a", } var VerifierABI = VerifierMetaData.ABI @@ -264,6 +264,28 @@ func (_Verifier *VerifierCallerSession) TypeAndVersion() (string, error) { return _Verifier.Contract.TypeAndVersion(&_Verifier.CallOpts) } +func (_Verifier *VerifierCaller) VerifyView(opts *bind.CallOpts, signedReport []byte) ([]byte, error) { + var out []interface{} + err := _Verifier.contract.Call(opts, &out, "verifyView", signedReport) + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +func (_Verifier *VerifierSession) VerifyView(signedReport []byte) ([]byte, error) { + return _Verifier.Contract.VerifyView(&_Verifier.CallOpts, signedReport) +} + +func (_Verifier *VerifierCallerSession) VerifyView(signedReport []byte) ([]byte, error) { + return _Verifier.Contract.VerifyView(&_Verifier.CallOpts, signedReport) +} + func (_Verifier *VerifierTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { return _Verifier.contract.Transact(opts, "acceptOwnership") } @@ -1323,6 +1345,8 @@ type VerifierInterface interface { TypeAndVersion(opts *bind.CallOpts) (string, error) + VerifyView(opts *bind.CallOpts, signedReport []byte) ([]byte, error) + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) ActivateConfig(opts *bind.TransactOpts, configDigest [32]byte) (*types.Transaction, error) diff --git a/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt index fc60eb24df..e93151d400 100644 --- a/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/gethwrappers/llo-feeds/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -24,6 +24,6 @@ reward_manager_v0_5_0: ../../contracts/solc/llo-feeds/v0.5.0/RewardManager/Rewar stream_config_store: ../../../contracts/solc/v0.8.19/StreamConfigStore/StreamConfigStore.abi ../../../contracts/solc/v0.8.19/StreamConfigStore/StreamConfigStore.bin 45ae1b0a45a90b3dee076023052aef73c212c8ef8825b829397f751f6b0a1598 verifier: ../../contracts/solc/llo-feeds/v0.3.0/Verifier/Verifier.sol/Verifier.abi.json ../../contracts/solc/llo-feeds/v0.3.0/Verifier/Verifier.sol/Verifier.bin f3ecfbfb27f5cab1c516a6903a8351d36de5d1337f4db6ccfd2c0c0b8c077167 verifier_proxy: ../../contracts/solc/llo-feeds/v0.3.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.abi.json ../../contracts/solc/llo-feeds/v0.3.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.bin b03883f83a03829d58a58f5e71dbd88aab4db4fc24a5fa7f86e3246743412dd0 -verifier_proxy_v0_5_0: ../../contracts/solc/llo-feeds/v0.5.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.abi.json ../../contracts/solc/llo-feeds/v0.5.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.bin b03883f83a03829d58a58f5e71dbd88aab4db4fc24a5fa7f86e3246743412dd0 -verifier_v0_5_0: ../../contracts/solc/llo-feeds/v0.5.0/Verifier/Verifier.sol/Verifier.abi.json ../../contracts/solc/llo-feeds/v0.5.0/Verifier/Verifier.sol/Verifier.bin f66917f40d113128b0a42e727548be380cff3e66f79300ccb06a51d4dca8179a +verifier_proxy_v0_5_0: ../../contracts/solc/llo-feeds/v0.5.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.abi.json ../../contracts/solc/llo-feeds/v0.5.0/VerifierProxy/VerifierProxy.sol/VerifierProxy.bin 7cb893e3bd59acb9d3009b63efa65e89b309eb57fa6e23ee68201015a7048859 +verifier_v0_5_0: ../../contracts/solc/llo-feeds/v0.5.0/Verifier/Verifier.sol/Verifier.abi.json ../../contracts/solc/llo-feeds/v0.5.0/Verifier/Verifier.sol/Verifier.bin b27ef9f7b6fbafec441ee6eb680f7d7091436df3ed9b926dfa87d8dd4864e032 werc20_mock: ../../../contracts/solc/v0.8.19/WERC20Mock.abi ../../../contracts/solc/v0.8.19/WERC20Mock.bin ff2ca3928b2aa9c412c892cb8226c4d754c73eeb291bb7481c32c48791b2aa94