From 22d77f95e1355986120580576632e9880ce36ffe Mon Sep 17 00:00:00 2001 From: Justin Kaseman Date: Thu, 9 Nov 2023 19:53:33 -0500 Subject: [PATCH 1/6] FUN-958 - Golf golf changes, store repeated .length as a variable (#11251) --- .../gas-snapshots/functions.gas-snapshot | 52 +++++++++---------- .../functions/dev/v1_X/FunctionsBilling.sol | 9 ++-- .../dev/v1_X/FunctionsCoordinator.sol | 15 +++--- .../functions_coordinator.go | 2 +- ...rapper-dependency-versions-do-not-edit.txt | 2 +- 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/contracts/gas-snapshots/functions.gas-snapshot b/contracts/gas-snapshots/functions.gas-snapshot index af95b75df10..d15666f8857 100644 --- a/contracts/gas-snapshots/functions.gas-snapshot +++ b/contracts/gas-snapshots/functions.gas-snapshot @@ -1,12 +1,12 @@ -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14534216) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14534194) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14534210) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14545630) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14545607) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14545579) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14545530) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14545519) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14545563) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14534206) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14534184) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14534200) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14545620) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14545597) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14545569) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14545520) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14545509) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14545553) FunctionsBilling_Constructor:test_Constructor_Success() (gas: 14812) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_RevertIfNotRouter() (gas: 13282) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_Success() (gas: 15897) @@ -17,20 +17,20 @@ FunctionsBilling_GetAdminFee:test_GetAdminFee_Success() (gas: 18226) FunctionsBilling_GetConfig:test_GetConfig_Success() (gas: 23671) FunctionsBilling_GetDONFee:test_GetDONFee_Success() (gas: 15792) FunctionsBilling_GetWeiPerUnitLink:test_GetWeiPerUnitLink_Success() (gas: 31773) -FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertIfInsufficientBalance() (gas: 70138) -FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertWithNoBalance() (gas: 106295) -FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceNoAmountGiven() (gas: 140174) -FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceValidAmountGiven() (gas: 142502) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertIfInsufficientBalance() (gas: 70128) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_RevertWithNoBalance() (gas: 106285) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceNoAmountGiven() (gas: 140164) +FunctionsBilling_OracleWithdraw:test_OracleWithdraw_SuccessTransmitterWithBalanceValidAmountGiven() (gas: 142492) FunctionsBilling_OracleWithdrawAll:test_OracleWithdrawAll_RevertIfNotOwner() (gas: 13296) FunctionsBilling_OracleWithdrawAll:test_OracleWithdrawAll_SuccessPaysTransmittersWithBalance() (gas: 147278) FunctionsBilling_UpdateConfig:test_UpdateConfig_RevertIfNotOwner() (gas: 18974) FunctionsBilling_UpdateConfig:test_UpdateConfig_Success() (gas: 38251) -FunctionsBilling__DisperseFeePool:test__DisperseFeePool_RevertIfNotSet() (gas: 8801) +FunctionsBilling__DisperseFeePool:test__DisperseFeePool_RevertIfNotSet() (gas: 8810) FunctionsBilling__FulfillAndBill:test__FulfillAndBill_RevertIfInvalidCommitment() (gas: 13302) FunctionsBilling__FulfillAndBill:test__FulfillAndBill_Success() (gas: 180763) FunctionsClient_Constructor:test_Constructor_Success() (gas: 7573) -FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 504364) -FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 205568) +FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 504354) +FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 205558) FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_RevertIfNotRouter() (gas: 14623) FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_Success() (gas: 22923) FunctionsClient__SendRequest:test__SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 55059) @@ -51,17 +51,17 @@ FunctionsRequest_DEFAULT_BUFFER_SIZE:test_DEFAULT_BUFFER_SIZE() (gas: 246) FunctionsRequest_EncodeCBOR:test_EncodeCBOR_Success() (gas: 223) FunctionsRequest_REQUEST_DATA_VERSION:test_REQUEST_DATA_VERSION() (gas: 225) FunctionsRouter_Constructor:test_Constructor_Success() (gas: 12007) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 174037) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 164368) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 174027) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 164358) FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidCommitment() (gas: 38115) FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidRequestId() (gas: 35238) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 182513) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 182503) FunctionsRouter_Fulfill:test_Fulfill_RevertIfNotCommittedCoordinator() (gas: 28086) -FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 158055) -FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 327615) -FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 341236) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2516517) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 546996) +FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 158046) +FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 327605) +FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 341226) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2516507) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 546986) FunctionsRouter_GetAdminFee:test_GetAdminFee_Success() (gas: 17983) FunctionsRouter_GetAllowListId:test_GetAllowListId_Success() (gas: 12904) FunctionsRouter_GetConfig:test_GetConfig_Success() (gas: 37159) @@ -144,8 +144,8 @@ FunctionsSubscriptions_GetTotalBalance:test_GetTotalBalance_Success() (gas: 1501 FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96) (runs: 256, μ: 43685, ~: 45548) FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96) (runs: 256, μ: 46197, ~: 48060) FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNotLink(uint96) (runs: 256, μ: 14295, ~: 14295) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfPaused(uint96) (runs: 256, μ: 51089, ~: 53040) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 86057, ~: 89604) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfPaused(uint96) (runs: 256, μ: 51177, ~: 53040) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 85879, ~: 89604) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfAmountMoreThanBalance() (gas: 20745) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfBalanceInvariant() (gas: 189) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfNoAmount() (gas: 15638) diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol index bd13a3a5a1a..e4b6fe7d90c 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol @@ -374,15 +374,16 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { // All transmitters are assumed to also be observers // Pay out the DON fee to all transmitters address[] memory transmitters = _getTransmitters(); - if (transmitters.length == 0) { + uint256 numberOfTransmitters = transmitters.length; + if (numberOfTransmitters == 0) { revert NoTransmittersSet(); } - uint96 feePoolShare = s_feePool / uint96(transmitters.length); + uint96 feePoolShare = s_feePool / uint96(numberOfTransmitters); // Bounded by "maxNumOracles" on OCR2Abstract.sol - for (uint256 i = 0; i < transmitters.length; ++i) { + for (uint256 i = 0; i < numberOfTransmitters; ++i) { s_withdrawableTokens[transmitters[i]] += feePoolShare; } - s_feePool -= feePoolShare * uint96(transmitters.length); + s_feePool -= feePoolShare * uint96(numberOfTransmitters); } // Overriden in FunctionsCoordinator.sol diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol index 2caab41c746..3ee4931e97c 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol @@ -142,19 +142,20 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli report, (bytes32[], bytes[], bytes[], bytes[], bytes[]) ); + uint256 numberOfFulfillments = uint8(requestIds.length); if ( - requestIds.length == 0 || - requestIds.length != results.length || - requestIds.length != errors.length || - requestIds.length != onchainMetadata.length || - requestIds.length != offchainMetadata.length + numberOfFulfillments == 0 || + numberOfFulfillments != results.length || + numberOfFulfillments != errors.length || + numberOfFulfillments != onchainMetadata.length || + numberOfFulfillments != offchainMetadata.length ) { revert ReportInvalid(); } // Bounded by "MaxRequestBatchSize" on the Job's ReportingPluginConfig - for (uint256 i = 0; i < requestIds.length; ++i) { + for (uint256 i = 0; i < numberOfFulfillments; ++i) { FunctionsResponse.FulfillResult result = FunctionsResponse.FulfillResult( _fulfillAndBill( requestIds[i], @@ -162,7 +163,7 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli errors[i], onchainMetadata[i], offchainMetadata[i], - uint8(requestIds.length) // will not exceed "MaxRequestBatchSize" on the Job's ReportingPluginConfig + uint8(numberOfFulfillments) // will not exceed "MaxRequestBatchSize" on the Job's ReportingPluginConfig ) ); diff --git a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go index 397ea3d18bb..917107524bf 100644 --- a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go +++ b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go @@ -72,7 +72,7 @@ type FunctionsResponseRequestMeta struct { var FunctionsCoordinatorMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"linkToNativeFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InconsistentReportData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoTransmittersSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouterOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RouterMustBeSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedPublicKeyChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsupportedRequestDataVersion\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CommitmentDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requestInitiator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"callbackGasLimit\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"}],\"name\":\"OracleResponse\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"juelsPerGas\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"l1FeeShareWei\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"callbackCostJuels\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"totalCostJuels\",\"type\":\"uint96\"}],\"name\":\"RequestBilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"deleteCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"gasPriceWei\",\"type\":\"uint256\"}],\"name\":\"estimateCost\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAdminFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getDONFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThresholdPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleWithdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"donPublicKey\",\"type\":\"bytes\"}],\"name\":\"setDONPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"thresholdPublicKey\",\"type\":\"bytes\"}],\"name\":\"setThresholdPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"availableBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"initiatedRequests\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint64\",\"name\":\"completedRequests\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"}],\"internalType\":\"structFunctionsResponse.RequestMeta\",\"name\":\"request\",\"type\":\"tuple\"}],\"name\":\"startRequest\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60c06040523480156200001157600080fd5b506040516200560838038062005608833981016040819052620000349162000474565b8282828260013380600081620000915760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c457620000c48162000140565b50505015156080526001600160a01b038116620000f457604051632530e88560e11b815260040160405180910390fd5b6001600160a01b0390811660a052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200013482620001eb565b50505050505062000633565b336001600160a01b038216036200019a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000088565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001f562000349565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033e9083906200057d565b60405180910390a150565b6200035362000355565b565b6000546001600160a01b03163314620003535760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000088565b80516001600160a01b0381168114620003c957600080fd5b919050565b60405161012081016001600160401b03811182821017156200040057634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c957600080fd5b80516001600160481b0381168114620003c957600080fd5b805164ffffffffff81168114620003c957600080fd5b805161ffff81168114620003c957600080fd5b80516001600160e01b0381168114620003c957600080fd5b60008060008385036101608112156200048c57600080fd5b6200049785620003b1565b935061012080601f1983011215620004ae57600080fd5b620004b8620003ce565b9150620004c86020870162000406565b8252620004d86040870162000406565b6020830152620004eb6060870162000406565b6040830152620004fe6080870162000406565b60608301526200051160a087016200041b565b60808301526200052460c0870162000433565b60a08301526200053760e0870162000449565b60c08301526101006200054c8188016200045c565b60e08401526200055e82880162000406565b90830152509150620005746101408501620003b1565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005d060808401826001600160481b03169052565b5060a0830151620005ea60a084018264ffffffffff169052565b5060c08301516200060160c084018261ffff169052565b5060e08301516200061d60e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b60805160a051614f856200068360003960008181610845015281816109d301528181610ca601528181610f3a015281816110450152818161183001526133b70152600061126e0152614f856000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a03660046138fb565b61059c565b005b6101a56101b5366004613aa4565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b6040516102039190613bc8565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c366004613c69565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613cf8565b6108d7565b6101a5610a90565b6101a5610b92565b6101a56102933660046138fb565b610d92565b6102a0610de2565b6040516102039190613d82565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613d95565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613dae565b610fd4565b6040516102039190613f03565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab366004613f57565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b604051610203919061400e565b61053b6105363660046140fe565b61182c565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f661198c565b6101a561056e366004614217565b6119e3565b61057b61240f565b604051908152602001610203565b6101a56105973660046142e4565b612668565b6105a461267c565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec82848361439a565b505050565b6105f96126ff565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f51848629061083690839061400e565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d291906144c0565b905090565b6108df612707565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6126ff565b610ba2612707565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd2614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c31614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf5614531565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d8781614560565b9050610bb1565b5050565b610d9a61267c565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec82848361439a565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e6090614301565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea890614301565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed490614301565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a883614598565b6128b2565b90506110bf60608301604084016142e4565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a08801614685565b61111f610160880161014089016142e4565b61112988806146a2565b61113b6101208b016101008c01614707565b60208b01356111516101008d0160e08e01614722565b8b6040516111679998979695949392919061473f565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16040805160608101825260025480825260035460ff8082166020850152610100909104169282019290925290831461125c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f636f6e666967446967657374206d69736d6174636800000000000000000000006044820152606401610b0d565b61126a8b8b8b8b8b8b612d50565b60007f0000000000000000000000000000000000000000000000000000000000000000156112c7576002826020015183604001516112a891906147e7565b6112b2919061482f565b6112bd9060016147e7565b60ff1690506112dd565b60208201516112d79060016147e7565b60ff1690505b888114611346576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b8887146113af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f7369676e617475726573206f7574206f6620726567697374726174696f6e00006044820152606401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113f2576113f2614851565b600281111561140357611403614851565b905250905060028160200151600281111561142057611420614851565b14801561146757506006816000015160ff168154811061144257611442614531565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b6114cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b50505050506114da613893565b6000808a8a6040516114ed929190614880565b604051908190038120611504918e90602001614890565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b8981101561180e57600060018489846020811061156d5761156d614531565b61157a91901a601b6147e7565b8e8e8681811061158c5761158c614531565b905060200201358d8d878181106115a5576115a5614531565b90506020020135604051600081526020016040526040516115e2949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611604573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff8082168552929650929450840191610100900416600281111561168457611684614851565b600281111561169557611695614851565b90525092506001836020015160028111156116b2576116b2614851565b14611719576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061173357611733614531565b602002015173ffffffffffffffffffffffffffffffffffffffff16146117b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117cf576117cf614531565b73ffffffffffffffffffffffffffffffffffffffff90921660209290920201526117fa6001866147e7565b9450508061180790614560565b905061154e565b50505061181f833383858e8e612e07565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b1580156118cc57600080fd5b505afa1580156118e0573d6000803e3d6000fd5b5050505066038d7ea4c68000821115611925576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061192f610841565b9050600061197287878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b905061198085858385612fd7565b98975050505050505050565b6060600c805461199b90614301565b90506000036119d6576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea890614301565b855185518560ff16601f831115611a56576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611ac0576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611b4e576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611b598160036148a4565b8311611bc1576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611bc961267c565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611c109088613144565b60055415611dc557600554600090611c2a906001906148bb565b9050600060058281548110611c4157611c41614531565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611c7b57611c7b614531565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611cfb57611cfb6148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611d6457611d646148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611c10915050565b60005b81515181101561222c5760006004600084600001518481518110611dee57611dee614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611e3857611e38614851565b14611e9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611ed057611ed0614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001617610100836002811115611f7157611f71614851565b021790555060009150611f819050565b6004600084602001518481518110611f9b57611f9b614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611fe557611fe5614851565b1461204c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff82168152602081016002815250600460008460200151848151811061207f5761207f614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561212057612120614851565b02179055505082518051600592508390811061213e5761213e614531565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106121ba576121ba614531565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9092169190911790558061222481614560565b915050611dc8565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff43811682029290921780855592048116929182916014916122e4918491740100000000000000000000000000000000000000009004166148fd565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123434630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a0015161315d565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986123fa988b9891977401000000000000000000000000000000000000000090920463ffffffff1696909591949193919261491a565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa15801561259d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c191906149ca565b5093505092505080426125d491906148bb565b836020015163ffffffff161080156125f657506000836020015163ffffffff16115b1561262457505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b60008213612661576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b61267061267c565b61267981613208565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146126fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6126fd61267c565b600b546bffffffffffffffffffffffff1660000361272157565b600061272b610de2565b90508051600003612768576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8051600b54600091612787916bffffffffffffffffffffffff16614a1a565b905060005b82518110156128535781600a60008584815181106127ac576127ac614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128149190614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508061284c90614560565b905061278c565b5081516128609082614a6a565b600b80546000906128809084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612a6d576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612aaa8560e001513a848860800151612fd7565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612b06576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612b1f9190614a92565b905060003087604001518860a001518960c001516001612b3f9190614aa5565b8a5180516020918201206101008d015160e08e0151604051612bf398979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612d029190613f03565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612d5d8260206148a4565b612d688560206148a4565b612d7488610144614a92565b612d7e9190614a92565b612d889190614a92565b612d93906000614a92565b9050368114612dfe576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b606080808080612e1986880188614ba1565b8451949950929750909550935091501580612e3657508351855114155b80612e4357508251855114155b80612e5057508151855114155b80612e5d57508051855114155b15612e94576040517f0be3632800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b8551811015612fc9576000612f2e878381518110612eb757612eb7614531565b6020026020010151878481518110612ed157612ed1614531565b6020026020010151878581518110612eeb57612eeb614531565b6020026020010151878681518110612f0557612f05614531565b6020026020010151878781518110612f1f57612f1f614531565b60200260200101518c516132fd565b90506000816006811115612f4457612f44614851565b1480612f6157506001816006811115612f5f57612f5f614851565b145b15612fb857868281518110612f7857612f78614531565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b50612fc281614560565b9050612e97565b505050505050505050505050565b600854600090790100000000000000000000000000000000000000000000000000900464ffffffffff1684101561303257600854790100000000000000000000000000000000000000000000000000900464ffffffffff1693505b6008546000906127109061304c9063ffffffff16876148a4565b6130569190614c73565b6130609086614a92565b60085490915060009087906130999063ffffffff6c010000000000000000000000008204811691680100000000000000009004166148fd565b6130a391906148fd565b63ffffffff16905060006130ed6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b9050600061310e826130ff85876148a4565b6131099190614a92565b613753565b9050600061312a68ffffffffffffffffff808916908a16614a45565b90506131368183614a45565b9a9950505050505050505050565b600061314e610de2565b511115610d8e57610d8e612707565b6000808a8a8a8a8a8a8a8a8a60405160200161318199989796959493929190614c87565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080848060200190518101906133149190614d53565b905060003a82610120015183610100015161332f9190614e1b565b64ffffffffff1661334091906148a4565b905060008460ff166133886000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b6133929190614c73565b905060006133a36131098385614a92565b905060006133b03a613753565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298e8e868b60e0015168ffffffffffffffffff168961340f9190614a45565b338d6040518763ffffffff1660e01b815260040161343296959493929190614e39565b60408051808303816000875af1158015613450573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134749190614eb5565b9092509050600082600681111561348d5761348d614851565b14806134aa575060018260068111156134a8576134a8614851565b145b156136005760008e8152600760205260408120556134c88185614a45565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0890151600b805468ffffffffffffffffff9092169390929161353491859116614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508d7f90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e0468487848b60c0015168ffffffffffffffffff168c60e0015168ffffffffffffffffff16878b6135b39190614a45565b6135bd9190614a45565b6135c79190614a45565b604080516bffffffffffffffffffffffff9586168152602081019490945291841683830152909216606082015290519081900360800190a25b509c9b505050505050505050505050565b60004661361d81613787565b1561369957606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561366e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136929190614ee8565b9392505050565b6136a2816137aa565b1561374a5773420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e84604051806080016040528060488152602001614f3160489139604051602001613702929190614f01565b6040516020818303038152906040526040518263ffffffff1660e01b815260040161372d9190613bc8565b602060405180830381865afa15801561366e573d6000803e3d6000fd5b50600092915050565b600061378161376061240f565b61377284670de0b6b3a76400006148a4565b61377c9190614c73565b6137f1565b92915050565b600061a4b182148061379b575062066eed82145b8061378157505062066eee1490565b6000600a8214806137bc57506101a482145b806137c9575062aa37dc82145b806137d5575061210582145b806137e2575062014a3382145b8061378157505062014a341490565b60006bffffffffffffffffffffffff82111561388f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f8401126138c457600080fd5b50813567ffffffffffffffff8111156138dc57600080fd5b6020830191508360208285010111156138f457600080fd5b9250929050565b6000806020838503121561390e57600080fd5b823567ffffffffffffffff81111561392557600080fd5b613931858286016138b2565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff811182821017156139905761399061393d565b60405290565b604051610160810167ffffffffffffffff811182821017156139905761399061393d565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613a0157613a0161393d565b604052919050565b63ffffffff8116811461267957600080fd5b803561117081613a09565b68ffffffffffffffffff8116811461267957600080fd5b803561117081613a26565b64ffffffffff8116811461267957600080fd5b803561117081613a48565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b60006101208284031215613ab757600080fd5b613abf61396c565b613ac883613a1b565b8152613ad660208401613a1b565b6020820152613ae760408401613a1b565b6040820152613af860608401613a1b565b6060820152613b0960808401613a3d565b6080820152613b1a60a08401613a5b565b60a0820152613b2b60c08401613a66565b60c0820152613b3c60e08401613a78565b60e0820152610100613b4f818501613a1b565b908201529392505050565b60005b83811015613b75578181015183820152602001613b5d565b50506000910152565b60008151808452613b96816020860160208601613b5a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006136926020830184613b7e565b600082601f830112613bec57600080fd5b813567ffffffffffffffff811115613c0657613c0661393d565b613c3760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016139ba565b818152846020838601011115613c4c57600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613c7b57600080fd5b813567ffffffffffffffff811115613c9257600080fd5b613c9e84828501613bdb565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461267957600080fd5b803561117081613ca6565b6bffffffffffffffffffffffff8116811461267957600080fd5b803561117081613cd3565b60008060408385031215613d0b57600080fd5b8235613d1681613ca6565b91506020830135613d2681613cd3565b809150509250929050565b600081518084526020808501945080840160005b83811015613d7757815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613d45565b509495945050505050565b6020815260006136926020830184613d31565b600060208284031215613da757600080fd5b5035919050565b600060208284031215613dc057600080fd5b813567ffffffffffffffff811115613dd757600080fd5b8201610160818503121561369257600080fd5b805182526020810151613e15602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613e3560408401826bffffffffffffffffffffffff169052565b506060810151613e5d606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613e79608084018267ffffffffffffffff169052565b5060a0810151613e9160a084018263ffffffff169052565b5060c0810151613eae60c084018268ffffffffffffffffff169052565b5060e0810151613ecb60e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b61016081016137818284613dea565b60008083601f840112613f2457600080fd5b50813567ffffffffffffffff811115613f3c57600080fd5b6020830191508360208260051b85010111156138f457600080fd5b60008060008060008060008060e0898b031215613f7357600080fd5b606089018a811115613f8457600080fd5b8998503567ffffffffffffffff80821115613f9e57600080fd5b613faa8c838d016138b2565b909950975060808b0135915080821115613fc357600080fd5b613fcf8c838d01613f12565b909750955060a08b0135915080821115613fe857600080fd5b50613ff58b828c01613f12565b999c989b50969995989497949560c00135949350505050565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151614062608084018268ffffffffffffffffff169052565b5060a083015161407b60a084018264ffffffffff169052565b5060c083015161409160c084018261ffff169052565b5060e08301516140c160e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461267957600080fd5b8035611170816140dd565b60008060008060006080868803121561411657600080fd5b8535614121816140dd565b9450602086013567ffffffffffffffff81111561413d57600080fd5b614149888289016138b2565b909550935050604086013561415d81613a09565b949793965091946060013592915050565b600067ffffffffffffffff8211156141885761418861393d565b5060051b60200190565b600082601f8301126141a357600080fd5b813560206141b86141b38361416e565b6139ba565b82815260059290921b840181019181810190868411156141d757600080fd5b8286015b848110156141fb5780356141ee81613ca6565b83529183019183016141db565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c0878903121561423057600080fd5b863567ffffffffffffffff8082111561424857600080fd5b6142548a838b01614192565b9750602089013591508082111561426a57600080fd5b6142768a838b01614192565b965061428460408a01614206565b9550606089013591508082111561429a57600080fd5b6142a68a838b01613bdb565b94506142b460808a016140f3565b935060a08901359150808211156142ca57600080fd5b506142d789828a01613bdb565b9150509295509295509295565b6000602082840312156142f657600080fd5b813561369281613ca6565b600181811c9082168061431557607f821691505b60208210810361434e577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c8101602086101561437b5750805b601f850160051c820191505b81811015610a8857828155600101614387565b67ffffffffffffffff8311156143b2576143b261393d565b6143c6836143c08354614301565b83614354565b6000601f84116001811461441857600085156143e25750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556144ae565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156144675786850135825560209485019460019092019101614447565b50868210156144a2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b805161117081613a26565b6000602082840312156144d257600080fd5b815161369281613a26565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115612661576126616144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203614591576145916144dd565b5060010190565b600061016082360312156145ab57600080fd5b6145b3613996565b823567ffffffffffffffff8111156145ca57600080fd5b6145d636828601613bdb565b825250602083013560208201526145ef60408401613cc8565b604082015261460060608401613ced565b606082015261461160808401613a3d565b608082015261462260a084016140f3565b60a082015261463360c084016140f3565b60c082015261464460e08401613a1b565b60e0820152610100614657818501613a66565b908201526101206146698482016140f3565b9082015261014061467b848201613cc8565b9082015292915050565b60006020828403121561469757600080fd5b8135613692816140dd565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126146d757600080fd5b83018035915067ffffffffffffffff8211156146f257600080fd5b6020019150368190038213156138f457600080fd5b60006020828403121561471957600080fd5b61369282613a66565b60006020828403121561473457600080fd5b813561369281613a09565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061313660e0830184613dea565b60ff8181168382160190811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061484257614842614800565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b8082028115828204841417613781576137816144dd565b81810381811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff818116838216019080821115612661576126616144dd565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261494a8184018a613d31565b9050828103608084015261495e8189613d31565b905060ff871660a084015282810360c084015261497b8187613b7e565b905067ffffffffffffffff851660e08401528281036101008401526149a08185613b7e565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a086880312156149e257600080fd5b6149eb866149b0565b9450602086015193506040860151925060608601519150614a0e608087016149b0565b90509295509295909350565b60006bffffffffffffffffffffffff80841680614a3957614a39614800565b92169190910492915050565b6bffffffffffffffffffffffff818116838216019080821115612661576126616144dd565b6bffffffffffffffffffffffff8181168382160280821691908281146140d5576140d56144dd565b80820180821115613781576137816144dd565b67ffffffffffffffff818116838216019080821115612661576126616144dd565b600082601f830112614ad757600080fd5b81356020614ae76141b38361416e565b82815260059290921b84018101918181019086841115614b0657600080fd5b8286015b848110156141fb5780358352918301918301614b0a565b600082601f830112614b3257600080fd5b81356020614b426141b38361416e565b82815260059290921b84018101918181019086841115614b6157600080fd5b8286015b848110156141fb57803567ffffffffffffffff811115614b855760008081fd5b614b938986838b0101613bdb565b845250918301918301614b65565b600080600080600060a08688031215614bb957600080fd5b853567ffffffffffffffff80821115614bd157600080fd5b614bdd89838a01614ac6565b96506020880135915080821115614bf357600080fd5b614bff89838a01614b21565b95506040880135915080821115614c1557600080fd5b614c2189838a01614b21565b94506060880135915080821115614c3757600080fd5b614c4389838a01614b21565b93506080880135915080821115614c5957600080fd5b50614c6688828901614b21565b9150509295509295909350565b600082614c8257614c82614800565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614cce8285018b613d31565b91508382036080850152614ce2828a613d31565b915060ff881660a085015283820360c0850152614cff8288613b7e565b90861660e085015283810361010085015290506149a08185613b7e565b805161117081613ca6565b805161117081613cd3565b8051611170816140dd565b805161117081613a09565b805161117081613a48565b60006101608284031215614d6657600080fd5b614d6e613996565b82518152614d7e60208401614d1c565b6020820152614d8f60408401614d27565b6040820152614da060608401614d1c565b6060820152614db160808401614d32565b6080820152614dc260a08401614d3d565b60a0820152614dd360c084016144b5565b60c0820152614de460e084016144b5565b60e0820152610100614df7818501614d48565b90820152610120614e09848201614d48565b90820152610140613b4f848201614d3d565b64ffffffffff818116838216019080821115612661576126616144dd565b6000610200808352614e4d8184018a613b7e565b90508281036020840152614e618189613b7e565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614eaa905060a0830184613dea565b979650505050505050565b60008060408385031215614ec857600080fd5b825160078110614ed757600080fd5b6020840151909250613d2681613cd3565b600060208284031215614efa57600080fd5b5051919050565b60008351614f13818460208801613b5a565b835190830190614f27818360208801613b5a565b0194935050505056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", + Bin: "0x60c06040523480156200001157600080fd5b506040516200560838038062005608833981016040819052620000349162000474565b8282828260013380600081620000915760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c457620000c48162000140565b50505015156080526001600160a01b038116620000f457604051632530e88560e11b815260040160405180910390fd5b6001600160a01b0390811660a052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200013482620001eb565b50505050505062000633565b336001600160a01b038216036200019a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000088565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001f562000349565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033e9083906200057d565b60405180910390a150565b6200035362000355565b565b6000546001600160a01b03163314620003535760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000088565b80516001600160a01b0381168114620003c957600080fd5b919050565b60405161012081016001600160401b03811182821017156200040057634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c957600080fd5b80516001600160481b0381168114620003c957600080fd5b805164ffffffffff81168114620003c957600080fd5b805161ffff81168114620003c957600080fd5b80516001600160e01b0381168114620003c957600080fd5b60008060008385036101608112156200048c57600080fd5b6200049785620003b1565b935061012080601f1983011215620004ae57600080fd5b620004b8620003ce565b9150620004c86020870162000406565b8252620004d86040870162000406565b6020830152620004eb6060870162000406565b6040830152620004fe6080870162000406565b60608301526200051160a087016200041b565b60808301526200052460c0870162000433565b60a08301526200053760e0870162000449565b60c08301526101006200054c8188016200045c565b60e08401526200055e82880162000406565b90830152509150620005746101408501620003b1565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005d060808401826001600160481b03169052565b5060a0830151620005ea60a084018264ffffffffff169052565b5060c08301516200060160c084018261ffff169052565b5060e08301516200061d60e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b60805160a051614f856200068360003960008181610845015281816109d301528181610ca601528181610f3a015281816110450152818161183001526133b70152600061126e0152614f856000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a03660046138fb565b61059c565b005b6101a56101b5366004613aa4565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b6040516102039190613bc8565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c366004613c69565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613cf8565b6108d7565b6101a5610a90565b6101a5610b92565b6101a56102933660046138fb565b610d92565b6102a0610de2565b6040516102039190613d82565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613d95565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613dae565b610fd4565b6040516102039190613f03565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab366004613f57565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b604051610203919061400e565b61053b6105363660046140fe565b61182c565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f661198c565b6101a561056e366004614217565b6119e3565b61057b61240f565b604051908152602001610203565b6101a56105973660046142e4565b612668565b6105a461267c565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec82848361439a565b505050565b6105f96126ff565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f51848629061083690839061400e565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d291906144c0565b905090565b6108df612707565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6126ff565b610ba2612707565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd2614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c31614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf5614531565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d8781614560565b9050610bb1565b5050565b610d9a61267c565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec82848361439a565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e6090614301565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea890614301565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed490614301565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a883614598565b6128b3565b90506110bf60608301604084016142e4565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a08801614685565b61111f610160880161014089016142e4565b61112988806146a2565b61113b6101208b016101008c01614707565b60208b01356111516101008d0160e08e01614722565b8b6040516111679998979695949392919061473f565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16040805160608101825260025480825260035460ff8082166020850152610100909104169282019290925290831461125c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f636f6e666967446967657374206d69736d6174636800000000000000000000006044820152606401610b0d565b61126a8b8b8b8b8b8b612d51565b60007f0000000000000000000000000000000000000000000000000000000000000000156112c7576002826020015183604001516112a891906147e7565b6112b2919061482f565b6112bd9060016147e7565b60ff1690506112dd565b60208201516112d79060016147e7565b60ff1690505b888114611346576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b8887146113af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f7369676e617475726573206f7574206f6620726567697374726174696f6e00006044820152606401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113f2576113f2614851565b600281111561140357611403614851565b905250905060028160200151600281111561142057611420614851565b14801561146757506006816000015160ff168154811061144257611442614531565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b6114cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b50505050506114da613893565b6000808a8a6040516114ed929190614880565b604051908190038120611504918e90602001614890565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b8981101561180e57600060018489846020811061156d5761156d614531565b61157a91901a601b6147e7565b8e8e8681811061158c5761158c614531565b905060200201358d8d878181106115a5576115a5614531565b90506020020135604051600081526020016040526040516115e2949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611604573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff8082168552929650929450840191610100900416600281111561168457611684614851565b600281111561169557611695614851565b90525092506001836020015160028111156116b2576116b2614851565b14611719576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061173357611733614531565b602002015173ffffffffffffffffffffffffffffffffffffffff16146117b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117cf576117cf614531565b73ffffffffffffffffffffffffffffffffffffffff90921660209290920201526117fa6001866147e7565b9450508061180790614560565b905061154e565b50505061181f833383858e8e612e08565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b1580156118cc57600080fd5b505afa1580156118e0573d6000803e3d6000fd5b5050505066038d7ea4c68000821115611925576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061192f610841565b9050600061197287878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b905061198085858385612fd7565b98975050505050505050565b6060600c805461199b90614301565b90506000036119d6576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea890614301565b855185518560ff16601f831115611a56576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611ac0576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611b4e576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611b598160036148a4565b8311611bc1576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611bc961267c565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611c109088613144565b60055415611dc557600554600090611c2a906001906148bb565b9050600060058281548110611c4157611c41614531565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611c7b57611c7b614531565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611cfb57611cfb6148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611d6457611d646148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611c10915050565b60005b81515181101561222c5760006004600084600001518481518110611dee57611dee614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611e3857611e38614851565b14611e9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611ed057611ed0614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001617610100836002811115611f7157611f71614851565b021790555060009150611f819050565b6004600084602001518481518110611f9b57611f9b614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611fe557611fe5614851565b1461204c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff82168152602081016002815250600460008460200151848151811061207f5761207f614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561212057612120614851565b02179055505082518051600592508390811061213e5761213e614531565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106121ba576121ba614531565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9092169190911790558061222481614560565b915050611dc8565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff43811682029290921780855592048116929182916014916122e4918491740100000000000000000000000000000000000000009004166148fd565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123434630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a0015161315d565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986123fa988b9891977401000000000000000000000000000000000000000090920463ffffffff1696909591949193919261491a565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa15801561259d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c191906149ca565b5093505092505080426125d491906148bb565b836020015163ffffffff161080156125f657506000836020015163ffffffff16115b1561262457505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b60008213612661576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b61267061267c565b61267981613208565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146126fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6126fd61267c565b600b546bffffffffffffffffffffffff1660000361272157565b600061272b610de2565b8051909150600081900361276b576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600b5460009061278a9083906bffffffffffffffffffffffff16614a1a565b905060005b828110156128555781600a60008684815181106127ae576127ae614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128169190614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508061284e90614560565b905061278f565b506128608282614a6a565b600b80546000906128809084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612a6e576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612aab8560e001513a848860800151612fd7565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612b07576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612b209190614a92565b905060003087604001518860a001518960c001516001612b409190614aa5565b8a5180516020918201206101008d015160e08e0151604051612bf498979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612d039190613f03565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612d5e8260206148a4565b612d698560206148a4565b612d7588610144614a92565b612d7f9190614a92565b612d899190614a92565b612d94906000614a92565b9050368114612dff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b606080808080612e1a86880188614ba1565b84519499509297509095509350915060ff16801580612e3a575084518114155b80612e46575083518114155b80612e52575082518114155b80612e5e575081518114155b15612e95576040517f0be3632800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b81811015612fc8576000612f2d888381518110612eb757612eb7614531565b6020026020010151888481518110612ed157612ed1614531565b6020026020010151888581518110612eeb57612eeb614531565b6020026020010151888681518110612f0557612f05614531565b6020026020010151888781518110612f1f57612f1f614531565b6020026020010151886132fd565b90506000816006811115612f4357612f43614851565b1480612f6057506001816006811115612f5e57612f5e614851565b145b15612fb757878281518110612f7757612f77614531565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b50612fc181614560565b9050612e98565b50505050505050505050505050565b600854600090790100000000000000000000000000000000000000000000000000900464ffffffffff1684101561303257600854790100000000000000000000000000000000000000000000000000900464ffffffffff1693505b6008546000906127109061304c9063ffffffff16876148a4565b6130569190614c73565b6130609086614a92565b60085490915060009087906130999063ffffffff6c010000000000000000000000008204811691680100000000000000009004166148fd565b6130a391906148fd565b63ffffffff16905060006130ed6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b9050600061310e826130ff85876148a4565b6131099190614a92565b613753565b9050600061312a68ffffffffffffffffff808916908a16614a45565b90506131368183614a45565b9a9950505050505050505050565b600061314e610de2565b511115610d8e57610d8e612707565b6000808a8a8a8a8a8a8a8a8a60405160200161318199989796959493929190614c87565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080848060200190518101906133149190614d53565b905060003a82610120015183610100015161332f9190614e1b565b64ffffffffff1661334091906148a4565b905060008460ff166133886000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b6133929190614c73565b905060006133a36131098385614a92565b905060006133b03a613753565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298e8e868b60e0015168ffffffffffffffffff168961340f9190614a45565b338d6040518763ffffffff1660e01b815260040161343296959493929190614e39565b60408051808303816000875af1158015613450573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134749190614eb5565b9092509050600082600681111561348d5761348d614851565b14806134aa575060018260068111156134a8576134a8614851565b145b156136005760008e8152600760205260408120556134c88185614a45565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0890151600b805468ffffffffffffffffff9092169390929161353491859116614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508d7f90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e0468487848b60c0015168ffffffffffffffffff168c60e0015168ffffffffffffffffff16878b6135b39190614a45565b6135bd9190614a45565b6135c79190614a45565b604080516bffffffffffffffffffffffff9586168152602081019490945291841683830152909216606082015290519081900360800190a25b509c9b505050505050505050505050565b60004661361d81613787565b1561369957606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561366e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136929190614ee8565b9392505050565b6136a2816137aa565b1561374a5773420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e84604051806080016040528060488152602001614f3160489139604051602001613702929190614f01565b6040516020818303038152906040526040518263ffffffff1660e01b815260040161372d9190613bc8565b602060405180830381865afa15801561366e573d6000803e3d6000fd5b50600092915050565b600061378161376061240f565b61377284670de0b6b3a76400006148a4565b61377c9190614c73565b6137f1565b92915050565b600061a4b182148061379b575062066eed82145b8061378157505062066eee1490565b6000600a8214806137bc57506101a482145b806137c9575062aa37dc82145b806137d5575061210582145b806137e2575062014a3382145b8061378157505062014a341490565b60006bffffffffffffffffffffffff82111561388f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f8401126138c457600080fd5b50813567ffffffffffffffff8111156138dc57600080fd5b6020830191508360208285010111156138f457600080fd5b9250929050565b6000806020838503121561390e57600080fd5b823567ffffffffffffffff81111561392557600080fd5b613931858286016138b2565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff811182821017156139905761399061393d565b60405290565b604051610160810167ffffffffffffffff811182821017156139905761399061393d565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613a0157613a0161393d565b604052919050565b63ffffffff8116811461267957600080fd5b803561117081613a09565b68ffffffffffffffffff8116811461267957600080fd5b803561117081613a26565b64ffffffffff8116811461267957600080fd5b803561117081613a48565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b60006101208284031215613ab757600080fd5b613abf61396c565b613ac883613a1b565b8152613ad660208401613a1b565b6020820152613ae760408401613a1b565b6040820152613af860608401613a1b565b6060820152613b0960808401613a3d565b6080820152613b1a60a08401613a5b565b60a0820152613b2b60c08401613a66565b60c0820152613b3c60e08401613a78565b60e0820152610100613b4f818501613a1b565b908201529392505050565b60005b83811015613b75578181015183820152602001613b5d565b50506000910152565b60008151808452613b96816020860160208601613b5a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006136926020830184613b7e565b600082601f830112613bec57600080fd5b813567ffffffffffffffff811115613c0657613c0661393d565b613c3760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016139ba565b818152846020838601011115613c4c57600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613c7b57600080fd5b813567ffffffffffffffff811115613c9257600080fd5b613c9e84828501613bdb565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461267957600080fd5b803561117081613ca6565b6bffffffffffffffffffffffff8116811461267957600080fd5b803561117081613cd3565b60008060408385031215613d0b57600080fd5b8235613d1681613ca6565b91506020830135613d2681613cd3565b809150509250929050565b600081518084526020808501945080840160005b83811015613d7757815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613d45565b509495945050505050565b6020815260006136926020830184613d31565b600060208284031215613da757600080fd5b5035919050565b600060208284031215613dc057600080fd5b813567ffffffffffffffff811115613dd757600080fd5b8201610160818503121561369257600080fd5b805182526020810151613e15602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613e3560408401826bffffffffffffffffffffffff169052565b506060810151613e5d606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613e79608084018267ffffffffffffffff169052565b5060a0810151613e9160a084018263ffffffff169052565b5060c0810151613eae60c084018268ffffffffffffffffff169052565b5060e0810151613ecb60e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b61016081016137818284613dea565b60008083601f840112613f2457600080fd5b50813567ffffffffffffffff811115613f3c57600080fd5b6020830191508360208260051b85010111156138f457600080fd5b60008060008060008060008060e0898b031215613f7357600080fd5b606089018a811115613f8457600080fd5b8998503567ffffffffffffffff80821115613f9e57600080fd5b613faa8c838d016138b2565b909950975060808b0135915080821115613fc357600080fd5b613fcf8c838d01613f12565b909750955060a08b0135915080821115613fe857600080fd5b50613ff58b828c01613f12565b999c989b50969995989497949560c00135949350505050565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151614062608084018268ffffffffffffffffff169052565b5060a083015161407b60a084018264ffffffffff169052565b5060c083015161409160c084018261ffff169052565b5060e08301516140c160e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461267957600080fd5b8035611170816140dd565b60008060008060006080868803121561411657600080fd5b8535614121816140dd565b9450602086013567ffffffffffffffff81111561413d57600080fd5b614149888289016138b2565b909550935050604086013561415d81613a09565b949793965091946060013592915050565b600067ffffffffffffffff8211156141885761418861393d565b5060051b60200190565b600082601f8301126141a357600080fd5b813560206141b86141b38361416e565b6139ba565b82815260059290921b840181019181810190868411156141d757600080fd5b8286015b848110156141fb5780356141ee81613ca6565b83529183019183016141db565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c0878903121561423057600080fd5b863567ffffffffffffffff8082111561424857600080fd5b6142548a838b01614192565b9750602089013591508082111561426a57600080fd5b6142768a838b01614192565b965061428460408a01614206565b9550606089013591508082111561429a57600080fd5b6142a68a838b01613bdb565b94506142b460808a016140f3565b935060a08901359150808211156142ca57600080fd5b506142d789828a01613bdb565b9150509295509295509295565b6000602082840312156142f657600080fd5b813561369281613ca6565b600181811c9082168061431557607f821691505b60208210810361434e577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c8101602086101561437b5750805b601f850160051c820191505b81811015610a8857828155600101614387565b67ffffffffffffffff8311156143b2576143b261393d565b6143c6836143c08354614301565b83614354565b6000601f84116001811461441857600085156143e25750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556144ae565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156144675786850135825560209485019460019092019101614447565b50868210156144a2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b805161117081613a26565b6000602082840312156144d257600080fd5b815161369281613a26565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115612661576126616144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203614591576145916144dd565b5060010190565b600061016082360312156145ab57600080fd5b6145b3613996565b823567ffffffffffffffff8111156145ca57600080fd5b6145d636828601613bdb565b825250602083013560208201526145ef60408401613cc8565b604082015261460060608401613ced565b606082015261461160808401613a3d565b608082015261462260a084016140f3565b60a082015261463360c084016140f3565b60c082015261464460e08401613a1b565b60e0820152610100614657818501613a66565b908201526101206146698482016140f3565b9082015261014061467b848201613cc8565b9082015292915050565b60006020828403121561469757600080fd5b8135613692816140dd565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126146d757600080fd5b83018035915067ffffffffffffffff8211156146f257600080fd5b6020019150368190038213156138f457600080fd5b60006020828403121561471957600080fd5b61369282613a66565b60006020828403121561473457600080fd5b813561369281613a09565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061313660e0830184613dea565b60ff8181168382160190811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061484257614842614800565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b8082028115828204841417613781576137816144dd565b81810381811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff818116838216019080821115612661576126616144dd565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261494a8184018a613d31565b9050828103608084015261495e8189613d31565b905060ff871660a084015282810360c084015261497b8187613b7e565b905067ffffffffffffffff851660e08401528281036101008401526149a08185613b7e565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a086880312156149e257600080fd5b6149eb866149b0565b9450602086015193506040860151925060608601519150614a0e608087016149b0565b90509295509295909350565b60006bffffffffffffffffffffffff80841680614a3957614a39614800565b92169190910492915050565b6bffffffffffffffffffffffff818116838216019080821115612661576126616144dd565b6bffffffffffffffffffffffff8181168382160280821691908281146140d5576140d56144dd565b80820180821115613781576137816144dd565b67ffffffffffffffff818116838216019080821115612661576126616144dd565b600082601f830112614ad757600080fd5b81356020614ae76141b38361416e565b82815260059290921b84018101918181019086841115614b0657600080fd5b8286015b848110156141fb5780358352918301918301614b0a565b600082601f830112614b3257600080fd5b81356020614b426141b38361416e565b82815260059290921b84018101918181019086841115614b6157600080fd5b8286015b848110156141fb57803567ffffffffffffffff811115614b855760008081fd5b614b938986838b0101613bdb565b845250918301918301614b65565b600080600080600060a08688031215614bb957600080fd5b853567ffffffffffffffff80821115614bd157600080fd5b614bdd89838a01614ac6565b96506020880135915080821115614bf357600080fd5b614bff89838a01614b21565b95506040880135915080821115614c1557600080fd5b614c2189838a01614b21565b94506060880135915080821115614c3757600080fd5b614c4389838a01614b21565b93506080880135915080821115614c5957600080fd5b50614c6688828901614b21565b9150509295509295909350565b600082614c8257614c82614800565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614cce8285018b613d31565b91508382036080850152614ce2828a613d31565b915060ff881660a085015283820360c0850152614cff8288613b7e565b90861660e085015283810361010085015290506149a08185613b7e565b805161117081613ca6565b805161117081613cd3565b8051611170816140dd565b805161117081613a09565b805161117081613a48565b60006101608284031215614d6657600080fd5b614d6e613996565b82518152614d7e60208401614d1c565b6020820152614d8f60408401614d27565b6040820152614da060608401614d1c565b6060820152614db160808401614d32565b6080820152614dc260a08401614d3d565b60a0820152614dd360c084016144b5565b60c0820152614de460e084016144b5565b60e0820152610100614df7818501614d48565b90820152610120614e09848201614d48565b90820152610140613b4f848201614d3d565b64ffffffffff818116838216019080821115612661576126616144dd565b6000610200808352614e4d8184018a613b7e565b90508281036020840152614e618189613b7e565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614eaa905060a0830184613dea565b979650505050505050565b60008060408385031215614ec857600080fd5b825160078110614ed757600080fd5b6020840151909250613d2681613cd3565b600060208284031215614efa57600080fd5b5051919050565b60008351614f13818460208801613b5a565b835190830190614f27818360208801613b5a565b0194935050505056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", } var FunctionsCoordinatorABI = FunctionsCoordinatorMetaData.ABI diff --git a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 41524c3a829..534e5543e86 100644 --- a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -4,7 +4,7 @@ functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServ functions_billing_registry_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77 functions_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.bin 2368f537a04489c720a46733f8596c4fc88a31062ecfa966d05f25dd98608aca functions_client_example: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.bin abf32e69f268f40e8530eb8d8e96bf310b798a4c0049a58022d9d2fb527b601b -functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 96416d5be2ae4625395567397da88f71b215005cf8ad71a1cdaa56e6b5e16908 +functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 38e168fa57c9626140e1e4d05f4124b4b69bd775e6e0f4481e017ad86c4d95a0 functions_load_test_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.bin c8dbbd5ebb34435800d6674700068837c3a252db60046a14b0e61e829db517de functions_oracle_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.bin 3ca70f966f8fe751987f0ccb50bebb6aa5be77e4a9f835d1ae99e0e9bfb7d52c functions_router: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.bin 9dedd3a36043605fd9bedf821e7ec5b4281a5c7ae2e4a1955f37aff8ba13519f From 96ae30c1eecd5251f1830ba6c40b83863fe0fa65 Mon Sep 17 00:00:00 2001 From: Justin Kaseman Date: Thu, 9 Nov 2023 21:24:25 -0500 Subject: [PATCH 2/6] Remove Functions OCR2Base config digest check & use custom errors (#11249) --- .../gas-snapshots/functions.gas-snapshot | 44 +++++++-------- .../dev/v1_X/FunctionsCoordinator.sol | 6 ++- .../v0.8/functions/dev/v1_X/ocr/OCR2Base.sol | 54 ++++++------------- .../functions_coordinator.go | 4 +- ...rapper-dependency-versions-do-not-edit.txt | 2 +- 5 files changed, 46 insertions(+), 64 deletions(-) diff --git a/contracts/gas-snapshots/functions.gas-snapshot b/contracts/gas-snapshots/functions.gas-snapshot index d15666f8857..521b3bffac1 100644 --- a/contracts/gas-snapshots/functions.gas-snapshot +++ b/contracts/gas-snapshots/functions.gas-snapshot @@ -1,12 +1,12 @@ -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14534206) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14534184) -ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14534200) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14545620) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14545597) -ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14545569) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14545520) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14545509) -ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14545553) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() (gas: 14600662) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() (gas: 14600640) +ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum:test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() (gas: 14600656) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() (gas: 14612076) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() (gas: 14612053) +ChainSpecificUtil__getCurrentTxL1GasFees_Base:test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() (gas: 14612025) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() (gas: 14611976) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() (gas: 14611965) +ChainSpecificUtil__getCurrentTxL1GasFees_Optimism:test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() (gas: 14612009) FunctionsBilling_Constructor:test_Constructor_Success() (gas: 14812) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_RevertIfNotRouter() (gas: 13282) FunctionsBilling_DeleteCommitment:test_DeleteCommitment_Success() (gas: 15897) @@ -29,8 +29,8 @@ FunctionsBilling__DisperseFeePool:test__DisperseFeePool_RevertIfNotSet() (gas: 8 FunctionsBilling__FulfillAndBill:test__FulfillAndBill_RevertIfInvalidCommitment() (gas: 13302) FunctionsBilling__FulfillAndBill:test__FulfillAndBill_Success() (gas: 180763) FunctionsClient_Constructor:test_Constructor_Success() (gas: 7573) -FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 504354) -FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 205558) +FunctionsClient_FulfillRequest:test_FulfillRequest_MaximumGas() (gas: 497786) +FunctionsClient_FulfillRequest:test_FulfillRequest_MinimumGas() (gas: 198990) FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_RevertIfNotRouter() (gas: 14623) FunctionsClient_HandleOracleFulfillment:test_HandleOracleFulfillment_Success() (gas: 22923) FunctionsClient__SendRequest:test__SendRequest_RevertIfInvalidCallbackGasLimit() (gas: 55059) @@ -51,17 +51,17 @@ FunctionsRequest_DEFAULT_BUFFER_SIZE:test_DEFAULT_BUFFER_SIZE() (gas: 246) FunctionsRequest_EncodeCBOR:test_EncodeCBOR_Success() (gas: 223) FunctionsRequest_REQUEST_DATA_VERSION:test_REQUEST_DATA_VERSION() (gas: 225) FunctionsRouter_Constructor:test_Constructor_Success() (gas: 12007) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 174027) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 164358) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedCostExceedsCommitment() (gas: 167459) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInsufficientGas() (gas: 157790) FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidCommitment() (gas: 38115) FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedInvalidRequestId() (gas: 35238) -FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 182503) +FunctionsRouter_Fulfill:test_Fulfill_RequestNotProcessedSubscriptionBalanceInvariant() (gas: 175935) FunctionsRouter_Fulfill:test_Fulfill_RevertIfNotCommittedCoordinator() (gas: 28086) -FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 158046) -FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 327605) -FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 341226) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2516507) -FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 546986) +FunctionsRouter_Fulfill:test_Fulfill_RevertIfPaused() (gas: 151478) +FunctionsRouter_Fulfill:test_Fulfill_SuccessClientNoLongerExists() (gas: 321037) +FunctionsRouter_Fulfill:test_Fulfill_SuccessFulfilled() (gas: 334658) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackReverts() (gas: 2509939) +FunctionsRouter_Fulfill:test_Fulfill_SuccessUserCallbackRunsOutOfGas() (gas: 540418) FunctionsRouter_GetAdminFee:test_GetAdminFee_Success() (gas: 17983) FunctionsRouter_GetAllowListId:test_GetAllowListId_Success() (gas: 12904) FunctionsRouter_GetConfig:test_GetConfig_Success() (gas: 37159) @@ -141,11 +141,11 @@ FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_Reve FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_RevertIfStartIsAfterEnd() (gas: 13459) FunctionsSubscriptions_GetSubscriptionsInRange:test_GetSubscriptionsInRange_Success() (gas: 59592) FunctionsSubscriptions_GetTotalBalance:test_GetTotalBalance_Success() (gas: 15010) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96) (runs: 256, μ: 43685, ~: 45548) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96) (runs: 256, μ: 46197, ~: 48060) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoCalldata(uint96) (runs: 256, μ: 43863, ~: 45548) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNoSubscription(uint96) (runs: 256, μ: 46375, ~: 48060) FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfCallerIsNotLink(uint96) (runs: 256, μ: 14295, ~: 14295) FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_RevertIfPaused(uint96) (runs: 256, μ: 51177, ~: 53040) -FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 85879, ~: 89604) +FunctionsSubscriptions_OnTokenTransfer:test_OnTokenTransfer_Success(uint96) (runs: 256, μ: 86057, ~: 89604) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfAmountMoreThanBalance() (gas: 20745) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfBalanceInvariant() (gas: 189) FunctionsSubscriptions_OracleWithdraw:test_OracleWithdraw_RevertIfNoAmount() (gas: 15638) diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol index 3ee4931e97c..15949a497e7 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol @@ -44,7 +44,7 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli address router, Config memory config, address linkToNativeFeed - ) OCR2Base(true) FunctionsBilling(router, config, linkToNativeFeed) {} + ) OCR2Base() FunctionsBilling(router, config, linkToNativeFeed) {} /// @inheritdoc IFunctionsCoordinator function getThresholdPublicKey() external view override returns (bytes memory) { @@ -151,7 +151,9 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli numberOfFulfillments != onchainMetadata.length || numberOfFulfillments != offchainMetadata.length ) { - revert ReportInvalid(); + revert ReportInvalid( + "All fields on the report must be of equal length: requestIds, results, errors, onchainMetadata, offchainMetadata" + ); } // Bounded by "MaxRequestBatchSize" on the Job's ReportingPluginConfig diff --git a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol index dd9ea84a519..375159bf4c9 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/ocr/OCR2Base.sol @@ -10,17 +10,10 @@ import {OCR2Abstract} from "./OCR2Abstract.sol"; * doc, which refers to this contract as simply the "contract". */ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { - error ReportInvalid(); + error ReportInvalid(string message); error InvalidConfig(string message); - bool internal immutable i_uniqueReports; - - constructor(bool uniqueReports) ConfirmedOwner(msg.sender) { - i_uniqueReports = uniqueReports; - } - - // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables - uint256 private constant maxUint32 = (1 << 32) - 1; + constructor() ConfirmedOwner(msg.sender) {} // incremented each time a new config is posted. This count is incorporated // into the config digest, to prevent replay attacks. @@ -144,12 +137,12 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { // Bounded by MAX_NUM_ORACLES in OCR2Abstract.sol for (uint256 i = 0; i < args.signers.length; i++) { + if (args.signers[i] == address(0)) revert InvalidConfig("signer must not be empty"); + if (args.transmitters[i] == address(0)) revert InvalidConfig("transmitter must not be empty"); // add new signer/transmitter addresses - // solhint-disable-next-line custom-errors - require(s_oracles[args.signers[i]].role == Role.Unset, "repeated signer address"); + if (s_oracles[args.signers[i]].role != Role.Unset) revert InvalidConfig("repeated signer address"); s_oracles[args.signers[i]] = Oracle(uint8(i), Role.Signer); - // solhint-disable-next-line custom-errors - require(s_oracles[args.transmitters[i]].role == Role.Unset, "repeated transmitter address"); + if (s_oracles[args.transmitters[i]].role != Role.Unset) revert InvalidConfig("repeated transmitter address"); s_oracles[args.transmitters[i]] = Oracle(uint8(i), Role.Transmitter); s_signers.push(args.signers[i]); s_transmitters.push(args.transmitters[i]); @@ -287,8 +280,7 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { ss.length * 32 + // 32 bytes per entry in _ss 0; // placeholder - // solhint-disable-next-line custom-errors - require(msg.data.length == expected, "calldata length mismatch"); + if (msg.data.length != expected) revert ReportInvalid("calldata length mismatch"); } /** @@ -319,30 +311,20 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { emit Transmitted(configDigest, uint32(epochAndRound >> 8)); - ConfigInfo memory configInfo = s_configInfo; - // solhint-disable-next-line custom-errors - require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); + // The following check is disabled to allow both current and proposed routes to submit reports using the same OCR config digest + // Chainlink Functions uses globally unique request IDs. Metadata about the request is stored and checked in the Coordinator and Router + // require(configInfo.latestConfigDigest == configDigest, "configDigest mismatch"); _requireExpectedMsgDataLength(report, rs, ss); - uint256 expectedNumSignatures; - if (i_uniqueReports) { - expectedNumSignatures = (configInfo.n + configInfo.f) / 2 + 1; - } else { - expectedNumSignatures = configInfo.f + 1; - } + uint256 expectedNumSignatures = (s_configInfo.n + s_configInfo.f) / 2 + 1; - // solhint-disable-next-line custom-errors - require(rs.length == expectedNumSignatures, "wrong number of signatures"); - // solhint-disable-next-line custom-errors - require(rs.length == ss.length, "signatures out of registration"); + if (rs.length != expectedNumSignatures) revert ReportInvalid("wrong number of signatures"); + if (rs.length != ss.length) revert ReportInvalid("report rs and ss must be of equal length"); Oracle memory transmitter = s_oracles[msg.sender]; - // solhint-disable-next-line custom-errors - require( // Check that sender is authorized to report - transmitter.role == Role.Transmitter && msg.sender == s_transmitters[transmitter.index], - "unauthorized transmitter" - ); + if (transmitter.role != Role.Transmitter && msg.sender != s_transmitters[transmitter.index]) + revert ReportInvalid("unauthorized transmitter"); } address[MAX_NUM_ORACLES] memory signed; @@ -357,10 +339,8 @@ abstract contract OCR2Base is ConfirmedOwner, OCR2Abstract { for (uint256 i = 0; i < rs.length; ++i) { address signer = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); o = s_oracles[signer]; - // solhint-disable-next-line custom-errors - require(o.role == Role.Signer, "address not authorized to sign"); - // solhint-disable-next-line custom-errors - require(signed[o.index] == address(0), "non-unique signature"); + if (o.role != Role.Signer) revert ReportInvalid("address not authorized to sign"); + if (signed[o.index] != address(0)) revert ReportInvalid("non-unique signature"); signed[o.index] = signer; signerCount += 1; } diff --git a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go index 917107524bf..3e3fac16d13 100644 --- a/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go +++ b/core/gethwrappers/functions/generated/functions_coordinator/functions_coordinator.go @@ -71,8 +71,8 @@ type FunctionsResponseRequestMeta struct { } var FunctionsCoordinatorMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"linkToNativeFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InconsistentReportData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoTransmittersSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouterOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RouterMustBeSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedPublicKeyChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsupportedRequestDataVersion\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CommitmentDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requestInitiator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"callbackGasLimit\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"}],\"name\":\"OracleResponse\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"juelsPerGas\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"l1FeeShareWei\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"callbackCostJuels\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"totalCostJuels\",\"type\":\"uint96\"}],\"name\":\"RequestBilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"deleteCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"gasPriceWei\",\"type\":\"uint256\"}],\"name\":\"estimateCost\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAdminFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getDONFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThresholdPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleWithdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"donPublicKey\",\"type\":\"bytes\"}],\"name\":\"setDONPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"thresholdPublicKey\",\"type\":\"bytes\"}],\"name\":\"setThresholdPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"availableBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"initiatedRequests\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint64\",\"name\":\"completedRequests\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"}],\"internalType\":\"structFunctionsResponse.RequestMeta\",\"name\":\"request\",\"type\":\"tuple\"}],\"name\":\"startRequest\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60c06040523480156200001157600080fd5b506040516200560838038062005608833981016040819052620000349162000474565b8282828260013380600081620000915760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c457620000c48162000140565b50505015156080526001600160a01b038116620000f457604051632530e88560e11b815260040160405180910390fd5b6001600160a01b0390811660a052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200013482620001eb565b50505050505062000633565b336001600160a01b038216036200019a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000088565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001f562000349565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033e9083906200057d565b60405180910390a150565b6200035362000355565b565b6000546001600160a01b03163314620003535760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000088565b80516001600160a01b0381168114620003c957600080fd5b919050565b60405161012081016001600160401b03811182821017156200040057634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c957600080fd5b80516001600160481b0381168114620003c957600080fd5b805164ffffffffff81168114620003c957600080fd5b805161ffff81168114620003c957600080fd5b80516001600160e01b0381168114620003c957600080fd5b60008060008385036101608112156200048c57600080fd5b6200049785620003b1565b935061012080601f1983011215620004ae57600080fd5b620004b8620003ce565b9150620004c86020870162000406565b8252620004d86040870162000406565b6020830152620004eb6060870162000406565b6040830152620004fe6080870162000406565b60608301526200051160a087016200041b565b60808301526200052460c0870162000433565b60a08301526200053760e0870162000449565b60c08301526101006200054c8188016200045c565b60e08401526200055e82880162000406565b90830152509150620005746101408501620003b1565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005d060808401826001600160481b03169052565b5060a0830151620005ea60a084018264ffffffffff169052565b5060c08301516200060160c084018261ffff169052565b5060e08301516200061d60e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b60805160a051614f856200068360003960008181610845015281816109d301528181610ca601528181610f3a015281816110450152818161183001526133b70152600061126e0152614f856000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a03660046138fb565b61059c565b005b6101a56101b5366004613aa4565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b6040516102039190613bc8565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c366004613c69565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613cf8565b6108d7565b6101a5610a90565b6101a5610b92565b6101a56102933660046138fb565b610d92565b6102a0610de2565b6040516102039190613d82565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613d95565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613dae565b610fd4565b6040516102039190613f03565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab366004613f57565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b604051610203919061400e565b61053b6105363660046140fe565b61182c565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f661198c565b6101a561056e366004614217565b6119e3565b61057b61240f565b604051908152602001610203565b6101a56105973660046142e4565b612668565b6105a461267c565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec82848361439a565b505050565b6105f96126ff565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f51848629061083690839061400e565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d291906144c0565b905090565b6108df612707565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6126ff565b610ba2612707565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd2614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c31614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf5614531565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d8781614560565b9050610bb1565b5050565b610d9a61267c565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec82848361439a565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e6090614301565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea890614301565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed490614301565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a883614598565b6128b3565b90506110bf60608301604084016142e4565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a08801614685565b61111f610160880161014089016142e4565b61112988806146a2565b61113b6101208b016101008c01614707565b60208b01356111516101008d0160e08e01614722565b8b6040516111679998979695949392919061473f565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16040805160608101825260025480825260035460ff8082166020850152610100909104169282019290925290831461125c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f636f6e666967446967657374206d69736d6174636800000000000000000000006044820152606401610b0d565b61126a8b8b8b8b8b8b612d51565b60007f0000000000000000000000000000000000000000000000000000000000000000156112c7576002826020015183604001516112a891906147e7565b6112b2919061482f565b6112bd9060016147e7565b60ff1690506112dd565b60208201516112d79060016147e7565b60ff1690505b888114611346576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b8887146113af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f7369676e617475726573206f7574206f6620726567697374726174696f6e00006044820152606401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113f2576113f2614851565b600281111561140357611403614851565b905250905060028160200151600281111561142057611420614851565b14801561146757506006816000015160ff168154811061144257611442614531565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1633145b6114cd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b50505050506114da613893565b6000808a8a6040516114ed929190614880565b604051908190038120611504918e90602001614890565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b8981101561180e57600060018489846020811061156d5761156d614531565b61157a91901a601b6147e7565b8e8e8681811061158c5761158c614531565b905060200201358d8d878181106115a5576115a5614531565b90506020020135604051600081526020016040526040516115e2949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015611604573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff8082168552929650929450840191610100900416600281111561168457611684614851565b600281111561169557611695614851565b90525092506001836020015160028111156116b2576116b2614851565b14611719576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061173357611733614531565b602002015173ffffffffffffffffffffffffffffffffffffffff16146117b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117cf576117cf614531565b73ffffffffffffffffffffffffffffffffffffffff90921660209290920201526117fa6001866147e7565b9450508061180790614560565b905061154e565b50505061181f833383858e8e612e08565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b1580156118cc57600080fd5b505afa1580156118e0573d6000803e3d6000fd5b5050505066038d7ea4c68000821115611925576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061192f610841565b9050600061197287878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b905061198085858385612fd7565b98975050505050505050565b6060600c805461199b90614301565b90506000036119d6576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea890614301565b855185518560ff16601f831115611a56576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611ac0576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611b4e576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611b598160036148a4565b8311611bc1576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611bc961267c565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611c109088613144565b60055415611dc557600554600090611c2a906001906148bb565b9050600060058281548110611c4157611c41614531565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611c7b57611c7b614531565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611cfb57611cfb6148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611d6457611d646148ce565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611c10915050565b60005b81515181101561222c5760006004600084600001518481518110611dee57611dee614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611e3857611e38614851565b14611e9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611ed057611ed0614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00001617610100836002811115611f7157611f71614851565b021790555060009150611f819050565b6004600084602001518481518110611f9b57611f9b614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611fe557611fe5614851565b1461204c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff82168152602081016002815250600460008460200151848151811061207f5761207f614531565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561212057612120614851565b02179055505082518051600592508390811061213e5761213e614531565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106121ba576121ba614531565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9092169190911790558061222481614560565b915050611dc8565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff43811682029290921780855592048116929182916014916122e4918491740100000000000000000000000000000000000000009004166148fd565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123434630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a0015161315d565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986123fa988b9891977401000000000000000000000000000000000000000090920463ffffffff1696909591949193919261491a565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa15801561259d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125c191906149ca565b5093505092505080426125d491906148bb565b836020015163ffffffff161080156125f657506000836020015163ffffffff16115b1561262457505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b60008213612661576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b61267061267c565b61267981613208565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146126fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6126fd61267c565b600b546bffffffffffffffffffffffff1660000361272157565b600061272b610de2565b8051909150600081900361276b576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600b5460009061278a9083906bffffffffffffffffffffffff16614a1a565b905060005b828110156128555781600a60008684815181106127ae576127ae614531565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128169190614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508061284e90614560565b905061278f565b506128608282614a6a565b600b80546000906128809084906bffffffffffffffffffffffff1661450c565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612a6e576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612aab8560e001513a848860800151612fd7565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612b07576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612b209190614a92565b905060003087604001518860a001518960c001516001612b409190614aa5565b8a5180516020918201206101008d015160e08e0151604051612bf498979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612d039190613f03565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612d5e8260206148a4565b612d698560206148a4565b612d7588610144614a92565b612d7f9190614a92565b612d899190614a92565b612d94906000614a92565b9050368114612dff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b606080808080612e1a86880188614ba1565b84519499509297509095509350915060ff16801580612e3a575084518114155b80612e46575083518114155b80612e52575082518114155b80612e5e575081518114155b15612e95576040517f0be3632800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b81811015612fc8576000612f2d888381518110612eb757612eb7614531565b6020026020010151888481518110612ed157612ed1614531565b6020026020010151888581518110612eeb57612eeb614531565b6020026020010151888681518110612f0557612f05614531565b6020026020010151888781518110612f1f57612f1f614531565b6020026020010151886132fd565b90506000816006811115612f4357612f43614851565b1480612f6057506001816006811115612f5e57612f5e614851565b145b15612fb757878281518110612f7757612f77614531565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b50612fc181614560565b9050612e98565b50505050505050505050505050565b600854600090790100000000000000000000000000000000000000000000000000900464ffffffffff1684101561303257600854790100000000000000000000000000000000000000000000000000900464ffffffffff1693505b6008546000906127109061304c9063ffffffff16876148a4565b6130569190614c73565b6130609086614a92565b60085490915060009087906130999063ffffffff6c010000000000000000000000008204811691680100000000000000009004166148fd565b6130a391906148fd565b63ffffffff16905060006130ed6000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b9050600061310e826130ff85876148a4565b6131099190614a92565b613753565b9050600061312a68ffffffffffffffffff808916908a16614a45565b90506131368183614a45565b9a9950505050505050505050565b600061314e610de2565b511115610d8e57610d8e612707565b6000808a8a8a8a8a8a8a8a8a60405160200161318199989796959493929190614c87565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff821603613287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080848060200190518101906133149190614d53565b905060003a82610120015183610100015161332f9190614e1b565b64ffffffffff1661334091906148a4565b905060008460ff166133886000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061361192505050565b6133929190614c73565b905060006133a36131098385614a92565b905060006133b03a613753565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298e8e868b60e0015168ffffffffffffffffff168961340f9190614a45565b338d6040518763ffffffff1660e01b815260040161343296959493929190614e39565b60408051808303816000875af1158015613450573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906134749190614eb5565b9092509050600082600681111561348d5761348d614851565b14806134aa575060018260068111156134a8576134a8614851565b145b156136005760008e8152600760205260408120556134c88185614a45565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0890151600b805468ffffffffffffffffff9092169390929161353491859116614a45565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508d7f90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e0468487848b60c0015168ffffffffffffffffff168c60e0015168ffffffffffffffffff16878b6135b39190614a45565b6135bd9190614a45565b6135c79190614a45565b604080516bffffffffffffffffffffffff9586168152602081019490945291841683830152909216606082015290519081900360800190a25b509c9b505050505050505050505050565b60004661361d81613787565b1561369957606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561366e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906136929190614ee8565b9392505050565b6136a2816137aa565b1561374a5773420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e84604051806080016040528060488152602001614f3160489139604051602001613702929190614f01565b6040516020818303038152906040526040518263ffffffff1660e01b815260040161372d9190613bc8565b602060405180830381865afa15801561366e573d6000803e3d6000fd5b50600092915050565b600061378161376061240f565b61377284670de0b6b3a76400006148a4565b61377c9190614c73565b6137f1565b92915050565b600061a4b182148061379b575062066eed82145b8061378157505062066eee1490565b6000600a8214806137bc57506101a482145b806137c9575062aa37dc82145b806137d5575061210582145b806137e2575062014a3382145b8061378157505062014a341490565b60006bffffffffffffffffffffffff82111561388f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f8401126138c457600080fd5b50813567ffffffffffffffff8111156138dc57600080fd5b6020830191508360208285010111156138f457600080fd5b9250929050565b6000806020838503121561390e57600080fd5b823567ffffffffffffffff81111561392557600080fd5b613931858286016138b2565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff811182821017156139905761399061393d565b60405290565b604051610160810167ffffffffffffffff811182821017156139905761399061393d565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613a0157613a0161393d565b604052919050565b63ffffffff8116811461267957600080fd5b803561117081613a09565b68ffffffffffffffffff8116811461267957600080fd5b803561117081613a26565b64ffffffffff8116811461267957600080fd5b803561117081613a48565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b60006101208284031215613ab757600080fd5b613abf61396c565b613ac883613a1b565b8152613ad660208401613a1b565b6020820152613ae760408401613a1b565b6040820152613af860608401613a1b565b6060820152613b0960808401613a3d565b6080820152613b1a60a08401613a5b565b60a0820152613b2b60c08401613a66565b60c0820152613b3c60e08401613a78565b60e0820152610100613b4f818501613a1b565b908201529392505050565b60005b83811015613b75578181015183820152602001613b5d565b50506000910152565b60008151808452613b96816020860160208601613b5a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006136926020830184613b7e565b600082601f830112613bec57600080fd5b813567ffffffffffffffff811115613c0657613c0661393d565b613c3760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016139ba565b818152846020838601011115613c4c57600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613c7b57600080fd5b813567ffffffffffffffff811115613c9257600080fd5b613c9e84828501613bdb565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461267957600080fd5b803561117081613ca6565b6bffffffffffffffffffffffff8116811461267957600080fd5b803561117081613cd3565b60008060408385031215613d0b57600080fd5b8235613d1681613ca6565b91506020830135613d2681613cd3565b809150509250929050565b600081518084526020808501945080840160005b83811015613d7757815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613d45565b509495945050505050565b6020815260006136926020830184613d31565b600060208284031215613da757600080fd5b5035919050565b600060208284031215613dc057600080fd5b813567ffffffffffffffff811115613dd757600080fd5b8201610160818503121561369257600080fd5b805182526020810151613e15602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613e3560408401826bffffffffffffffffffffffff169052565b506060810151613e5d606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613e79608084018267ffffffffffffffff169052565b5060a0810151613e9160a084018263ffffffff169052565b5060c0810151613eae60c084018268ffffffffffffffffff169052565b5060e0810151613ecb60e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b61016081016137818284613dea565b60008083601f840112613f2457600080fd5b50813567ffffffffffffffff811115613f3c57600080fd5b6020830191508360208260051b85010111156138f457600080fd5b60008060008060008060008060e0898b031215613f7357600080fd5b606089018a811115613f8457600080fd5b8998503567ffffffffffffffff80821115613f9e57600080fd5b613faa8c838d016138b2565b909950975060808b0135915080821115613fc357600080fd5b613fcf8c838d01613f12565b909750955060a08b0135915080821115613fe857600080fd5b50613ff58b828c01613f12565b999c989b50969995989497949560c00135949350505050565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151614062608084018268ffffffffffffffffff169052565b5060a083015161407b60a084018264ffffffffff169052565b5060c083015161409160c084018261ffff169052565b5060e08301516140c160e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461267957600080fd5b8035611170816140dd565b60008060008060006080868803121561411657600080fd5b8535614121816140dd565b9450602086013567ffffffffffffffff81111561413d57600080fd5b614149888289016138b2565b909550935050604086013561415d81613a09565b949793965091946060013592915050565b600067ffffffffffffffff8211156141885761418861393d565b5060051b60200190565b600082601f8301126141a357600080fd5b813560206141b86141b38361416e565b6139ba565b82815260059290921b840181019181810190868411156141d757600080fd5b8286015b848110156141fb5780356141ee81613ca6565b83529183019183016141db565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c0878903121561423057600080fd5b863567ffffffffffffffff8082111561424857600080fd5b6142548a838b01614192565b9750602089013591508082111561426a57600080fd5b6142768a838b01614192565b965061428460408a01614206565b9550606089013591508082111561429a57600080fd5b6142a68a838b01613bdb565b94506142b460808a016140f3565b935060a08901359150808211156142ca57600080fd5b506142d789828a01613bdb565b9150509295509295509295565b6000602082840312156142f657600080fd5b813561369281613ca6565b600181811c9082168061431557607f821691505b60208210810361434e577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c8101602086101561437b5750805b601f850160051c820191505b81811015610a8857828155600101614387565b67ffffffffffffffff8311156143b2576143b261393d565b6143c6836143c08354614301565b83614354565b6000601f84116001811461441857600085156143e25750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556144ae565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156144675786850135825560209485019460019092019101614447565b50868210156144a2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b805161117081613a26565b6000602082840312156144d257600080fd5b815161369281613a26565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115612661576126616144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203614591576145916144dd565b5060010190565b600061016082360312156145ab57600080fd5b6145b3613996565b823567ffffffffffffffff8111156145ca57600080fd5b6145d636828601613bdb565b825250602083013560208201526145ef60408401613cc8565b604082015261460060608401613ced565b606082015261461160808401613a3d565b608082015261462260a084016140f3565b60a082015261463360c084016140f3565b60c082015261464460e08401613a1b565b60e0820152610100614657818501613a66565b908201526101206146698482016140f3565b9082015261014061467b848201613cc8565b9082015292915050565b60006020828403121561469757600080fd5b8135613692816140dd565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126146d757600080fd5b83018035915067ffffffffffffffff8211156146f257600080fd5b6020019150368190038213156138f457600080fd5b60006020828403121561471957600080fd5b61369282613a66565b60006020828403121561473457600080fd5b813561369281613a09565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061313660e0830184613dea565b60ff8181168382160190811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061484257614842614800565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b8082028115828204841417613781576137816144dd565b81810381811115613781576137816144dd565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff818116838216019080821115612661576126616144dd565b600061012063ffffffff808d1684528b6020850152808b1660408501525080606084015261494a8184018a613d31565b9050828103608084015261495e8189613d31565b905060ff871660a084015282810360c084015261497b8187613b7e565b905067ffffffffffffffff851660e08401528281036101008401526149a08185613b7e565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a086880312156149e257600080fd5b6149eb866149b0565b9450602086015193506040860151925060608601519150614a0e608087016149b0565b90509295509295909350565b60006bffffffffffffffffffffffff80841680614a3957614a39614800565b92169190910492915050565b6bffffffffffffffffffffffff818116838216019080821115612661576126616144dd565b6bffffffffffffffffffffffff8181168382160280821691908281146140d5576140d56144dd565b80820180821115613781576137816144dd565b67ffffffffffffffff818116838216019080821115612661576126616144dd565b600082601f830112614ad757600080fd5b81356020614ae76141b38361416e565b82815260059290921b84018101918181019086841115614b0657600080fd5b8286015b848110156141fb5780358352918301918301614b0a565b600082601f830112614b3257600080fd5b81356020614b426141b38361416e565b82815260059290921b84018101918181019086841115614b6157600080fd5b8286015b848110156141fb57803567ffffffffffffffff811115614b855760008081fd5b614b938986838b0101613bdb565b845250918301918301614b65565b600080600080600060a08688031215614bb957600080fd5b853567ffffffffffffffff80821115614bd157600080fd5b614bdd89838a01614ac6565b96506020880135915080821115614bf357600080fd5b614bff89838a01614b21565b95506040880135915080821115614c1557600080fd5b614c2189838a01614b21565b94506060880135915080821115614c3757600080fd5b614c4389838a01614b21565b93506080880135915080821115614c5957600080fd5b50614c6688828901614b21565b9150509295509295909350565b600082614c8257614c82614800565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614cce8285018b613d31565b91508382036080850152614ce2828a613d31565b915060ff881660a085015283820360c0850152614cff8288613b7e565b90861660e085015283810361010085015290506149a08185613b7e565b805161117081613ca6565b805161117081613cd3565b8051611170816140dd565b805161117081613a09565b805161117081613a48565b60006101608284031215614d6657600080fd5b614d6e613996565b82518152614d7e60208401614d1c565b6020820152614d8f60408401614d27565b6040820152614da060608401614d1c565b6060820152614db160808401614d32565b6080820152614dc260a08401614d3d565b60a0820152614dd360c084016144b5565b60c0820152614de460e084016144b5565b60e0820152610100614df7818501614d48565b90820152610120614e09848201614d48565b90820152610140613b4f848201614d3d565b64ffffffffff818116838216019080821115612661576126616144dd565b6000610200808352614e4d8184018a613b7e565b90508281036020840152614e618189613b7e565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614eaa905060a0830184613dea565b979650505050505050565b60008060408385031215614ec857600080fd5b825160078110614ed757600080fd5b6020840151909250613d2681613cd3565b600060208284031215614efa57600080fd5b5051919050565b60008351614f13818460208801613b5a565b835190830190614f27818360208801613b5a565b0194935050505056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"router\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"linkToNativeFeed\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"EmptyPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InconsistentReportData\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidCalldata\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"InvalidConfig\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"linkWei\",\"type\":\"int256\"}],\"name\":\"InvalidLinkWeiPrice\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSubscription\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"MustBeSubOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoTransmittersSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouter\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByRouterOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PaymentTooLarge\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"ReportInvalid\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RouterMustBeSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedPublicKeyChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnauthorizedSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnsupportedRequestDataVersion\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"CommitmentDeleted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"previousConfigBlockNumber\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"configCount\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"signers\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"transmitters\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"f\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"onchainConfig\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"offchainConfigVersion\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"offchainConfig\",\"type\":\"bytes\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"requestInitiator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"callbackGasLimit\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"indexed\":false,\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"name\":\"OracleRequest\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"transmitter\",\"type\":\"address\"}],\"name\":\"OracleResponse\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"juelsPerGas\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"l1FeeShareWei\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"callbackCostJuels\",\"type\":\"uint96\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"totalCostJuels\",\"type\":\"uint96\"}],\"name\":\"RequestBilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"name\":\"Transmitted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"}],\"name\":\"deleteCommitment\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"gasPriceWei\",\"type\":\"uint256\"}],\"name\":\"estimateCost\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAdminFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"getDONFee\",\"outputs\":[{\"internalType\":\"uint72\",\"name\":\"\",\"type\":\"uint72\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getDONPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThresholdPublicKey\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getWeiPerUnitLink\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDetails\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"configCount\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blockNumber\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestConfigDigestAndEpoch\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"scanLogs\",\"type\":\"bool\"},{\"internalType\":\"bytes32\",\"name\":\"configDigest\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"epoch\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"amount\",\"type\":\"uint96\"}],\"name\":\"oracleWithdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"oracleWithdrawAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_signers\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_transmitters\",\"type\":\"address[]\"},{\"internalType\":\"uint8\",\"name\":\"_f\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"_onchainConfig\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"_offchainConfigVersion\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"_offchainConfig\",\"type\":\"bytes\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"donPublicKey\",\"type\":\"bytes\"}],\"name\":\"setDONPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"thresholdPublicKey\",\"type\":\"bytes\"}],\"name\":\"setThresholdPublicKey\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"flags\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"requestingContract\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"availableBalance\",\"type\":\"uint96\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"initiatedRequests\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint16\",\"name\":\"dataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint64\",\"name\":\"completedRequests\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"subscriptionOwner\",\"type\":\"address\"}],\"internalType\":\"structFunctionsResponse.RequestMeta\",\"name\":\"request\",\"type\":\"tuple\"}],\"name\":\"startRequest\",\"outputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"requestId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"coordinator\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"estimatedTotalCostJuels\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"client\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"subscriptionId\",\"type\":\"uint64\"},{\"internalType\":\"uint32\",\"name\":\"callbackGasLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"adminFee\",\"type\":\"uint72\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint40\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint40\"},{\"internalType\":\"uint32\",\"name\":\"timeoutTimestamp\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsResponse.Commitment\",\"name\":\"commitment\",\"type\":\"tuple\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[3]\",\"name\":\"reportContext\",\"type\":\"bytes32[3]\"},{\"internalType\":\"bytes\",\"name\":\"report\",\"type\":\"bytes\"},{\"internalType\":\"bytes32[]\",\"name\":\"rs\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"ss\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32\",\"name\":\"rawVs\",\"type\":\"bytes32\"}],\"name\":\"transmit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transmitters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"fulfillmentGasPriceOverEstimationBP\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"feedStalenessSeconds\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadBeforeCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"gasOverheadAfterCallback\",\"type\":\"uint32\"},{\"internalType\":\"uint72\",\"name\":\"donFee\",\"type\":\"uint72\"},{\"internalType\":\"uint40\",\"name\":\"minimumEstimateGasPriceWei\",\"type\":\"uint40\"},{\"internalType\":\"uint16\",\"name\":\"maxSupportedRequestDataVersion\",\"type\":\"uint16\"},{\"internalType\":\"uint224\",\"name\":\"fallbackNativePerUnitLink\",\"type\":\"uint224\"},{\"internalType\":\"uint32\",\"name\":\"requestTimeoutSeconds\",\"type\":\"uint32\"}],\"internalType\":\"structFunctionsBilling.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"updateConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b50604051620057423803806200574283398101604081905262000034916200046d565b8282828233806000816200008f5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b0384811691909117909155811615620000c257620000c28162000139565b5050506001600160a01b038116620000ed57604051632530e88560e11b815260040160405180910390fd5b6001600160a01b03908116608052600b80549183166c01000000000000000000000000026001600160601b039092169190911790556200012d82620001e4565b5050505050506200062c565b336001600160a01b03821603620001935760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c66000000000000000000604482015260640162000086565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b620001ee62000342565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff16600160f01b026001600160f01b0364ffffffffff909216600160c81b0264ffffffffff60c81b196001600160481b03909416600160801b0293909316600160801b600160f01b031963ffffffff9586166c010000000000000000000000000263ffffffff60601b19978716680100000000000000000297909716600160401b600160801b0319998716640100000000026001600160401b0319909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e0830151610100840151909216600160e01b026001600160e01b0390921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f5184862906200033790839062000576565b60405180910390a150565b6200034c6200034e565b565b6000546001600160a01b031633146200034c5760405162461bcd60e51b815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e657200000000000000000000604482015260640162000086565b80516001600160a01b0381168114620003c257600080fd5b919050565b60405161012081016001600160401b0381118282101715620003f957634e487b7160e01b600052604160045260246000fd5b60405290565b805163ffffffff81168114620003c257600080fd5b80516001600160481b0381168114620003c257600080fd5b805164ffffffffff81168114620003c257600080fd5b805161ffff81168114620003c257600080fd5b80516001600160e01b0381168114620003c257600080fd5b60008060008385036101608112156200048557600080fd5b6200049085620003aa565b935061012080601f1983011215620004a757600080fd5b620004b1620003c7565b9150620004c160208701620003ff565b8252620004d160408701620003ff565b6020830152620004e460608701620003ff565b6040830152620004f760808701620003ff565b60608301526200050a60a0870162000414565b60808301526200051d60c087016200042c565b60a08301526200053060e0870162000442565b60c08301526101006200054581880162000455565b60e084015262000557828801620003ff565b908301525091506200056d6101408501620003aa565b90509250925092565b815163ffffffff908116825260208084015182169083015260408084015182169083015260608084015191821690830152610120820190506080830151620005c960808401826001600160481b03169052565b5060a0830151620005e360a084018264ffffffffff169052565b5060c0830151620005fa60c084018261ffff169052565b5060e08301516200061660e08401826001600160e01b03169052565b506101009283015163ffffffff16919092015290565b6080516150d06200067260003960008181610845015281816109d301528181610ca601528181610f3a0152818161104501528181611789015261350201526150d06000f3fe608060405234801561001057600080fd5b506004361061018d5760003560e01c806381ff7048116100e3578063c3f909d41161008c578063e3d0e71211610066578063e3d0e71214610560578063e4ddcea614610573578063f2fde38b1461058957600080fd5b8063c3f909d4146103b0578063d227d24514610528578063d328a91e1461055857600080fd5b8063a631571e116100bd578063a631571e1461035d578063afcb95d71461037d578063b1dc65a41461039d57600080fd5b806381ff7048146102b557806385b214cf146103225780638da5cb5b1461033557600080fd5b806366316d8d116101455780637f15e1661161011f5780637f15e16614610285578063814118341461029857806381f1b938146102ad57600080fd5b806366316d8d1461026257806379ba5097146102755780637d4807871461027d57600080fd5b8063181f5a7711610176578063181f5a77146101ba5780632a905ccc1461020c57806359b5b7ac1461022e57600080fd5b8063083a5466146101925780631112dadc146101a7575b600080fd5b6101a56101a0366004613a46565b61059c565b005b6101a56101b5366004613bef565b6105f1565b6101f66040518060400160405280601c81526020017f46756e6374696f6e7320436f6f7264696e61746f722076312e312e300000000081525081565b6040516102039190613d13565b60405180910390f35b610214610841565b60405168ffffffffffffffffff9091168152602001610203565b61021461023c366004613db4565b50600854700100000000000000000000000000000000900468ffffffffffffffffff1690565b6101a5610270366004613e43565b6108d7565b6101a5610a90565b6101a5610b92565b6101a5610293366004613a46565b610d92565b6102a0610de2565b6040516102039190613ecd565b6101f6610e51565b6102ff60015460025463ffffffff74010000000000000000000000000000000000000000830481169378010000000000000000000000000000000000000000000000009093041691565b6040805163ffffffff948516815293909216602084015290820152606001610203565b6101a5610330366004613ee0565b610f22565b60005460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610203565b61037061036b366004613ef9565b610fd4565b604051610203919061404e565b604080516001815260006020820181905291810191909152606001610203565b6101a56103ab3660046140a2565b611175565b61051b6040805161012081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081019190915250604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c01000000000000000000000000810483166060830152700100000000000000000000000000000000810468ffffffffffffffffff166080830152790100000000000000000000000000000000000000000000000000810464ffffffffff1660a08301527e01000000000000000000000000000000000000000000000000000000000000900461ffff1660c08201526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08301527c0100000000000000000000000000000000000000000000000000000000900490911661010082015290565b6040516102039190614159565b61053b610536366004614249565b611785565b6040516bffffffffffffffffffffffff9091168152602001610203565b6101f66118e5565b6101a561056e366004614362565b61193c565b61057b6124b8565b604051908152602001610203565b6101a561059736600461442f565b612711565b6105a4612725565b60008190036105df576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d6105ec8284836144e5565b505050565b6105f96127a8565b80516008805460208401516040808601516060870151608088015160a089015160c08a015161ffff167e01000000000000000000000000000000000000000000000000000000000000027dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff64ffffffffff909216790100000000000000000000000000000000000000000000000000027fffff0000000000ffffffffffffffffffffffffffffffffffffffffffffffffff68ffffffffffffffffff90941670010000000000000000000000000000000002939093167fffff0000000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9586166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff9787166801000000000000000002979097167fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff998716640100000000027fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000909b169c87169c909c1799909917979097169990991793909317959095169390931793909317929092169390931790915560e08301516101008401519092167c0100000000000000000000000000000000000000000000000000000000027bffffffffffffffffffffffffffffffffffffffffffffffffffffffff90921691909117600955517f5f32d06f5e83eda3a68e0e964ef2e6af5cb613e8117aa103c2d6bca5f518486290610836908390614159565b60405180910390a150565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16632a905ccc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156108ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d2919061460b565b905090565b6108df6127b0565b806bffffffffffffffffffffffff166000036109195750336000908152600a60205260409020546bffffffffffffffffffffffff16610973565b336000908152600a60205260409020546bffffffffffffffffffffffff80831691161015610973576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152600a6020526040812080548392906109a09084906bffffffffffffffffffffffff16614657565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506109f57f000000000000000000000000000000000000000000000000000000000000000090565b6040517f66316d8d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301526bffffffffffffffffffffffff8416602483015291909116906366316d8d90604401600060405180830381600087803b158015610a7457600080fd5b505af1158015610a88573d6000803e3d6000fd5b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff163314610b16576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610b9a6127a8565b610ba26127b0565b6000610bac610de2565b905060005b8151811015610d8e576000600a6000848481518110610bd257610bd261467c565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252810191909152604001600020546bffffffffffffffffffffffff1690508015610d7d576000600a6000858581518110610c3157610c3161467c565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610cc87f000000000000000000000000000000000000000000000000000000000000000090565b73ffffffffffffffffffffffffffffffffffffffff166366316d8d848481518110610cf557610cf561467c565b6020026020010151836040518363ffffffff1660e01b8152600401610d4a92919073ffffffffffffffffffffffffffffffffffffffff9290921682526bffffffffffffffffffffffff16602082015260400190565b600060405180830381600087803b158015610d6457600080fd5b505af1158015610d78573d6000803e3d6000fd5b505050505b50610d87816146ab565b9050610bb1565b5050565b610d9a612725565b6000819003610dd5576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c6105ec8284836144e5565b60606006805480602002602001604051908101604052809291908181526020018280548015610e4757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e1c575b5050505050905090565b6060600d8054610e609061444c565b9050600003610e9b576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600d8054610ea89061444c565b80601f0160208091040260200160405190810160405280929190818152602001828054610ed49061444c565b8015610e475780601f10610ef657610100808354040283529160200191610e47565b820191906000526020600020905b815481529060010190602001808311610f0457509395945050505050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610f91576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008181526007602052604080822091909155517f8a4b97add3359bd6bcf5e82874363670eb5ad0f7615abddbd0ed0a3a98f0f416906108369083815260200190565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101919091523373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000161461109c576040517fc41a5b0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6110ad6110a8836146e3565b61295c565b90506110bf606083016040840161442f565b815173ffffffffffffffffffffffffffffffffffffffff91909116907fbf50768ccf13bd0110ca6d53a9c4f1f3271abdd4c24a56878863ed25b20598ff3261110d60c0870160a088016147d0565b61111f6101608801610140890161442f565b61112988806147ed565b61113b6101208b016101008c01614852565b60208b01356111516101008d0160e08e0161486d565b8b6040516111679998979695949392919061488a565b60405180910390a35b919050565b60005a604080518b3580825262ffffff6020808f0135600881901c929092169084015293945092917fb04e63db38c49950639fa09d29872f21f5d49d614f3a969d8adf3d4b52e41a62910160405180910390a16111d68a8a8a8a8a8a612dfa565b6003546000906002906111f49060ff80821691610100900416614932565b6111fe919061497a565b611209906001614932565b60ff169050878114611277576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f77726f6e67206e756d626572206f66207369676e6174757265730000000000006044820152606401610b0d565b878614611306576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f7265706f727420727320616e64207373206d757374206265206f66206571756160448201527f6c206c656e6774680000000000000000000000000000000000000000000000006064820152608401610b0d565b3360009081526004602090815260408083208151808301909252805460ff808216845292939192918401916101009091041660028111156113495761134961499c565b600281111561135a5761135a61499c565b90525090506002816020015160028111156113775761137761499c565b141580156113c057506006816000015160ff168154811061139a5761139a61467c565b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff163314155b15611427576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f756e617574686f72697a6564207472616e736d697474657200000000000000006044820152606401610b0d565b505050506114336139de565b6000808a8a6040516114469291906149cb565b60405190819003812061145d918e906020016149db565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120838301909252600080845290830152915060005b898110156117675760006001848984602081106114c6576114c661467c565b6114d391901a601b614932565b8e8e868181106114e5576114e561467c565b905060200201358d8d878181106114fe576114fe61467c565b905060200201356040516000815260200160405260405161153b949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa15801561155d573d6000803e3d6000fd5b5050604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081015173ffffffffffffffffffffffffffffffffffffffff811660009081526004602090815290849020838501909452835460ff808216855292965092945084019161010090041660028111156115dd576115dd61499c565b60028111156115ee576115ee61499c565b905250925060018360200151600281111561160b5761160b61499c565b14611672576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f61646472657373206e6f7420617574686f72697a656420746f207369676e00006044820152606401610b0d565b8251600090879060ff16601f811061168c5761168c61467c565b602002015173ffffffffffffffffffffffffffffffffffffffff161461170e576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f6e6f6e2d756e69717565207369676e61747572650000000000000000000000006044820152606401610b0d565b8086846000015160ff16601f81106117285761172861467c565b73ffffffffffffffffffffffffffffffffffffffff9092166020929092020152611753600186614932565b94505080611760906146ab565b90506114a7565b505050611778833383858e8e612eb1565b5050505050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006040517f10fc49c100000000000000000000000000000000000000000000000000000000815267ffffffffffffffff8816600482015263ffffffff8516602482015273ffffffffffffffffffffffffffffffffffffffff91909116906310fc49c19060440160006040518083038186803b15801561182557600080fd5b505afa158015611839573d6000803e3d6000fd5b5050505066038d7ea4c6800082111561187e576040517f8129bbcd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611888610841565b905060006118cb87878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061023c92505050565b90506118d985858385613122565b98975050505050505050565b6060600c80546118f49061444c565b905060000361192f576040517f4f42be3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600c8054610ea89061444c565b855185518560ff16601f8311156119af576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f746f6f206d616e79207369676e657273000000000000000000000000000000006044820152606401610b0d565b80600003611a19576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f66206d75737420626520706f73697469766500000000000000000000000000006044820152606401610b0d565b818314611aa7576040517f89a61989000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f6f7261636c6520616464726573736573206f7574206f6620726567697374726160448201527f74696f6e000000000000000000000000000000000000000000000000000000006064820152608401610b0d565b611ab28160036149ef565b8311611b1a576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661756c74792d6f7261636c65206620746f6f206869676800000000000000006044820152606401610b0d565b611b22612725565b6040805160c0810182528a8152602081018a905260ff89169181018290526060810188905267ffffffffffffffff8716608082015260a0810186905290611b69908861328f565b60055415611d1e57600554600090611b8390600190614a06565b9050600060058281548110611b9a57611b9a61467c565b60009182526020822001546006805473ffffffffffffffffffffffffffffffffffffffff90921693509084908110611bd457611bd461467c565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff85811684526004909252604080842080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000090811690915592909116808452922080549091169055600580549192509080611c5457611c54614a19565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690550190556006805480611cbd57611cbd614a19565b60008281526020902081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90810180547fffffffffffffffffffffffff000000000000000000000000000000000000000016905501905550611b69915050565b60005b8151518110156122d557815180516000919083908110611d4357611d4361467c565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611dc8576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f7369676e6572206d757374206e6f7420626520656d70747900000000000000006044820152606401610b0d565b600073ffffffffffffffffffffffffffffffffffffffff1682602001518281518110611df657611df661467c565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603611e7b576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f7472616e736d6974746572206d757374206e6f7420626520656d7074790000006044820152606401610b0d565b60006004600084600001518481518110611e9757611e9761467c565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff166002811115611ee157611ee161499c565b14611f48576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f7265706561746564207369676e657220616464726573730000000000000000006044820152606401610b0d565b6040805180820190915260ff82168152600160208201528251805160049160009185908110611f7957611f7961467c565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000161761010083600281111561201a5761201a61499c565b02179055506000915061202a9050565b60046000846020015184815181106120445761204461467c565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff16825281019190915260400160002054610100900460ff16600281111561208e5761208e61499c565b146120f5576040517f89a6198900000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f7265706561746564207472616e736d69747465722061646472657373000000006044820152606401610b0d565b6040805180820190915260ff8216815260208101600281525060046000846020015184815181106121285761212861467c565b60209081029190910181015173ffffffffffffffffffffffffffffffffffffffff168252818101929092526040016000208251815460ff9091167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082168117835592840151919283917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000016176101008360028111156121c9576121c961499c565b0217905550508251805160059250839081106121e7576121e761467c565b602090810291909101810151825460018101845560009384529282902090920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90931692909217909155820151805160069190839081106122635761226361467c565b60209081029190910181015182546001810184556000938452919092200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff909216919091179055806122cd816146ab565b915050611d21565b506040810151600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff909216919091179055600180547fffffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffff8116780100000000000000000000000000000000000000000000000063ffffffff438116820292909217808555920481169291829160149161238d91849174010000000000000000000000000000000000000000900416614a48565b92506101000a81548163ffffffff021916908363ffffffff1602179055506123ec4630600160149054906101000a900463ffffffff1663ffffffff16856000015186602001518760400151886060015189608001518a60a001516132a8565b600281905582518051600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1661010060ff9093169290920291909117905560015460208501516040808701516060880151608089015160a08a015193517f1591690b8638f5fb2dbec82ac741805ac5da8b45dc5263f4875b0496fdce4e05986124a3988b9891977401000000000000000000000000000000000000000090920463ffffffff16969095919491939192614a65565b60405180910390a15050505050505050505050565b604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116838501526c0100000000000000000000000080830482166060850152700100000000000000000000000000000000830468ffffffffffffffffff166080850152790100000000000000000000000000000000000000000000000000830464ffffffffff1660a0808601919091527e0100000000000000000000000000000000000000000000000000000000000090930461ffff1660c08501526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08601527c01000000000000000000000000000000000000000000000000000000009004909116610100840152600b5484517ffeaf968c00000000000000000000000000000000000000000000000000000000815294516000958694859490930473ffffffffffffffffffffffffffffffffffffffff169263feaf968c926004808401938290030181865afa158015612646573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061266a9190614b15565b50935050925050804261267d9190614a06565b836020015163ffffffff1610801561269f57506000836020015163ffffffff16115b156126cd57505060e001517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16919050565b6000821361270a576040517f43d4cf6600000000000000000000000000000000000000000000000000000000815260048101839052602401610b0d565b5092915050565b612719612725565b61272281613353565b50565b60005473ffffffffffffffffffffffffffffffffffffffff1633146127a6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610b0d565b565b6127a6612725565b600b546bffffffffffffffffffffffff166000036127ca57565b60006127d4610de2565b80519091506000819003612814576040517f30274b3a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600b546000906128339083906bffffffffffffffffffffffff16614b65565b905060005b828110156128fe5781600a60008684815181106128575761285761467c565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282829054906101000a90046bffffffffffffffffffffffff166128bf9190614b90565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550806128f7906146ab565b9050612838565b506129098282614bb5565b600b80546000906129299084906bffffffffffffffffffffffff16614657565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550505050565b6040805161016081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e0810182905261010081018290526101208101829052610140810191909152604080516101208101825260085463ffffffff80821683526401000000008204811660208401526801000000000000000082048116938301939093526c0100000000000000000000000081048316606083015268ffffffffffffffffff700100000000000000000000000000000000820416608083015264ffffffffff79010000000000000000000000000000000000000000000000000082041660a083015261ffff7e01000000000000000000000000000000000000000000000000000000000000909104811660c083018190526009547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811660e08501527c0100000000000000000000000000000000000000000000000000000000900490931661010080840191909152850151919291161115612b17576040517fdada758700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600854600090700100000000000000000000000000000000900468ffffffffffffffffff1690506000612b548560e001513a848860800151613122565b9050806bffffffffffffffffffffffff1685606001516bffffffffffffffffffffffff161015612bb0576040517ff4d678b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083610100015163ffffffff1642612bc99190614bdd565b905060003087604001518860a001518960c001516001612be99190614bf0565b8a5180516020918201206101008d015160e08e0151604051612c9d98979695948c918c9132910173ffffffffffffffffffffffffffffffffffffffff9a8b168152988a1660208a015267ffffffffffffffff97881660408a0152959096166060880152608087019390935261ffff9190911660a086015263ffffffff90811660c08601526bffffffffffffffffffffffff9190911660e0850152919091166101008301529091166101208201526101400190565b6040516020818303038152906040528051906020012090506040518061016001604052808281526020013073ffffffffffffffffffffffffffffffffffffffff168152602001846bffffffffffffffffffffffff168152602001886040015173ffffffffffffffffffffffffffffffffffffffff1681526020018860a0015167ffffffffffffffff1681526020018860e0015163ffffffff168152602001886080015168ffffffffffffffffff1681526020018568ffffffffffffffffff168152602001866040015163ffffffff1664ffffffffff168152602001866060015163ffffffff1664ffffffffff1681526020018363ffffffff16815250955085604051602001612dac919061404e565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152918152815160209283012060009384526007909252909120555092949350505050565b6000612e078260206149ef565b612e128560206149ef565b612e1e88610144614bdd565b612e289190614bdd565b612e329190614bdd565b612e3d906000614bdd565b9050368114612ea8576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f63616c6c64617461206c656e677468206d69736d6174636800000000000000006044820152606401610b0d565b50505050505050565b606080808080612ec386880188614cec565b84519499509297509095509350915060ff16801580612ee3575084518114155b80612eef575083518114155b80612efb575082518114155b80612f07575081518114155b15612fe0576040517f660bd4ba00000000000000000000000000000000000000000000000000000000815260206004820152607060248201527f416c6c206669656c6473206f6e20746865207265706f7274206d75737420626560448201527f206f6620657175616c206c656e6774683a20726571756573744964732c20726560648201527f73756c74732c206572726f72732c206f6e636861696e4d657461646174612c2060848201527f6f6666636861696e4d657461646174610000000000000000000000000000000060a482015260c401610b0d565b60005b818110156131135760006130788883815181106130025761300261467c565b602002602001015188848151811061301c5761301c61467c565b60200260200101518885815181106130365761303661467c565b60200260200101518886815181106130505761305061467c565b602002602001015188878151811061306a5761306a61467c565b602002602001015188613448565b9050600081600681111561308e5761308e61499c565b14806130ab575060018160068111156130a9576130a961499c565b145b15613102578782815181106130c2576130c261467c565b60209081029190910181015160405133815290917fc708e0440951fd63499c0f7a73819b469ee5dd3ecc356c0ab4eb7f18389009d9910160405180910390a25b5061310c816146ab565b9050612fe3565b50505050505050505050505050565b600854600090790100000000000000000000000000000000000000000000000000900464ffffffffff1684101561317d57600854790100000000000000000000000000000000000000000000000000900464ffffffffff1693505b600854600090612710906131979063ffffffff16876149ef565b6131a19190614dbe565b6131ab9086614bdd565b60085490915060009087906131e49063ffffffff6c01000000000000000000000000820481169168010000000000000000900416614a48565b6131ee9190614a48565b63ffffffff16905060006132386000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061375c92505050565b905060006132598261324a85876149ef565b6132549190614bdd565b61389e565b9050600061327568ffffffffffffffffff808916908a16614b90565b90506132818183614b90565b9a9950505050505050505050565b6000613299610de2565b511115610d8e57610d8e6127b0565b6000808a8a8a8a8a8a8a8a8a6040516020016132cc99989796959493929190614dd2565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e01000000000000000000000000000000000000000000000000000000000000179150509998505050505050505050565b3373ffffffffffffffffffffffffffffffffffffffff8216036133d2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610b0d565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000808480602001905181019061345f9190614e9e565b905060003a82610120015183610100015161347a9190614f66565b64ffffffffff1661348b91906149ef565b905060008460ff166134d36000368080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061375c92505050565b6134dd9190614dbe565b905060006134ee6132548385614bdd565b905060006134fb3a61389e565b90506000807f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663330605298e8e868b60e0015168ffffffffffffffffff168961355a9190614b90565b338d6040518763ffffffff1660e01b815260040161357d96959493929190614f84565b60408051808303816000875af115801561359b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135bf9190615000565b909250905060008260068111156135d8576135d861499c565b14806135f5575060018260068111156135f3576135f361499c565b145b1561374b5760008e8152600760205260408120556136138185614b90565b336000908152600a6020526040812080547fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166bffffffffffffffffffffffff93841617905560e0890151600b805468ffffffffffffffffff9092169390929161367f91859116614b90565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055508d7f90815c2e624694e8010bffad2bcefaf96af282ef1bc2ebc0042d1b89a585e0468487848b60c0015168ffffffffffffffffff168c60e0015168ffffffffffffffffff16878b6136fe9190614b90565b6137089190614b90565b6137129190614b90565b604080516bffffffffffffffffffffffff9586168152602081019490945291841683830152909216606082015290519081900360800190a25b509c9b505050505050505050505050565b600046613768816138d2565b156137e457606c73ffffffffffffffffffffffffffffffffffffffff1663c6f7de0e6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156137b9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137dd9190615033565b9392505050565b6137ed816138f5565b156138955773420000000000000000000000000000000000000f73ffffffffffffffffffffffffffffffffffffffff166349948e0e8460405180608001604052806048815260200161507c6048913960405160200161384d92919061504c565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016138789190613d13565b602060405180830381865afa1580156137b9573d6000803e3d6000fd5b50600092915050565b60006138cc6138ab6124b8565b6138bd84670de0b6b3a76400006149ef565b6138c79190614dbe565b61393c565b92915050565b600061a4b18214806138e6575062066eed82145b806138cc57505062066eee1490565b6000600a82148061390757506101a482145b80613914575062aa37dc82145b80613920575061210582145b8061392d575062014a3382145b806138cc57505062014a341490565b60006bffffffffffffffffffffffff8211156139da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203960448201527f36206269747300000000000000000000000000000000000000000000000000006064820152608401610b0d565b5090565b604051806103e00160405280601f906020820280368337509192915050565b60008083601f840112613a0f57600080fd5b50813567ffffffffffffffff811115613a2757600080fd5b602083019150836020828501011115613a3f57600080fd5b9250929050565b60008060208385031215613a5957600080fd5b823567ffffffffffffffff811115613a7057600080fd5b613a7c858286016139fd565b90969095509350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051610120810167ffffffffffffffff81118282101715613adb57613adb613a88565b60405290565b604051610160810167ffffffffffffffff81118282101715613adb57613adb613a88565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715613b4c57613b4c613a88565b604052919050565b63ffffffff8116811461272257600080fd5b803561117081613b54565b68ffffffffffffffffff8116811461272257600080fd5b803561117081613b71565b64ffffffffff8116811461272257600080fd5b803561117081613b93565b803561ffff8116811461117057600080fd5b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461117057600080fd5b60006101208284031215613c0257600080fd5b613c0a613ab7565b613c1383613b66565b8152613c2160208401613b66565b6020820152613c3260408401613b66565b6040820152613c4360608401613b66565b6060820152613c5460808401613b88565b6080820152613c6560a08401613ba6565b60a0820152613c7660c08401613bb1565b60c0820152613c8760e08401613bc3565b60e0820152610100613c9a818501613b66565b908201529392505050565b60005b83811015613cc0578181015183820152602001613ca8565b50506000910152565b60008151808452613ce1816020860160208601613ca5565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006137dd6020830184613cc9565b600082601f830112613d3757600080fd5b813567ffffffffffffffff811115613d5157613d51613a88565b613d8260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f84011601613b05565b818152846020838601011115613d9757600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215613dc657600080fd5b813567ffffffffffffffff811115613ddd57600080fd5b613de984828501613d26565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461272257600080fd5b803561117081613df1565b6bffffffffffffffffffffffff8116811461272257600080fd5b803561117081613e1e565b60008060408385031215613e5657600080fd5b8235613e6181613df1565b91506020830135613e7181613e1e565b809150509250929050565b600081518084526020808501945080840160005b83811015613ec257815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101613e90565b509495945050505050565b6020815260006137dd6020830184613e7c565b600060208284031215613ef257600080fd5b5035919050565b600060208284031215613f0b57600080fd5b813567ffffffffffffffff811115613f2257600080fd5b820161016081850312156137dd57600080fd5b805182526020810151613f60602084018273ffffffffffffffffffffffffffffffffffffffff169052565b506040810151613f8060408401826bffffffffffffffffffffffff169052565b506060810151613fa8606084018273ffffffffffffffffffffffffffffffffffffffff169052565b506080810151613fc4608084018267ffffffffffffffff169052565b5060a0810151613fdc60a084018263ffffffff169052565b5060c0810151613ff960c084018268ffffffffffffffffff169052565b5060e081015161401660e084018268ffffffffffffffffff169052565b506101008181015164ffffffffff9081169184019190915261012080830151909116908301526101409081015163ffffffff16910152565b61016081016138cc8284613f35565b60008083601f84011261406f57600080fd5b50813567ffffffffffffffff81111561408757600080fd5b6020830191508360208260051b8501011115613a3f57600080fd5b60008060008060008060008060e0898b0312156140be57600080fd5b606089018a8111156140cf57600080fd5b8998503567ffffffffffffffff808211156140e957600080fd5b6140f58c838d016139fd565b909950975060808b013591508082111561410e57600080fd5b61411a8c838d0161405d565b909750955060a08b013591508082111561413357600080fd5b506141408b828c0161405d565b999c989b50969995989497949560c00135949350505050565b815163ffffffff9081168252602080840151821690830152604080840151821690830152606080840151918216908301526101208201905060808301516141ad608084018268ffffffffffffffffff169052565b5060a08301516141c660a084018264ffffffffff169052565b5060c08301516141dc60c084018261ffff169052565b5060e083015161420c60e08401827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff169052565b506101008381015163ffffffff8116848301525b505092915050565b67ffffffffffffffff8116811461272257600080fd5b803561117081614228565b60008060008060006080868803121561426157600080fd5b853561426c81614228565b9450602086013567ffffffffffffffff81111561428857600080fd5b614294888289016139fd565b90955093505060408601356142a881613b54565b949793965091946060013592915050565b600067ffffffffffffffff8211156142d3576142d3613a88565b5060051b60200190565b600082601f8301126142ee57600080fd5b813560206143036142fe836142b9565b613b05565b82815260059290921b8401810191818101908684111561432257600080fd5b8286015b8481101561434657803561433981613df1565b8352918301918301614326565b509695505050505050565b803560ff8116811461117057600080fd5b60008060008060008060c0878903121561437b57600080fd5b863567ffffffffffffffff8082111561439357600080fd5b61439f8a838b016142dd565b975060208901359150808211156143b557600080fd5b6143c18a838b016142dd565b96506143cf60408a01614351565b955060608901359150808211156143e557600080fd5b6143f18a838b01613d26565b94506143ff60808a0161423e565b935060a089013591508082111561441557600080fd5b5061442289828a01613d26565b9150509295509295509295565b60006020828403121561444157600080fd5b81356137dd81613df1565b600181811c9082168061446057607f821691505b602082108103614499577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156105ec57600081815260208120601f850160051c810160208610156144c65750805b601f850160051c820191505b81811015610a88578281556001016144d2565b67ffffffffffffffff8311156144fd576144fd613a88565b6145118361450b835461444c565b8361449f565b6000601f841160018114614563576000851561452d5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b1783556145f9565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156145b25786850135825560209485019460019092019101614592565b50868210156145ed577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b805161117081613b71565b60006020828403121561461d57600080fd5b81516137dd81613b71565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff82811682821603908082111561270a5761270a614628565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036146dc576146dc614628565b5060010190565b600061016082360312156146f657600080fd5b6146fe613ae1565b823567ffffffffffffffff81111561471557600080fd5b61472136828601613d26565b8252506020830135602082015261473a60408401613e13565b604082015261474b60608401613e38565b606082015261475c60808401613b88565b608082015261476d60a0840161423e565b60a082015261477e60c0840161423e565b60c082015261478f60e08401613b66565b60e08201526101006147a2818501613bb1565b908201526101206147b484820161423e565b908201526101406147c6848201613e13565b9082015292915050565b6000602082840312156147e257600080fd5b81356137dd81614228565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261482257600080fd5b83018035915067ffffffffffffffff82111561483d57600080fd5b602001915036819003821315613a3f57600080fd5b60006020828403121561486457600080fd5b6137dd82613bb1565b60006020828403121561487f57600080fd5b81356137dd81613b54565b73ffffffffffffffffffffffffffffffffffffffff8a8116825267ffffffffffffffff8a166020830152881660408201526102406060820181905281018690526000610260878982850137600083890182015261ffff8716608084015260a0830186905263ffffffff851660c0840152601f88017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016830101905061328160e0830184613f35565b60ff81811683821601908111156138cc576138cc614628565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600060ff83168061498d5761498d61494b565b8060ff84160491505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b8183823760009101908152919050565b828152606082602083013760800192915050565b80820281158282048414176138cc576138cc614628565b818103818111156138cc576138cc614628565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b63ffffffff81811683821601908082111561270a5761270a614628565b600061012063ffffffff808d1684528b6020850152808b16604085015250806060840152614a958184018a613e7c565b90508281036080840152614aa98189613e7c565b905060ff871660a084015282810360c0840152614ac68187613cc9565b905067ffffffffffffffff851660e0840152828103610100840152614aeb8185613cc9565b9c9b505050505050505050505050565b805169ffffffffffffffffffff8116811461117057600080fd5b600080600080600060a08688031215614b2d57600080fd5b614b3686614afb565b9450602086015193506040860151925060608601519150614b5960808701614afb565b90509295509295909350565b60006bffffffffffffffffffffffff80841680614b8457614b8461494b565b92169190910492915050565b6bffffffffffffffffffffffff81811683821601908082111561270a5761270a614628565b6bffffffffffffffffffffffff81811683821602808216919082811461422057614220614628565b808201808211156138cc576138cc614628565b67ffffffffffffffff81811683821601908082111561270a5761270a614628565b600082601f830112614c2257600080fd5b81356020614c326142fe836142b9565b82815260059290921b84018101918181019086841115614c5157600080fd5b8286015b848110156143465780358352918301918301614c55565b600082601f830112614c7d57600080fd5b81356020614c8d6142fe836142b9565b82815260059290921b84018101918181019086841115614cac57600080fd5b8286015b8481101561434657803567ffffffffffffffff811115614cd05760008081fd5b614cde8986838b0101613d26565b845250918301918301614cb0565b600080600080600060a08688031215614d0457600080fd5b853567ffffffffffffffff80821115614d1c57600080fd5b614d2889838a01614c11565b96506020880135915080821115614d3e57600080fd5b614d4a89838a01614c6c565b95506040880135915080821115614d6057600080fd5b614d6c89838a01614c6c565b94506060880135915080821115614d8257600080fd5b614d8e89838a01614c6c565b93506080880135915080821115614da457600080fd5b50614db188828901614c6c565b9150509295509295909350565b600082614dcd57614dcd61494b565b500490565b60006101208b835273ffffffffffffffffffffffffffffffffffffffff8b16602084015267ffffffffffffffff808b166040850152816060850152614e198285018b613e7c565b91508382036080850152614e2d828a613e7c565b915060ff881660a085015283820360c0850152614e4a8288613cc9565b90861660e08501528381036101008501529050614aeb8185613cc9565b805161117081613df1565b805161117081613e1e565b805161117081614228565b805161117081613b54565b805161117081613b93565b60006101608284031215614eb157600080fd5b614eb9613ae1565b82518152614ec960208401614e67565b6020820152614eda60408401614e72565b6040820152614eeb60608401614e67565b6060820152614efc60808401614e7d565b6080820152614f0d60a08401614e88565b60a0820152614f1e60c08401614600565b60c0820152614f2f60e08401614600565b60e0820152610100614f42818501614e93565b90820152610120614f54848201614e93565b90820152610140613c9a848201614e88565b64ffffffffff81811683821601908082111561270a5761270a614628565b6000610200808352614f988184018a613cc9565b90508281036020840152614fac8189613cc9565b6bffffffffffffffffffffffff88811660408601528716606085015273ffffffffffffffffffffffffffffffffffffffff861660808501529150614ff5905060a0830184613f35565b979650505050505050565b6000806040838503121561501357600080fd5b82516007811061502257600080fd5b6020840151909250613e7181613e1e565b60006020828403121561504557600080fd5b5051919050565b6000835161505e818460208801613ca5565b835190830190615072818360208801613ca5565b0194935050505056fe307866666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666a164736f6c6343000813000a", } var FunctionsCoordinatorABI = FunctionsCoordinatorMetaData.ABI diff --git a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 534e5543e86..93c4e64a3af 100644 --- a/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/functions/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -4,7 +4,7 @@ functions_allow_list: ../../../contracts/solc/v0.8.19/functions/v1_X/TermsOfServ functions_billing_registry_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsBillingRegistryEventsMock.bin 50deeb883bd9c3729702be335c0388f9d8553bab4be5e26ecacac496a89e2b77 functions_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClient.bin 2368f537a04489c720a46733f8596c4fc88a31062ecfa966d05f25dd98608aca functions_client_example: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsClientExample.bin abf32e69f268f40e8530eb8d8e96bf310b798a4c0049a58022d9d2fb527b601b -functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 38e168fa57c9626140e1e4d05f4124b4b69bd775e6e0f4481e017ad86c4d95a0 +functions_coordinator: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsCoordinator.bin 4e05ca5e624b7a1e604b81b84bc088818b376d533f556ba1c2ee586b7eb38b68 functions_load_test_client: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsLoadTestClient.bin c8dbbd5ebb34435800d6674700068837c3a252db60046a14b0e61e829db517de functions_oracle_events_mock: ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.abi ../../../contracts/solc/v0.8.6/functions/v0_0_0/FunctionsOracleEventsMock.bin 3ca70f966f8fe751987f0ccb50bebb6aa5be77e4a9f835d1ae99e0e9bfb7d52c functions_router: ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.abi ../../../contracts/solc/v0.8.19/functions/v1_X/FunctionsRouter.bin 9dedd3a36043605fd9bedf821e7ec5b4281a5c7ae2e4a1955f37aff8ba13519f From 75d0743eae6f419756f5de12ddd922a711809c35 Mon Sep 17 00:00:00 2001 From: Cedric Date: Fri, 10 Nov 2023 11:07:07 +0000 Subject: [PATCH 3/6] Add medianpoc to plugins dockerfile (#11247) --- GNUmakefile | 4 ++++ plugins/chainlink.Dockerfile | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 32f74e285e9..69d82da6c84 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -61,6 +61,10 @@ chainlink-local-start: install-median: ## Build & install the chainlink-median binary. go install $(GOFLAGS) ./plugins/cmd/chainlink-median +.PHONY: install-medianpoc +install-medianpoc: ## Build & install the chainlink-medianpoc binary. + go install $(GOFLAGS) ./plugins/cmd/chainlink-medianpoc + .PHONY: docker ## Build the chainlink docker image docker: docker buildx build \ diff --git a/plugins/chainlink.Dockerfile b/plugins/chainlink.Dockerfile index 001ee30bf74..f07fab48122 100644 --- a/plugins/chainlink.Dockerfile +++ b/plugins/chainlink.Dockerfile @@ -19,6 +19,9 @@ RUN make install-chainlink # Build LOOP Plugins RUN make install-median +# Install medianpoc binary +RUN make install-medianpoc + RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana | xargs -I % ln -s % /chainlink-solana RUN mkdir /chainlink-starknet RUN go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/relayer | xargs -I % ln -s % /chainlink-starknet/relayer @@ -49,6 +52,7 @@ RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ && apt-get clean all COPY --from=buildgo /go/bin/chainlink /usr/local/bin/ +COPY --from=buildgo /go/bin/chainlink-medianpoc /usr/local/bin/ COPY --from=buildgo /go/bin/chainlink-median /usr/local/bin/ ENV CL_MEDIAN_CMD chainlink-median From 0918b37d4e27c465a2e360167ad6e1ca594fe6e5 Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Fri, 10 Nov 2023 15:52:08 +0100 Subject: [PATCH 4/6] solidity style guide 2.0 (#10995) * solidity style guide 2.0 * split into rules and guidelines * improve layout --- contracts/STYLE.md | 234 ----------------------- contracts/STYLE_GUIDE.md | 391 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+), 234 deletions(-) delete mode 100644 contracts/STYLE.md create mode 100644 contracts/STYLE_GUIDE.md diff --git a/contracts/STYLE.md b/contracts/STYLE.md deleted file mode 100644 index d9692a65210..00000000000 --- a/contracts/STYLE.md +++ /dev/null @@ -1,234 +0,0 @@ -# Solidity Style Guide - -## Background - -Our starting point is the [official Solidity Style Guide](https://solidity.readthedocs.io/en/v0.8.0/style-guide.html) and [ConsenSys's Secure Development practices](https://consensys.github.io/smart-contract-best-practices/), but we deviate in some ways. We lean heavily on [Prettier](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc) for formatting, and if you have to set up a new Solidity project we recommend starting with [our prettier config](https://github.com/smartcontractkit/chainlink/blob/develop/.prettierrc.js). We are trying to automate as much of this styleguide with Solhint as possible. - -### Code Organization - -- Group functionality together. E.g. Declare structs, events, and helper functions near the functions that use them. This is helpful when reading code because the related pieces are localized. It is also consistent with inheritance and libraries, which are separate pieces of code designed for a specific goal. - - Why not follow the Solidity recommendation of grouping by visibility? Visibility is clearly defined next to the method signature, making it trivial to check. However, searching can be deceiving because of inherited methods. Given this inconsistency in grouping, we find it easier to read and more consistent to organize code around functionality. Additionally, we recommend testing the public interface for any Solidity contract to ensure it only exposes expected methods. - -### Delineate Unaudited Code - -- In a large repo it is worthwhile to keep code that has not yet been audited separate from the code that has been audited. This allows you to easily keep track of which files need to be reviewed. - - E.g. we keep unaudited code in a directory named `dev`. Only once it has been audited we move the audited files out of `dev` and only then is it considered safe to deploy. - -## Variables - -### Visibility - -- All contract variables should be private. Getters should be explicitly written and documented when you want to expose a variable publicly. Whether a getter function reads from storage, a constant, or calculates a value from somewhere else, that’s all implementation details that should not be exposed to the consumer by casing or other conventions. - -Examples: - -Good: - -```javascript -uint256 private s_myVar; - -function getMyVar() external view returns(uint256){ - return s_myVar; -} -``` - -Bad: - -```javascript -uint256 public s_myVar; -``` - -### Naming and Casing - -- Function arguments are named like this: `argumentName`. No leading or trailing underscores necessary. -- Storage variables prefixed with an `s_` to make it clear that they live in storage and are expensive to read and write: `s_variableName`. They should always be private, and you should write explicit getters if you want to expose a storage variable. -- Immutable variables should be prefixed with an `i_` to make it clear that they are immutable. E.g. `i_decimalPlaces`. They should always be private, and you should write explicit getters if you want to expose an immutable variable. -- Internal/private constants should be all caps with underscores: `FOO_BAR`. Like other contract variables, constants should not be public. Create getter methods if you want to publicly expose constants. -- Explicitly declare variable size: `uint256` not just `uint`. In addition to being explicit, it matches the naming used to calculate function selectors. - -Examples: - -Good: - -```javascript -uint256 private s_myVar; -uint256 private immutable i_myImmutVar; -uint256 private constant MY_CONST_VAR; - -function multiplyMyVar(uint256 multiplier) external view returns(uint256){ - return multiplier * s_myVar; -} -``` - -Bad: - -```javascript -uint private s_myVar; -uint256 private immutable myImmutVar; -uint256 private constant s_myConstVar; - -function multiplyMyVar_(uint _multiplier) external view returns(uint256){ - return _mutliplier * s_myVar; -} -``` - -### Types - -- If you are storing an address and know/expect it to be of a type(or interface), make the variable that type. This more clearly documents the behavior of this variable than the `address` type and often leads to less casting code whenever the address is used. - -Examples: - -Good: - -```javascript -import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -// . -// . -// . -AggregatorV3Interface private s_priceFeed; - -constructor(address priceFeed) { - s_priceFeed = AggregatorV3Interface(priceFeed); -} -``` - -Bad: - -```javascript -import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -// . -// . -// . -address private s_priceFeed; - -constructor(address priceFeed) { - s_priceFeed = priceFeed; -} -``` - -## Functions - -### Visibility - -- Method visibility should always be explicitly declared. Contract’s [public interfaces should be tested](https://github.com/smartcontractkit/chainlink/blob/master/contracts/test/test-helpers/helpers.ts#L221) to enforce this and make sure that internal logic is not accidentally exposed. - -### Naming - -- Function names should start with imperative verbs, not nouns or other tenses. - - `requestData` not `dataRequest` - - `approve` not `approved` -- Prefix private and internal methods with an underscore. There should never be a publicly callable method starting with an underscore. - - E.g. `_setOwner(address)` -- Prefix your public getters with `get` and your public setters with `set`. - - `getConfig` and `setConfig`. - -## Modifiers - -- Only extract a modifier once a check is duplicated in multiple places. Modifiers arguably hurt readability, so we have found that they are not worth extracting until there is duplication. -- Modifiers should be treated as if they are view functions. They should not change state, only read it. While it is possible to change state in a modifier, it is unconventional and surprising. - -### Naming - -There are two common classes of modifiers, and their name should be prefixed accordingly to quickly represent their behavior: - -- Control flow modifiers: Prefix the modifier name with `if` in the case that a modifier only enables or disables the subsequent code in the modified method, but does not revert. -- Reverting modifiers: Prefix the modifier name with `validate` in the case that a modifier reverts if a condition is not met. - -### Return Values - -- If an address is cast as a contract type, return the type, do not cast back to the address type. This prevents the consumer of the method signature from having to cast again, but presents an equivalent API for off-chain APIs. Additionally it is a more declarative API, providing more context if we return a type. - -## Events - -- Events should only be triggered on state changes. If the value is set but not changed, we prefer avoiding a log emission indicating a change. (e.g. Either do not emit a log, or name the event `ConfigSet` instead of `ConfigUpdated`.) - -### Naming - -- When possible event names should correspond to the method they are in or the action that is being taken. Events preferably follow the format , where the action performed is the past tense of the imperative verb in the method name. e.g. calling `setConfig` should emit an event called `ConfigSet`, not `ConfigUpdated` in a method named `setConfig`. - -## Errors - -### Use Custom Errors - -Whenever possible (Solidity v0.8+) use [custom errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) instead of emitting strings. This saves contract code size and simultaneously provides more informative error messages. - -### Expose Errors - -It is common to call a contract and then check the call succeeded: - -```javascript -(bool success, ) = to.call(data); -require(success, "Contract call failed"); -``` - -While this may look descriptive it swallows the error. Instead bubble up the error: - -```javascript -error YourError(bytes response); - -(bool success, bytes memory response) = to.call(data); -if (!success) { revert YourError(response); } -``` - -This will cost slightly more gas to copy the response into memory, but will ultimately make contract usage more understandable and easier to debug. Whether it is worth the extra gas is a judgement call you’ll have to make based on your needs. - -The original error will not be human readable in an off-chain explorer because it is RLP hex encoded but is easily decoded with standard Solidity ABI decoding tools, or a hex to UTF-8 converter and some basic ABI knowledge. - -## Control Flow - -### `if` Statements - -Always wrap the result statement of your `if` conditions in a closure, even if it is only one line. - -Bad: - -```javascript - if (condition) statement; -``` - -Good: - -```javascript - if (condition) { statement; } -``` - -## Interfaces - -### Scope - -- Interfaces should be as concise as reasonably possible. Break it up into smaller composable interfaces when that is sensible. - -### Naming - -- Up through Solidity version 0.8: Interfaces should be named `FooInterface`, this follows our historical naming pattern. -- Starting in Solidity v0.9: Interfaces should be named `IFoo` instead of `FooInterface`. This follows the patterns of popular [libraries like OpenZeppelin’s](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L9). - -## Vendor Dependencies - -- That’s it, vendor your Solidity dependencies. Supply chain attacks are all the rage these days. There is not yet a silver bullet for best way to vendor, it depends on the size of your project and your needs. You should be as explicit as possible about where the code comes from and make sure that this is enforced in some way; e.g. reference a hash. Some options: - - NPM packages work for repos already in the JavaScript ecosystem. If you go this route you should lock to a hash of the repo or use a proxy registry like GitHub Packages. - - Git submodules are great if you don’t mind git submodules. - - Copy and paste the code into a `vendor` directory. Record attribution of the code and license in the repo along with the commit or version that you pulled the code from. - -## Common Behaviors - -### Transferring Ownership - -- When transferring control, whether it is of a token or a role in a contract, prefer "safe ownership" transfer patterns where the recipient must accept ownership. This avoids accidentally burning the control. This is also inline with the secure pattern of [prefer pull over push](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls). - -### Use Factories - -- If you expect multiple instances of a contract to be deployed, it is probably best to [build a factory](https://www.quicknode.com/guides/solidity/how-to-create-a-smart-contract-factory-in-solidity-using-hardhat) as this allows for simpler deployments later. Additionally it reduces the burden of verifying the correctness of the contract deployment. If many people have to deploy an instance of a contract then doing so with a contract makes it much easier for verification because instead of checking the code hash and/or the compiler and maybe the source code, you only have to check that the contract was deployed through a factory. -- Factories can add some pain when deploying with immutable variables. In general it is difficult to parse those out immutable variables from internal transactions. There is nothing inherently wrong with contracts deployed in this manner but at the time of writing they may not easily verify on Etherscan. - -### Call with Exact Gas - -- `call` accepts a gas parameter, but that parameter is a ceiling on gas usage. If a transaction does not have enough gas, `call` will simply provide as much gas as it safely can. This is unintuitive and can lead to transactions failing for unexpected reasons. We have [an implementation of `callWithExactGas`](https://github.com/smartcontractkit/chainlink/blob/075f3e2caf61b8685d2dc78714f1ee39764fda17/contracts/src/v0.8/KeeperRegistry.sol#L792) to ensure the precise gas amount requested is provided. - -## Picking a Pragma - -- If a contract or library is expected to be imported by outside parties then the pragma should be kept as loose as possible without sacrificing safety. We publish versions for every minor semver version of Solidity, and maintain a corresponding set of tests for each published version. - - Examples: libraries, interfaces, abstract contracts, and contracts expected to be inherited from -- Otherwise, Solidity contracts should have a pragma which is locked to a specific version. - - Example: Most concrete contracts. -- Avoid changing pragmas after audit. Unless there is a bug that has affects your contract, then you should try to stick to a known good pragma. In practice this means we typically only support one (occasionally two) pragma for any “major”(minor by semver naming) Solidity version. diff --git a/contracts/STYLE_GUIDE.md b/contracts/STYLE_GUIDE.md new file mode 100644 index 00000000000..3868117d4b9 --- /dev/null +++ b/contracts/STYLE_GUIDE.md @@ -0,0 +1,391 @@ +# Structure + +This guide is split into two sections: [Guidelines](#guidelines) and [Rules](#rules). +Guidelines are recommendations that should be followed but are hard to enforce in an automated way. +Rules are all enforced through CI, this can be through Solhint rules or other tools. + +## Background + +Our starting point is the [official Solidity Style Guide](https://docs.soliditylang.org/en/v0.8.21/style-guide.html) and [ConsenSys's Secure Development practices](https://consensys.github.io/smart-contract-best-practices/), but we deviate in some ways. We lean heavily on [Prettier](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc) for formatting, and if you have to set up a new Solidity project we recommend starting with [our prettier config](https://github.com/smartcontractkit/chainlink/blob/develop/contracts/.prettierrc). We are trying to automate as much of this styleguide with Solhint as possible. + +This guide is not meant to be applied retroactively. There is no need to rewrite existing code to adhere to this guide, and when making (small) changes in existing files, it is not required to do so in accordance to this guide if it would conflict with other practices in that existing file. Consistency is preferred. + +We will be looking into `forge fmt`, but for now we still use `prettier`. + + +# Guidelines + +## Code Organization +- Group functionality together. E.g. Declare structs, events, and helper functions near the functions that use them. This is helpful when reading code because the related pieces are localized. It is also consistent with inheritance and libraries, which are separate pieces of code designed for a specific goal. +- 🤔Why not follow the Solidity recommendation of grouping by visibility? Visibility is clearly defined next to the method signature, making it trivial to check. However, searching can be deceiving because of inherited methods. Given this inconsistency in grouping, we find it easier to read and more consistent to organize code around functionality. Additionally, we recommend testing the public interface for any Solidity contract to ensure it only exposes expected methods. +- Follow the [Solidity folder structure CLIP](https://github.com/smartcontractkit/CLIPs/tree/main/clips/2023-04-13-solidity-folder-structure) + +### Delineate Unaudited Code + +- In a large repo it is worthwhile to keep code that has not yet been audited separate from the code that has been audited. This allows you to easily keep track of which files need to be reviewed. + - E.g. we keep unaudited code in a directory named `dev` that exists within each projects folder. Only once it has been audited we move the audited files out of `dev` and only then is it considered safe to deploy. + - This `dev` folder also has implications for when code is valid for bug bounties, so be extra careful to move functionality out of a `dev` folder. + + +## comments +- Besides comment above functions/structs, comments should live everywhere a reader might be confused. + Don’t overestimate the reader of your contract, expect confusion in many places and document accordingly. + This will help massively during audits and onboarding new team members. +- Headers should be used to group functionality, the following header style and length is recommended. + - Don’t use headers for a single function, or to say “getters”. Group by functionality e.g. the `Tokens and pools` , or `fees` logic within the CCIP OnRamp. + +```solidity + // ================================================================ + // │ Tokens and pools │ + // ================================================================ + +.... + + // ================================================================ + // │ Fees │ + // ================================================================ +``` + +## Variables + +- Function arguments are named like this: `argumentName`. No leading or trailing underscores necessary. +- Names should be explicit on the unit it contains, e.g. a network fee that is charged in USD cents + +```solidity +uint256 fee; // bad +uint256 networkFee; // bad +uint256 networkFeeUSD; // bad +uint256 networkFeeUSDCents; // good +``` + +### Types + +- If you are storing an address and know/expect it to be of a type(or interface), make the variable that type. This more clearly documents the behavior of this variable than the `address` type and often leads to less casting code whenever the address is used. + +### Structs + +- All structs should be packed to have the lowest memory footprint to reduce gas usage. Even structs that will never be written to storage should be packed. + - A contract can be considered a struct; it should also be packed to reduce gas cost. +- Structs should contain struct packing comments to clearly indicate the storage slot layout + - Using the exact characters from the example below will ensure visually appealing struct packing comments. + - Notice there is no line on the unpacked last `fee` item. +- Struct should contain comments, clearly indicating the denomination of values e.g. 0.01 USD if the variable name doesn’t already do that (which it should). + - Simple tool that could help packing structs and adding comments: https://github.com/RensR/Spack + +```solidity +/// @dev Struct to hold the fee configuration for a fee token, same as the FeeTokenConfig but with +/// token included so that an array of these can be passed in to setFeeTokenConfig to set the mapping +struct FeeTokenConfigArgs { + address token; // ────────────╮ Token address + uint32 networkFeeUSD; // │ Flat network fee to charge for messages, multiples of 0.01 USD + // │ multiline comments should work like this. More fee info + uint64 gasMultiplier; // ─────╯ Price multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost + uint64 premiumMultiplier; // ─╮ Multiplier for fee-token-specific premiums + bool enabled; // ─────────────╯ Whether this fee token is enabled + uint256 fee; // The flat fee the user pays in juels +} +``` +## Functions + +### Naming + +- Function names should start with imperative verbs, not nouns or other tenses. + - `requestData` not `dataRequest` + - `approve` not `approved` + - `getFeeParameters` not `feeParameters` + +- Prefix your public getters with `get` and your public setters with `set`. + - `getConfig` and `setConfig`. + +### Return Values + +- If an address is cast as a contract type, return the type, do not cast back to the address type. + This prevents the consumer of the method signature from having to cast again, but presents an equivalent API for off-chain APIs. + Additionally, it is a more declarative API, providing more context if we return a type. + +## Modifiers + +- Only extract a modifier once a check is duplicated in multiple places. Modifiers arguably hurt readability, so we have found that they are not worth extracting until there is duplication. +- Modifiers should be treated as if they are view functions. They should not change state, only read it. While it is possible to change state in a modifier, it is unconventional and surprising. +- Modifiers tend to bloat contract size because the code is duplicated wherever the modifier is used. + +## Events + +- Events should only be triggered on state changes. If the value is set but not changed, we prefer avoiding a log emission indicating a change. (e.g. Either do not emit a log, or name the event `ConfigSet` instead of `ConfigUpdated`.) +- Events should be emitted for all state changes, not emitting should be an exception +- When possible event names should correspond to the method they are in or the action that is being taken. Events preferably follow the format , where the action performed is the past tense of the imperative verb in the method name. e.g. calling `setConfig` should emit an event called `ConfigSet`, not `ConfigUpdated` in a method named `setConfig`. + + +### Expose Errors + +It is common to call a contract and then check the call succeeded: + +```solidity +(bool success, ) = to.call(data); +require(success, "Contract call failed"); +``` + +While this may look descriptive it swallows the error. Instead, bubble up the error: + +```solidity +bool success; +retData = new bytes(maxReturnBytes); +assembly { + // call and return whether we succeeded. ignore return data + // call(gas,addr,value,argsOffset,argsLength,retOffset,retLength) + success := call(gasLimit, target, 0, add(payload, 0x20), mload(payload), 0, 0) + + // limit our copy to maxReturnBytes bytes + let toCopy := returndatasize() + if gt(toCopy, maxReturnBytes) { + toCopy := maxReturnBytes + } + // Store the length of the copied bytes + mstore(retData, toCopy) + // copy the bytes from retData[0:_toCopy] + returndatacopy(add(retData, 0x20), 0, toCopy) +} +return (success, retData); +``` + +This will cost slightly more gas to copy the response into memory, but will ultimately make contract usage more understandable and easier to debug. Whether it is worth the extra gas is a judgement call you’ll have to make based on your needs. + +The original error will not be human-readable in an off-chain explorer because it is RLP hex encoded but is easily decoded with standard Solidity ABI decoding tools, or a hex to UTF-8 converter and some basic ABI knowledge. + + +## Interfaces + +- Interfaces should be as concise as reasonably possible. Break it up into smaller composable interfaces when that is sensible. + +## Dependencies + +- Prefer not reinventing the wheel, especially if there is an Openzeppelin wheel. +- The `shared` folder can be treated as a first party dependency and it is recommend to check if some functionality might already be in there before either writing it yourself or adding a third party dependency. +- When we have reinvented the wheel already (like with ownership), it is OK to keep using these contracts. If there are clear benefits of using another standard like OZ, we can deprecate the custom implementation and start using the new standard in all new projects. Migration will not be required unless there are serious issues with the old implementation. +- When the decision is made to use a new standard, it is no longer allowed to use the old standard for new projects. + +### Vendor dependencies + +- That’s it, vendor your Solidity dependencies. Supply chain attacks are all the rage these days. There is not yet a silver bullet for best way to vendor, it depends on the size of your project and your needs. You should be as explicit as possible about where the code comes from and make sure that this is enforced in some way; e.g. reference a hash. Some options: + - NPM packages work for repos already in the JavaScript ecosystem. If you go this route you should lock to a hash of the repo or use a proxy registry like GitHub Packages. + - Copy and paste the code into a `vendor` directory. Record attribution of the code and license in the repo along with the commit or version that you pulled the code from. + - Foundry uses git submodules for its dependencies. We only use the `forge-std` lib through submodules, we don’t import any non-Foundry-testing code through this method. + + +## Common Behaviors + +### Transferring Ownership + +- When transferring control, whether it is of a token or a role in a contract, prefer "safe ownership" transfer patterns where the recipient must accept ownership. This avoids accidentally burning the control. This is also inline with the secure pattern of [prefer pull over push](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls). + +### Call with Exact Gas + +- `call` accepts a gas parameter, but that parameter is a ceiling on gas usage. If a transaction does not have enough gas, `call` will simply provide as much gas as it safely can. This is unintuitive and can lead to transactions failing for unexpected reasons. We have [an implementation of `callWithExactGas`](https://github.com/smartcontractkit/chainlink/blob/075f3e2caf61b8685d2dc78714f1ee39764fda17/contracts/src/v0.8/KeeperRegistry.sol#L792) to ensure the precise gas amount requested is provided. + +### Sending tokens + +- Prefer [ERC20.safeTransfer](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#SafeERC20) over ERC20.transfer + +### Gas golfing + +- Golf your code. Make it cheap, within reason. + - Focus on the hot path +- Most of the cost of executing Solidity is related to reading/writing storage +- Calling other contracts will also be costly +- Common types to safely use are + - uint40 for timestamps (or uint32 if you really need the space) + - uint96 for link, as there are only 1b link tokens +- prefer `++i` over `i++` +- If you’re unsure about golfing, ask in the #tech-solidity channel + +## Testing + +- Test using Foundry. +- Aim for at least 90% *useful* coverage as a baseline, but (near) 100% is achievable in Solidity. Always 100% test the critical path. + - Make sure to test for each event emitted + - Test each reverting path +- Consider fuzzing, start with stateless (very easy in Foundry) and if that works, try stateful fuzzing. +- Consider fork testing if applicable + +### Foundry + +- Create a Foundry profile for each project folder in `foundry.toml` +- Foundry tests live in the project folder in `src`, not in the `contracts/test/` folder +- Set the block number and timestamp. It is preferred to set these values to some reasonable value close to reality. +- There should be no code between `vm.expectEmit`/`vm.expectRevert` and the function call + +## Picking a Pragma + +- If a contract or library is expected to be imported by outside parties then the pragma should be kept as loose as possible without sacrificing safety. We publish versions for every minor semver version of Solidity, and maintain a corresponding set of tests for each published version. + - Examples: libraries, interfaces, abstract contracts, and contracts expected to be inherited from +- Otherwise, Solidity contracts should have a pragma which is locked to a specific version. + - Example: Most concrete contracts. +- Avoid changing pragmas after audit. Unless there is a bug that has affects your contract, then you should try to stick to a known good pragma. In practice this means we typically only support one (occasionally two) pragma for any “major”(minor by semver naming) Solidity version. +- The current advised pragma is `0.8.19` or higher, lower versions should be avoided when starting a new project. Newer versions can be considered. +- All contracts should have a SPDX license identifier. If unsure about which one to pick, please consult with legal. Most older contracts have been MIT, but some of the newer products have been using BUSL-1.1 + + +## Versioning + +Contracts should implement the following interface + +```solidity +interface ITypeAndVersion { + function typeAndVersion() external pure returns (string memory); +} +``` + +Here are some examples of what this should look like: + +```solidity +contract AccessControlledFoo is Foo { + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "AccessControlledFoo 1.0.0"; +} + +contract OffchainAggregator is ITypeAndVersion { + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "OffchainAggregator 1.0.0"; + + function getData() public returns(uint256) { + return 4; + } +} + +// Next version of Aggregator contract +contract SuperDuperAggregator is ITypeAndVersion { + /// This is a new contract that has not been released yet, so we + /// add a `-dev` suffix to the typeAndVersion. + + // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables + string public constant override typeAndVersion = "SuperDuperAggregator 1.1.0-dev"; + + function getData() public returns(uint256) { + return 5; + } +} +``` + +All contracts will expose a `typeAndVersion` constant. +The string has the following format: `-` with the `-dev` part only being applicable to contracts that have not been fully released. +Try to fit it into 32 bytes to keep impact on contract sizes minimal. +Solhint will complain about a public constant variable that isn’t FULL_CAPS without the solhint-disable comment. + + + + + + + + + + +# Rules + +All rules have a `rule` tag which indicated how the rule is enforced. + + +## Comments + +- Comments should be in the `//` (default) or `///` (natspec) format, not the `/* */` format. + - rule: `tbd` +- Comments should follow [NatSpec](https://docs.soliditylang.org/en/latest/natspec-format.html) + - rule: `tbd` + +## Imports + +- Imports should always be explicit + - rule: `no-global-import` +- Imports have follow the following format: + - rule: `tbd` + +```solidity +import {IInterface} from "../interfaces/IInterface.sol"; + +import {AnythingElse} from "../code/AnythingElse.sol"; + +import {ThirdPartyCode} from "../../vendor/ThirdPartyCode.sol"; +``` + +- An example would be + +```solidity +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {IPool} from "../interfaces/pools/IPool.sol"; + +import {AggregateRateLimiter} from "../AggregateRateLimiter.sol"; +import {Client} from "../libraries/Client.sol"; + +import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; +``` + +## Variables + +### Visibility + +All contract variables should be private by default. Getters should be explicitly written and documented when you want to expose a variable publicly. +Whether a getter function reads from storage, a constant, or calculates a value from somewhere else, that’s all implementation details that should not be exposed to the consumer by casing or other conventions. + +rule: tbd + +### Naming and Casing + +- Storage variables prefixed with an `s_` to make it clear that they live in storage and are expensive to read and write: `s_variableName`. They should always be private, and you should write explicit getters if you want to expose a storage variable. + - rule: `chainlink-solidity/prefix-storage-variables-with-s-underscore` +- Immutable variables should be prefixed with an `i_` to make it clear that they are immutable. E.g. `i_decimalPlaces`. They should always be private, and you should write explicit getters if you want to expose an immutable variable. + - rule: `chainlink-solidity/prefix-immutable-variables-with-i` +- Internal/private constants should be all caps with underscores: `FOO_BAR`. Like other contract variables, constants should not be public. Create getter methods if you want to publicly expose constants. + - rule: `chainlink-solidity/all-caps-constant-storage-variables` +- Explicitly declare variable size: `uint256` not just `uint`. In addition to being explicit, it matches the naming used to calculate function selectors. + - rule: `explicit-types` +- Mapping should always be named if Solidity allows it (≥0.8.18) + - rule: `tbd` + + +## Functions + +### Visibility + +- Method visibility should always be explicitly declared. + - rule: `state-visibility` + +- Prefix private and internal methods with an underscore. There should never be a publicly callable method starting with an underscore. + - E.g. `_setOwner(address)` + - rule: `chainlink-solidity/prefix-internal-functions-with-underscore` + +### Return values + +- Returned values should always be explicit. Using named return values and then returning with an empty return should be avoided + - rule: `chainlink-solidity/explicit-returns` + +```solidity +// Bad +function getNum() external view returns (uint64 num) { + num = 4; + return; +} + +// Good +function getNum() external view returns (uint64 num) { + num = 4; + return num; +} + +// Good +function getNum() external view returns (uint64 num) { + return 4; +} +``` + +## Errors + +Use [custom errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) instead of emitting strings. This saves contract code size and simultaneously provides more informative error messages. + +rule: `custom-errors` + +## Interfaces + +Interfaces should be named `IFoo` instead of `FooInterface`. This follows the patterns of popular [libraries like OpenZeppelin’s](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L9). + +rule: `tbd` \ No newline at end of file From 41025f45fcf3c5e7135d03f0e77dfb66ee770163 Mon Sep 17 00:00:00 2001 From: Tate Date: Fri, 10 Nov 2023 08:37:12 -0700 Subject: [PATCH 5/6] [TT-689] Send slack notification on test base image build/publish failure (#11248) --- .github/workflows/integration-tests-publish.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/integration-tests-publish.yml b/.github/workflows/integration-tests-publish.yml index 60f67f03574..a66ea612281 100644 --- a/.github/workflows/integration-tests-publish.yml +++ b/.github/workflows/integration-tests-publish.yml @@ -37,3 +37,11 @@ jobs: QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} + - name: Notify Slack + if: failure() + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} + with: + channel-id: "#team-test-tooling-internal" + slack-message: ":x: :mild-panic-intensifies: Publish Integration Test Image failed: ${{ job.html_url }}\n${{ format('https://github.com/smartcontractkit/chainlink/actions/runs/{0}', github.run_id) }}" From 4a6f2fe749e19d92be013b9b8f59cefe829b07eb Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Fri, 10 Nov 2023 19:52:39 +0100 Subject: [PATCH 6/6] turn off 0.6 & 0.7 tests (#11132) * turn off 0.6 tests * reduce splits and mark other tests as sow * add modification protection * rm 0.7 tests --- .github/workflows/solidity.yml | 18 + contracts/ci.json | 22 +- contracts/test/v0.6/AggregatorFacade.test.ts | 167 - contracts/test/v0.6/BasicConsumer.test.ts | 243 -- contracts/test/v0.6/BlockhashStore.test.ts | 285 -- contracts/test/v0.6/Chainlink.test.ts | 186 - contracts/test/v0.6/ChainlinkClient.test.ts | 376 -- contracts/test/v0.6/CheckedMath.test.ts | 183 - .../v0.6/DeviationFlaggingValidator.test.ts | 296 -- contracts/test/v0.6/Flags.test.ts | 405 -- contracts/test/v0.6/FluxAggregator.test.ts | 3252 -------------- contracts/test/v0.6/Median.test.ts | 237 - contracts/test/v0.6/Owned.test.ts | 84 - contracts/test/v0.6/SignedSafeMath.test.ts | 187 - .../v0.6/SimpleReadAccessController.test.ts | 250 -- .../v0.6/SimpleWriteAccessController.test.ts | 214 - contracts/test/v0.6/VRFD20.test.ts | 303 -- contracts/test/v0.7/AggregatorProxy.test.ts | 743 ---- .../test/v0.7/AuthorizedForwarder.test.ts | 444 -- contracts/test/v0.7/Chainlink.test.ts | 186 - contracts/test/v0.7/ChainlinkClient.test.ts | 454 -- .../CompoundPriceFlaggingValidator.test.ts | 471 -- contracts/test/v0.7/ConfirmedOwner.test.ts | 136 - contracts/test/v0.7/KeeperRegistry1_1.test.ts | 1725 -------- contracts/test/v0.7/Operator.test.ts | 3819 ----------------- contracts/test/v0.7/OperatorFactory.test.ts | 293 -- .../v0.7/StalenessFlaggingValidator.test.ts | 632 --- .../v0.7/UpkeepRegistrationRequests.test.ts | 603 --- contracts/test/v0.7/VRFD20.test.ts | 303 -- contracts/test/v0.7/gasUsage.test.ts | 178 - 30 files changed, 27 insertions(+), 16668 deletions(-) delete mode 100644 contracts/test/v0.6/AggregatorFacade.test.ts delete mode 100644 contracts/test/v0.6/BasicConsumer.test.ts delete mode 100644 contracts/test/v0.6/BlockhashStore.test.ts delete mode 100644 contracts/test/v0.6/Chainlink.test.ts delete mode 100644 contracts/test/v0.6/ChainlinkClient.test.ts delete mode 100644 contracts/test/v0.6/CheckedMath.test.ts delete mode 100644 contracts/test/v0.6/DeviationFlaggingValidator.test.ts delete mode 100644 contracts/test/v0.6/Flags.test.ts delete mode 100644 contracts/test/v0.6/FluxAggregator.test.ts delete mode 100644 contracts/test/v0.6/Median.test.ts delete mode 100644 contracts/test/v0.6/Owned.test.ts delete mode 100644 contracts/test/v0.6/SignedSafeMath.test.ts delete mode 100644 contracts/test/v0.6/SimpleReadAccessController.test.ts delete mode 100644 contracts/test/v0.6/SimpleWriteAccessController.test.ts delete mode 100644 contracts/test/v0.6/VRFD20.test.ts delete mode 100644 contracts/test/v0.7/AggregatorProxy.test.ts delete mode 100644 contracts/test/v0.7/AuthorizedForwarder.test.ts delete mode 100644 contracts/test/v0.7/Chainlink.test.ts delete mode 100644 contracts/test/v0.7/ChainlinkClient.test.ts delete mode 100644 contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts delete mode 100644 contracts/test/v0.7/ConfirmedOwner.test.ts delete mode 100644 contracts/test/v0.7/KeeperRegistry1_1.test.ts delete mode 100644 contracts/test/v0.7/Operator.test.ts delete mode 100644 contracts/test/v0.7/OperatorFactory.test.ts delete mode 100644 contracts/test/v0.7/StalenessFlaggingValidator.test.ts delete mode 100644 contracts/test/v0.7/UpkeepRegistrationRequests.test.ts delete mode 100644 contracts/test/v0.7/VRFD20.test.ts delete mode 100644 contracts/test/v0.7/gasUsage.test.ts diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 069d9de45ab..5699657fa5d 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -20,11 +20,29 @@ jobs: - uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1 id: changes with: + list-files: "csv" filters: | src: - 'contracts/**/*' - '.github/workflows/solidity.yml' - '.github/workflows/solidity-foundry.yml' + old_sol: + - 'contracts/src/v0.4/**/*' + - 'contracts/src/v0.5/**/*' + - 'contracts/src/v0.6/**/*' + - 'contracts/src/v0.7/**/*' + + + - name: Fail if read-only files have changed + if: ${{ steps.changes.outputs.old_sol == 'true' }} + run: | + echo "One or more read-only Solidity file(s) has changed." + for file in ${{ steps.changes.outputs.old_sol_files }}; do + echo "$file was changed" + done + exit 1 + + prepublish-test: needs: [changes] diff --git a/contracts/ci.json b/contracts/ci.json index dd1fbb0a88f..f1eff76513c 100644 --- a/contracts/ci.json +++ b/contracts/ci.json @@ -6,23 +6,19 @@ "dir": "cross-version", "numOfSplits": 1 }, - { - "dir": "v0.6", - "numOfSplits": 1 - }, - { - "dir": "v0.7", - "numOfSplits": 1 - }, { "dir": "v0.8", - "numOfSplits": 8, + "numOfSplits": 6, "slowTests": [ - "Keeper", - "Cron.test", - "CronUpkeep.test", + "Cron", + "CronUpkeep", + "VRFSubscriptionBalanceMonitor", "EthBalanceMonitor", - "CanaryUpkeep" + "KeeperRegistrar", + "KeeperRegistry1_2", + "KeeperRegistry1_3", + "KeeperRegistry2_0", + "KeeperRegistry2_1" ] } ] diff --git a/contracts/test/v0.6/AggregatorFacade.test.ts b/contracts/test/v0.6/AggregatorFacade.test.ts deleted file mode 100644 index f85c24ae6cd..00000000000 --- a/contracts/test/v0.6/AggregatorFacade.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { ethers } from 'hardhat' -import { numToBytes32, publicAbi } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, Signer } from 'ethers' -import { getUsers } from '../test-helpers/setup' -import { convertFufillParams, decodeRunRequest } from '../test-helpers/oracle' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' - -let defaultAccount: Signer - -let linkTokenFactory: ContractFactory -let aggregatorFactory: ContractFactory -let oracleFactory: ContractFactory -let aggregatorFacadeFactory: ContractFactory - -before(async () => { - const users = await getUsers() - - defaultAccount = users.roles.defaultAccount - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - defaultAccount, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.4/Aggregator.sol:Aggregator', - defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) -}) - -describe('AggregatorFacade', () => { - const jobId1 = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000001' - const previousResponse = numToBytes32(54321) - const response = numToBytes32(67890) - const decimals = 18 - const description = 'LINK / USD: Historic Aggregator Facade' - - let link: Contract - let aggregator: Contract - let oc1: Contract - let facade: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(defaultAccount).deploy() - oc1 = await oracleFactory.connect(defaultAccount).deploy(link.address) - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(link.address, 0, 1, [oc1.address], [jobId1]) - facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, decimals, description) - - let requestTx = await aggregator.requestRateUpdate() - let receipt = await requestTx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - await oc1.fulfillOracleRequest( - ...convertFufillParams(request, previousResponse), - ) - requestTx = await aggregator.requestRateUpdate() - receipt = await requestTx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await oc1.fulfillOracleRequest(...convertFufillParams(request, response)) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(facade, [ - 'aggregator', - 'decimals', - 'description', - 'getAnswer', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'version', - ]) - }) - - describe('#constructor', () => { - it('uses the decimals set in the constructor', async () => { - bigNumEquals(decimals, await facade.decimals()) - }) - - it('uses the description set in the constructor', async () => { - assert.equal(description, await facade.description()) - }) - - it('sets the version to 2', async () => { - bigNumEquals(2, await facade.version()) - }) - }) - - describe('#getAnswer/latestAnswer', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(response, await facade.latestAnswer()) - const latestRound = await facade.latestRound() - bigNumEquals(response, await facade.getAnswer(latestRound)) - }) - }) - - describe('#getTimestamp/latestTimestamp', () => { - it('pulls the timestamp from the aggregator', async () => { - const height = await aggregator.latestTimestamp() - assert.notEqual('0', height.toString()) - bigNumEquals(height, await facade.latestTimestamp()) - const latestRound = await facade.latestRound() - bigNumEquals( - await aggregator.latestTimestamp(), - await facade.getTimestamp(latestRound), - ) - }) - }) - - describe('#getRoundData', () => { - it('assembles the requested round data', async () => { - const previousId = (await facade.latestRound()).sub(1) - const round = await facade.getRoundData(previousId) - bigNumEquals(previousId, round.roundId) - bigNumEquals(previousResponse, round.answer) - bigNumEquals(await facade.getTimestamp(previousId), round.startedAt) - bigNumEquals(await facade.getTimestamp(previousId), round.updatedAt) - bigNumEquals(previousId, round.answeredInRound) - }) - - it('returns zero data for non-existing rounds', async () => { - const roundId = 13371337 - await evmRevert(facade.getRoundData(roundId), 'No data present') - }) - }) - - describe('#latestRoundData', () => { - it('assembles the requested round data', async () => { - const latestId = await facade.latestRound() - const round = await facade.latestRoundData() - bigNumEquals(latestId, round.roundId) - bigNumEquals(response, round.answer) - bigNumEquals(await facade.getTimestamp(latestId), round.startedAt) - bigNumEquals(await facade.getTimestamp(latestId), round.updatedAt) - bigNumEquals(latestId, round.answeredInRound) - }) - - describe('when there is no latest round', () => { - beforeEach(async () => { - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(link.address, 0, 1, [oc1.address], [jobId1]) - facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, decimals, description) - }) - - it('assembles the requested round data', async () => { - await evmRevert(facade.latestRoundData(), 'No data present') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/BasicConsumer.test.ts b/contracts/test/v0.6/BasicConsumer.test.ts deleted file mode 100644 index ce0b7c643e2..00000000000 --- a/contracts/test/v0.6/BasicConsumer.test.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { ethers } from 'hardhat' -import { toWei, increaseTime5Minutes, toHex } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, constants, Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import { - convertFufillParams, - decodeRunRequest, - encodeOracleRequest, - RunRequest, -} from '../test-helpers/oracle' -import cbor from 'cbor' -import { makeDebug } from '../test-helpers/debug' - -const d = makeDebug('BasicConsumer') -let basicConsumerFactory: ContractFactory -let oracleFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - roles.oracleNode, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('BasicConsumer', () => { - const specId = '0x4c7b7ffb66b344fbaa64995af81e355a'.padEnd(66, '0') - const currency = 'USD' - const payment = toWei('1') - let link: Contract - let oc: Contract - let cc: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await oracleFactory.connect(roles.oracleNode).deploy(link.address) - cc = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address, specId) - }) - - it('has a predictable gas price [ @skip-coverage ]', async () => { - const rec = await ethers.provider.getTransactionReceipt( - cc.deployTransaction.hash ?? '', - ) - assert.isBelow(rec.gasUsed?.toNumber() ?? -1, 1750000) - }) - - describe('#requestEthereumPrice', () => { - describe('without LINK', () => { - it('reverts', async () => - await expect(cc.requestEthereumPrice(currency, payment)).to.be.reverted) - }) - - describe('with LINK', () => { - beforeEach(async () => { - await link.transfer(cc.address, toWei('1')) - }) - - it('triggers a log event in the Oracle contract', async () => { - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - const log = receipt?.logs?.[3] - assert.equal(log?.address.toLowerCase(), oc.address.toLowerCase()) - - const request = decodeRunRequest(log) - const expected = { - path: ['USD'], - get: 'https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY', - } - - assert.equal(toHex(specId), request.specId) - bigNumEquals(toWei('1'), request.payment) - assert.equal(cc.address.toLowerCase(), request.requester.toLowerCase()) - assert.equal(1, request.dataVersion) - assert.deepEqual(expected, cbor.decodeFirstSync(request.data)) - }) - - it('has a reasonable gas cost [ @skip-coverage ]', async () => { - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - assert.isBelow(receipt?.gasUsed?.toNumber() ?? -1, 140000) - }) - }) - }) - - describe('#fulfillOracleRequest', () => { - const response = ethers.utils.formatBytes32String('1,000,000.00') - let request: RunRequest - - beforeEach(async () => { - await link.transfer(cc.address, toWei('1')) - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt?.logs?.[3]) - }) - - it('records the data given to it by the oracle', async () => { - await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const currentPrice = await cc.currentPrice() - assert.equal(currentPrice, response) - }) - - it('logs the data given to it by the oracle', async () => { - const tx = await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - const receipt = await tx.wait() - - assert.equal(2, receipt?.logs?.length) - const log = receipt?.logs?.[1] - - assert.equal(log?.topics[2], response) - }) - - describe('when the consumer does not recognize the request ID', () => { - let otherRequest: RunRequest - - beforeEach(async () => { - // Create a request directly via the oracle, rather than through the - // chainlink client (consumer). The client should not respond to - // fulfillment of this request, even though the oracle will faithfully - // forward the fulfillment to it. - const args = encodeOracleRequest( - toHex(specId), - cc.address, - basicConsumerFactory.interface.getSighash('fulfill'), - 43, - constants.HashZero, - ) - const tx = await link.transferAndCall(oc.address, 0, args) - const receipt = await tx.wait() - - otherRequest = decodeRunRequest(receipt?.logs?.[2]) - }) - - it('does not accept the data provided', async () => { - d('otherRequest %s', otherRequest) - await oc - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(otherRequest, response)) - - const received = await cc.currentPrice() - - assert.equal(ethers.utils.parseBytes32String(received), '') - }) - }) - - describe('when called by anyone other than the oracle contract', () => { - it('does not accept the data provided', async () => { - await evmRevert( - cc.connect(roles.oracleNode).fulfill(request.requestId, response), - ) - - const received = await cc.currentPrice() - assert.equal(ethers.utils.parseBytes32String(received), '') - }) - }) - }) - - describe('#cancelRequest', () => { - const depositAmount = toWei('1') - let request: RunRequest - - beforeEach(async () => { - await link.transfer(cc.address, depositAmount) - const tx = await cc.requestEthereumPrice(currency, payment) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('before 5 minutes', () => { - it('cant cancel the request', () => - evmRevert( - cc - .connect(roles.consumer) - .cancelRequest( - oc.address, - request.requestId, - request.payment, - request.callbackFunc, - request.expiration, - ), - )) - }) - - describe('after 5 minutes', () => { - it('can cancel the request', async () => { - await increaseTime5Minutes(ethers.provider) - - await cc - .connect(roles.consumer) - .cancelRequest( - oc.address, - request.requestId, - request.payment, - request.callbackFunc, - request.expiration, - ) - }) - }) - }) - - describe('#withdrawLink', () => { - const depositAmount = toWei('1') - - beforeEach(async () => { - await link.transfer(cc.address, depositAmount) - const balance = await link.balanceOf(cc.address) - bigNumEquals(balance, depositAmount) - }) - - it('transfers LINK out of the contract', async () => { - await cc.connect(roles.consumer).withdrawLink() - const ccBalance = await link.balanceOf(cc.address) - const consumerBalance = BigNumber.from( - await link.balanceOf(await roles.consumer.getAddress()), - ) - bigNumEquals(ccBalance, 0) - bigNumEquals(consumerBalance, depositAmount) - }) - }) -}) diff --git a/contracts/test/v0.6/BlockhashStore.test.ts b/contracts/test/v0.6/BlockhashStore.test.ts deleted file mode 100644 index 453b2eca3b1..00000000000 --- a/contracts/test/v0.6/BlockhashStore.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas -let blockhashStoreTestHelperFactory: ContractFactory - -type TestBlocks = { - num: number - rlpHeader: Uint8Array - hash: string -} - -const mainnetBlocks: TestBlocks[] = [ - { - num: 10000467, - rlpHeader: ethers.utils.arrayify( - '0xf90215a058ee3c05e880cb25a3db92b9f1479c5453690ca97f9bcbb18d21965d3213578ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ea674fdde714fd979de3edf0f56aa9716b898ec8a0a448355652812a7d518b5c979a15bba02cfe4576d8eb61e8b5731ecc37f2bec6a0049f25ed97f9ed9a9c8521ab39cd2c48438d1d18c84dcab5bf494c19595bd462a0b1169f28bdbe5dd61ebc20b7a459be9d7fa898f5a3ba5fed6d502d94b9a8101bb901001000008180000210000080010001080310e004800c3040000060484000010804088050044302a500240041040010012120840002400005092000808000640012081000880010008040200208000004050800400002244044006041040040010890040504020040008004222502000800220000021800006400802036500000000400014640d00020002110000001440000001509543802080004210004100de04744a2810000000032250080810000502210c04289480800000423080800004000a020220030203000020001000000042c00420090000008003308459020e010a01000200190900040e81000040040000020000a8044001000202010000600c087086c49cadb1b57839898538398909483984b9e845eb02fbf94505059452d65746865726d696e652d6575312d34a06d0287c21536fac432714bd3f3712ff1a7e409faf1b10edac9b9547da1d4f7b188930531280477460c', - ), - hash: '0x4a65bcdf3466a16740b74849cc10fc57d4acb24cce148665482812699a400464', - }, - { - num: 10000468, - rlpHeader: ethers.utils.arrayify( - '0xf9020da04a65bcdf3466a16740b74849cc10fc57d4acb24cce148665482812699a400464a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479404668ec2f57cc15c381b461b9fedab5d451c8f7fa0bcd4ddbb7125a5c06df96d862921dc0bba8664b3f759a233fe565a615c0ab3eaa0087ab379852c83e4b660de1668fc93333201ad0d233167ea6cef8bacaf5cba2aa0d81855037b2a6b56eba0c2ed129fb4102fb831b0baa187a0f6e0c155400f7855b9010080040040200000000010102081000000500040010408040800010110000000008000005808020000902021818000210000000000081100401000400014400001041008000020448800180128800008000200000420e01200000000000000011000001000020000208000b42200a0008000510200080200008c002018108010014030200000080000000002000010008000011008004003081000400080100803040080040300000002044080480000000000008080101000000050000000000840000002200040000a0080000442008006005502800000040008000890201002022402208002900020900000000080000100100201080000000003400000004887086d57541477ba839898548398968083989147845eb02fc28c73706964657230380b03ac53a076c676a0ab090b373b6242851a4beab7b8cdc9d3ebe211747a255b78c0278c42880ea13d40042dd1e6', - ), - hash: '0x00fd2589a272b85ffaf63223641571bf95891c936b7514ee4e87a593e52de7c9', - }, - { - num: 10000469, - rlpHeader: ethers.utils.arrayify( - '0xf90211a000fd2589a272b85ffaf63223641571bf95891c936b7514ee4e87a593e52de7c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347945a0b54d5dc17e0aadc383d2db43b0a0d3e029c4ca01b28d3b4e4d3442a9e6caed9f80b6a639bce6f3a283d4e004e6bb44e483ceeeba067c00d9067bc023b8fab8e3afd1bc0f2470f08003bdf9f167fbfeede2422ac4ea09d8b344d9ab1b7288f8c5e644c53b1a5288da6d6ee0e388ec76f291def48da15b90100c462095870a26a0804132e208110329710d459054558159c103208d44820002016108136199200061063699d8400254a22828c11b5512e3303c98ec7747cc02d00161880c2f2c580e806bccc04805190265f096601342058020a8324c277735d8202220412f03303201252a3000038883a4bb0010e6b004408306232150a84d110100d0c4b9d228022812602c05c801d20500d4ed10010ce2400428a96950a98050c00e603292a806c4983b25814880000440a23821191121996410c5110c949616c2066a4a0488087d4c226c14208042c00d609b5cc44051400219d93626818728612a9b18690e03c902014a900e0018828011494b80d4708799b0d8a83cace87086e64fefefb48839898558398968083986664845eb02fc7906574682d70726f2d687a662d74303032a09f1918a362b55ebd072cc9548fb74f89301d41c2a1feb13c08a1c2c3cb0606d88810dfa530069367fb', - ), - hash: '0x325fde74e261fc483a16506bbc711b645b043ad24c7db0136845a1be262cf0c9', - }, - { - num: 10000470, - rlpHeader: ethers.utils.arrayify( - '0xf90215a0325fde74e261fc483a16506bbc711b645b043ad24c7db0136845a1be262cf0c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ea674fdde714fd979de3edf0f56aa9716b898ec8a020647cfa35563093442a12d80bf2bacb83da1de8340366677f3822591a334ccea066ad7285f6c5b6407f62c6b65a83aeaaa71ad9a97c2bb15139140f2dbb60f7e0a0c0e633851d0b5ce661ecc054517425e82425fcc6170db9693e5b5a6dd5ef6d6bb90100c0c000c1520708182080c8e461891c2402800a80d44a00034259414012012a5006a1416331181504902044960808f1129018800311621e920886804693749b10542400142e984580ccba634881c4156962200ecfb004000005468db44842781c59923110262660802315006106388b028412c42c000820c508e66b7851fa68002008144cd7860cd884280802915163399c168d5a11b0649486084110149469a1e61c31134204b903206566885180bc0426c0c6c0a4d408e182242f08180d204c624a040248425041ac028010d088820402ba4bd38c2d1215829300543465603822110500811290490148049300040e000c280086a09e8100089818ce480a887e87086c4965bf3c8a839898568398705c839847d2845eb02fe994505059452d65746865726d696e652d6575312d35a09d8ae288d0eede524f3ef5e6cfcc5ba07f380bc695bb71578a7b91cfa517071b8859d0976006378e52', - ), - hash: '0x5cf096dfd1fc2d2947a96fdec5377ab7beaa0eb00c80728a3b96f0864cec506a', - }, -] - -const maticBlocks: TestBlocks[] = [ - { - num: 10000467, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0212093b89337e6741aca0c6c1cbfc64b56155bdcc3623fa9bcbfa0498fa135aba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0ac0ec242516093308f7a2cc6965f38835eb3db69cba93401daef672666a3aefea0d06985e9ae671d22fb4b3d73ef4e074a448f66b4769ad8d59c3b8aef1ede15e2a00076d4897a88e08c25ca12c558f622d03d532d8b674e8c6b9847000b98dbe480b90100040000000200000000000000000000000000000000000000100000000000400000000000000000000000002800000000100080000000000000000001000000400000000000000000000000080002008000000000000000000021000000000000000000000200000000001000000008000000000008000001800800100000000000010000000010100000800000000000001000000200100000000000000000002000000004000000000000000080010000000000000000200000000000000040000000420000000000010000000000000004040004000000001000001000200100100080000000000400000000100000100000000000000000000000021000000e839898538401312d008302e54b84600df884b861d78301091883626f7288676f312e31352e35856c696e7578000000000000000003eb49c29f5facd767206f64b8a5c9b325bced5c9156f489c6281c68eddc9e5f2ef1177c02a99d8ab6216dcf2879eefddfc27c75ffa9ef6a2185ce9983d1434901a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x6c3b869ca26fece236545f7914d8249651d729852dc1445f53a94d5a59cdc9da', - }, - { - num: 10000468, - rlpHeader: ethers.utils.arrayify( - '0xf9025da06c3b869ca26fece236545f7914d8249651d729852dc1445f53a94d5a59cdc9daa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a0e05ccfb09764e5cd6811ef2c2616d4a57f187be84235e2569c9b8d70489f1a44a0aea27aed2ad1d553e30501e6fe47fee0842c3b7ce5867e579b29975f02ec4282b90100008000100000000000000400080800000009000000010020000000000800000000000000080000000000000000000000000080000080000820400000000000000000200000000000000000080000008000200000200000000003009000020000000200000010000000001000000000000000000000000000800040100000000000000000000010000000100100000000000000000102004000000040000000002000000008000000000000000000000000000000200000000000000000000041000000020000080001010000000000000008000000110000001001800020000000100000000001400000040000000000000010010000000001000000001000000e839898548401312d00830494ed84600df886b861d78301091883626f7288676f312e31352e35856c696e75780000000000000000aa8ed86143b48b6aa7170d2083c3a7be31cbdfdc40f39badb8747f4c2198279a71c0d3eb5d25f3b7da5a48b887f61e22fe0baa692aa03807ad12f6fe25af087e00a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x258aa48bde013579fbfef2e222bcc222b1f57bf898a71c623f9024229c9f6111', - }, - { - num: 10000469, - rlpHeader: ethers.utils.arrayify( - '0xf9025aa0258aa48bde013579fbfef2e222bcc222b1f57bf898a71c623f9024229c9f6111a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e839898558401312d008084600df888b861d78301091883626f7288676f312e31352e35856c696e75780000000000000000bd8668cc5d89583a7cc26fb96650e61f045ffe5248ae80c667ba7648df41e3d552060998ac151f2d15bd1b98f0a2a50c4281729a4c0aae4758a3bad280207c2901a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x611779767f1deb5a17723ec71d1b397b18a0fc9a40d282810a33bd6a0a5f46f9', - }, - { - num: 10000470, - rlpHeader: ethers.utils.arrayify( - '0xf9025aa0611779767f1deb5a17723ec71d1b397b18a0fc9a40d282810a33bd6a0a5f46f9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fa236c78bbe5939cc62985e32582c2158468a5b2b4dd02d514edb0bea95f0fd3a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e839898568401312d008084600df88ab861d78301091883626f7288676f312e31352e35856c696e75780000000000000000b617675c3b01e98319508130e1a583d57ce6b3a8a97fa2fbdaa33673cc6c609d6f7c361c833838f54b724d3a83cdd73e2398bb147970cd0b057865386cb08e1300a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x2edf2f5c5faa5046b2304f76c92096a25e7c4343a7b75c36b29e8e9755d93397', - }, -] - -// The following headers from Binance Smart Chain were retrieved using `go run -// binance.go`, where binance.go contains -// -// package main -// -// import ( -// "context" -// "fmt" -// "log" -// "math/big" -// "math/rand" -// "strings" -// -// "github.com/ethereum/go-ethereum/ethclient" -// "github.com/ethereum/go-ethereum/rlp" -// ) -// -// var tsBlockTemplate = ` -// { -// num: %d, -// rlpHeader: ethers.utils.arrayify( -// '0x%x', -// ), -// hash: '0x%x', -// }, -// ` -// -// func main() { -// client, err := ethclient.Dial("https://bsc-dataseed.binance.org/") -// if err != nil { -// log.Fatal(err) -// } -// -// header, err := client.HeaderByNumber(context.Background(), nil) -// if err != nil { -// log.Fatal(err) -// } -// topBlockNum := header.Number.Int64() -// numBlocks := int64(4) -// if topBlockNum < numBlocks { -// log.Fatalf("need at least %d consecutive blocks", numBlocks) -// } -// targetBlock := int64(rand.Intn(int(topBlockNum - numBlocks))) -// simulatedHeadBlock := targetBlock + numBlocks - 1 -// for blockNum := targetBlock; blockNum <= simulatedHeadBlock; blockNum++ { -// header, err := client.HeaderByNumber(context.Background(), big.NewInt(blockNum)) -// if err != nil { -// log.Fatal(err) -// } -// s, err := rlp.EncodeToBytes(header) -// if err != nil { -// log.Fatalf("could not encode header: got error %s from %v", err, header) -// } -// // fmt.Printf("header for block number %d: 0x%x\n", blockNum, s) -// fmt.Printf(strings.TrimLeft(tsBlockTemplate, "\n"), blockNum, s, header.Hash()) -// } -// } -const binanceBlocks: TestBlocks[] = [ - { - num: 1875651, - rlpHeader: ethers.utils.arrayify( - '0xf9025da029c26248bebbe0d0acb209d13ac9337c4b5c313696c031dd63b3cd16cbdc0c21a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794b8f7166496996a7da21cf1f1b04d9b3e26a3d077a03f962867b5e86191c3280bd52c4249587e08ddfa9851cea981fb7a5721c9157aa05924ae05d17347687ba81d093aee159ccc65cefc8314b0515ef921e553df05a2a089af99a7afa586e7d67062d051df4255304bb730f6d62fdd3bdb207f1513b23bb901000100000000000000000800000000000000000000000200000000000000800000000000000200100000000000000800000000000000000000000000000000000000000000000000800000140800000008201000001000000202000000001200000000002002020000000000000000080000000000000002000000001000000000000002000000008010000000000000000002040080008400280000c00000081000400000004000000010000000020000000000000000000000000000000000000001000210200000000000000000000800000000000000000000000000002010000004000000000001000000000000000000000800020000000000000000000002831c9ec38401c9c380830789c2845f9faab1b861d883010002846765746888676f312e31332e34856c696e7578000000000000003311ee6830f31dc9116d8a59178b539d91eb6811c1d533c4a59bf77262689c552218bb1eae9cb9d6bf6e1066bea78052c8767313ace71c919d02e70760bd255401a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0xe0a935b1e37420ac1d855215bdad4730a5ffe315eda287c6c18aa86c426ede74', - }, - { - num: 1875652, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0e0a935b1e37420ac1d855215bdad4730a5ffe315eda287c6c18aa86c426ede74a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c2be4ec20253b8642161bc3f444f53679c1f3d47a0dbf2d40cf5533b65ac9b5be35cac75f7b106244ba14ee476a832c28d46a53452a04f83b8a51d3e17b6a02a0caa79acc7597996c5b8c68dba12c33095ae086089eea02fa2642645b2de17227a6c18c3fa491f54b3bdfe8ac8e04924a33a005a0e9e61b901000100000100000000000008000000000000000000040000000000000000800000000000000000000000000000000800000800000000000400000000000020000040100080000000000000000800000000209000001000000200000000801000400800002002030000000000000100080000002000000002004000011000000002000100040000000000100000000000000000040100009000300000000000000002004000004000000000000000020000002000000010000000200000800000000001000280000000000000008000000000000000800000000000020000002000041000000000000001200020001000080000002a40020040000000000000000002831c9ec48401c9c38083044b40845f9faab4b861d883010002846765746888676f312e31332e34856c696e757800000000000000cfc02687b2394922055792a8e67dad566f6690de06b229d752433b2067207b5f43b9f3c63f91cea5a79bbfc51d9132b933a706ab504038a92f37d57af2bb6c2e01a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x629e5abcae42940e00d7b38aa7b2ecccfbab582cb7a0b2c3658c2dad8e66549d', - }, - { - num: 1875653, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0629e5abcae42940e00d7b38aa7b2ecccfbab582cb7a0b2c3658c2dad8e66549da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794ce2fd7544e0b2cc94692d4a704debef7bcb61328a0718e7db53041585a814d658c32c88fd550c2c6d200826020008925e0a0f7967fa000fbf842e492a47cc9786885783259a08aed71055e78216006408932515fd960a0c7ffeb2189b8fcde43733cf1958cdb1c38c44052cfbb41125382240c232a98f8b901000000000000000000000000000000000000000002000000000004000000000000000000010000000000000000000000000000000000000200000000004020200000010000000800000000208800000000201000000000000000080000000000000000002002220000000000000000080000000000000000000000001000000000100000000000080010000000000000000000040000000000000000000000000002000000000008000000004000000000000000000000200000000000000000000000000202000000000000000000000000000000000008000000000000002080001000000000000001000000000000000000080100000000000000000000000002831c9ec58401c9c38083025019845f9faab7b861d883010002846765746888676f312e31332e34856c696e7578000000000000008c3c7a5c83e930fbd9d14f83c9b3931f032f0f678919c35b8b32ca6dae9948950bfa326fae134fa234fa7b84c06bdc3f7c6d6414c2a266df1339e563be8bd9cc00a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0xae8574651adabfd0ca55e2cee0e2e639ced73ec1cc0a35debeeceee6943442a9', - }, - { - num: 1875654, - rlpHeader: ethers.utils.arrayify( - '0xf9025da0ae8574651adabfd0ca55e2cee0e2e639ced73ec1cc0a35debeeceee6943442a9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794d6caa02bbebaebb5d7e581e4b66559e635f805ffa02df6a1173c63ec0a8acc46c818670f030aece1154b9f3bbc70f46a8427dd8dd6a0fa8835c499682d8c90759ff9ea1c291048755b967e48880a0fc21d19ec034a59a0b4e22607cb105c04156044b3f98c2cecae1553b45aa9b6044c37573791a27576b901000200000008000000000001000000000000000000000020000000000000020000000000000000000000000000000000000000000000000040000000000220000000000000000400000000001802000000201000000000000000000000000000000000002002020000000000000000080000000000000000000000001000000000000000000000100000000000000000000000040000000000000000010200200002000400000000400000000200000000000000080000000000000000000008000000000200000000000000000000000000000000000000000000000000002000001000000000000001000000000000000000000000000000000008080000000002831c9ec68401c9c3808301e575845f9faabab861d883010002846765746888676f312e31332e34856c696e757800000000000000399e73b0e963ec029e815623a414aa852508a28dd9799a1bf4e2380c8db687a46cc5b6cc20352ae21e35cfd28124a32fcd49ac8fac5b03901b3e03963e4fff5801a00000000000000000000000000000000000000000000000000000000000000000880000000000000000', - ), - hash: '0x189990455c59a5dea78071df9a2008ede292ff0a062fc5c4c6ca35fbe476f834', - }, -] - -before(async () => { - personas = (await getUsers()).personas - blockhashStoreTestHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BlockhashStoreTestHelper.sol:BlockhashStoreTestHelper', - personas.Default, - ) -}) - -runBlockhashStoreTests(mainnetBlocks, 'Ethereum') -runBlockhashStoreTests(maticBlocks, 'Matic') -runBlockhashStoreTests(binanceBlocks, 'Binance Smart Chain') - -async function runBlockhashStoreTests( - blocks: TestBlocks[], - description: string, -) { - describe(`BlockhashStore (${description})`, () => { - let blockhashStoreTestHelper: Contract - - beforeEach(async () => { - blockhashStoreTestHelper = await blockhashStoreTestHelperFactory - .connect(personas.Default) - .deploy() - - const [lastBlock] = blocks.slice(-1) - await blockhashStoreTestHelper - .connect(personas.Default) - .godmodeSetHash(lastBlock.num, lastBlock.hash) - assert.strictEqual( - await blockhashStoreTestHelper.getBlockhash(lastBlock.num), - lastBlock.hash, - ) - }) - - it('getBlockhash reverts for unknown blockhashes', async () => { - await expect( - blockhashStoreTestHelper.getBlockhash(99999999), - ).to.be.revertedWith('blockhash not found in store') - }) - - it('storeVerifyHeader records valid blockhashes', async () => { - for (let i = blocks.length - 2; i >= 0; i--) { - assert.strictEqual( - ethers.utils.keccak256(blocks[i + 1].rlpHeader), - await blockhashStoreTestHelper.getBlockhash(blocks[i + 1].num), - ) - await blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(blocks[i].num, blocks[i + 1].rlpHeader) - assert.strictEqual( - await blockhashStoreTestHelper.getBlockhash(blocks[i].num), - blocks[i].hash, - ) - } - }) - - it('storeVerifyHeader rejects unknown headers', async () => { - const unknownBlock = blocks[0] - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(unknownBlock.num - 1, unknownBlock.rlpHeader), - ).to.be.revertedWith('header has unknown blockhash') - }) - - it('storeVerifyHeader rejects corrupted headers', async () => { - const [lastBlock] = blocks.slice(-1) - const modifiedHeader = new Uint8Array(lastBlock.rlpHeader) - modifiedHeader[137] += 1 - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .storeVerifyHeader(lastBlock.num - 1, modifiedHeader), - ).to.be.revertedWith('header has unknown blockhash') - }) - - it('store accepts recent block numbers', async () => { - await ethers.provider.send('evm_mine', []) - - const n = (await ethers.provider.getBlockNumber()) - 1 - await blockhashStoreTestHelper.connect(personas.Default).store(n) - - assert.equal( - await blockhashStoreTestHelper.getBlockhash(n), - (await ethers.provider.getBlock(n)).hash, - ) - }) - - it('store rejects future block numbers', async () => { - await expect( - blockhashStoreTestHelper.connect(personas.Default).store(99999999999), - ).to.be.revertedWith('blockhash(n) failed') - }) - - it('store rejects old block numbers', async () => { - for (let i = 0; i < 300; i++) { - await ethers.provider.send('evm_mine', []) - } - - await expect( - blockhashStoreTestHelper - .connect(personas.Default) - .store((await ethers.provider.getBlockNumber()) - 256), - ).to.be.revertedWith('blockhash(n) failed') - }) - - it('storeEarliest works', async () => { - for (let i = 0; i < 300; i++) { - await ethers.provider.send('evm_mine', []) - } - - await blockhashStoreTestHelper.connect(personas.Default).storeEarliest() - - const n = (await ethers.provider.getBlockNumber()) - 256 - assert.equal( - await blockhashStoreTestHelper.getBlockhash(n), - (await ethers.provider.getBlock(n)).hash, - ) - }) - }) -} diff --git a/contracts/test/v0.6/Chainlink.test.ts b/contracts/test/v0.6/Chainlink.test.ts deleted file mode 100644 index f3587dc30a7..00000000000 --- a/contracts/test/v0.6/Chainlink.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi, decodeDietCBOR, hexToBuf } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, providers, Signer } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { makeDebug } from '../test-helpers/debug' - -const debug = makeDebug('ChainlinkTestHelper') -let concreteChainlinkFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - concreteChainlinkFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ChainlinkTestHelper.sol:ChainlinkTestHelper', - roles.defaultAccount, - ) -}) - -describe('ChainlinkTestHelper', () => { - let ccl: Contract - let defaultAccount: Signer - - beforeEach(async () => { - defaultAccount = roles.defaultAccount - ccl = await concreteChainlinkFactory.connect(defaultAccount).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(ccl, [ - 'add', - 'addBytes', - 'addInt', - 'addStringArray', - 'addUint', - 'closeEvent', - 'setBuffer', - ]) - }) - - async function parseCCLEvent(tx: providers.TransactionResponse) { - const receipt = await tx.wait() - const data = receipt.logs?.[0].data - const d = debug.extend('parseCCLEvent') - d('data %s', data) - return ethers.utils.defaultAbiCoder.decode(['bytes'], data ?? '') - } - - describe('#close', () => { - it('handles empty payloads', async () => { - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, {}) - }) - }) - - describe('#setBuffer', () => { - it('emits the buffer', async () => { - await ccl.setBuffer('0xA161616162') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { a: 'b' }) - }) - }) - - describe('#add', () => { - it('stores and logs keys and values', async () => { - await ccl.add('first', 'word!!') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 'word!!' }) - }) - - it('handles two entries', async () => { - await ccl.add('first', 'uno') - await ccl.add('second', 'dos') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 'uno', - second: 'dos', - }) - }) - }) - - describe('#addBytes', () => { - it('stores and logs keys and values', async () => { - await ccl.addBytes('first', '0xaabbccddeeff') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = hexToBuf('0xaabbccddeeff') - assert.deepEqual(decoded, { first: expected }) - }) - - it('handles two entries', async () => { - await ccl.addBytes('first', '0x756E6F') - await ccl.addBytes('second', '0x646F73') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - const expectedFirst = hexToBuf('0x756E6F') - const expectedSecond = hexToBuf('0x646F73') - assert.deepEqual(decoded, { - first: expectedFirst, - second: expectedSecond, - }) - }) - - it('handles strings', async () => { - await ccl.addBytes('first', ethers.utils.toUtf8Bytes('apple')) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = ethers.utils.toUtf8Bytes('apple') - assert.deepEqual(decoded, { first: expected }) - }) - }) - - describe('#addInt', () => { - it('stores and logs keys and values', async () => { - await ccl.addInt('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addInt('first', 1) - await ccl.addInt('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addUint', () => { - it('stores and logs keys and values', async () => { - await ccl.addUint('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addUint('first', 1) - await ccl.addUint('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addStringArray', () => { - it('stores and logs keys and values', async () => { - await ccl.addStringArray('word', [ - ethers.utils.formatBytes32String('seinfeld'), - ethers.utils.formatBytes32String('"4"'), - ethers.utils.formatBytes32String('LIFE'), - ]) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { word: ['seinfeld', '"4"', 'LIFE'] }) - }) - }) -}) diff --git a/contracts/test/v0.6/ChainlinkClient.test.ts b/contracts/test/v0.6/ChainlinkClient.test.ts deleted file mode 100644 index bfd43d7f3f9..00000000000 --- a/contracts/test/v0.6/ChainlinkClient.test.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { - convertFufillParams, - decodeCCRequest, - decodeRunRequest, - RunRequest, -} from '../test-helpers/oracle' -import { decodeDietCBOR } from '../test-helpers/helpers' -import { evmRevert } from '../test-helpers/matchers' - -let concreteChainlinkClientFactory: ContractFactory -let emptyOracleFactory: ContractFactory -let getterSetterFactory: ContractFactory -let oracleFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - - concreteChainlinkClientFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ChainlinkClientTestHelper.sol:ChainlinkClientTestHelper', - roles.defaultAccount, - ) - emptyOracleFactory = await ethers.getContractFactory( - 'src/v0.6/tests/EmptyOracle.sol:EmptyOracle', - roles.defaultAccount, - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.5/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.4/Oracle.sol:Oracle', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('ChainlinkClientTestHelper', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let cc: Contract - let gs: Contract - let oc: Contract - let newoc: Contract - let link: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await oracleFactory.connect(roles.defaultAccount).deploy(link.address) - newoc = await oracleFactory - .connect(roles.defaultAccount) - .deploy(link.address) - gs = await getterSetterFactory.connect(roles.defaultAccount).deploy() - cc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - }) - - describe('#newRequest', () => { - it('forwards the information to the oracle contract through the link token', async () => { - const tx = await cc.publicNewRequest( - specId, - gs.address, - ethers.utils.toUtf8Bytes('requestedBytes32(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - - assert.equal(1, receipt.logs?.length) - const [jId, cbAddr, cbFId, cborData] = receipt.logs - ? decodeCCRequest(receipt.logs[0]) - : [] - const params = decodeDietCBOR(cborData ?? '') - - assert.equal(specId, jId) - assert.equal(gs.address, cbAddr) - assert.equal('0xed53e511', cbFId) - assert.deepEqual({}, params) - }) - }) - - describe('#chainlinkRequest(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#chainlinkRequestTo(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#cancelChainlinkRequest', () => { - let requestId: string - // a concrete chainlink attached to an empty oracle - let ecc: Contract - - beforeEach(async () => { - const emptyOracle = await emptyOracleFactory - .connect(roles.defaultAccount) - .deploy() - ecc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, emptyOracle.address) - - const tx = await ecc.publicRequest( - specId, - ecc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - requestId = (events?.[0]?.args as any).id - }) - - it('emits an event from the contract showing the run was cancelled', async () => { - const tx = await ecc.publicCancelRequest( - requestId, - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ) - const { events } = await tx.wait() - - assert.equal(1, events?.length) - assert.equal(events?.[0].event, 'ChainlinkCancelled') - assert.equal(requestId, (events?.[0].args as any).id) - }) - - it('throws if given a bogus event ID', async () => { - await evmRevert( - ecc.publicCancelRequest( - ethers.utils.formatBytes32String('bogusId'), - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ), - ) - }) - }) - - describe('#recordChainlinkFulfillment(modifier)', () => { - let request: RunRequest - - beforeEach(async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - const { logs } = await tx.wait() - - const event = logs && cc.interface.parseLog(logs[0]) - - assert.equal(1, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not an authorized node to fulfill requests', - ) - }) - }) - - describe('#fulfillChainlinkRequest(function)', () => { - let request: RunRequest - - beforeEach(async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes( - 'publicFulfillChainlinkRequest(bytes32,bytes32)', - ), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - const { logs } = await tx.wait() - const event = logs && cc.interface.parseLog(logs[0]) - - assert.equal(1, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args?.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not an authorized node to fulfill requests', - ) - }) - }) - - describe('#chainlinkToken', () => { - it('returns the Link Token address', async () => { - const addr = await cc.publicChainlinkToken() - assert.equal(addr, link.address) - }) - }) - - describe('#addExternalRequest', () => { - let mock: Contract - let request: RunRequest - - beforeEach(async () => { - mock = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - - const tx = await cc.publicRequest( - specId, - mock.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - await mock.publicAddExternalRequest(oc.address, request.requestId) - }) - - it('allows the external request to be fulfilled', async () => { - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - }) - - it('does not allow the same requestId to be used', async () => { - await evmRevert( - cc.publicAddExternalRequest(newoc.address, request.requestId), - ) - }) - }) -}) diff --git a/contracts/test/v0.6/CheckedMath.test.ts b/contracts/test/v0.6/CheckedMath.test.ts deleted file mode 100644 index 14520d9d9b9..00000000000 --- a/contracts/test/v0.6/CheckedMath.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MIT -// Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c9630526e24ba53d9647787588a19ffaa3dd65e1/test/math/SignedSafeMath.test.js - -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { BigNumber, constants, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let mathFactory: ContractFactory -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - mathFactory = await ethers.getContractFactory( - 'src/v0.6/tests/CheckedMathTestHelper.sol:CheckedMathTestHelper', - personas.Default, - ) -}) - -const int256Max = constants.MaxInt256 -const int256Min = constants.MinInt256 - -describe('CheckedMath', () => { - let math: Contract - - beforeEach(async () => { - math = await mathFactory.connect(personas.Default).deploy() - }) - - describe('#add', () => { - const a = BigNumber.from('1234') - const b = BigNumber.from('5678') - - it('is commutative', async () => { - const c1 = await math.add(a, b) - const c2 = await math.add(b, a) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('is commutative with big numbers', async () => { - const c1 = await math.add(int256Max, int256Min) - const c2 = await math.add(int256Min, int256Max) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('returns false when overflowing', async () => { - const c1 = await math.add(int256Max, 1) - const c2 = await math.add(1, int256Max) - - bigNumEquals(0, c1.result) - bigNumEquals(0, c2.result) - assert.isFalse(c1.ok) - assert.isFalse(c2.ok) - }) - - it('returns false when underflowing', async () => { - const c1 = await math.add(int256Min, -1) - const c2 = await math.add(-1, int256Min) - - bigNumEquals(0, c1.result) - bigNumEquals(0, c2.result) - assert.isFalse(c1.ok) - assert.isFalse(c2.ok) - }) - }) - - describe('#sub', () => { - const a = BigNumber.from('1234') - const b = BigNumber.from('5678') - - it('subtracts correctly if it does not overflow and the result is negative', async () => { - const c = await math.sub(a, b) - const expected = a.sub(b) - - bigNumEquals(expected, c.result) - assert.isTrue(c.ok) - }) - - it('subtracts correctly if it does not overflow and the result is positive', async () => { - const c = await math.sub(b, a) - const expected = b.sub(a) - - bigNumEquals(expected, c.result) - assert.isTrue(c.ok) - }) - - it('returns false on overflow', async () => { - const c = await math.sub(int256Max, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('returns false on underflow', async () => { - const c = await math.sub(int256Min, 1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) - - describe('#mul', () => { - const a = BigNumber.from('5678') - const b = BigNumber.from('-1234') - - it('is commutative', async () => { - const c1 = await math.mul(a, b) - const c2 = await math.mul(b, a) - - bigNumEquals(c1.result, c2.result) - assert.isTrue(c1.ok) - assert.isTrue(c2.ok) - }) - - it('multiplies by 0 correctly', async () => { - const c = await math.mul(a, 0) - - bigNumEquals(0, c.result) - assert.isTrue(c.ok) - }) - - it('returns false on multiplication overflow', async () => { - const c = await math.mul(int256Max, 2) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('returns false when the integer minimum is negated', async () => { - const c = await math.mul(int256Min, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) - - describe('#div', () => { - const a = BigNumber.from('5678') - const b = BigNumber.from('-5678') - - it('divides correctly', async () => { - const c = await math.div(a, b) - - bigNumEquals(a.div(b), c.result) - assert.isTrue(c.ok) - }) - - it('divides a 0 numerator correctly', async () => { - const c = await math.div(0, a) - - bigNumEquals(0, c.result) - assert.isTrue(c.ok) - }) - - it('returns complete number result on non-even division', async () => { - const c = await math.div(7000, 5678) - - bigNumEquals(1, c.result) - assert.isTrue(c.ok) - }) - - it('reverts when 0 is the denominator', async () => { - const c = await math.div(a, 0) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - - it('reverts on underflow with a negative denominator', async () => { - const c = await math.div(int256Min, -1) - - bigNumEquals(0, c.result) - assert.isFalse(c.ok) - }) - }) -}) diff --git a/contracts/test/v0.6/DeviationFlaggingValidator.test.ts b/contracts/test/v0.6/DeviationFlaggingValidator.test.ts deleted file mode 100644 index f79a8c7aa47..00000000000 --- a/contracts/test/v0.6/DeviationFlaggingValidator.test.ts +++ /dev/null @@ -1,296 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - validatorFactory = await ethers.getContractFactory( - 'src/v0.6/DeviationFlaggingValidator.sol:DeviationFlaggingValidator', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) -}) - -describe('DeviationFlaggingValidator', () => { - let validator: Contract - let flags: Contract - let ac: Contract - const flaggingThreshold = 10000 // 10% - const previousRoundId = 2 - const previousValue = 1000000 - const currentRoundId = 3 - const currentValue = 1000000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, flaggingThreshold) - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'THRESHOLD_MULTIPLIER', - 'flaggingThreshold', - 'flags', - 'isValid', - 'setFlagsAddress', - 'setFlaggingThreshold', - 'validate', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the arguments passed in', async () => { - assert.equal(flags.address, await validator.flags()) - bigNumEquals(flaggingThreshold, await validator.flaggingThreshold()) - }) - }) - - describe('#validate', () => { - describe('when the deviation is greater than the threshold', () => { - const currentValue = 1100010 - - it('does raises a flag for the calling address', async () => { - await expect( - validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ), - ) - .to.emit(flags, 'FlagRaised') - .withArgs(await personas.Nelly.getAddress()) - }) - - it('uses less than the gas allotted by the aggregator', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert(receipt) - if (receipt && receipt.gasUsed) { - assert.isAbove(receipt.gasUsed.toNumber(), 60000) - } - }) - }) - - describe('when the deviation is less than or equal to the threshold', () => { - const currentValue = 1100009 - - it('does raises a flag for the calling address', async () => { - await expect( - validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ), - ).to.not.emit(flags, 'FlagRaised') - }) - - it('uses less than the gas allotted by the aggregator', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert(receipt) - if (receipt && receipt.gasUsed) { - assert.isAbove(receipt.gasUsed.toNumber(), 24000) - } - }) - }) - - describe('when called with a previous value of zero', () => { - const previousValue = 0 - - it('does not raise any flags', async () => { - const tx = await validator - .connect(personas.Nelly) - .validate( - previousRoundId, - previousValue, - currentRoundId, - currentValue, - ) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('#isValid', () => { - const previousValue = 1000000 - - describe('with a validation larger than the deviation', () => { - const currentValue = 1100010 - it('is not valid', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with a validation smaller than the deviation', () => { - const currentValue = 1100009 - it('is valid', async () => { - assert.isTrue( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with positive previous and negative current', () => { - const previousValue = 1000000 - const currentValue = -900000 - it('correctly detects the difference', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('with negative previous and positive current', () => { - const previousValue = -900000 - const currentValue = 1000000 - it('correctly detects the difference', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the difference overflows', () => { - const previousValue = BigNumber.from(2).pow(255).sub(1) - const currentValue = BigNumber.from(-1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the rounding overflows', () => { - const previousValue = BigNumber.from(2).pow(255).div(10000) - const currentValue = BigNumber.from(1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - - describe('when the division overflows', () => { - const previousValue = BigNumber.from(2).pow(255).sub(1) - const currentValue = BigNumber.from(-1) - - it('does not revert and returns false', async () => { - assert.isFalse( - await validator.isValid(0, previousValue, 1, currentValue), - ) - }) - }) - }) - - describe('#setFlaggingThreshold', () => { - const newThreshold = 777 - - it('changes the flagging thresold', async () => { - assert.equal(flaggingThreshold, await validator.flaggingThreshold()) - - await validator.connect(personas.Carol).setFlaggingThreshold(newThreshold) - - assert.equal(newThreshold, await validator.flaggingThreshold()) - }) - - it('emits a log event only when actually changed', async () => { - await expect( - validator.connect(personas.Carol).setFlaggingThreshold(newThreshold), - ) - .to.emit(validator, 'FlaggingThresholdUpdated') - .withArgs(flaggingThreshold, newThreshold) - - await expect( - validator.connect(personas.Carol).setFlaggingThreshold(newThreshold), - ).to.not.emit(validator, 'FlaggingThresholdUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - validator.connect(personas.Neil).setFlaggingThreshold(newThreshold), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) - - describe('#setFlagsAddress', () => { - const newFlagsAddress = '0x0123456789012345678901234567890123456789' - - it('changes the flags address', async () => { - assert.equal(flags.address, await validator.flags()) - - await validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress) - - assert.equal(newFlagsAddress, await validator.flags()) - }) - - it('emits a log event only when actually changed', async () => { - await expect( - validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress), - ) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsAddress) - - await expect( - validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress), - ).to.not.emit(validator, 'FlagsAddressUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - validator.connect(personas.Neil).setFlagsAddress(newFlagsAddress), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/Flags.test.ts b/contracts/test/v0.6/Flags.test.ts deleted file mode 100644 index 8f589184299..00000000000 --- a/contracts/test/v0.6/Flags.test.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let flagsFactory: ContractFactory -let consumerFactory: ContractFactory - -let controller: Contract -let flags: Contract -let consumer: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Nelly, - ) - consumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/FlagsTestHelper.sol:FlagsTestHelper', - personas.Nelly, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Nelly, - ) -}) - -describe('Flags', () => { - beforeEach(async () => { - controller = await controllerFactory.deploy() - flags = await flagsFactory.deploy(controller.address) - await flags.disableAccessCheck() - consumer = await consumerFactory.deploy(flags.address) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(flags, [ - 'getFlag', - 'getFlags', - 'lowerFlags', - 'raiseFlag', - 'raiseFlags', - 'raisingAccessController', - 'setRaisingAccessController', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - // AccessControl methods: - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - 'hasAccess', - ]) - }) - - describe('#raiseFlag', () => { - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(false, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect(flags.connect(personas.Nelly).raiseFlag(consumer.address)) - .to.emit(flags, 'FlagRaised') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .raiseFlag(consumer.address) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by an enabled setter', () => { - beforeEach(async () => { - await controller - .connect(personas.Nelly) - .addAccess(await personas.Neil.getAddress()) - }) - - it('sets the flags', async () => { - await flags.connect(personas.Neil).raiseFlag(consumer.address), - assert.equal(true, await flags.getFlag(consumer.address)) - }) - }) - - describe('when called by a non-enabled setter', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).raiseFlag(consumer.address), - ).to.be.revertedWith('Not allowed to raise flags') - }) - }) - - describe('when called when there is no raisingAccessController', () => { - beforeEach(async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController( - '0x0000000000000000000000000000000000000000', - ), - ).to.emit(flags, 'RaisingAccessControllerUpdated') - assert.equal( - '0x0000000000000000000000000000000000000000', - await flags.raisingAccessController(), - ) - }) - - it('succeeds for the owner', async () => { - await flags.connect(personas.Nelly).raiseFlag(consumer.address) - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('reverts for non-owner', async () => { - await expect(flags.connect(personas.Neil).raiseFlag(consumer.address)) - .to.be.reverted - }) - }) - }) - - describe('#raiseFlags', () => { - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(false, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect( - flags.connect(personas.Nelly).raiseFlags([consumer.address]), - ) - .to.emit(flags, 'FlagRaised') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .raiseFlags([consumer.address]) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by an enabled setter', () => { - beforeEach(async () => { - await controller - .connect(personas.Nelly) - .addAccess(await personas.Neil.getAddress()) - }) - - it('sets the flags', async () => { - await flags.connect(personas.Neil).raiseFlags([consumer.address]), - assert.equal(true, await flags.getFlag(consumer.address)) - }) - }) - - describe('when called by a non-enabled setter', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.revertedWith('Not allowed to raise flags') - }) - }) - - describe('when called when there is no raisingAccessController', () => { - beforeEach(async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController( - '0x0000000000000000000000000000000000000000', - ), - ).to.emit(flags, 'RaisingAccessControllerUpdated') - - assert.equal( - '0x0000000000000000000000000000000000000000', - await flags.raisingAccessController(), - ) - }) - - it('succeeds for the owner', async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - assert.equal(true, await flags.getFlag(consumer.address)) - }) - - it('reverts for non-owners', async () => { - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.reverted - }) - }) - }) - - describe('#lowerFlags', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).raiseFlags([consumer.address]) - }) - - describe('when called by the owner', () => { - it('updates the warning flag', async () => { - assert.equal(true, await flags.getFlag(consumer.address)) - - await flags.connect(personas.Nelly).lowerFlags([consumer.address]) - - assert.equal(false, await flags.getFlag(consumer.address)) - }) - - it('emits an event log', async () => { - await expect( - flags.connect(personas.Nelly).lowerFlags([consumer.address]), - ) - .to.emit(flags, 'FlagLowered') - .withArgs(consumer.address) - }) - - describe('if a flag has already been raised', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).lowerFlags([consumer.address]) - }) - - it('emits an event log', async () => { - const tx = await flags - .connect(personas.Nelly) - .lowerFlags([consumer.address]) - const receipt = await tx.wait() - assert.equal(0, receipt.events?.length) - }) - }) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - flags.connect(personas.Neil).lowerFlags([consumer.address]), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) - - describe('#getFlag', () => { - describe('if the access control is turned on', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).enableAccessCheck() - }) - - it('reverts', async () => { - await expect(consumer.getFlag(consumer.address)).to.be.revertedWith( - 'No access', - ) - }) - - describe('if access is granted to the address', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).addAccess(consumer.address) - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - }) - }) - - describe('if the access control is turned off', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).disableAccessCheck() - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - - describe('if access is granted to the address', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).addAccess(consumer.address) - }) - - it('does not revert', async () => { - await consumer.getFlag(consumer.address) - }) - }) - }) - }) - - describe('#getFlags', () => { - beforeEach(async () => { - await flags.connect(personas.Nelly).disableAccessCheck() - await flags - .connect(personas.Nelly) - .raiseFlags([ - await personas.Neil.getAddress(), - await personas.Norbert.getAddress(), - ]) - }) - - it('respects the access controls of #getFlag', async () => { - await flags.connect(personas.Nelly).enableAccessCheck() - - await expect(consumer.getFlag(consumer.address)).to.be.revertedWith( - 'No access', - ) - - await flags.connect(personas.Nelly).addAccess(consumer.address) - - await consumer.getFlag(consumer.address) - }) - - it('returns the flags in the order they are requested', async () => { - const response = await consumer.getFlags([ - await personas.Nelly.getAddress(), - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Norbert.getAddress(), - ]) - - assert.deepEqual([false, true, false, true], response) - }) - }) - - describe('#setRaisingAccessController', () => { - let controller2: Contract - - beforeEach(async () => { - controller2 = await controllerFactory.connect(personas.Nelly).deploy() - await controller2.connect(personas.Nelly).enableAccessCheck() - }) - - it('updates access control rules', async () => { - const neilAddress = await personas.Neil.getAddress() - await controller.connect(personas.Nelly).addAccess(neilAddress) - await flags.connect(personas.Neil).raiseFlags([consumer.address]) // doesn't raise - - await flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address) - - await expect( - flags.connect(personas.Neil).raiseFlags([consumer.address]), - ).to.be.revertedWith('Not allowed to raise flags') - }) - - it('emits a log announcing the change', async () => { - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address), - ) - .to.emit(flags, 'RaisingAccessControllerUpdated') - .withArgs(controller.address, controller2.address) - }) - - it('does not emit a log when there is no change', async () => { - await flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address) - - await expect( - flags - .connect(personas.Nelly) - .setRaisingAccessController(controller2.address), - ).to.not.emit(flags, 'RaisingAccessControllerUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - flags - .connect(personas.Neil) - .setRaisingAccessController(controller2.address), - ).to.be.revertedWith('Only callable by owner') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/FluxAggregator.test.ts b/contracts/test/v0.6/FluxAggregator.test.ts deleted file mode 100644 index 5a268ceebe9..00000000000 --- a/contracts/test/v0.6/FluxAggregator.test.ts +++ /dev/null @@ -1,3252 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - Signer, - Contract, - ContractFactory, - BigNumber, - BigNumberish, - ContractTransaction, - constants, -} from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import { - publicAbi, - toWei, - increaseTimeBy, - mineBlock, - evmWordToAddress, -} from '../test-helpers/helpers' -import { randomBytes } from '@ethersproject/random' -import { fail } from 'assert' - -let personas: Personas -let linkTokenFactory: ContractFactory -let fluxAggregatorFactory: ContractFactory -let validatorMockFactory: ContractFactory -let testHelperFactory: ContractFactory -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory -let gasGuzzlerFactory: ContractFactory -let emptyAddress: string - -before(async () => { - personas = (await getUsers()).personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - fluxAggregatorFactory = await ethers.getContractFactory( - 'src/v0.6/FluxAggregator.sol:FluxAggregator', - ) - validatorMockFactory = await ethers.getContractFactory( - 'src/v0.6/tests/AggregatorValidatorMock.sol:AggregatorValidatorMock', - ) - testHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/FluxAggregatorTestHelper.sol:FluxAggregatorTestHelper', - ) - validatorFactory = await ethers.getContractFactory( - 'src/v0.6/DeviationFlaggingValidator.sol:DeviationFlaggingValidator', - ) - flagsFactory = await ethers.getContractFactory('src/v0.6/Flags.sol:Flags') - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - ) - gasGuzzlerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/GasGuzzler.sol:GasGuzzler', - ) - emptyAddress = constants.AddressZero -}) - -describe('FluxAggregator', () => { - const paymentAmount = toWei('3') - const deposit = toWei('100') - const answer = 100 - const minAns = 1 - const maxAns = 1 - const rrDelay = 0 - const timeout = 1800 - const decimals = 18 - const description = 'LINK/USD' - const reserveRounds = 2 - const minSubmissionValue = BigNumber.from('1') - const maxSubmissionValue = BigNumber.from('100000000000000000000') - - let aggregator: Contract - let link: Contract - let testHelper: Contract - let validator: Contract - let gasGuzzler: Contract - let nextRound: number - let oracles: Signer[] - - async function updateFutureRounds( - aggregator: Contract, - overrides: { - minAnswers?: BigNumberish - maxAnswers?: BigNumberish - payment?: BigNumberish - restartDelay?: BigNumberish - timeout?: BigNumberish - } = {}, - ) { - overrides = overrides || {} - const round = { - payment: overrides.payment || paymentAmount, - minAnswers: overrides.minAnswers || minAns, - maxAnswers: overrides.maxAnswers || maxAns, - restartDelay: overrides.restartDelay || rrDelay, - timeout: overrides.timeout || timeout, - } - - return aggregator.updateFutureRounds( - round.payment, - round.minAnswers, - round.maxAnswers, - round.restartDelay, - round.timeout, - ) - } - - async function addOracles( - aggregator: Contract, - oraclesAndAdmin: Signer[], - minAnswers: number, - maxAnswers: number, - restartDelay: number, - ): Promise { - return aggregator.connect(personas.Carol).changeOracles( - [], - oraclesAndAdmin.map(async (oracle) => await oracle.getAddress()), - oraclesAndAdmin.map(async (admin) => await admin.getAddress()), - minAnswers, - maxAnswers, - restartDelay, - ) - } - - async function advanceRound( - aggregator: Contract, - submitters: Signer[], - currentSubmission: number = answer, - ): Promise { - for (const submitter of submitters) { - await aggregator.connect(submitter).submit(nextRound, currentSubmission) - } - nextRound++ - return nextRound - } - - const ShouldBeSet = 'expects it to be different' - const ShouldNotBeSet = 'expects it to equal' - let startingState: any - - async function checkOracleRoundState( - state: any, - want: { - eligibleToSubmit: boolean - roundId: BigNumberish - latestSubmission: BigNumberish - startedAt: string - timeout: BigNumberish - availableFunds: BigNumberish - oracleCount: BigNumberish - paymentAmount: BigNumberish - }, - ) { - assert.equal( - want.eligibleToSubmit, - state._eligibleToSubmit, - 'round state: unexecpted eligibility', - ) - bigNumEquals( - want.roundId, - state._roundId, - 'round state: unexpected Round ID', - ) - bigNumEquals( - want.latestSubmission, - state._latestSubmission, - 'round state: unexpected latest submission', - ) - if (want.startedAt === ShouldBeSet) { - assert.isAbove( - state._startedAt.toNumber(), - startingState._startedAt.toNumber(), - 'round state: expected the started at to be the same as previous', - ) - } else { - bigNumEquals( - 0, - state._startedAt, - 'round state: expected the started at not to be updated', - ) - } - bigNumEquals( - want.timeout, - state._timeout.toNumber(), - 'round state: unexepcted timeout', - ) - bigNumEquals( - want.availableFunds, - state._availableFunds, - 'round state: unexepected funds', - ) - bigNumEquals( - want.oracleCount, - state._oracleCount, - 'round state: unexpected oracle count', - ) - bigNumEquals( - want.paymentAmount, - state._paymentAmount, - 'round state: unexpected paymentamount', - ) - } - - beforeEach(async () => { - link = await linkTokenFactory.connect(personas.Default).deploy() - aggregator = await fluxAggregatorFactory - .connect(personas.Carol) - .deploy( - link.address, - paymentAmount, - timeout, - emptyAddress, - minSubmissionValue, - maxSubmissionValue, - decimals, - ethers.utils.formatBytes32String(description), - ) - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - bigNumEquals(deposit, await link.balanceOf(aggregator.address)) - nextRound = 1 - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(aggregator, [ - 'acceptAdmin', - 'allocatedFunds', - 'availableFunds', - 'changeOracles', - 'decimals', - 'description', - 'getAdmin', - 'getAnswer', - 'getOracles', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'linkToken', - 'maxSubmissionCount', - 'maxSubmissionValue', - 'minSubmissionCount', - 'minSubmissionValue', - 'onTokenTransfer', - 'oracleCount', - 'oracleRoundState', - 'paymentAmount', - 'requestNewRound', - 'restartDelay', - 'setRequesterPermissions', - 'setValidator', - 'submit', - 'timeout', - 'transferAdmin', - 'updateAvailableFunds', - 'updateFutureRounds', - 'withdrawFunds', - 'withdrawPayment', - 'withdrawablePayment', - 'validator', - 'version', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the paymentAmount', async () => { - bigNumEquals( - BigNumber.from(paymentAmount), - await aggregator.paymentAmount(), - ) - }) - - it('sets the timeout', async () => { - bigNumEquals(BigNumber.from(timeout), await aggregator.timeout()) - }) - - it('sets the decimals', async () => { - bigNumEquals(BigNumber.from(decimals), await aggregator.decimals()) - }) - - it('sets the description', async () => { - assert.equal( - ethers.utils.formatBytes32String(description), - await aggregator.description(), - ) - }) - - it('sets the version to 3', async () => { - bigNumEquals(3, await aggregator.version()) - }) - - it('sets the validator', async () => { - assert.equal(emptyAddress, await aggregator.validator()) - }) - }) - - describe('#submit', () => { - let minMax - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - it('updates the allocated and available funds counters', async () => { - bigNumEquals(0, await aggregator.allocatedFunds()) - - const tx = await aggregator - .connect(personas.Neil) - .submit(nextRound, answer) - const receipt = await tx.wait() - - bigNumEquals(paymentAmount, await aggregator.allocatedFunds()) - const expectedAvailable = deposit.sub(paymentAmount) - bigNumEquals(expectedAvailable, await aggregator.availableFunds()) - const logged = BigNumber.from( - receipt.logs?.[2].topics[1] ?? BigNumber.from(-1), - ) - bigNumEquals(expectedAvailable, logged) - }) - - it('emits a log event announcing submission details', async () => { - await expect(aggregator.connect(personas.Nelly).submit(nextRound, answer)) - .to.emit(aggregator, 'SubmissionReceived') - .withArgs(answer, nextRound, await personas.Nelly.getAddress()) - }) - - describe('when the minimum oracles have not reported', () => { - it('pays the oracles that have reported', async () => { - bigNumEquals( - 0, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Ned) - .withdrawablePayment(await personas.Ned.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - }) - - it('does not update the answer', async () => { - bigNumEquals(ethers.constants.Zero, await aggregator.latestAnswer()) - - // Not updated because of changes by the owner setting minSubmissionCount to 3 - await aggregator.connect(personas.Ned).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - bigNumEquals(ethers.constants.Zero, await aggregator.latestAnswer()) - }) - }) - - describe('when an oracle prematurely bumps the round', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 2, maxAnswers: 3 }) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound + 1, answer), - 'previous round not supersedable', - ) - }) - }) - - describe('when the minimum number of oracles have reported', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 2, maxAnswers: 3 }) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('updates the answer with the median', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - - await aggregator.connect(personas.Ned).submit(nextRound, 99) - bigNumEquals(99, await aggregator.latestAnswer()) // ((100+99) / 2).to_i - - await aggregator.connect(personas.Nelly).submit(nextRound, 101) - - bigNumEquals(100, await aggregator.latestAnswer()) - }) - - it('updates the updated timestamp', async () => { - const originalTimestamp = await aggregator.latestTimestamp() - assert.isAbove(originalTimestamp.toNumber(), 0) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const currentTimestamp = await aggregator.latestTimestamp() - assert.isAbove( - currentTimestamp.toNumber(), - originalTimestamp.toNumber(), - ) - }) - - it('announces the new answer with a log event', async () => { - const tx = await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const newAnswer = BigNumber.from( - receipt.logs?.[0].topics[1] ?? ethers.constants.Zero, - ) - - assert.equal(answer, newAnswer.toNumber()) - }) - - it('does not set the timedout flag', async () => { - evmRevert(aggregator.getRoundData(nextRound), 'No data present') - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const round = await aggregator.getRoundData(nextRound) - assert.equal(nextRound, round.answeredInRound.toNumber()) - }) - - it('updates the round details', async () => { - evmRevert(aggregator.latestRoundData(), 'No data present') - - increaseTimeBy(15, ethers.provider) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const roundAfter = await aggregator.getRoundData(nextRound) - bigNumEquals(nextRound, roundAfter.roundId) - bigNumEquals(answer, roundAfter.answer) - assert.isFalse(roundAfter.startedAt.isZero()) - bigNumEquals( - await aggregator.getTimestamp(nextRound), - roundAfter.updatedAt, - ) - bigNumEquals(nextRound, roundAfter.answeredInRound) - - assert.isBelow( - roundAfter.startedAt.toNumber(), - roundAfter.updatedAt.toNumber(), - ) - - const roundAfterLatest = await aggregator.latestRoundData() - bigNumEquals(roundAfter.roundId, roundAfterLatest.roundId) - bigNumEquals(roundAfter.answer, roundAfterLatest.answer) - bigNumEquals(roundAfter.startedAt, roundAfterLatest.startedAt) - bigNumEquals(roundAfter.updatedAt, roundAfterLatest.updatedAt) - bigNumEquals( - roundAfter.answeredInRound, - roundAfterLatest.answeredInRound, - ) - }) - }) - - describe('when an oracle submits for a round twice', () => { - it('reverts', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'cannot report on previous rounds', - ) - }) - }) - - describe('when updated after the max answers submitted', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - 'round not accepting submissions', - ) - }) - }) - - describe('when a new highest round number is passed in', () => { - it('increments the answer round', async () => { - const startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - bigNumEquals(1, startingState._roundId) - - await advanceRound(aggregator, oracles) - - const updatedState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - bigNumEquals(2, updatedState._roundId) - }) - - it('sets the startedAt time for the reporting round', async () => { - evmRevert(aggregator.getRoundData(nextRound), 'No data present') - - const tx = await aggregator - .connect(oracles[0]) - .submit(nextRound, answer) - await aggregator.connect(oracles[1]).submit(nextRound, answer) - await aggregator.connect(oracles[2]).submit(nextRound, answer) - const receipt = await tx.wait() - const block = await ethers.provider.getBlock(receipt.blockHash ?? '') - - const round = await aggregator.getRoundData(nextRound) - bigNumEquals(BigNumber.from(block.timestamp), round.startedAt) - }) - - it('announces a new round by emitting a log', async () => { - const tx = await aggregator - .connect(personas.Neil) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const topics = receipt.logs?.[0].topics ?? [] - const roundNumber = BigNumber.from(topics[1]) - const startedBy = evmWordToAddress(topics[2]) - - bigNumEquals(nextRound, roundNumber.toNumber()) - bigNumEquals(startedBy, await personas.Neil.getAddress()) - }) - }) - - describe('when a round is passed in higher than expected', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound + 1, answer), - 'invalid round to report', - ) - }) - }) - - describe('when called by a non-oracle', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Carol).submit(nextRound, answer), - 'not enabled oracle', - ) - }) - }) - - describe('when there are not sufficient available funds', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds( - await personas.Carol.getAddress(), - deposit.sub(paymentAmount.mul(oracles.length).mul(reserveRounds)), - ) - - // drain remaining funds - await advanceRound(aggregator, oracles) - await advanceRound(aggregator, oracles) - }) - - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'SafeMath: subtraction overflow', - ) - }) - }) - - describe('when a new round opens before the previous rounds closes', () => { - beforeEach(async () => { - oracles = [personas.Nancy, personas.Norbert] - await addOracles(aggregator, oracles, 3, 4, rrDelay) - await advanceRound(aggregator, [ - personas.Nelly, - personas.Neil, - personas.Nancy, - ]) - - // start the next round - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - - it('still allows the previous round to be answered', async () => { - await aggregator.connect(personas.Ned).submit(nextRound - 1, answer) - }) - - describe('once the current round is answered', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Nancy] - for (let i = 0; i < oracles.length; i++) { - await aggregator.connect(oracles[i]).submit(nextRound, answer) - } - }) - - it('does not allow reports for the previous round', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound - 1, answer), - 'invalid round to report', - ) - }) - }) - - describe('when the previous round has finished', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Norbert) - .submit(nextRound - 1, answer) - }) - - it('does not allow reports for the previous round', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound - 1, answer), - 'round not accepting submissions', - ) - }) - }) - }) - - describe('when price is updated mid-round', () => { - const newAmount = toWei('50') - - it('pays the same amount to all oracles per round', async () => { - await link.transfer( - aggregator.address, - newAmount.mul(oracles.length).mul(reserveRounds), - ) - await aggregator.updateAvailableFunds() - - bigNumEquals( - 0, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - 0, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await updateFutureRounds(aggregator, { payment: newAmount }) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Neil) - .withdrawablePayment(await personas.Neil.getAddress()), - ) - bigNumEquals( - paymentAmount, - await aggregator - .connect(personas.Nelly) - .withdrawablePayment(await personas.Nelly.getAddress()), - ) - }) - }) - - describe('when delay is on', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers: oracles.length, - maxAnswers: oracles.length, - restartDelay: 1, - }) - }) - - it("does not revert on the oracle's first round", async () => { - // Since lastUpdatedRound defaults to zero and that's the only - // indication that an oracle hasn't responded, this test guards against - // the situation where we don't check that and no one can start a round. - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('does revert before the delay', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - nextRound++ - - await evmRevert( - aggregator.connect(personas.Neil).submit(nextRound, answer), - 'previous round not supersedable', - ) - }) - }) - - describe('when an oracle starts a round before the restart delay is over', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator.connect(personas.Carol), { - minAnswers: 1, - maxAnswers: 1, - }) - - oracles = [personas.Neil, personas.Ned, personas.Nelly] - for (let i = 0; i < oracles.length; i++) { - await aggregator.connect(oracles[i]).submit(nextRound, answer) - nextRound++ - } - - const newDelay = 2 - // Since Ned and Nelly have answered recently, and we set the delay - // to 2, only Nelly can answer as she is the only oracle that hasn't - // started the last two rounds. - await updateFutureRounds(aggregator, { - maxAnswers: oracles.length, - restartDelay: newDelay, - }) - }) - - describe('when called by an oracle who has not answered recently', () => { - it('does not revert', async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - }) - - describe('when called by an oracle who answered recently', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - 'round not accepting submissions', - ) - - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'round not accepting submissions', - ) - }) - }) - }) - - describe('when the price is not updated for a round', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers: oracles.length, - maxAnswers: oracles.length, - restartDelay: 1, - }) - - for (const oracle of oracles) { - await aggregator.connect(oracle).submit(nextRound, answer) - } - nextRound++ - - await aggregator.connect(personas.Ned).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - await increaseTimeBy(timeout + 1, ethers.provider) - nextRound++ - }) - - it('allows a new round to be started', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - - it('sets the info for the previous round', async () => { - const previousRound = nextRound - 1 - let updated = await aggregator.getTimestamp(previousRound) - let ans = await aggregator.getAnswer(previousRound) - assert.equal(0, updated.toNumber()) - assert.equal(0, ans.toNumber()) - - const tx = await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer) - const receipt = await tx.wait() - - const block = await ethers.provider.getBlock(receipt.blockHash ?? '') - - updated = await aggregator.getTimestamp(previousRound) - ans = await aggregator.getAnswer(previousRound) - bigNumEquals(BigNumber.from(block.timestamp), updated) - assert.equal(answer, ans.toNumber()) - - const round = await aggregator.getRoundData(previousRound) - bigNumEquals(previousRound, round.roundId) - bigNumEquals(ans, round.answer) - bigNumEquals(updated, round.updatedAt) - bigNumEquals(previousRound - 1, round.answeredInRound) - }) - - it('sets the previous round as timed out', async () => { - const previousRound = nextRound - 1 - evmRevert(aggregator.getRoundData(previousRound), 'No data present') - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - - const round = await aggregator.getRoundData(previousRound) - assert.notEqual(round.roundId, round.answeredInRound) - bigNumEquals(previousRound - 1, round.answeredInRound) - }) - - it('still respects the delay restriction', async () => { - // expected to revert because the sender started the last round - await evmRevert( - aggregator.connect(personas.Ned).submit(nextRound, answer), - ) - }) - - it('uses the timeout set at the beginning of the round', async () => { - await updateFutureRounds(aggregator, { - timeout: timeout + 100000, - }) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('submitting values near the edges of allowed values', () => { - it('rejects values below the submission value range', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .submit(nextRound, minSubmissionValue.sub(1)), - 'value below minSubmissionValue', - ) - }) - - it('accepts submissions equal to the min submission value', async () => { - await aggregator - .connect(personas.Neil) - .submit(nextRound, minSubmissionValue) - }) - - it('accepts submissions equal to the max submission value', async () => { - await aggregator - .connect(personas.Neil) - .submit(nextRound, maxSubmissionValue) - }) - - it('rejects submissions equal to the max submission value', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .submit(nextRound, maxSubmissionValue.add(1)), - 'value above maxSubmissionValue', - ) - }) - }) - - describe('when a validator is set', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 1, maxAnswers: 1 }) - oracles = [personas.Nelly] - - validator = await validatorMockFactory.connect(personas.Carol).deploy() - await aggregator.connect(personas.Carol).setValidator(validator.address) - }) - - it('calls out to the validator', async () => { - await expect( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - ) - .to.emit(validator, 'Validated') - .withArgs(0, 0, nextRound, answer) - }) - }) - - describe('when the answer validator eats all gas', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { minAnswers: 1, maxAnswers: 1 }) - oracles = [personas.Nelly] - - gasGuzzler = await gasGuzzlerFactory.connect(personas.Carol).deploy() - await aggregator - .connect(personas.Carol) - .setValidator(gasGuzzler.address) - assert.equal(gasGuzzler.address, await aggregator.validator()) - }) - - it('still updates', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - - await aggregator - .connect(personas.Nelly) - .submit(nextRound, answer, { gasLimit: 500000 }) - - bigNumEquals(answer, await aggregator.latestAnswer()) - }) - }) - }) - - describe('#getAnswer', () => { - const answers = [1, 10, 101, 1010, 10101, 101010, 1010101] - - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - for (const answer of answers) { - await aggregator.connect(personas.Neil).submit(nextRound++, answer) - } - }) - - it('retrieves the answer recorded for past rounds', async () => { - for (let i = nextRound; i < nextRound; i++) { - const answer = await aggregator.getAnswer(i) - bigNumEquals(BigNumber.from(answers[i - 1]), answer) - } - }) - - it("returns 0 for answers greater than uint32's max", async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - const answer = await aggregator.getAnswer(overflowedId) - bigNumEquals(0, answer) - }) - }) - - describe('#getTimestamp', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - for (let i = 0; i < 10; i++) { - await aggregator.connect(personas.Neil).submit(nextRound++, i + 1) - } - }) - - it('retrieves the answer recorded for past rounds', async () => { - let lastTimestamp = ethers.constants.Zero - - for (let i = 1; i < nextRound; i++) { - const currentTimestamp = await aggregator.getTimestamp(i) - assert.isAtLeast(currentTimestamp.toNumber(), lastTimestamp.toNumber()) - lastTimestamp = currentTimestamp - } - }) - - it("returns 0 for answers greater than uint32's max", async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - const answer = await aggregator.getTimestamp(overflowedId) - bigNumEquals(0, answer) - }) - }) - - describe('#changeOracles', () => { - describe('adding oracles', () => { - it('increases the oracle count', async () => { - const pastCount = await aggregator.oracleCount() - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - const currentCount = await aggregator.oracleCount() - - bigNumEquals(currentCount, pastCount + 1) - }) - - it('adds the address in getOracles', async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - assert.deepEqual( - [await personas.Neil.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('updates the round details', async () => { - await addOracles( - aggregator, - [personas.Neil, personas.Ned, personas.Nelly], - 1, - 3, - 2, - ) - bigNumEquals(1, await aggregator.minSubmissionCount()) - bigNumEquals(3, await aggregator.maxSubmissionCount()) - bigNumEquals(2, await aggregator.restartDelay()) - }) - - it('emits a log', async () => { - const tx = await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - 1, - 1, - 0, - ) - expect(tx) - .to.emit(aggregator, 'OraclePermissionsUpdated') - .withArgs(await personas.Ned.getAddress(), true) - - expect(tx) - .to.emit(aggregator, 'OracleAdminUpdated') - .withArgs( - await personas.Ned.getAddress(), - await personas.Neil.getAddress(), - ) - }) - - describe('when the oracle has already been added', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - }) - - it('reverts', async () => { - await evmRevert( - addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay), - 'oracle already enabled', - ) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .changeOracles( - [], - [await personas.Neil.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when an oracle gets added mid-round', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await addOracles( - aggregator, - [personas.Nelly], - oracles.length + 1, - oracles.length + 1, - rrDelay, - ) - }) - - it('does not allow the oracle to update the round', async () => { - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'not yet enabled oracle', - ) - }) - - it('does allow the oracle to update future rounds', async () => { - // complete round - await aggregator.connect(personas.Ned).submit(nextRound, answer) - - // now can participate in new rounds - await aggregator.connect(personas.Nelly).submit(nextRound + 1, answer) - }) - }) - - describe('when an oracle is added after removed for a round', () => { - it('allows the oracle to update', async () => { - oracles = [personas.Neil, personas.Nelly] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - nextRound++ - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound++ - - await addOracles(aggregator, [personas.Nelly], 1, 1, rrDelay) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('when an oracle is added and immediately removed mid-round', () => { - it('allows the oracle to update', async () => { - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - nextRound++ - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound++ - - await addOracles(aggregator, [personas.Nelly], 1, 1, rrDelay) - - await aggregator.connect(personas.Nelly).submit(nextRound, answer) - }) - }) - - describe('when an oracle is re-added with a different admin address', () => { - it('reverts', async () => { - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Nelly.getAddress()], - [await personas.Carol.getAddress()], - 1, - 1, - rrDelay, - ), - 'owner cannot overwrite admin', - ) - }) - }) - - const limit = 77 - describe(`when adding more than ${limit} oracles`, () => { - let oracles: Signer[] - - beforeEach(async () => { - oracles = [] - for (let i = 0; i < limit; i++) { - const account = await new ethers.Wallet( - randomBytes(32), - ethers.provider, - ) - await personas.Default.sendTransaction({ - to: account.address, - value: toWei('0.1'), - }) - - oracles.push(account) - } - - await link.transfer( - aggregator.address, - paymentAmount.mul(limit).mul(reserveRounds), - ) - await aggregator.updateAvailableFunds() - - let addresses = oracles.slice(0, 50).map(async (o) => o.getAddress()) - await aggregator - .connect(personas.Carol) - .changeOracles([], addresses, addresses, 1, 50, rrDelay) - // add in two transactions to avoid gas limit issues - addresses = oracles.slice(50, 100).map(async (o) => o.getAddress()) - await aggregator - .connect(personas.Carol) - .changeOracles([], addresses, addresses, 1, oracles.length, rrDelay) - }) - - it('not use too much gas [ @skip-coverage ]', async () => { - let tx: any - assert.deepEqual( - // test adveserial quickselect algo - [2, 4, 6, 8, 10, 12, 14, 16, 1, 9, 5, 11, 3, 13, 7, 15], - adverserialQuickselectList(16), - ) - const inputs = adverserialQuickselectList(limit) - for (let i = 0; i < limit; i++) { - tx = await aggregator - .connect(oracles[i]) - .submit(nextRound, inputs[i]) - } - assert.isTrue(!!tx) - if (tx) { - const receipt = await tx.wait() - assert.isBelow(receipt.gasUsed.toNumber(), 600_000) - } - }) - - function adverserialQuickselectList(len: number): number[] { - const xs: number[] = [] - const pi: number[] = [] - for (let i = 0; i < len; i++) { - pi[i] = i - xs[i] = 0 - } - - for (let l = len; l > 0; l--) { - const pivot = Math.floor((l - 1) / 2) - xs[pi[pivot]] = l - const temp = pi[l - 1] - pi[l - 1] = pi[pivot] - pi[pivot] = temp - } - return xs - } - - it('reverts when another oracle is added', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Neil.getAddress()], - [await personas.Neil.getAddress()], - limit + 1, - limit + 1, - rrDelay, - ), - 'max oracles allowed', - ) - }) - }) - - it('reverts when minSubmissions is set to 0', async () => { - await evmRevert( - addOracles(aggregator, [personas.Neil], 0, 0, 0), - 'min must be greater than 0', - ) - }) - }) - - describe('removing oracles', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Nelly] - await addOracles( - aggregator, - oracles, - oracles.length, - oracles.length, - rrDelay, - ) - }) - - it('decreases the oracle count', async () => { - const pastCount = await aggregator.oracleCount() - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - const currentCount = await aggregator.oracleCount() - - expect(currentCount).to.equal(pastCount - 1) - }) - - it('updates the round details', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Neil.getAddress()], [], [], 1, 1, 0) - - bigNumEquals(1, await aggregator.minSubmissionCount()) - bigNumEquals(1, await aggregator.maxSubmissionCount()) - bigNumEquals(ethers.constants.Zero, await aggregator.restartDelay()) - }) - - it('emits a log', async () => { - await expect( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ), - ) - .to.emit(aggregator, 'OraclePermissionsUpdated') - .withArgs(await personas.Neil.getAddress(), false) - }) - - it('removes the address in getOracles', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Nelly.getAddress()], - await aggregator.getOracles(), - ) - }) - - describe('when the oracle is not currently added', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - }) - - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ), - 'oracle not enabled', - ) - }) - }) - - describe('when removing the last oracle', () => { - it('does not revert', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Nelly.getAddress()], [], [], 0, 0, 0) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - 0, - 0, - rrDelay, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when an oracle gets removed', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - }) - - it('is allowed to report on one more round', async () => { - // next round - await advanceRound(aggregator, [personas.Nelly]) - // finish round - await advanceRound(aggregator, [personas.Neil]) - - // cannot participate in future rounds - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'no longer allowed oracle', - ) - }) - }) - - describe('when an oracle gets removed mid-round', () => { - beforeEach(async () => { - await aggregator.connect(personas.Neil).submit(nextRound, answer) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 1, - 1, - rrDelay, - ) - }) - - it('is allowed to finish that round and one more round', async () => { - await advanceRound(aggregator, [personas.Nelly]) // finish round - - await advanceRound(aggregator, [personas.Nelly]) // next round - - // cannot participate in future rounds - await evmRevert( - aggregator.connect(personas.Nelly).submit(nextRound, answer), - 'no longer allowed oracle', - ) - }) - }) - - it('reverts when minSubmissions is set to 0', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - 0, - 0, - 0, - ), - 'min must be greater than 0', - ) - }) - }) - - describe('adding and removing oracles at once', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned] - await addOracles(aggregator, oracles, 1, 1, rrDelay) - }) - - it('can swap out oracles', async () => { - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - assert.notInclude( - await aggregator.getOracles(), - await personas.Nelly.getAddress(), - ) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [await personas.Nelly.getAddress()], - [await personas.Nelly.getAddress()], - 1, - 1, - rrDelay, - ) - - assert.notInclude( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - assert.include( - await aggregator.getOracles(), - await personas.Nelly.getAddress(), - ) - }) - - it('is possible to remove and add the same address', async () => { - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [await personas.Ned.getAddress()], - [await personas.Ned.getAddress()], - 1, - 1, - rrDelay, - ) - - assert.include( - await aggregator.getOracles(), - await personas.Ned.getAddress(), - ) - }) - }) - }) - - describe('#getOracles', () => { - describe('after adding oracles', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - assert.deepEqual( - [await personas.Neil.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('returns the addresses of added oracles', async () => { - await addOracles(aggregator, [personas.Ned], minAns, maxAns, rrDelay) - - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - - await addOracles(aggregator, [personas.Nelly], minAns, maxAns, rrDelay) - assert.deepEqual( - [ - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ], - await aggregator.getOracles(), - ) - }) - }) - - describe('after removing oracles', () => { - beforeEach(async () => { - await addOracles( - aggregator, - [personas.Neil, personas.Ned, personas.Nelly], - minAns, - maxAns, - rrDelay, - ) - - assert.deepEqual( - [ - await personas.Neil.getAddress(), - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ], - await aggregator.getOracles(), - ) - }) - - it('reorders when removing from the beginning', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Neil.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Nelly.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('reorders when removing from the middle', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Ned.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Nelly.getAddress()], - await aggregator.getOracles(), - ) - }) - - it('pops the last node off at the end', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [await personas.Nelly.getAddress()], - [], - [], - minAns, - maxAns, - rrDelay, - ) - assert.deepEqual( - [await personas.Neil.getAddress(), await personas.Ned.getAddress()], - await aggregator.getOracles(), - ) - }) - }) - }) - - describe('#withdrawFunds', () => { - it('succeeds', async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit) - - bigNumEquals(0, await aggregator.availableFunds()) - bigNumEquals( - deposit, - await link.balanceOf(await personas.Carol.getAddress()), - ) - }) - - it('does not let withdrawals happen multiple times', async () => { - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit) - - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'insufficient reserve funds', - ) - }) - - describe('with a number higher than the available LINK balance', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('fails', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'insufficient reserve funds', - ) - - bigNumEquals( - deposit.sub(paymentAmount), - await aggregator.availableFunds(), - ) - }) - }) - - describe('with oracles still present', () => { - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - await addOracles(aggregator, oracles, 1, 1, rrDelay) - - bigNumEquals(deposit, await aggregator.availableFunds()) - }) - - it('does not allow withdrawal with less than 2x rounds of payments', async () => { - const oracleReserve = paymentAmount - .mul(oracles.length) - .mul(reserveRounds) - const allowed = deposit.sub(oracleReserve) - - //one more than the allowed amount cannot be withdrawn - await evmRevert( - aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), allowed.add(1)), - 'insufficient reserve funds', - ) - - // the allowed amount can be withdrawn - await aggregator - .connect(personas.Carol) - .withdrawFunds(await personas.Carol.getAddress(), allowed) - }) - }) - - describe('when called by a non-owner', () => { - it('fails', async () => { - await evmRevert( - aggregator - .connect(personas.Eddy) - .withdrawFunds(await personas.Carol.getAddress(), deposit), - 'Only callable by owner', - ) - - bigNumEquals(deposit, await aggregator.availableFunds()) - }) - }) - }) - - describe('#updateFutureRounds', () => { - let minSubmissionCount, maxSubmissionCount - const newPaymentAmount = toWei('2') - const newMin = 1 - const newMax = 3 - const newDelay = 2 - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - minSubmissionCount = oracles.length - maxSubmissionCount = oracles.length - await addOracles( - aggregator, - oracles, - minSubmissionCount, - maxSubmissionCount, - rrDelay, - ) - - bigNumEquals(paymentAmount, await aggregator.paymentAmount()) - assert.equal(minSubmissionCount, await aggregator.minSubmissionCount()) - assert.equal(maxSubmissionCount, await aggregator.maxSubmissionCount()) - }) - - it('updates the min and max answer counts', async () => { - await updateFutureRounds(aggregator, { - payment: newPaymentAmount, - minAnswers: newMin, - maxAnswers: newMax, - restartDelay: newDelay, - }) - - bigNumEquals(newPaymentAmount, await aggregator.paymentAmount()) - bigNumEquals( - BigNumber.from(newMin), - await aggregator.minSubmissionCount(), - ) - bigNumEquals( - BigNumber.from(newMax), - await aggregator.maxSubmissionCount(), - ) - bigNumEquals(BigNumber.from(newDelay), await aggregator.restartDelay()) - }) - - it('emits a log announcing the new round details', async () => { - await expect( - updateFutureRounds(aggregator, { - payment: newPaymentAmount, - minAnswers: newMin, - maxAnswers: newMax, - restartDelay: newDelay, - timeout: timeout + 1, - }), - ) - .to.emit(aggregator, 'RoundDetailsUpdated') - .withArgs(newPaymentAmount, newMin, newMax, newDelay, timeout + 1) - }) - - describe('when it is set to higher than the number or oracles', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - maxAnswers: 4, - }), - 'max cannot exceed total', - ) - }) - }) - - describe('when it sets the min higher than the max', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - minAnswers: 3, - maxAnswers: 2, - }), - 'max must equal/exceed min', - ) - }) - }) - - describe('when delay equal or greater the oracle count', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator, { - restartDelay: 3, - }), - 'delay cannot exceed total', - ) - }) - }) - - describe('when the payment amount does not cover reserve rounds', () => { - beforeEach(async () => {}) - - it('reverts', async () => { - const most = deposit.div(oracles.length * reserveRounds) - - // Relaxed check for the revert message due to a bug in ethers where any error message - // that starts with insufficient funds will be incorrectly returned as 'insufficient funds for intrinsic transaction cost' - await updateFutureRounds(aggregator, { - payment: most.add(1), - }).then( - () => { - // onFulfillment callback - fail('expected to revert but did not') - }, - (error: any) => { - // onRejected callback - const message = - error instanceof Object && 'message' in error - ? error.message - : JSON.stringify(error) - assert.isTrue(message.includes('insufficient funds')) - }, - ) - - await updateFutureRounds(aggregator, { - payment: most, - }) - }) - }) - - describe('min oracles is set to 0', () => { - it('reverts', async () => { - await evmRevert( - aggregator.updateFutureRounds(paymentAmount, 0, 0, rrDelay, timeout), - 'min must be greater than 0', - ) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => { - await evmRevert( - updateFutureRounds(aggregator.connect(personas.Ned)), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#updateAvailableFunds', () => { - it('checks the LINK token to see if any additional funds are available', async () => { - const originalBalance = await aggregator.availableFunds() - - await aggregator.updateAvailableFunds() - - bigNumEquals(originalBalance, await aggregator.availableFunds()) - - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - - const newBalance = await aggregator.availableFunds() - bigNumEquals(originalBalance.add(deposit), newBalance) - }) - - it('removes allocated funds from the available balance', async () => { - const originalBalance = await aggregator.availableFunds() - - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - await link.transfer(aggregator.address, deposit) - await aggregator.updateAvailableFunds() - - const expected = originalBalance.add(deposit).sub(paymentAmount) - const newBalance = await aggregator.availableFunds() - bigNumEquals(expected, newBalance) - }) - - it('emits a log', async () => { - await link.transfer(aggregator.address, deposit) - - const tx = await aggregator.updateAvailableFunds() - const receipt = await tx.wait() - - const reportedBalance = BigNumber.from(receipt.logs?.[0].topics[1] ?? -1) - bigNumEquals(await aggregator.availableFunds(), reportedBalance) - }) - - describe('when the available funds have not changed', () => { - it('does not emit a log', async () => { - const tx = await aggregator.updateAvailableFunds() - const receipt = await tx.wait() - - assert.equal(0, receipt.logs?.length) - }) - }) - }) - - describe('#withdrawPayment', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], minAns, maxAns, rrDelay) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - - it('transfers LINK to the recipient', async () => { - const originalBalance = await link.balanceOf(aggregator.address) - bigNumEquals(0, await link.balanceOf(await personas.Neil.getAddress())) - - await aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount, - ) - - bigNumEquals( - originalBalance.sub(paymentAmount), - await link.balanceOf(aggregator.address), - ) - bigNumEquals( - paymentAmount, - await link.balanceOf(await personas.Neil.getAddress()), - ) - }) - - it('decrements the allocated funds counter', async () => { - const originalAllocation = await aggregator.allocatedFunds() - - await aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount, - ) - - bigNumEquals( - originalAllocation.sub(paymentAmount), - await aggregator.allocatedFunds(), - ) - }) - - describe('when the caller withdraws more than they have', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Neil.getAddress(), - paymentAmount.add(BigNumber.from(1)), - ), - 'insufficient withdrawable funds', - ) - }) - }) - - describe('when the caller is not the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Nelly) - .withdrawPayment( - await personas.Neil.getAddress(), - await personas.Nelly.getAddress(), - BigNumber.from(1), - ), - 'only callable by admin', - ) - }) - }) - }) - - describe('#transferAdmin', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ) - }) - - describe('when the admin tries to transfer the admin', () => { - it('works', async () => { - await expect( - aggregator - .connect(personas.Neil) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - ) - .to.emit(aggregator, 'OracleAdminUpdateRequested') - .withArgs( - await personas.Ned.getAddress(), - await personas.Neil.getAddress(), - await personas.Nelly.getAddress(), - ) - assert.equal( - await personas.Neil.getAddress(), - await aggregator.getAdmin(await personas.Ned.getAddress()), - ) - }) - }) - - describe('when the non-admin owner tries to update the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Carol) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - 'only callable by admin', - ) - }) - }) - - describe('when the non-admin oracle tries to update the admin', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ), - 'only callable by admin', - ) - }) - }) - }) - - describe('#acceptAdmin', () => { - beforeEach(async () => { - await aggregator - .connect(personas.Carol) - .changeOracles( - [], - [await personas.Ned.getAddress()], - [await personas.Neil.getAddress()], - minAns, - maxAns, - rrDelay, - ) - const tx = await aggregator - .connect(personas.Neil) - .transferAdmin( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ) - await tx.wait() - }) - - describe('when the new admin tries to accept', () => { - it('works', async () => { - await expect( - aggregator - .connect(personas.Nelly) - .acceptAdmin(await personas.Ned.getAddress()), - ) - .to.emit(aggregator, 'OracleAdminUpdated') - .withArgs( - await personas.Ned.getAddress(), - await personas.Nelly.getAddress(), - ) - assert.equal( - await personas.Nelly.getAddress(), - await aggregator.getAdmin(await personas.Ned.getAddress()), - ) - }) - }) - - describe('when someone other than the new admin tries to accept', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Ned) - .acceptAdmin(await personas.Ned.getAddress()), - 'only callable by pending admin', - ) - await evmRevert( - aggregator - .connect(personas.Neil) - .acceptAdmin(await personas.Ned.getAddress()), - 'only callable by pending admin', - ) - }) - }) - }) - - describe('#onTokenTransfer', () => { - it('updates the available balance', async () => { - const originalBalance = await aggregator.availableFunds() - - await aggregator.updateAvailableFunds() - - bigNumEquals(originalBalance, await aggregator.availableFunds()) - - await link.transferAndCall(aggregator.address, deposit, '0x') - - const newBalance = await aggregator.availableFunds() - bigNumEquals(originalBalance.add(deposit), newBalance) - }) - - it('reverts given calldata', async () => { - await evmRevert( - // error message is not bubbled up by link token - link.transferAndCall(aggregator.address, deposit, '0x12345678'), - ) - }) - }) - - describe('#requestNewRound', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - await aggregator.setRequesterPermissions( - await personas.Carol.getAddress(), - true, - 0, - ) - }) - - it('announces a new round via log event', async () => { - await expect(aggregator.requestNewRound()).to.emit(aggregator, 'NewRound') - }) - - it('returns the new round ID', async () => { - testHelper = await testHelperFactory.connect(personas.Carol).deploy() - await aggregator.setRequesterPermissions(testHelper.address, true, 0) - let roundId = await testHelper.requestedRoundId() - assert.equal(roundId.toNumber(), 0) - - await testHelper.requestNewRound(aggregator.address) - - // return value captured by test helper - roundId = await testHelper.requestedRoundId() - assert.isAbove(roundId.toNumber(), 0) - }) - - describe('when there is a round in progress', () => { - beforeEach(async () => { - await aggregator.requestNewRound() - }) - - it('reverts', async () => { - await evmRevert( - aggregator.requestNewRound(), - 'prev round must be supersedable', - ) - }) - - describe('when that round has timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('starts a new round', async () => { - await expect(aggregator.requestNewRound()).to.emit( - aggregator, - 'NewRound', - ) - }) - }) - }) - - describe('when there is a restart delay set', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Eddy.getAddress(), - true, - 1, - ) - }) - - it('reverts if a round is started before the delay', async () => { - await aggregator.connect(personas.Eddy).requestNewRound() - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - // Eddy can't start because of the delay - await evmRevert( - aggregator.connect(personas.Eddy).requestNewRound(), - 'must delay requests', - ) - // Carol starts a new round instead - await aggregator.connect(personas.Carol).requestNewRound() - - // round completes - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - - // now Eddy can start again - await aggregator.connect(personas.Eddy).requestNewRound() - }) - }) - - describe('when all oracles have been removed and then re-added', () => { - it('does not get stuck', async () => { - await aggregator - .connect(personas.Carol) - .changeOracles([await personas.Neil.getAddress()], [], [], 0, 0, 0) - - // advance a few rounds - for (let i = 0; i < 7; i++) { - await aggregator.requestNewRound() - nextRound = nextRound + 1 - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - } - - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - await aggregator.connect(personas.Neil).submit(nextRound, answer) - }) - }) - }) - - describe('#setRequesterPermissions', () => { - beforeEach(async () => { - await addOracles(aggregator, [personas.Neil], 1, 1, 0) - - await aggregator.connect(personas.Neil).submit(nextRound, answer) - nextRound = nextRound + 1 - }) - - describe('when called by the owner', () => { - it('allows the specified address to start new rounds', async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - - await aggregator.connect(personas.Neil).requestNewRound() - }) - - it('emits a log announcing the update', async () => { - await expect( - aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ), - ) - .to.emit(aggregator, 'RequesterPermissionsSet') - .withArgs(await personas.Neil.getAddress(), true, 0) - }) - - describe('when the address is already authorized', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - }) - - it('does not emit a log for already authorized accounts', async () => { - const tx = await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - const receipt = await tx.wait() - assert.equal(0, receipt?.logs?.length) - }) - }) - - describe('when permission is removed by the owner', () => { - beforeEach(async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - true, - 0, - ) - }) - - it('does not allow the specified address to start new rounds', async () => { - await aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - false, - 0, - ) - - await evmRevert( - aggregator.connect(personas.Neil).requestNewRound(), - 'not authorized requester', - ) - }) - - it('emits a log announcing the update', async () => { - await expect( - aggregator.setRequesterPermissions( - await personas.Neil.getAddress(), - false, - 0, - ), - ) - .to.emit(aggregator, 'RequesterPermissionsSet') - .withArgs(await personas.Neil.getAddress(), false, 0) - }) - - it('does not emit a log for accounts without authorization', async () => { - const tx = await aggregator.setRequesterPermissions( - await personas.Ned.getAddress(), - false, - 0, - ) - const receipt = await tx.wait() - assert.equal(0, receipt?.logs?.length) - }) - }) - }) - - describe('when called by a stranger', () => { - it('reverts', async () => { - await evmRevert( - aggregator - .connect(personas.Neil) - .setRequesterPermissions(await personas.Neil.getAddress(), true, 0), - 'Only callable by owner', - ) - - await evmRevert( - aggregator.connect(personas.Neil).requestNewRound(), - 'not authorized requester', - ) - }) - }) - }) - - describe('#oracleRoundState', () => { - describe('when round ID 0 is passed in', () => { - const previousSubmission = 42 - let baseFunds: any - let minAnswers: number - let maxAnswers: number - let submitters: Signer[] - - beforeEach(async () => { - oracles = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - personas.Norbert, - ] - minAnswers = 3 - maxAnswers = 4 - - await addOracles(aggregator, oracles, minAnswers, maxAnswers, rrDelay) - submitters = [ - personas.Nelly, - personas.Ned, - personas.Neil, - personas.Nancy, - ] - await advanceRound(aggregator, submitters, previousSubmission) - baseFunds = BigNumber.from(deposit).sub( - paymentAmount.mul(submitters.length), - ) - startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - }) - - it('returns all of the important round information', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('reverts if called by a contract', async () => { - testHelper = await testHelperFactory.connect(personas.Carol).deploy() - await evmRevert( - testHelper.readOracleRoundState( - aggregator.address, - await personas.Neil.getAddress(), - ), - 'off-chain reading only', - ) - }) - - describe('when the restart delay is not enforced', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers, - maxAnswers, - restartDelay: 0, - }) - }) - - describe('< min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('< min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Nelly]) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answer, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('>= min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Nancy, - personas.Ned, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('>= min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Nelly, - personas.Ned, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('max submissions and oracle not included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nancy, - personas.Norbert, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('max submissions and oracle included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('when the restart delay is enforced', () => { - beforeEach(async () => { - await updateFutureRounds(aggregator, { - minAnswers, - maxAnswers, - restartDelay: maxAnswers - 1, - }) - }) - - describe('< min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil, personas.Ned]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('< min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [personas.Neil, personas.Nelly]) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answer, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(2)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('>= min submissions and oracle not included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Ned, - personas.Nancy, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 2, - latestSubmission: previousSubmission, - startedAt: ShouldBeSet, - timeout, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('>= min submissions and oracle included', () => { - beforeEach(async () => { - await advanceRound(aggregator, [ - personas.Neil, - personas.Ned, - personas.Nelly, - ]) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('and timed out', () => { - beforeEach(async () => { - await increaseTimeBy(timeout + 1, ethers.provider) - await mineBlock(ethers.provider) - }) - - it('is eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, // restart delay enforced - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(3)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('max submissions and oracle not included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nancy, - personas.Norbert, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters, answer) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: previousSubmission, - startedAt: ShouldNotBeSet, - timeout: 0, // details have been deleted - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('max submissions and oracle included', () => { - beforeEach(async () => { - submitters = [ - personas.Neil, - personas.Ned, - personas.Nelly, - personas.Nancy, - ] - assert.equal( - submitters.length, - maxAnswers, - 'precondition, please update submitters if maxAnswers changes', - ) - await advanceRound(aggregator, submitters, answer) - }) - - it('is not eligible to submit', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 3, - latestSubmission: answer, - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: baseFunds.sub(paymentAmount.mul(4)), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - }) - - describe('when non-zero round ID 0 is passed in', () => { - const answers = [0, 42, 47, 52, 57] - let currentFunds: any - - beforeEach(async () => { - oracles = [personas.Neil, personas.Ned, personas.Nelly] - - await addOracles(aggregator, oracles, 2, 3, rrDelay) - startingState = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 0, - ) - await advanceRound(aggregator, oracles, answers[1]) - await advanceRound( - aggregator, - [personas.Neil, personas.Ned], - answers[2], - ) - await advanceRound(aggregator, oracles, answers[3]) - await advanceRound(aggregator, [personas.Neil], answers[4]) - const submissionsSoFar = 9 - currentFunds = BigNumber.from(deposit).sub( - paymentAmount.mul(submissionsSoFar), - ) - }) - - it('returns info about previous rounds', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 1, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 1, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount: 0, - }) - }) - - it('returns info about previous rounds that were not submitted to', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 2, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 2, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - describe('for the current round', () => { - describe('which has not been submitted to', () => { - it("returns info about the current round that hasn't been submitted to", async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 4, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 4, - latestSubmission: answers[3], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('returns info about the subsequent round', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 5, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 5, - latestSubmission: answers[3], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - - describe('which has been submitted to', () => { - beforeEach(async () => { - await aggregator.connect(personas.Nelly).submit(4, answers[4]) - }) - - it("returns info about the current round that hasn't been submitted to", async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 4, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 4, - latestSubmission: answers[4], - startedAt: ShouldBeSet, - timeout, - availableFunds: currentFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - - it('returns info about the subsequent round', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 5, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: true, - roundId: 5, - latestSubmission: answers[4], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds.sub(paymentAmount), - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - it('returns speculative info about future rounds', async () => { - const state = await aggregator.oracleRoundState( - await personas.Nelly.getAddress(), - 6, - ) - - await checkOracleRoundState(state, { - eligibleToSubmit: false, - roundId: 6, - latestSubmission: answers[3], - startedAt: ShouldNotBeSet, - timeout: 0, - availableFunds: currentFunds, - oracleCount: oracles.length, - paymentAmount, - }) - }) - }) - }) - - describe('#getRoundData', () => { - let latestRoundId: any - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - await advanceRound(aggregator, oracles, answer) - latestRoundId = await aggregator.latestRound() - }) - - it('returns the relevant round information', async () => { - const round = await aggregator.getRoundData(latestRoundId) - bigNumEquals(latestRoundId, round.roundId) - bigNumEquals(answer, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(latestRoundId, round.answeredInRound) - }) - - it('reverts if a round is not present', async () => { - await evmRevert( - aggregator.getRoundData(latestRoundId.add(1)), - 'No data present', - ) - }) - - it('reverts if a round ID is too big', async () => { - const overflowedId = BigNumber.from(2).pow(32).add(1) - - await evmRevert(aggregator.getRoundData(overflowedId), 'No data present') - }) - }) - - describe('#latestRoundData', () => { - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - describe('when an answer has already been received', () => { - beforeEach(async () => { - await advanceRound(aggregator, oracles, answer) - }) - - it('returns the relevant round info without reverting', async () => { - const round = await aggregator.latestRoundData() - const latestRoundId = await aggregator.latestRound() - - bigNumEquals(latestRoundId, round.roundId) - bigNumEquals(answer, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(latestRoundId, round.answeredInRound) - }) - }) - - it('reverts if a round is not present', async () => { - await evmRevert(aggregator.latestRoundData(), 'No data present') - }) - }) - - describe('#latestAnswer', () => { - beforeEach(async () => { - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - describe('when an answer has already been received', () => { - beforeEach(async () => { - await advanceRound(aggregator, oracles, answer) - }) - - it('returns the latest answer without reverting', async () => { - bigNumEquals(answer, await aggregator.latestAnswer()) - }) - }) - - it('returns zero', async () => { - bigNumEquals(0, await aggregator.latestAnswer()) - }) - }) - - describe('#setValidator', () => { - beforeEach(async () => { - validator = await validatorMockFactory.connect(personas.Carol).deploy() - assert.equal(emptyAddress, await aggregator.validator()) - }) - - it('emits a log event showing the validator was changed', async () => { - await expect( - aggregator.connect(personas.Carol).setValidator(validator.address), - ) - .to.emit(aggregator, 'ValidatorUpdated') - .withArgs(emptyAddress, validator.address) - assert.equal(validator.address, await aggregator.validator()) - - await expect( - aggregator.connect(personas.Carol).setValidator(validator.address), - ).to.not.emit(aggregator, 'ValidatorUpdated') - assert.equal(validator.address, await aggregator.validator()) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - aggregator.connect(personas.Neil).setValidator(validator.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('integrating with historic deviation checker', () => { - let validator: Contract - let flags: Contract - let ac: Contract - const flaggingThreshold = 1000 // 1% - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, flaggingThreshold) - await ac.connect(personas.Carol).addAccess(validator.address) - - await aggregator.connect(personas.Carol).setValidator(validator.address) - - oracles = [personas.Nelly] - const minMax = oracles.length - await addOracles(aggregator, oracles, minMax, minMax, rrDelay) - }) - - it('raises a flag on with high enough deviation', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, 100) - nextRound++ - - await expect(aggregator.connect(personas.Nelly).submit(nextRound, 102)) - .to.emit(flags, 'FlagRaised') - .withArgs(aggregator.address) - }) - - it('does not raise a flag with low enough deviation', async () => { - await aggregator.connect(personas.Nelly).submit(nextRound, 100) - nextRound++ - - await expect( - aggregator.connect(personas.Nelly).submit(nextRound, 101), - ).to.not.emit(flags, 'FlagRaised') - }) - }) -}) diff --git a/contracts/test/v0.6/Median.test.ts b/contracts/test/v0.6/Median.test.ts deleted file mode 100644 index 8aea4722b6d..00000000000 --- a/contracts/test/v0.6/Median.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Signer, Contract, ContractFactory, BigNumber } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let defaultAccount: Signer -let medianTestHelperFactory: ContractFactory -before(async () => { - const personas: Personas = (await getUsers()).personas - defaultAccount = personas.Default - medianTestHelperFactory = await ethers.getContractFactory( - 'src/v0.6/tests/MedianTestHelper.sol:MedianTestHelper', - defaultAccount, - ) -}) - -describe('Median', () => { - let median: Contract - - beforeEach(async () => { - median = await medianTestHelperFactory.connect(defaultAccount).deploy() - }) - - describe('testing various lists', () => { - const tests = [ - { - name: 'ordered ascending', - responses: [0, 1, 2, 3, 4, 5, 6, 7], - want: 3, - }, - { - name: 'ordered descending', - responses: [7, 6, 5, 4, 3, 2, 1, 0], - want: 3, - }, - { - name: 'unordered length 1', - responses: [20], - want: 20, - }, - { - name: 'unordered length 2', - responses: [20, 0], - want: 10, - }, - { - name: 'unordered length 3', - responses: [20, 0, 16], - want: 16, - }, - { - name: 'unordered length 4', - responses: [20, 0, 15, 16], - want: 15, - }, - { - name: 'unordered length 7', - responses: [1001, 1, 101, 10, 11, 0, 111], - want: 11, - }, - { - name: 'unordered length 9', - responses: [8, 8, 4, 5, 5, 7, 9, 5, 9], - want: 7, - }, - { - name: 'unordered long', - responses: [33, 44, 89, 101, 67, 7, 23, 55, 88, 324, 0, 88], - want: 61, // 67 + 55 / 2 - }, - { - name: 'unordered longer', - responses: [ - 333121, 323453, 337654, 345363, 345363, 333456, 335477, 333323, - 332352, 354648, 983260, 333856, 335468, 376987, 333253, 388867, - 337879, 333324, 338678, - ], - want: 335477, - }, - { - name: 'overflowing numbers', - responses: [ - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - ], - want: BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - }, - { - name: 'overflowing numbers', - responses: [ - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ), - BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819966', - ), - ], - want: BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819966', - ), - }, - { - name: 'really long', - responses: [ - 56, 2, 31, 33, 55, 38, 35, 12, 41, 47, 21, 22, 40, 39, 10, 32, 49, 3, - 54, 45, 53, 14, 20, 59, 1, 30, 24, 6, 5, 37, 58, 51, 46, 17, 29, 7, - 27, 9, 43, 8, 34, 42, 28, 23, 57, 0, 11, 48, 52, 50, 15, 16, 26, 25, - 4, 36, 19, 44, 18, 13, - ], - want: 29, - }, - ] - - for (const test of tests) { - it(test.name, async () => { - bigNumEquals(test.want, await median.publicGet(test.responses)) - }) - } - }) - - // long running (minutes) exhaustive test. - // skipped because very slow, but useful for thorough validation - xit('permutations', async () => { - const permutations = (list: number[]) => { - const result: number[][] = [] - const used: number[] = [] - - const permute = (unused: number[]) => { - if (unused.length == 0) { - result.push([...used]) - return - } - - for (let i = 0; i < unused.length; i++) { - const elem = unused.splice(i, 1)[0] - used.push(elem) - permute(unused) - unused.splice(i, 0, elem) - used.pop() - } - } - - permute(list) - return result - } - - { - const list = [0, 2, 5, 7, 8, 10] - for (const permuted of permutations(list)) { - for (let i = 0; i < list.length; i++) { - for (let j = 0; j < list.length; j++) { - if (i < j) { - const foo = await median.publicQuickselectTwo(permuted, i, j) - bigNumEquals(list[i], foo[0]) - bigNumEquals(list[j], foo[1]) - } - } - } - } - } - - { - const list = [0, 1, 1, 1, 2] - for (const permuted of permutations(list)) { - for (let i = 0; i < list.length; i++) { - for (let j = 0; j < list.length; j++) { - if (i < j) { - const foo = await median.publicQuickselectTwo(permuted, i, j) - bigNumEquals(list[i], foo[0]) - bigNumEquals(list[j], foo[1]) - } - } - } - } - } - }) - - // Checks the validity of the sorting network in `shortList` - describe('validate sorting network', () => { - const net = [ - [0, 1], - [2, 3], - [4, 5], - [0, 2], - [1, 3], - [4, 6], - [1, 2], - [5, 6], - [0, 4], - [1, 5], - [2, 6], - [1, 4], - [3, 6], - [2, 4], - [3, 5], - [3, 4], - ] - - // See: https://en.wikipedia.org/wiki/Sorting_network#Zero-one_principle - xit('zero-one principle', async () => { - const sortWithNet = (list: number[]) => { - for (const [i, j] of net) { - if (list[i] > list[j]) { - ;[list[i], list[j]] = [list[j], list[i]] - } - } - } - - for (let n = 0; n < (1 << 7) - 1; n++) { - const list = [ - (n >> 6) & 1, - (n >> 5) & 1, - (n >> 4) & 1, - (n >> 3) & 1, - (n >> 2) & 1, - (n >> 1) & 1, - (n >> 0) & 1, - ] - const sum = list.reduce((a, b) => a + b, 0) - sortWithNet(list) - const sortedSum = list.reduce((a, b) => a + b, 0) - assert.equal(sortedSum, sum, 'Number of zeros and ones changed') - list.reduce((switched, i) => { - assert.isTrue(!switched || i != 0, 'error at n=' + n.toString()) - return i != 0 - }, false) - } - }) - }) -}) diff --git a/contracts/test/v0.6/Owned.test.ts b/contracts/test/v0.6/Owned.test.ts deleted file mode 100644 index f522b9c44c9..00000000000 --- a/contracts/test/v0.6/Owned.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Signer, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let owner: Signer -let nonOwner: Signer -let newOwner: Signer - -let ownedFactory: ContractFactory -let owned: Contract - -before(async () => { - personas = (await getUsers()).personas - owner = personas.Carol - nonOwner = personas.Neil - newOwner = personas.Ned - ownedFactory = await ethers.getContractFactory( - 'src/v0.6/Owned.sol:Owned', - owner, - ) -}) - -describe('Owned', () => { - beforeEach(async () => { - owned = await ownedFactory.connect(owner).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(owned, ['acceptOwnership', 'owner', 'transferOwnership']) - }) - - describe('#constructor', () => { - it('assigns ownership to the deployer', async () => { - const [actual, expected] = await Promise.all([ - owner.getAddress(), - owned.owner(), - ]) - - assert.equal(actual, expected) - }) - }) - - describe('#transferOwnership', () => { - describe('when called by an owner', () => { - it('emits a log', async () => { - await expect( - owned.connect(owner).transferOwnership(await newOwner.getAddress()), - ) - .to.emit(owned, 'OwnershipTransferRequested') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await expect( - owned.connect(nonOwner).transferOwnership(await newOwner.getAddress()), - ).to.be.reverted) - }) - - describe('#acceptOwnership', () => { - describe('after #transferOwnership has been called', () => { - beforeEach(async () => { - await owned - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - }) - - it('allows the recipient to call it', async () => { - await expect(owned.connect(newOwner).acceptOwnership()) - .to.emit(owned, 'OwnershipTransferred') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow a non-recipient to call it', async () => - await expect(owned.connect(nonOwner).acceptOwnership()).to.be.reverted) - }) - }) -}) diff --git a/contracts/test/v0.6/SignedSafeMath.test.ts b/contracts/test/v0.6/SignedSafeMath.test.ts deleted file mode 100644 index e942f64d6b5..00000000000 --- a/contracts/test/v0.6/SignedSafeMath.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { ethers } from 'hardhat' -import { expect } from 'chai' -import { Signer, Contract, ContractFactory, BigNumber } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals } from '../test-helpers/matchers' - -let defaultAccount: Signer -let concreteSignedSafeMathFactory: ContractFactory - -before(async () => { - const personas: Personas = (await getUsers()).personas - defaultAccount = personas.Default - concreteSignedSafeMathFactory = await ethers.getContractFactory( - 'src/v0.6/tests/ConcreteSignedSafeMath.sol:ConcreteSignedSafeMath', - defaultAccount, - ) -}) - -describe('SignedSafeMath', () => { - // a version of the adder contract where we make all ABI exposed functions constant - // TODO: submit upstream PR to support constant contract type generation - let adder: Contract - let response: BigNumber - - const INT256_MAX = BigNumber.from( - '57896044618658097711785492504343953926634992332820282019728792003956564819967', - ) - const INT256_MIN = BigNumber.from( - '-57896044618658097711785492504343953926634992332820282019728792003956564819968', - ) - - beforeEach(async () => { - adder = await concreteSignedSafeMathFactory.connect(defaultAccount).deploy() - }) - - describe('#add', () => { - describe('given a positive and a positive', () => { - it('works', async () => { - response = await adder.testAdd(1, 2) - bigNumEquals(3, response) - }) - - it('works with zero', async () => { - response = await adder.testAdd(INT256_MAX, 0) - bigNumEquals(INT256_MAX, response) - }) - - describe('when both are large enough to overflow', () => { - it('throws', async () => { - await expect(adder.testAdd(INT256_MAX, 1)).to.be.revertedWith( - 'SignedSafeMath: addition overflow', - ) - }) - }) - }) - - describe('given a negative and a negative', () => { - it('works', async () => { - response = await adder.testAdd(-1, -2) - bigNumEquals(-3, response) - }) - - it('works with zero', async () => { - response = await adder.testAdd(INT256_MIN, 0) - bigNumEquals(INT256_MIN, response) - }) - - describe('when both are large enough to overflow', () => { - it('throws', async () => { - await expect(adder.testAdd(INT256_MIN, -1)).to.be.revertedWith( - 'SignedSafeMath: addition overflow', - ) - }) - }) - }) - - describe('given a positive and a negative', () => { - it('works', async () => { - response = await adder.testAdd(1, -2) - bigNumEquals(-1, response) - }) - }) - - describe('given a negative and a positive', () => { - it('works', async () => { - response = await adder.testAdd(-1, 2) - bigNumEquals(1, response) - }) - }) - }) - - describe('#avg', () => { - describe('given a positive and a positive', () => { - it('works', async () => { - response = await adder.testAvg(2, 4) - bigNumEquals(3, response) - }) - - it('works with zero', async () => { - response = await adder.testAvg(0, 4) - bigNumEquals(2, response) - response = await adder.testAvg(4, 0) - bigNumEquals(2, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MAX, INT256_MAX) - bigNumEquals(INT256_MAX, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(1, 2) - bigNumEquals(1, response) - }) - }) - - describe('given a negative and a negative', () => { - it('works', async () => { - response = await adder.testAvg(-2, -4) - bigNumEquals(-3, response) - }) - - it('works with zero', async () => { - response = await adder.testAvg(0, -4) - bigNumEquals(-2, response) - response = await adder.testAvg(-4, 0) - bigNumEquals(-2, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MIN, INT256_MIN) - bigNumEquals(INT256_MIN, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(-1, -2) - bigNumEquals(-1, response) - }) - }) - - describe('given a positive and a negative', () => { - it('works', async () => { - response = await adder.testAvg(2, -4) - bigNumEquals(-1, response) - response = await adder.testAvg(4, -2) - bigNumEquals(1, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MAX, -2) - bigNumEquals(INT256_MAX.sub(2).div(2), response) - response = await adder.testAvg(INT256_MAX, INT256_MIN) - bigNumEquals(0, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(1, -4) - bigNumEquals(-1, response) - response = await adder.testAvg(4, -1) - bigNumEquals(1, response) - }) - }) - - describe('given a negative and a positive', () => { - it('works', async () => { - response = await adder.testAvg(-2, 4) - bigNumEquals(1, response) - response = await adder.testAvg(-4, 2) - bigNumEquals(-1, response) - }) - - it('works with large numbers', async () => { - response = await adder.testAvg(INT256_MIN, 2) - bigNumEquals(INT256_MIN.add(2).div(2), response) - response = await adder.testAvg(INT256_MIN, INT256_MAX) - bigNumEquals(0, response) - }) - - it('rounds towards zero', async () => { - response = await adder.testAvg(-1, 4) - bigNumEquals(1, response) - response = await adder.testAvg(-4, 1) - bigNumEquals(-1, response) - }) - }) - }) -}) diff --git a/contracts/test/v0.6/SimpleReadAccessController.test.ts b/contracts/test/v0.6/SimpleReadAccessController.test.ts deleted file mode 100644 index 7b76bc38cad..00000000000 --- a/contracts/test/v0.6/SimpleReadAccessController.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Transaction } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let controller: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleReadAccessController.sol:SimpleReadAccessController', - personas.Carol, - ) -}) - -describe('SimpleReadAccessController', () => { - beforeEach(async () => { - controller = await controllerFactory.connect(personas.Carol).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(controller, [ - 'hasAccess', - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('defaults checkEnabled to true', async () => { - assert(await controller.checkEnabled()) - }) - }) - - describe('#hasAccess', () => { - it('allows unauthorized calls originating from the same account', async () => { - assert.isTrue( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('blocks unauthorized calls originating from different accounts', async () => { - assert.isFalse( - await controller - .connect(personas.Carol) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Carol.getAddress(), '0x00'), - ) - }) - }) - - describe('#addAccess', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .addAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - tx = await controller.addAccess(await personas.Eddy.getAddress()) - }) - - it('adds the address to the controller', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx) - .to.emit(controller, 'AddedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.addAccess( - await personas.Eddy.getAddress(), - ) - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#removeAccess', () => { - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .removeAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - tx = await controller.removeAccess(await personas.Eddy.getAddress()) - }) - - it('removes the address from the controller', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx) - .to.emit(controller, 'RemovedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.removeAccess( - await personas.Eddy.getAddress(), - ) - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#disableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).disableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - assert.isTrue(await controller.checkEnabled()) - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.disableAccessCheck() - }) - - it('sets checkEnabled to false', async () => { - assert.isFalse(await controller.checkEnabled()) - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('allows users without access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessDisabled') - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.disableAccessCheck() - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) - - describe('#enableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).enableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.disableAccessCheck() - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.enableAccessCheck() - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('does not allow users without access', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx).to.emit(controller, 'CheckAccessEnabled') - }) - - describe('when called twice', () => { - it('does not emit a log', async () => { - const tx2 = await controller.enableAccessCheck() - const receipt = await tx2.wait() - assert.equal(receipt.events?.length, 0) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.6/SimpleWriteAccessController.test.ts b/contracts/test/v0.6/SimpleWriteAccessController.test.ts deleted file mode 100644 index ae6c1691f91..00000000000 --- a/contracts/test/v0.6/SimpleWriteAccessController.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Transaction } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' - -let personas: Personas - -let controllerFactory: ContractFactory -let controller: Contract - -before(async () => { - personas = (await getUsers()).personas - controllerFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) -}) - -describe('SimpleWriteAccessController', () => { - beforeEach(async () => { - controller = await controllerFactory.connect(personas.Carol).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(controller, [ - 'hasAccess', - 'addAccess', - 'disableAccessCheck', - 'enableAccessCheck', - 'removeAccess', - 'checkEnabled', - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('defaults checkEnabled to true', async () => { - assert(await controller.checkEnabled()) - }) - }) - - describe('#hasAccess', () => { - it('allows unauthorized calls originating from the same account', async () => { - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('blocks unauthorized calls originating from different accounts', async () => { - assert.isFalse( - await controller - .connect(personas.Carol) - .hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - assert.isFalse( - await controller - .connect(personas.Eddy) - .hasAccess(await personas.Carol.getAddress(), '0x00'), - ) - }) - }) - - describe('#addAccess', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .addAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - tx = await controller.addAccess(await personas.Eddy.getAddress()) - }) - - it('adds the address to the controller', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx) - .to.emit(controller, 'AddedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - }) - }) - - describe('#removeAccess', () => { - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller - .connect(personas.Eddy) - .removeAccess(await personas.Eddy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - tx = await controller.removeAccess(await personas.Eddy.getAddress()) - }) - - it('removes the address from the controller', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - expect(tx) - .to.emit(controller, 'RemovedAccess') - .withArgs(await personas.Eddy.getAddress()) - }) - }) - }) - - describe('#disableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).disableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - assert.isTrue(await controller.checkEnabled()) - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.disableAccessCheck() - }) - - it('sets checkEnabled to false', async () => { - assert.isFalse(await controller.checkEnabled()) - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('allows users without access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessDisabled') - }) - }) - }) - - describe('#enableAccessCheck', () => { - describe('when called by a non-owner', () => { - it('reverts', async () => { - await expect( - controller.connect(personas.Eddy).enableAccessCheck(), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('when called by the owner', () => { - let tx: Transaction - beforeEach(async () => { - await controller.disableAccessCheck() - await controller.addAccess(await personas.Eddy.getAddress()) - tx = await controller.enableAccessCheck() - }) - - it('allows users with access', async () => { - assert.isTrue( - await controller.hasAccess(await personas.Eddy.getAddress(), '0x00'), - ) - }) - - it('does not allow users without access', async () => { - assert.isFalse( - await controller.hasAccess(await personas.Ned.getAddress(), '0x00'), - ) - }) - - it('announces the change via a log', async () => { - await expect(tx).to.emit(controller, 'CheckAccessEnabled') - }) - }) - }) -}) diff --git a/contracts/test/v0.6/VRFD20.test.ts b/contracts/test/v0.6/VRFD20.test.ts deleted file mode 100644 index 77141be6230..00000000000 --- a/contracts/test/v0.6/VRFD20.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { getUsers, Personas, Roles } from '../test-helpers/setup' -import { - evmWordToAddress, - getLog, - publicAbi, - toBytes32String, - toWei, - numToBytes32, - getLogs, -} from '../test-helpers/helpers' - -let roles: Roles -let personas: Personas -let linkTokenFactory: ContractFactory -let vrfCoordinatorMockFactory: ContractFactory -let vrfD20Factory: ContractFactory - -before(async () => { - const users = await getUsers() - - roles = users.roles - personas = users.personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - vrfCoordinatorMockFactory = await ethers.getContractFactory( - 'src/v0.6/tests/VRFCoordinatorMock.sol:VRFCoordinatorMock', - roles.defaultAccount, - ) - vrfD20Factory = await ethers.getContractFactory( - 'src/v0.6/examples/VRFD20.sol:VRFD20', - roles.defaultAccount, - ) -}) - -describe('VRFD20', () => { - const deposit = toWei('1') - const fee = toWei('0.1') - const keyHash = toBytes32String('keyHash') - - let link: Contract - let vrfCoordinator: Contract - let vrfD20: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - vrfCoordinator = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - vrfD20 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await link.transfer(vrfD20.address, deposit) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(vrfD20, [ - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - //VRFConsumerBase - 'rawFulfillRandomness', - // VRFD20 - 'rollDice', - 'house', - 'withdrawLINK', - 'keyHash', - 'fee', - 'setKeyHash', - 'setFee', - ]) - }) - - describe('#withdrawLINK', () => { - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .withdrawLINK(await roles.stranger.getAddress(), deposit), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when not enough LINK in the contract', async () => { - const withdrawAmount = deposit.mul(2) - await expect( - vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK( - await roles.defaultAccount.getAddress(), - withdrawAmount, - ), - ).to.be.reverted - }) - }) - - describe('success', () => { - it('withdraws LINK', async () => { - const startingAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - const expectedAmount = BigNumber.from(startingAmount).add(deposit) - await vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK(await roles.defaultAccount.getAddress(), deposit) - const actualAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - assert.equal(actualAmount.toString(), expectedAmount.toString()) - }) - }) - }) - - describe('#setKeyHash', () => { - const newHash = toBytes32String('newhash') - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setKeyHash(newHash), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the key hash', async () => { - await vrfD20.setKeyHash(newHash) - const actualHash = await vrfD20.keyHash() - assert.equal(actualHash, newHash) - }) - }) - }) - - describe('#setFee', () => { - const newFee = 1234 - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setFee(newFee), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the fee', async () => { - await vrfD20.setFee(newFee) - const actualFee = await vrfD20.fee() - assert.equal(actualFee.toString(), newFee.toString()) - }) - }) - }) - - describe('#house', () => { - describe('failure', () => { - it('reverts when dice not rolled', async () => { - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Dice not rolled') - }) - - it('reverts when dice roll is in progress', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Roll in progress') - }) - }) - - describe('success', () => { - it('returns the correct house', async () => { - const randomness = 98765 - const expectedHouse = 'Martell' - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - const eventRequestId = log?.topics?.[1] - await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - }) - }) - - describe('#rollDice', () => { - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - }) - - it('emits a RandomnessRequest event from the VRFCoordinator', async () => { - const log = await getLog(tx, 2) - const topics = log?.topics - assert.equal(evmWordToAddress(topics?.[1]), vrfD20.address) - assert.equal(topics?.[2], keyHash) - assert.equal(topics?.[3], constants.HashZero) - }) - }) - - describe('failure', () => { - it('reverts when LINK balance is zero', async () => { - const vrfD202 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await expect( - vrfD202.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Not enough LINK to pay fee') - }) - - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when the roller rolls more than once', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Already rolled') - }) - }) - }) - - describe('#fulfillRandomness', () => { - const randomness = 98765 - const expectedModResult = (randomness % 20) + 1 - const expectedHouse = 'Martell' - let eventRequestId: string - beforeEach(async () => { - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - }) - - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - }) - - it('emits a DiceLanded event', async () => { - const log = await getLog(tx, 0) - assert.equal(log?.topics[1], eventRequestId) - assert.equal(log?.topics[2], numToBytes32(expectedModResult)) - }) - - it('sets the correct dice roll result', async () => { - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - - it('allows someone else to roll', async () => { - const secondRandomness = 55555 - tx = await vrfD20.rollDice(await personas.Ned.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - secondRandomness, - vrfD20.address, - ) - }) - }) - - describe('failure', () => { - it('does not fulfill when fulfilled by the wrong VRFcoordinator', async () => { - const vrfCoordinator2 = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - - const tx = await vrfCoordinator2.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/AggregatorProxy.test.ts b/contracts/test/v0.7/AggregatorProxy.test.ts deleted file mode 100644 index 6e8ee41983d..00000000000 --- a/contracts/test/v0.7/AggregatorProxy.test.ts +++ /dev/null @@ -1,743 +0,0 @@ -import { ethers } from 'hardhat' -import { - increaseTimeBy, - numToBytes32, - publicAbi, - toWei, -} from '../test-helpers/helpers' -import { assert } from 'chai' -import { BigNumber, constants, Contract, ContractFactory, Signer } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let defaultAccount: Signer - -let linkTokenFactory: ContractFactory -let aggregatorFactory: ContractFactory -let historicAggregatorFactory: ContractFactory -let aggregatorFacadeFactory: ContractFactory -let aggregatorProxyFactory: ContractFactory -let fluxAggregatorFactory: ContractFactory -let reverterFactory: ContractFactory - -before(async () => { - const users = await getUsers() - - personas = users.personas - defaultAccount = users.roles.defaultAccount - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - defaultAccount, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - defaultAccount, - ) - historicAggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV2Aggregator.sol:MockV2Aggregator', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) - historicAggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV2Aggregator.sol:MockV2Aggregator', - defaultAccount, - ) - aggregatorFacadeFactory = await ethers.getContractFactory( - 'src/v0.6/AggregatorFacade.sol:AggregatorFacade', - defaultAccount, - ) - aggregatorProxyFactory = await ethers.getContractFactory( - 'src/v0.7/dev/AggregatorProxy.sol:AggregatorProxy', - defaultAccount, - ) - fluxAggregatorFactory = await ethers.getContractFactory( - 'src/v0.6/FluxAggregator.sol:FluxAggregator', - defaultAccount, - ) - reverterFactory = await ethers.getContractFactory( - 'src/v0.6/tests/Reverter.sol:Reverter', - defaultAccount, - ) -}) - -describe('AggregatorProxy', () => { - const deposit = toWei('100') - const response = numToBytes32(54321) - const response2 = numToBytes32(67890) - const decimals = 18 - const phaseBase = BigNumber.from(2).pow(64) - - let link: Contract - let aggregator: Contract - let aggregator2: Contract - let historicAggregator: Contract - let proxy: Contract - let flux: Contract - let reverter: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(defaultAccount).deploy() - aggregator = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response) - await link.transfer(aggregator.address, deposit) - proxy = await aggregatorProxyFactory - .connect(defaultAccount) - .deploy(aggregator.address) - const emptyAddress = constants.AddressZero - flux = await fluxAggregatorFactory - .connect(personas.Carol) - .deploy(link.address, 0, 0, emptyAddress, 0, 0, 18, 'TEST / LINK') - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(proxy, [ - 'aggregator', - 'confirmAggregator', - 'decimals', - 'description', - 'getAnswer', - 'getRoundData', - 'getTimestamp', - 'latestAnswer', - 'latestRound', - 'latestRoundData', - 'latestTimestamp', - 'phaseAggregators', - 'phaseId', - 'proposeAggregator', - 'proposedAggregator', - 'proposedGetRoundData', - 'proposedLatestRoundData', - 'version', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('constructor', () => { - it('sets the proxy phase and aggregator', async () => { - bigNumEquals(1, await proxy.phaseId()) - assert.equal(aggregator.address, await proxy.phaseAggregators(1)) - }) - }) - - describe('#latestRound', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(phaseBase.add(1), await proxy.latestRound()) - }) - }) - - describe('#latestAnswer', () => { - it('pulls the rate from the aggregator', async () => { - bigNumEquals(response, await proxy.latestAnswer()) - const latestRound = await proxy.latestRound() - bigNumEquals(response, await proxy.getAnswer(latestRound)) - }) - - describe('after being updated to another contract', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - await link.transfer(aggregator2.address, deposit) - bigNumEquals(response2, await aggregator2.latestAnswer()) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('pulls the rate from the new aggregator', async () => { - bigNumEquals(response2, await proxy.latestAnswer()) - const latestRound = await proxy.latestRound() - bigNumEquals(response2, await proxy.getAnswer(latestRound)) - }) - }) - - describe('when the relevant info is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const actual = await proxy.latestAnswer() - bigNumEquals(0, actual) - }) - }) - }) - - describe('#getAnswer', () => { - describe('when the relevant round is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) - const actual = await proxy.getAnswer(proxyId) - bigNumEquals(0, actual) - }) - }) - - describe('when the answer reverts in a non-predicted way', () => { - it('reverts', async () => { - reverter = await reverterFactory.connect(defaultAccount).deploy() - await proxy.proposeAggregator(reverter.address) - await proxy.confirmAggregator(reverter.address) - assert.equal(reverter.address, await proxy.aggregator()) - - const proxyId = phaseBase.mul(await proxy.phaseId()) - - await evmRevert(proxy.getAnswer(proxyId), 'Raised by Reverter.sol') - }) - }) - - describe('after being updated to another contract', () => { - let preUpdateRoundId: BigNumber - let preUpdateAnswer: BigNumber - - beforeEach(async () => { - preUpdateRoundId = await proxy.latestRound() - preUpdateAnswer = await proxy.latestAnswer() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - await link.transfer(aggregator2.address, deposit) - bigNumEquals(response2, await aggregator2.latestAnswer()) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('reports answers for previous phases', async () => { - const actualAnswer = await proxy.getAnswer(preUpdateRoundId) - bigNumEquals(preUpdateAnswer, actualAnswer) - }) - }) - - describe('when the relevant info is not available', () => { - it('returns 0', async () => { - const actual = await proxy.getAnswer(phaseBase.mul(777)) - bigNumEquals(0, actual) - }) - }) - - describe('when the round ID is too large', () => { - const overflowRoundId = BigNumber.from(2) - .pow(255) - .add(phaseBase) // get the original phase - .add(1) // get the original round - it('returns 0', async () => { - const actual = await proxy.getTimestamp(overflowRoundId) - bigNumEquals(0, actual) - }) - }) - }) - - describe('#getTimestamp', () => { - describe('when the relevant round is not available', () => { - beforeEach(async () => { - await proxy.proposeAggregator(flux.address) - await proxy.confirmAggregator(flux.address) - }) - - it('does not revert when called with a non existent ID', async () => { - const proxyId = phaseBase.mul(await proxy.phaseId()).add(1) - const actual = await proxy.getTimestamp(proxyId) - bigNumEquals(0, actual) - }) - }) - - describe('when the relevant info is not available', () => { - it('returns 0', async () => { - const actual = await proxy.getTimestamp(phaseBase.mul(777)) - bigNumEquals(0, actual) - }) - }) - - describe('when the round ID is too large', () => { - const overflowRoundId = BigNumber.from(2) - .pow(255) - .add(phaseBase) // get the original phase - .add(1) // get the original round - - it('returns 0', async () => { - const actual = await proxy.getTimestamp(overflowRoundId) - bigNumEquals(0, actual) - }) - }) - }) - - describe('#latestTimestamp', () => { - beforeEach(async () => { - const height = await aggregator.latestTimestamp() - assert.notEqual('0', height.toString()) - }) - - it('pulls the timestamp from the aggregator', async () => { - bigNumEquals( - await aggregator.latestTimestamp(), - await proxy.latestTimestamp(), - ) - const latestRound = await proxy.latestRound() - bigNumEquals( - await aggregator.latestTimestamp(), - await proxy.getTimestamp(latestRound), - ) - }) - - describe('after being updated to another contract', () => { - beforeEach(async () => { - await increaseTimeBy(30, ethers.provider) - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - const height2 = await aggregator2.latestTimestamp() - assert.notEqual('0', height2.toString()) - - const height1 = await aggregator.latestTimestamp() - assert.notEqual( - height1.toString(), - height2.toString(), - 'Height1 and Height2 should not be equal', - ) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('pulls the timestamp from the new aggregator', async () => { - bigNumEquals( - await aggregator2.latestTimestamp(), - await proxy.latestTimestamp(), - ) - const latestRound = await proxy.latestRound() - bigNumEquals( - await aggregator2.latestTimestamp(), - await proxy.getTimestamp(latestRound), - ) - }) - }) - }) - - describe('#getRoundData', () => { - describe('when pointed at a Historic Aggregator', () => { - beforeEach(async () => { - historicAggregator = await historicAggregatorFactory - .connect(defaultAccount) - .deploy(response2) - await proxy.proposeAggregator(historicAggregator.address) - await proxy.confirmAggregator(historicAggregator.address) - }) - - it('reverts', async () => { - const latestRoundId = await historicAggregator.latestRound() - await evmRevert(proxy.getRoundData(latestRoundId)) - }) - - describe('when pointed at an Aggregator Facade', () => { - beforeEach(async () => { - const facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy(aggregator.address, 18, 'LINK/USD: Aggregator Facade') - await proxy.proposeAggregator(facade.address) - await proxy.confirmAggregator(facade.address) - }) - - it('works for a valid roundId', async () => { - const aggId = await aggregator.latestRound() - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - }) - - describe('when pointed at a FluxAggregator', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('works for a valid round ID', async () => { - const aggId = phaseBase.sub(2) - await aggregator2 - .connect(personas.Carol) - .updateRoundData(aggId, response2, 77, 42) - - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - bigNumEquals(42, round.startedAt) - bigNumEquals(77, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - - it('reads round ID of a previous phase', async () => { - const oldphaseId = phaseBase.mul(await proxy.phaseId()) - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - - const aggId = await aggregator.latestRound() - const proxyId = oldphaseId.add(aggId) - - const round = await proxy.getRoundData(proxyId) - bigNumEquals(proxyId, round.id) - bigNumEquals(response, round.answer) - - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.startedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.startedAt, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - }) - - describe('#latestRoundData', () => { - describe('when pointed at a Historic Aggregator', () => { - beforeEach(async () => { - historicAggregator = await historicAggregatorFactory - .connect(defaultAccount) - .deploy(response2) - await proxy.proposeAggregator(historicAggregator.address) - await proxy.confirmAggregator(historicAggregator.address) - }) - - it('reverts', async () => { - await evmRevert(proxy.latestRoundData()) - }) - - describe('when pointed at an Aggregator Facade', () => { - beforeEach(async () => { - const facade = await aggregatorFacadeFactory - .connect(defaultAccount) - .deploy( - historicAggregator.address, - 17, - 'DOGE/ZWL: Aggregator Facade', - ) - await proxy.proposeAggregator(facade.address) - await proxy.confirmAggregator(facade.address) - }) - - it('does not revert', async () => { - const aggId = await historicAggregator.latestRound() - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.latestRoundData() - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - const nowSeconds = new Date().valueOf() / 1000 - assert.isAbove(round.updatedAt.toNumber(), nowSeconds - 120) - bigNumEquals(round.updatedAt, round.startedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - - it('uses the decimals set in the constructor', async () => { - bigNumEquals(17, await proxy.decimals()) - }) - - it('uses the description set in the constructor', async () => { - assert.equal('DOGE/ZWL: Aggregator Facade', await proxy.description()) - }) - - it('sets the version to 2', async () => { - bigNumEquals(2, await proxy.version()) - }) - }) - }) - - describe('when pointed at a FluxAggregator', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - - await proxy.proposeAggregator(aggregator2.address) - await proxy.confirmAggregator(aggregator2.address) - }) - - it('does not revert', async () => { - const aggId = phaseBase.sub(2) - await aggregator2 - .connect(personas.Carol) - .updateRoundData(aggId, response2, 77, 42) - - const phaseId = phaseBase.mul(await proxy.phaseId()) - const proxyId = phaseId.add(aggId) - - const round = await proxy.latestRoundData() - bigNumEquals(proxyId, round.id) - bigNumEquals(response2, round.answer) - bigNumEquals(42, round.startedAt) - bigNumEquals(77, round.updatedAt) - bigNumEquals(proxyId, round.answeredInRound) - }) - - it('uses the decimals of the aggregator', async () => { - bigNumEquals(18, await proxy.decimals()) - }) - - it('uses the description of the aggregator', async () => { - assert.equal( - 'v0.6/tests/MockV3Aggregator.sol', - await proxy.description(), - ) - }) - - it('uses the version of the aggregator', async () => { - bigNumEquals(0, await proxy.version()) - }) - }) - }) - - describe('#proposeAggregator', () => { - beforeEach(async () => { - await proxy.transferOwnership(await personas.Carol.getAddress()) - await proxy.connect(personas.Carol).acceptOwnership() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, 1) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - - describe('when called by the owner', () => { - it('sets the address of the proposed aggregator', async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.proposedAggregator()) - }) - - it('emits an AggregatorProposed event', async () => { - const tx = await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 1) - assert.equal(eventLog?.[0].event, 'AggregatorProposed') - assert.equal(eventLog?.[0].args?.[0], aggregator.address) - assert.equal(eventLog?.[0].args?.[1], aggregator2.address) - }) - }) - - describe('when called by a non-owner', () => { - it('does not update', async () => { - await evmRevert( - proxy.connect(personas.Neil).proposeAggregator(aggregator2.address), - 'Only callable by owner', - ) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - }) - }) - - describe('#confirmAggregator', () => { - beforeEach(async () => { - await proxy.transferOwnership(await personas.Carol.getAddress()) - await proxy.connect(personas.Carol).acceptOwnership() - - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, 1) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - - describe('when called by the owner', () => { - beforeEach(async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - }) - - it('sets the address of the new aggregator', async () => { - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.aggregator()) - }) - - it('increases the phase', async () => { - bigNumEquals(1, await proxy.phaseId()) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - bigNumEquals(2, await proxy.phaseId()) - }) - - it('increases the round ID', async () => { - bigNumEquals(phaseBase.add(1), await proxy.latestRound()) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - bigNumEquals(phaseBase.mul(2).add(1), await proxy.latestRound()) - }) - - it('sets the proxy phase and aggregator', async () => { - assert.equal( - '0x0000000000000000000000000000000000000000', - await proxy.phaseAggregators(2), - ) - - await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - - assert.equal(aggregator2.address, await proxy.phaseAggregators(2)) - }) - - it('emits an AggregatorConfirmed event', async () => { - const tx = await proxy - .connect(personas.Carol) - .confirmAggregator(aggregator2.address) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 1) - assert.equal(eventLog?.[0].event, 'AggregatorConfirmed') - assert.equal(eventLog?.[0].args?.[0], aggregator.address) - assert.equal(eventLog?.[0].args?.[1], aggregator2.address) - }) - }) - - describe('when called by a non-owner', () => { - beforeEach(async () => { - await proxy - .connect(personas.Carol) - .proposeAggregator(aggregator2.address) - }) - - it('does not update', async () => { - await evmRevert( - proxy.connect(personas.Neil).confirmAggregator(aggregator2.address), - 'Only callable by owner', - ) - - assert.equal(aggregator.address, await proxy.aggregator()) - }) - }) - }) - - describe('#proposedGetRoundData', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - }) - - describe('when an aggregator has been proposed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .proposeAggregator(aggregator2.address) - assert.equal(await proxy.proposedAggregator(), aggregator2.address) - }) - - it('returns the data for the proposed aggregator', async () => { - const roundId = await aggregator2.latestRound() - const round = await proxy.proposedGetRoundData(roundId) - bigNumEquals(roundId, round.id) - bigNumEquals(response2, round.answer) - }) - - describe('after the aggregator has been confirmed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .confirmAggregator(aggregator2.address) - assert.equal(await proxy.aggregator(), aggregator2.address) - }) - - it('reverts', async () => { - const roundId = await aggregator2.latestRound() - await evmRevert( - proxy.proposedGetRoundData(roundId), - 'No proposed aggregator present', - ) - }) - }) - }) - }) - - describe('#proposedLatestRoundData', () => { - beforeEach(async () => { - aggregator2 = await aggregatorFactory - .connect(defaultAccount) - .deploy(decimals, response2) - }) - - describe('when an aggregator has been proposed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .proposeAggregator(aggregator2.address) - assert.equal(await proxy.proposedAggregator(), aggregator2.address) - }) - - it('returns the data for the proposed aggregator', async () => { - const roundId = await aggregator2.latestRound() - const round = await proxy.proposedLatestRoundData() - bigNumEquals(roundId, round.id) - bigNumEquals(response2, round.answer) - }) - - describe('after the aggregator has been confirmed', () => { - beforeEach(async () => { - await proxy - .connect(defaultAccount) - .confirmAggregator(aggregator2.address) - assert.equal(await proxy.aggregator(), aggregator2.address) - }) - - it('reverts', async () => { - await evmRevert( - proxy.proposedLatestRoundData(), - 'No proposed aggregator present', - ) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/AuthorizedForwarder.test.ts b/contracts/test/v0.7/AuthorizedForwarder.test.ts deleted file mode 100644 index e1fa2f1f708..00000000000 --- a/contracts/test/v0.7/AuthorizedForwarder.test.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, ContractReceipt } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let getterSetterFactory: ContractFactory -let forwarderFactory: ContractFactory -let brokenFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles -const zeroAddress = ethers.constants.AddressZero - -before(async () => { - const users = await getUsers() - - roles = users.roles - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - brokenFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Broken.sol:Broken', - roles.defaultAccount, - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('AuthorizedForwarder', () => { - let link: Contract - let forwarder: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .deploy( - link.address, - await roles.defaultAccount.getAddress(), - zeroAddress, - '0x', - ) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(forwarder, [ - 'forward', - 'getAuthorizedSenders', - 'getChainlinkToken', - 'isAuthorizedSender', - 'ownerForward', - 'setAuthorizedSenders', - 'transferOwnershipWithMessage', - 'typeAndVersion', - // ConfirmedOwner - 'transferOwnership', - 'acceptOwnership', - 'owner', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the authorized forwarder', async () => { - assert.equal( - await forwarder.typeAndVersion(), - 'AuthorizedForwarder 1.0.0', - ) - }) - }) - - describe('deployment', () => { - it('sets the correct link token', async () => { - assert.equal(await forwarder.getChainlinkToken(), link.address) - }) - - it('reverts on zeroAddress value for link token', async () => { - await evmRevert( - forwarderFactory.connect(roles.defaultAccount).deploy( - zeroAddress, // Link Address - await roles.defaultAccount.getAddress(), - zeroAddress, - '0x', - ), - ) - }) - - it('sets no authorized senders', async () => { - const senders = await forwarder.getAuthorizedSenders() - assert.equal(senders.length, 0) - }) - }) - - describe('#setAuthorizedSenders', () => { - let newSenders: string[] - let receipt: ContractReceipt - describe('when called by the owner', () => { - describe('set authorized senders containing duplicate/s', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - }) - it('reverts with a must not have duplicate senders message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must not have duplicate senders', - ) - }) - }) - - describe('setting 3 authorized senders', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const tx = await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - receipt = await tx.wait() - }) - - it('adds the authorized nodes', async () => { - const authorizedSenders = await forwarder.getAuthorizedSenders() - assert.equal(newSenders.length, authorizedSenders.length) - for (let i = 0; i < authorizedSenders.length; i++) { - assert.equal(authorizedSenders[i], newSenders[i]) - } - }) - - it('emits an event', async () => { - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'AuthorizedSendersChanged') - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, await roles.defaultAccount.getAddress()], - ) - assert.equal(responseEvent?.data, encodedSenders) - }) - - it('replaces the authorized nodes', async () => { - const newSenders = await forwarder - .connect(roles.defaultAccount) - .getAuthorizedSenders() - assert.notIncludeOrderedMembers(newSenders, [ - await roles.oracleNode.getAddress(), - ]) - }) - - after(async () => { - await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - }) - - describe('setting 0 authorized senders', () => { - beforeEach(async () => { - newSenders = [] - }) - - it('reverts with a minimum senders message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must have at least 1 sender', - ) - }) - }) - }) - - describe('when called by a non-owner', () => { - it('cannot add an authorized node', async () => { - await evmRevert( - forwarder - .connect(roles.stranger) - .setAuthorizedSenders([await roles.stranger.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#forward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - }) - - describe('when called by an unauthorized node', () => { - it('reverts', async () => { - await evmRevert( - forwarder.connect(roles.stranger).forward(mock.address, payload), - ) - }) - }) - - describe('when called by an authorized node', () => { - beforeEach(async () => { - await forwarder - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - }) - - describe('when destination call reverts', () => { - let brokenMock: Contract - let brokenPayload: string - let brokenMsgPayload: string - - beforeEach(async () => { - brokenMock = await brokenFactory - .connect(roles.defaultAccount) - .deploy() - brokenMsgPayload = brokenFactory.interface.encodeFunctionData( - brokenFactory.interface.getFunction('revertWithMessage'), - ['Failure message'], - ) - - brokenPayload = brokenFactory.interface.encodeFunctionData( - brokenFactory.interface.getFunction('revertSilently'), - [], - ) - }) - - describe('when reverts with message', () => { - it('return revert message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(brokenMock.address, brokenMsgPayload), - "reverted with reason string 'Failure message'", - ) - }) - }) - - describe('when reverts without message', () => { - it('return silent failure message', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(brokenMock.address, brokenPayload), - 'Forwarded call reverted without reason', - ) - }) - }) - }) - - describe('when sending to a non-contract address', () => { - it('reverts', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - }) - - describe('when attempting to forward to the link token', () => { - it('reverts', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') // any Link Token function - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .forward(link.address, sighash), - ) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .forward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('perceives the message is sent by the AuthorizedForwarder', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .forward(mock.address, payload) - await expect(tx) - .to.emit(mock, 'SetBytes') - .withArgs(forwarder.address, bytes) - }) - }) - }) - }) - - describe('#transferOwnershipWithMessage', () => { - const message = '0x42' - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - forwarder - .connect(roles.stranger) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ), - 'Only callable by owner', - ) - }) - }) - - describe('when called by the owner', () => { - it('calls the normal ownership transfer proposal', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ) - const receipt = await tx.wait() - - assert.equal(receipt?.events?.[0]?.event, 'OwnershipTransferRequested') - assert.equal(receipt?.events?.[0]?.address, forwarder.address) - assert.equal( - receipt?.events?.[0]?.args?.[0], - await roles.defaultAccount.getAddress(), - ) - assert.equal( - receipt?.events?.[0]?.args?.[1], - await roles.stranger.getAddress(), - ) - }) - - it('calls the normal ownership transfer proposal', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .transferOwnershipWithMessage( - await roles.stranger.getAddress(), - message, - ) - const receipt = await tx.wait() - - assert.equal( - receipt?.events?.[1]?.event, - 'OwnershipTransferRequestedWithMessage', - ) - assert.equal(receipt?.events?.[1]?.address, forwarder.address) - assert.equal( - receipt?.events?.[1]?.args?.[0], - await roles.defaultAccount.getAddress(), - ) - assert.equal( - receipt?.events?.[1]?.args?.[1], - await roles.stranger.getAddress(), - ) - assert.equal(receipt?.events?.[1]?.args?.[2], message) - }) - }) - }) - - describe('#ownerForward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - forwarder.connect(roles.stranger).ownerForward(mock.address, payload), - ) - }) - }) - - describe('when called by owner', () => { - describe('when attempting to forward to the link token', () => { - it('does not revert', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') // any Link Token function - - await forwarder - .connect(roles.defaultAccount) - .ownerForward(link.address, sighash) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('reverts when sending to a non-contract address', async () => { - await evmRevert( - forwarder - .connect(roles.defaultAccount) - .ownerForward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - - it('perceives the message is sent by the Operator', async () => { - const tx = await forwarder - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await expect(tx) - .to.emit(mock, 'SetBytes') - .withArgs(forwarder.address, bytes) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/Chainlink.test.ts b/contracts/test/v0.7/Chainlink.test.ts deleted file mode 100644 index 7792895934c..00000000000 --- a/contracts/test/v0.7/Chainlink.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi, decodeDietCBOR, hexToBuf } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, providers, Signer } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { makeDebug } from '../test-helpers/debug' - -const debug = makeDebug('ChainlinkTestHelper') -let concreteChainlinkFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - concreteChainlinkFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ChainlinkTestHelper.sol:ChainlinkTestHelper', - roles.defaultAccount, - ) -}) - -describe('ChainlinkTestHelper', () => { - let ccl: Contract - let defaultAccount: Signer - - beforeEach(async () => { - defaultAccount = roles.defaultAccount - ccl = await concreteChainlinkFactory.connect(defaultAccount).deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(ccl, [ - 'add', - 'addBytes', - 'addInt', - 'addStringArray', - 'addUint', - 'closeEvent', - 'setBuffer', - ]) - }) - - async function parseCCLEvent(tx: providers.TransactionResponse) { - const receipt = await tx.wait() - const data = receipt.logs?.[0].data - const d = debug.extend('parseCCLEvent') - d('data %s', data) - return ethers.utils.defaultAbiCoder.decode(['bytes'], data ?? '') - } - - describe('#close', () => { - it('handles empty payloads', async () => { - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, {}) - }) - }) - - describe('#setBuffer', () => { - it('emits the buffer', async () => { - await ccl.setBuffer('0xA161616162') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { a: 'b' }) - }) - }) - - describe('#add', () => { - it('stores and logs keys and values', async () => { - await ccl.add('first', 'word!!') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 'word!!' }) - }) - - it('handles two entries', async () => { - await ccl.add('first', 'uno') - await ccl.add('second', 'dos') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 'uno', - second: 'dos', - }) - }) - }) - - describe('#addBytes', () => { - it('stores and logs keys and values', async () => { - await ccl.addBytes('first', '0xaabbccddeeff') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = hexToBuf('0xaabbccddeeff') - assert.deepEqual(decoded, { first: expected }) - }) - - it('handles two entries', async () => { - await ccl.addBytes('first', '0x756E6F') - await ccl.addBytes('second', '0x646F73') - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - const expectedFirst = hexToBuf('0x756E6F') - const expectedSecond = hexToBuf('0x646F73') - assert.deepEqual(decoded, { - first: expectedFirst, - second: expectedSecond, - }) - }) - - it('handles strings', async () => { - await ccl.addBytes('first', ethers.utils.toUtf8Bytes('apple')) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = ethers.utils.toUtf8Bytes('apple') - assert.deepEqual(decoded, { first: expected }) - }) - }) - - describe('#addInt', () => { - it('stores and logs keys and values', async () => { - await ccl.addInt('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addInt('first', 1) - await ccl.addInt('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addUint', () => { - it('stores and logs keys and values', async () => { - await ccl.addUint('first', 1) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { first: 1 }) - }) - - it('handles two entries', async () => { - await ccl.addUint('first', 1) - await ccl.addUint('second', 2) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - - assert.deepEqual(decoded, { - first: 1, - second: 2, - }) - }) - }) - - describe('#addStringArray', () => { - it('stores and logs keys and values', async () => { - await ccl.addStringArray('word', [ - ethers.utils.formatBytes32String('seinfeld'), - ethers.utils.formatBytes32String('"4"'), - ethers.utils.formatBytes32String('LIFE'), - ]) - const tx = await ccl.closeEvent() - const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - assert.deepEqual(decoded, { word: ['seinfeld', '"4"', 'LIFE'] }) - }) - }) -}) diff --git a/contracts/test/v0.7/ChainlinkClient.test.ts b/contracts/test/v0.7/ChainlinkClient.test.ts deleted file mode 100644 index 198d382af79..00000000000 --- a/contracts/test/v0.7/ChainlinkClient.test.ts +++ /dev/null @@ -1,454 +0,0 @@ -import { ethers } from 'hardhat' -import { assert } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { Roles, getUsers } from '../test-helpers/setup' -import { - convertFufillParams, - decodeCCRequest, - decodeRunRequest, - RunRequest, -} from '../test-helpers/oracle' -import { decodeDietCBOR } from '../test-helpers/helpers' -import { evmRevert } from '../test-helpers/matchers' - -let concreteChainlinkClientFactory: ContractFactory -let emptyOracleFactory: ContractFactory -let getterSetterFactory: ContractFactory -let operatorFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - roles = (await getUsers()).roles - - concreteChainlinkClientFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ChainlinkClientTestHelper.sol:ChainlinkClientTestHelper', - roles.defaultAccount, - ) - emptyOracleFactory = await ethers.getContractFactory( - 'src/v0.6/tests/EmptyOracle.sol:EmptyOracle', - roles.defaultAccount, - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.5/tests/GetterSetter.sol:GetterSetter', - roles.defaultAccount, - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('ChainlinkClientTestHelper', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let cc: Contract - let gs: Contract - let oc: Contract - let newoc: Contract - let link: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - oc = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - newoc = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - gs = await getterSetterFactory.connect(roles.defaultAccount).deploy() - cc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - }) - - describe('#newRequest', () => { - it('forwards the information to the oracle contract through the link token', async () => { - const tx = await cc.publicNewRequest( - specId, - gs.address, - ethers.utils.toUtf8Bytes('requestedBytes32(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - - assert.equal(1, receipt.logs?.length) - const [jId, cbAddr, cbFId, cborData] = receipt.logs - ? decodeCCRequest(receipt.logs[0]) - : [] - const params = decodeDietCBOR(cborData ?? '') - - assert.equal(specId, jId) - assert.equal(gs.address, cbAddr) - assert.equal('0xed53e511', cbFId) - assert.deepEqual({}, params) - }) - }) - - describe('#chainlinkRequest(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#chainlinkRequestTo(Request)', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestRunTo( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#requestOracleData', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestOracleData( - specId, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const { events, logs } = await tx.wait() - - assert.equal(4, events?.length) - - assert.equal(logs?.[0].address, cc.address) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - }) - - describe('#requestOracleDataFrom', () => { - it('emits an event from the contract showing the run ID', async () => { - const tx = await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - - assert.equal(4, events?.length) - assert.equal(events?.[0].event, 'ChainlinkRequested') - }) - - it('emits an event on the target oracle contract', async () => { - const tx = await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - const event = logs && newoc.interface.parseLog(logs[3]) - - assert.equal(4, logs?.length) - assert.equal(event?.name, 'OracleRequest') - }) - - it('does not modify the stored oracle address', async () => { - await cc.publicRequestOracleDataFrom( - newoc.address, - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - - const actualOracleAddress = await cc.publicOracleAddress() - assert.equal(oc.address, actualOracleAddress) - }) - }) - - describe('#cancelChainlinkRequest', () => { - let requestId: string - // a concrete chainlink attached to an empty oracle - let ecc: Contract - - beforeEach(async () => { - const emptyOracle = await emptyOracleFactory - .connect(roles.defaultAccount) - .deploy() - ecc = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, emptyOracle.address) - - const tx = await ecc.publicRequest( - specId, - ecc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { events } = await tx.wait() - requestId = (events?.[0]?.args as any).id - }) - - it('emits an event from the contract showing the run was cancelled', async () => { - const tx = await ecc.publicCancelRequest( - requestId, - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ) - const { events } = await tx.wait() - - assert.equal(1, events?.length) - assert.equal(events?.[0].event, 'ChainlinkCancelled') - assert.equal(requestId, (events?.[0].args as any).id) - }) - - it('throws if given a bogus event ID', async () => { - await evmRevert( - ecc.publicCancelRequest( - ethers.utils.formatBytes32String('bogusId'), - 0, - ethers.utils.hexZeroPad('0x', 4), - 0, - ), - ) - }) - }) - - describe('#recordChainlinkFulfillment(modifier)', () => { - let request: RunRequest - - beforeEach(async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - const { logs } = await tx.wait() - - const event = logs && cc.interface.parseLog(logs[1]) - - assert.equal(2, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not authorized sender', - ) - }) - }) - - describe('#fulfillChainlinkRequest(function)', () => { - let request: RunRequest - - beforeEach(async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - const tx = await cc.publicRequest( - specId, - cc.address, - ethers.utils.toUtf8Bytes( - 'publicFulfillChainlinkRequest(bytes32,bytes32)', - ), - 0, - ) - const { logs } = await tx.wait() - - request = decodeRunRequest(logs?.[3]) - }) - - it('emits an event marking the request fulfilled', async () => { - const tx = await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - const { logs } = await tx.wait() - const event = logs && cc.interface.parseLog(logs[1]) - - assert.equal(2, logs?.length) - assert.equal(event?.name, 'ChainlinkFulfilled') - assert.equal(request.requestId, event?.args?.id) - }) - - it('should only allow one fulfillment per id', async () => { - await oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - - await evmRevert( - oc - .connect(roles.defaultAccount) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Must have a valid requestId', - ) - }) - - it('should only allow the oracle to fulfill the request', async () => { - await evmRevert( - oc - .connect(roles.stranger) - .fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ), - 'Not authorized sender', - ) - }) - }) - - describe('#chainlinkToken', () => { - it('returns the Link Token address', async () => { - const addr = await cc.publicChainlinkToken() - assert.equal(addr, link.address) - }) - }) - - describe('#addExternalRequest', () => { - let mock: Contract - let request: RunRequest - - beforeEach(async () => { - mock = await concreteChainlinkClientFactory - .connect(roles.defaultAccount) - .deploy(link.address, oc.address) - - const tx = await cc.publicRequest( - specId, - mock.address, - ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), - 0, - ) - const receipt = await tx.wait() - - request = decodeRunRequest(receipt.logs?.[3]) - await mock.publicAddExternalRequest(oc.address, request.requestId) - }) - - it('allows the external request to be fulfilled', async () => { - await oc.setAuthorizedSenders([await roles.defaultAccount.getAddress()]) - await oc.fulfillOracleRequest( - ...convertFufillParams( - request, - ethers.utils.formatBytes32String('hi mom!'), - ), - ) - }) - - it('does not allow the same requestId to be used', async () => { - await evmRevert( - cc.publicAddExternalRequest(newoc.address, request.requestId), - ) - }) - }) -}) diff --git a/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts b/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts deleted file mode 100644 index 315f7bd9e6b..00000000000 --- a/contracts/test/v0.7/CompoundPriceFlaggingValidator.test.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { ethers } from 'hardhat' -import { evmWordToAddress, getLogs, publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { - BigNumber, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let acFactory: ContractFactory -let flagsFactory: ContractFactory -let aggregatorFactory: ContractFactory -let compoundOracleFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - - validatorFactory = await ethers.getContractFactory( - 'src/v0.7/dev/CompoundPriceFlaggingValidator.sol:CompoundPriceFlaggingValidator', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - personas.Carol, - ) - compoundOracleFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockCompoundOracle.sol:MockCompoundOracle', - personas.Carol, - ) -}) - -describe('CompoundPriceFlaggingVlidator', () => { - let validator: Contract - let aggregator: Contract - let compoundOracle: Contract - let flags: Contract - let ac: Contract - - const aggregatorDecimals = 18 - // 1000 - const initialAggregatorPrice = BigNumber.from('1000000000000000000000') - - const compoundSymbol = 'ETH' - const compoundDecimals = 6 - // 1100 (10% deviation from aggregator price) - const initialCompoundPrice = BigNumber.from('1100000000') - - // (50,000,000 / 1,000,000,000) = 0.05 = 5% deviation threshold - const initialDeviationNumerator = 50_000_000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - aggregator = await aggregatorFactory - .connect(personas.Carol) - .deploy(aggregatorDecimals, initialAggregatorPrice) - compoundOracle = await compoundOracleFactory - .connect(personas.Carol) - .deploy() - await compoundOracle.setPrice( - compoundSymbol, - initialCompoundPrice, - compoundDecimals, - ) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address, compoundOracle.address) - await validator - .connect(personas.Carol) - .setFeedDetails( - aggregator.address, - compoundSymbol, - compoundDecimals, - initialDeviationNumerator, - ) - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'update', - 'check', - 'setFeedDetails', - 'setFlagsAddress', - 'setCompoundOpenOracleAddress', - 'getFeedDetails', - 'flags', - 'compoundOpenOracle', - // Upkeep methods: - 'checkUpkeep', - 'performUpkeep', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the owner', async () => { - assert.equal(await validator.owner(), await personas.Carol.getAddress()) - }) - - it('sets the arguments passed in', async () => { - assert.equal(await validator.flags(), flags.address) - assert.equal(await validator.compoundOpenOracle(), compoundOracle.address) - }) - }) - - describe('#setOpenOracleAddress', () => { - let newCompoundOracle: Contract - let tx: ContractTransaction - - beforeEach(async () => { - newCompoundOracle = await compoundOracleFactory - .connect(personas.Carol) - .deploy() - tx = await validator - .connect(personas.Carol) - .setCompoundOpenOracleAddress(newCompoundOracle.address) - }) - - it('changes the compound oracke address', async () => { - assert.equal( - await validator.compoundOpenOracle(), - newCompoundOracle.address, - ) - }) - - it('emits a log event', async () => { - await expect(tx) - .to.emit(validator, 'CompoundOpenOracleAddressUpdated') - .withArgs(compoundOracle.address, newCompoundOracle.address) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setCompoundOpenOracleAddress(newCompoundOracle.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setFlagsAddress', () => { - let newFlagsContract: Contract - let tx: ContractTransaction - - beforeEach(async () => { - newFlagsContract = await flagsFactory - .connect(personas.Carol) - .deploy(ac.address) - tx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsContract.address) - }) - - it('changes the flags address', async () => { - assert.equal(await validator.flags(), newFlagsContract.address) - }) - - it('emits a log event', async () => { - await expect(tx) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsContract.address) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setFlagsAddress(newFlagsContract.address), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setFeedDetails', () => { - let mockAggregator: Contract - let tx: ContractTransaction - const symbol = 'BTC' - const decimals = 8 - const deviationNumerator = 50_000_000 // 5% - - beforeEach(async () => { - await compoundOracle.connect(personas.Carol).setPrice('BTC', 1500000, 2) - mockAggregator = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, 4000000000000) - tx = await validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - deviationNumerator, - ) - }) - - it('sets the correct state', async () => { - const response = await validator - .connect(personas.Carol) - .getFeedDetails(mockAggregator.address) - - assert.equal(response[0], symbol) - assert.equal(response[1], decimals) - assert.equal(response[2].toString(), deviationNumerator.toString()) - }) - - it('uses the existing symbol if one already exists', async () => { - const newSymbol = 'LINK' - - await compoundOracle - .connect(personas.Carol) - .setPrice(newSymbol, 1500000, 2) - - tx = await validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - newSymbol, - decimals, - deviationNumerator, - ) - - // Check the event - await expect(tx) - .to.emit(validator, 'FeedDetailsSet') - .withArgs(mockAggregator.address, symbol, decimals, deviationNumerator) - - // Check the state - const response = await validator - .connect(personas.Carol) - .getFeedDetails(mockAggregator.address) - assert.equal(response[0], symbol) - }) - - it('emits an event', async () => { - await expect(tx) - .to.emit(validator, 'FeedDetailsSet') - .withArgs(mockAggregator.address, symbol, decimals, deviationNumerator) - }) - - it('fails when given a 0 numerator', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails(mockAggregator.address, symbol, decimals, 0), - 'Invalid threshold numerator', - ) - }) - - it('fails when given a numerator above 1 billion', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - 1_200_000_000, - ), - 'Invalid threshold numerator', - ) - }) - - it('fails when the compound price is invalid', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setFeedDetails( - mockAggregator.address, - 'TEST', - decimals, - deviationNumerator, - ), - 'Invalid Compound price', - ) - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setFeedDetails( - mockAggregator.address, - symbol, - decimals, - deviationNumerator, - ), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#check', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('returns the deviated aggregator', async () => { - const aggregators = [aggregator.address] - const response = await validator.check(aggregators) - assert.equal(response.length, 1) - assert.equal(response[0], aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('returns an empty array', async () => { - const aggregators = [aggregator.address] - const response = await validator.check(aggregators) - assert.equal(response.length, 0) - }) - }) - }) - }) - - describe('#update', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('raises a flag on the flags contract', async () => { - const aggregators = [aggregator.address] - const tx = await validator.connect(personas.Carol).update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('does nothing', async () => { - const aggregators = [aggregator.address] - const tx = await validator.connect(personas.Carol).update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) - }) - - describe('#checkUpkeep', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('returns the deviated aggregator', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator - .connect(personas.Carol) - .checkUpkeep(encodedAggregators) - - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse?.[0]?.[0], aggregators[0]) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('returns an empty array', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator - .connect(personas.Carol) - .checkUpkeep(encodedAggregators) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse?.[0]?.length, 0) - }) - }) - }) - }) - - describe('#performUpkeep', () => { - describe('with a single aggregator', () => { - describe('with a deviated price exceding threshold', () => { - it('raises a flag on the flags contract', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator - .connect(personas.Carol) - .performUpkeep(encodedAggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), aggregator.address) - }) - }) - - describe('with a price within the threshold', () => { - const newCompoundPrice = BigNumber.from('1000000000') - beforeEach(async () => { - await compoundOracle.setPrice( - 'ETH', - newCompoundPrice, - compoundDecimals, - ) - }) - - it('does nothing', async () => { - const aggregators = [aggregator.address] - const encodedAggregators = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator - .connect(personas.Carol) - .performUpkeep(encodedAggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/ConfirmedOwner.test.ts b/contracts/test/v0.7/ConfirmedOwner.test.ts deleted file mode 100644 index 3502cd15bc2..00000000000 --- a/contracts/test/v0.7/ConfirmedOwner.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { ethers } from 'hardhat' -import { publicAbi } from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { Contract, ContractFactory, Signer } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let confirmedOwnerTestHelperFactory: ContractFactory -let confirmedOwnerFactory: ContractFactory - -let personas: Personas -let owner: Signer -let nonOwner: Signer -let newOwner: Signer - -before(async () => { - const users = await getUsers() - personas = users.personas - owner = personas.Carol - nonOwner = personas.Neil - newOwner = personas.Ned - - confirmedOwnerTestHelperFactory = await ethers.getContractFactory( - 'src/v0.7/tests/ConfirmedOwnerTestHelper.sol:ConfirmedOwnerTestHelper', - owner, - ) - confirmedOwnerFactory = await ethers.getContractFactory( - 'src/v0.7/ConfirmedOwner.sol:ConfirmedOwner', - owner, - ) -}) - -describe('ConfirmedOwner', () => { - let confirmedOwner: Contract - - beforeEach(async () => { - confirmedOwner = await confirmedOwnerTestHelperFactory - .connect(owner) - .deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(confirmedOwner, [ - 'acceptOwnership', - 'owner', - 'transferOwnership', - // test helper public methods - 'modifierOnlyOwner', - ]) - }) - - describe('#constructor', () => { - it('assigns ownership to the deployer', async () => { - const [actual, expected] = await Promise.all([ - owner.getAddress(), - confirmedOwner.owner(), - ]) - - assert.equal(actual, expected) - }) - - it('reverts if assigned to the zero address', async () => { - await evmRevert( - confirmedOwnerFactory - .connect(owner) - .deploy(ethers.constants.AddressZero), - 'Cannot set owner to zero', - ) - }) - }) - - describe('#onlyOwner modifier', () => { - describe('when called by an owner', () => { - it('successfully calls the method', async () => { - const tx = await confirmedOwner.connect(owner).modifierOnlyOwner() - await expect(tx).to.emit(confirmedOwner, 'Here') - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await evmRevert(confirmedOwner.connect(nonOwner).modifierOnlyOwner())) - }) - }) - - describe('#transferOwnership', () => { - describe('when called by an owner', () => { - it('emits a log', async () => { - const tx = await confirmedOwner - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - await expect(tx) - .to.emit(confirmedOwner, 'OwnershipTransferRequested') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow ownership transfer to self', async () => { - await evmRevert( - confirmedOwner - .connect(owner) - .transferOwnership(await owner.getAddress()), - 'Cannot transfer to self', - ) - }) - }) - }) - - describe('when called by anyone but the owner', () => { - it('reverts', async () => - await evmRevert( - confirmedOwner - .connect(nonOwner) - .transferOwnership(await newOwner.getAddress()), - )) - }) - - describe('#acceptOwnership', () => { - describe('after #transferOwnership has been called', () => { - beforeEach(async () => { - await confirmedOwner - .connect(owner) - .transferOwnership(await newOwner.getAddress()) - }) - - it('allows the recipient to call it', async () => { - const tx = await confirmedOwner.connect(newOwner).acceptOwnership() - await expect(tx) - .to.emit(confirmedOwner, 'OwnershipTransferred') - .withArgs(await owner.getAddress(), await newOwner.getAddress()) - }) - - it('does not allow a non-recipient to call it', async () => - await evmRevert(confirmedOwner.connect(nonOwner).acceptOwnership())) - }) - }) -}) diff --git a/contracts/test/v0.7/KeeperRegistry1_1.test.ts b/contracts/test/v0.7/KeeperRegistry1_1.test.ts deleted file mode 100644 index 4e3a8c91b35..00000000000 --- a/contracts/test/v0.7/KeeperRegistry1_1.test.ts +++ /dev/null @@ -1,1725 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { evmRevert } from '../test-helpers/matchers' -import { getUsers, Personas } from '../test-helpers/setup' -import { BigNumber, BigNumberish, Signer } from 'ethers' -import { LinkToken__factory as LinkTokenFactory } from '../../typechain/factories/LinkToken__factory' -import { KeeperRegistry1_1__factory as KeeperRegistryFactory } from '../../typechain/factories/KeeperRegistry1_1__factory' -import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../typechain/factories/MockV3Aggregator__factory' -import { UpkeepMock__factory as UpkeepMockFactory } from '../../typechain/factories/UpkeepMock__factory' -import { UpkeepReverter__factory as UpkeepReverterFactory } from '../../typechain/factories/UpkeepReverter__factory' -import { KeeperRegistry1_1 as KeeperRegistry } from '../../typechain/KeeperRegistry1_1' -import { MockV3Aggregator } from '../../typechain/MockV3Aggregator' -import { LinkToken } from '../../typechain/LinkToken' -import { UpkeepMock } from '../../typechain/UpkeepMock' -import { toWei } from '../test-helpers/helpers' - -async function getUpkeepID(tx: any) { - const receipt = await tx.wait() - return receipt.events[0].args.id -} - -// ----------------------------------------------------------------------------------------------- -// DEV: these *should* match the perform/check gas overhead values in the contract and on the node -const PERFORM_GAS_OVERHEAD = BigNumber.from(90000) -const CHECK_GAS_OVERHEAD = BigNumber.from(170000) -// ----------------------------------------------------------------------------------------------- - -// Smart contract factories -let linkTokenFactory: LinkTokenFactory -let mockV3AggregatorFactory: MockV3AggregatorFactory -let keeperRegistryFactory: KeeperRegistryFactory -let upkeepMockFactory: UpkeepMockFactory -let upkeepReverterFactory: UpkeepReverterFactory - -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - // need full path because there are two contracts with name MockV3Aggregator - mockV3AggregatorFactory = (await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - )) as unknown as MockV3AggregatorFactory - // @ts-ignore bug in autogen file - keeperRegistryFactory = await ethers.getContractFactory('KeeperRegistry1_1') - upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') - upkeepReverterFactory = await ethers.getContractFactory('UpkeepReverter') -}) - -describe('KeeperRegistry1_1', () => { - const linkEth = BigNumber.from(300000000) - const gasWei = BigNumber.from(100) - const linkDivisibility = BigNumber.from('1000000000000000000') - const executeGas = BigNumber.from('100000') - const paymentPremiumBase = BigNumber.from('1000000000') - const paymentPremiumPPB = BigNumber.from('250000000') - const flatFeeMicroLink = BigNumber.from(0) - const blockCountPerTurn = BigNumber.from(3) - const emptyBytes = '0x00' - const zeroAddress = ethers.constants.AddressZero - const extraGas = BigNumber.from('250000') - const registryGasOverhead = BigNumber.from('80000') - const stalenessSeconds = BigNumber.from(43820) - const gasCeilingMultiplier = BigNumber.from(1) - const maxCheckGas = BigNumber.from(20000000) - const fallbackGasPrice = BigNumber.from(200) - const fallbackLinkPrice = BigNumber.from(200000000) - - let owner: Signer - let keeper1: Signer - let keeper2: Signer - let keeper3: Signer - let nonkeeper: Signer - let admin: Signer - let payee1: Signer - let payee2: Signer - let payee3: Signer - - let linkToken: LinkToken - let linkEthFeed: MockV3Aggregator - let gasPriceFeed: MockV3Aggregator - let registry: KeeperRegistry - let mock: UpkeepMock - - let id: BigNumber - let keepers: string[] - let payees: string[] - - beforeEach(async () => { - owner = personas.Default - keeper1 = personas.Carol - keeper2 = personas.Eddy - keeper3 = personas.Nancy - nonkeeper = personas.Ned - admin = personas.Neil - payee1 = personas.Nelly - payee2 = personas.Norbert - payee3 = personas.Nick - - keepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - payees = [ - await payee1.getAddress(), - await payee2.getAddress(), - await payee3.getAddress(), - ] - - linkToken = await linkTokenFactory.connect(owner).deploy() - gasPriceFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(0, gasWei) - linkEthFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(9, linkEth) - registry = await keeperRegistryFactory - .connect(owner) - .deploy( - linkToken.address, - linkEthFeed.address, - gasPriceFeed.address, - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - mock = await upkeepMockFactory.deploy() - await linkToken - .connect(owner) - .transfer(await keeper1.getAddress(), toWei('1000')) - await linkToken - .connect(owner) - .transfer(await keeper2.getAddress(), toWei('1000')) - await linkToken - .connect(owner) - .transfer(await keeper3.getAddress(), toWei('1000')) - - await registry.connect(owner).setKeepers(keepers, payees) - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - }) - - const linkForGas = ( - upkeepGasSpent: BigNumberish, - premiumPPB?: BigNumberish, - flatFee?: BigNumberish, - ) => { - premiumPPB = premiumPPB === undefined ? paymentPremiumPPB : premiumPPB - flatFee = flatFee === undefined ? flatFeeMicroLink : flatFee - const gasSpent = registryGasOverhead.add(BigNumber.from(upkeepGasSpent)) - const base = gasWei.mul(gasSpent).mul(linkDivisibility).div(linkEth) - const premium = base.mul(premiumPPB).div(paymentPremiumBase) - const flatFeeJules = BigNumber.from(flatFee).mul('1000000000000') - return base.add(premium).add(flatFeeJules) - } - - describe('#setKeepers', () => { - const IGNORE_ADDRESS = '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF' - it('reverts when not called by the owner', async () => { - await evmRevert( - registry.connect(keeper1).setKeepers([], []), - 'Only callable by owner', - ) - }) - - it('reverts when adding the same keeper twice', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper1.getAddress()], - [await payee1.getAddress(), await payee1.getAddress()], - ), - 'cannot add keeper twice', - ) - }) - - it('reverts with different numbers of keepers/payees', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [await payee1.getAddress()], - ), - 'address lists not the same length', - ) - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress()], - [await payee1.getAddress(), await payee2.getAddress()], - ), - 'address lists not the same length', - ) - }) - - it('reverts if the payee is the zero address', async () => { - await evmRevert( - registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [ - await payee1.getAddress(), - '0x0000000000000000000000000000000000000000', - ], - ), - 'cannot set payee to the zero address', - ) - }) - - it('emits events for every keeper added and removed', async () => { - const oldKeepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - ] - const oldPayees = [await payee1.getAddress(), await payee2.getAddress()] - await registry.connect(owner).setKeepers(oldKeepers, oldPayees) - assert.deepEqual(oldKeepers, await registry.getKeeperList()) - - // remove keepers - const newKeepers = [ - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - const newPayees = [await payee2.getAddress(), await payee3.getAddress()] - const tx = await registry.connect(owner).setKeepers(newKeepers, newPayees) - assert.deepEqual(newKeepers, await registry.getKeeperList()) - - await expect(tx) - .to.emit(registry, 'KeepersUpdated') - .withArgs(newKeepers, newPayees) - }) - - it('updates the keeper to inactive when removed', async () => { - await registry.connect(owner).setKeepers(keepers, payees) - await registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper3.getAddress()], - [await payee1.getAddress(), await payee3.getAddress()], - ) - const added = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.isTrue(added.active) - const removed = await registry.getKeeperInfo(await keeper2.getAddress()) - assert.isFalse(removed.active) - }) - - it('does not change the payee if IGNORE_ADDRESS is used as payee', async () => { - const oldKeepers = [ - await keeper1.getAddress(), - await keeper2.getAddress(), - ] - const oldPayees = [await payee1.getAddress(), await payee2.getAddress()] - await registry.connect(owner).setKeepers(oldKeepers, oldPayees) - assert.deepEqual(oldKeepers, await registry.getKeeperList()) - - const newKeepers = [ - await keeper2.getAddress(), - await keeper3.getAddress(), - ] - const newPayees = [IGNORE_ADDRESS, await payee3.getAddress()] - const tx = await registry.connect(owner).setKeepers(newKeepers, newPayees) - assert.deepEqual(newKeepers, await registry.getKeeperList()) - - const ignored = await registry.getKeeperInfo(await keeper2.getAddress()) - assert.equal(await payee2.getAddress(), ignored.payee) - assert.equal(true, ignored.active) - - await expect(tx) - .to.emit(registry, 'KeepersUpdated') - .withArgs(newKeepers, newPayees) - }) - - it('reverts if the owner changes the payee', async () => { - await registry.connect(owner).setKeepers(keepers, payees) - await evmRevert( - registry - .connect(owner) - .setKeepers(keepers, [ - await payee1.getAddress(), - await payee2.getAddress(), - await owner.getAddress(), - ]), - 'cannot change payee', - ) - }) - }) - - describe('#registerUpkeep', () => { - it('reverts if the target is not a contract', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - zeroAddress, - executeGas, - await admin.getAddress(), - emptyBytes, - ), - 'target is not a contract', - ) - }) - - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry - .connect(keeper1) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ), - 'Only callable by owner or registrar', - ) - }) - - it('reverts if execute gas is too low', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - mock.address, - 2299, - await admin.getAddress(), - emptyBytes, - ), - 'min gas is 2300', - ) - }) - - it('reverts if execute gas is too high', async () => { - await evmRevert( - registry - .connect(owner) - .registerUpkeep( - mock.address, - 5000001, - await admin.getAddress(), - emptyBytes, - ), - 'max gas is 5000000', - ) - }) - - it('creates a record of the registration', async () => { - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - await expect(tx) - .to.emit(registry, 'UpkeepRegistered') - .withArgs(id, executeGas, await admin.getAddress()) - const registration = await registry.getUpkeep(id) - assert.equal(mock.address, registration.target) - assert.equal(0, registration.balance.toNumber()) - assert.equal(emptyBytes, registration.checkData) - assert(registration.maxValidBlocknumber.eq('0xffffffffffffffff')) - }) - }) - - describe('#addFunds', () => { - const amount = toWei('1') - - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - }) - - it('reverts if the registration does not exist', async () => { - await evmRevert( - registry.connect(keeper1).addFunds(id.add(1), amount), - 'upkeep must be active', - ) - }) - - it('adds to the balance of the registration', async () => { - await registry.connect(keeper1).addFunds(id, amount) - const registration = await registry.getUpkeep(id) - assert.isTrue(amount.eq(registration.balance)) - }) - - it('emits a log', async () => { - const tx = await registry.connect(keeper1).addFunds(id, amount) - await expect(tx) - .to.emit(registry, 'FundsAdded') - .withArgs(id, await keeper1.getAddress(), amount) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(id) - await evmRevert( - registry.connect(keeper1).addFunds(id, amount), - 'upkeep must be active', - ) - }) - }) - - describe('#checkUpkeep', () => { - it('reverts if the upkeep is not funded', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'insufficient funds', - ) - }) - - context('when the registration is funded', () => { - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('100')) - }) - - it('reverts if executed', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry.checkUpkeep(id, await keeper1.getAddress()), - 'only for simulated backend', - ) - }) - - it('reverts if the specified keeper is not valid', async () => { - await mock.setCanPerform(true) - await mock.setCanCheck(true) - await evmRevert( - registry.checkUpkeep(id, await owner.getAddress()), - 'only for simulated backend', - ) - }) - - context('and upkeep is not needed', () => { - beforeEach(async () => { - await mock.setCanCheck(false) - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'upkeep not needed', - ) - }) - }) - - context('and the upkeep check fails', () => { - beforeEach(async () => { - const reverter = await upkeepReverterFactory.deploy() - const tx = await registry - .connect(owner) - .registerUpkeep( - reverter.address, - 2500000, - await admin.getAddress(), - emptyBytes, - ) - id = await getUpkeepID(tx) - await linkToken - .connect(keeper1) - .approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('100')) - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'call to check target failed', - ) - }) - }) - - context('and upkeep check simulations succeeds', () => { - beforeEach(async () => { - await mock.setCanCheck(true) - await mock.setCanPerform(true) - }) - - context('and the registry is paused', () => { - beforeEach(async () => { - await registry.connect(owner).pause() - }) - - it('reverts', async () => { - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'Pausable: paused', - ) - - await registry.connect(owner).unpause() - - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - }) - }) - - it('returns true with pricing info if the target can execute', async () => { - const newGasMultiplier = BigNumber.from(10) - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - newGasMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - const response = await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - assert.isTrue(response.gasLimit.eq(executeGas)) - assert.isTrue(response.linkEth.eq(linkEth)) - assert.isTrue( - response.adjustedGasWei.eq(gasWei.mul(newGasMultiplier)), - ) - assert.isTrue( - response.maxLinkPayment.eq( - linkForGas(executeGas.toNumber()).mul(newGasMultiplier), - ), - ) - }) - - it('has a large enough gas overhead to cover upkeeps that use all their gas [ @skip-coverage ]', async () => { - await mock.setCheckGasToBurn(maxCheckGas) - await mock.setPerformGasToBurn(executeGas) - const gas = maxCheckGas - .add(executeGas) - .add(PERFORM_GAS_OVERHEAD) - .add(CHECK_GAS_OVERHEAD) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress(), { - gasLimit: gas, - }) - }) - }) - }) - }) - - describe('#performUpkeep', () => { - let _lastKeeper = keeper1 - async function getPerformPaymentAmount() { - _lastKeeper = _lastKeeper === keeper1 ? keeper2 : keeper1 - const before = ( - await registry.getKeeperInfo(await _lastKeeper.getAddress()) - ).balance - await registry.connect(_lastKeeper).performUpkeep(id, '0x') - const after = ( - await registry.getKeeperInfo(await _lastKeeper.getAddress()) - ).balance - const difference = after.sub(before) - return difference - } - - it('reverts if the registration is not funded', async () => { - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'insufficient funds', - ) - }) - - context('when the registration is funded', () => { - beforeEach(async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - }) - - it('does not revert if the target cannot execute', async () => { - const mockResponse = await mock - .connect(zeroAddress) - .callStatic.checkUpkeep('0x') - assert.isFalse(mockResponse.callable) - - await registry.connect(keeper3).performUpkeep(id, '0x') - }) - - it('returns false if the target cannot execute', async () => { - const mockResponse = await mock - .connect(zeroAddress) - .callStatic.checkUpkeep('0x') - assert.isFalse(mockResponse.callable) - - assert.isFalse( - await registry.connect(keeper1).callStatic.performUpkeep(id, '0x'), - ) - }) - - it('returns true if called', async () => { - await mock.setCanPerform(true) - - const response = await registry - .connect(keeper1) - .callStatic.performUpkeep(id, '0x') - assert.isTrue(response) - }) - - it('reverts if not enough gas supplied', async () => { - await mock.setCanPerform(true) - - await evmRevert( - registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasLimit: BigNumber.from('120000') }), - ) - }) - - it('executes the data passed to the registry', async () => { - await mock.setCanPerform(true) - - const performData = '0xc0ffeec0ffee' - const tx = await registry - .connect(keeper1) - .performUpkeep(id, performData, { gasLimit: extraGas }) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 2) - assert.equal(eventLog?.[1].event, 'UpkeepPerformed') - assert.equal(eventLog?.[1].args?.[0].toNumber(), id.toNumber()) - assert.equal(eventLog?.[1].args?.[1], true) - assert.equal(eventLog?.[1].args?.[2], await keeper1.getAddress()) - assert.isNotEmpty(eventLog?.[1].args?.[3]) - assert.equal(eventLog?.[1].args?.[4], performData) - }) - - it('updates payment balances', async () => { - const keeperBefore = await registry.getKeeperInfo( - await keeper1.getAddress(), - ) - const registrationBefore = await registry.getUpkeep(id) - const keeperLinkBefore = await linkToken.balanceOf( - await keeper1.getAddress(), - ) - const registryLinkBefore = await linkToken.balanceOf(registry.address) - - // Do the thing - await registry.connect(keeper1).performUpkeep(id, '0x') - - const keeperAfter = await registry.getKeeperInfo( - await keeper1.getAddress(), - ) - const registrationAfter = await registry.getUpkeep(id) - const keeperLinkAfter = await linkToken.balanceOf( - await keeper1.getAddress(), - ) - const registryLinkAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(keeperAfter.balance.gt(keeperBefore.balance)) - assert.isTrue(registrationBefore.balance.gt(registrationAfter.balance)) - assert.isTrue(keeperLinkAfter.eq(keeperLinkBefore)) - assert.isTrue(registryLinkBefore.eq(registryLinkAfter)) - }) - - it('only pays for gas used [ @skip-coverage ]', async () => { - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry.connect(keeper1).performUpkeep(id, '0x') - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas.toNumber()) - const totalTx = linkForGas(receipt.gasUsed.toNumber()) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).lt(difference)) // exact number is flaky - assert.isTrue(linkForGas(6000).gt(difference)) // instead test a range - }) - - it('only pays at a rate up to the gas ceiling [ @skip-coverage ]', async () => { - const multiplier = BigNumber.from(10) - const gasPrice = BigNumber.from('1000000000') // 10M x the gas feed's rate - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasPrice }) - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas).mul(multiplier) - const totalTx = linkForGas(receipt.gasUsed).mul(multiplier) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).mul(multiplier).lt(difference)) - assert.isTrue(linkForGas(6000).mul(multiplier).gt(difference)) - }) - - it('only pays as much as the node spent [ @skip-coverage ]', async () => { - const multiplier = BigNumber.from(10) - const gasPrice = BigNumber.from(200) // 2X the gas feed's rate - const effectiveMultiplier = BigNumber.from(2) - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - const before = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const tx = await registry - .connect(keeper1) - .performUpkeep(id, '0x', { gasPrice }) - const receipt = await tx.wait() - const after = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - - const max = linkForGas(executeGas.toNumber()).mul(effectiveMultiplier) - const totalTx = linkForGas(receipt.gasUsed).mul(effectiveMultiplier) - const difference = after.sub(before) - assert.isTrue(max.gt(totalTx)) - assert.isTrue(totalTx.gt(difference)) - assert.isTrue(linkForGas(5700).mul(effectiveMultiplier).lt(difference)) - assert.isTrue(linkForGas(6000).mul(effectiveMultiplier).gt(difference)) - }) - - it('pays the caller even if the target function fails', async () => { - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id = await getUpkeepID(tx) - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - const keeperBalanceBefore = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - - // Do the thing - await registry.connect(keeper1).performUpkeep(id, '0x') - - const keeperBalanceAfter = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - assert.isTrue(keeperBalanceAfter.gt(keeperBalanceBefore)) - }) - - it('reverts if called by a non-keeper', async () => { - await evmRevert( - registry.connect(nonkeeper).performUpkeep(id, '0x'), - 'only active keepers', - ) - }) - - it('reverts if the upkeep has been canceled', async () => { - await mock.setCanPerform(true) - - await registry.connect(owner).cancelUpkeep(id) - - await evmRevert( - registry.connect(keeper1).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('uses the fallback gas price if the feed price is stale [ @skip-coverage ]', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const answer = 100 - const updatedAt = 946684800 // New Years 2000 🥳 - const startedAt = 946684799 - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, answer, updatedAt, startedAt) - const amountWithStaleFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithStaleFeed)) - }) - - it('uses the fallback gas price if the feed price is non-sensical [ @skip-coverage ]', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const updatedAt = Math.floor(Date.now() / 1000) - const startedAt = 946684799 - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, -100, updatedAt, startedAt) - const amountWithNegativeFeed = await getPerformPaymentAmount() - await gasPriceFeed - .connect(owner) - .updateRoundData(roundId, 0, updatedAt, startedAt) - const amountWithZeroFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithNegativeFeed)) - assert.isTrue(normalAmount.lt(amountWithZeroFeed)) - }) - - it('uses the fallback if the link price feed is stale', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const answer = 100 - const updatedAt = 946684800 // New Years 2000 🥳 - const startedAt = 946684799 - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, answer, updatedAt, startedAt) - const amountWithStaleFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithStaleFeed)) - }) - - it('uses the fallback link price if the feed price is non-sensical', async () => { - const normalAmount = await getPerformPaymentAmount() - const roundId = 99 - const updatedAt = Math.floor(Date.now() / 1000) - const startedAt = 946684799 - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, -100, updatedAt, startedAt) - const amountWithNegativeFeed = await getPerformPaymentAmount() - await linkEthFeed - .connect(owner) - .updateRoundData(roundId, 0, updatedAt, startedAt) - const amountWithZeroFeed = await getPerformPaymentAmount() - assert.isTrue(normalAmount.lt(amountWithNegativeFeed)) - assert.isTrue(normalAmount.lt(amountWithZeroFeed)) - }) - - it('reverts if the same caller calls twice in a row', async () => { - await registry.connect(keeper1).performUpkeep(id, '0x') - await evmRevert( - registry.connect(keeper1).performUpkeep(id, '0x'), - 'keepers must take turns', - ) - await registry.connect(keeper2).performUpkeep(id, '0x') - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'keepers must take turns', - ) - await registry.connect(keeper1).performUpkeep(id, '0x') - }) - - it('has a large enough gas overhead to cover upkeeps that use all their gas [ @skip-coverage ]', async () => { - await mock.setPerformGasToBurn(executeGas) - await mock.setCanPerform(true) - const gas = executeGas.add(PERFORM_GAS_OVERHEAD) - const performData = '0xc0ffeec0ffee' - const tx = await registry - .connect(keeper1) - .performUpkeep(id, performData, { gasLimit: gas }) - const receipt = await tx.wait() - const eventLog = receipt?.events - - assert.equal(eventLog?.length, 2) - assert.equal(eventLog?.[1].event, 'UpkeepPerformed') - assert.equal(eventLog?.[1].args?.[0].toNumber(), id.toNumber()) - assert.equal(eventLog?.[1].args?.[1], true) - assert.equal(eventLog?.[1].args?.[2], await keeper1.getAddress()) - assert.isNotEmpty(eventLog?.[1].args?.[3]) - assert.equal(eventLog?.[1].args?.[4], performData) - }) - }) - }) - - describe('#withdrawFunds', () => { - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await registry.connect(keeper1).addFunds(id, toWei('1')) - }) - - it('reverts if called by anyone but the admin', async () => { - await evmRevert( - registry - .connect(owner) - .withdrawFunds(id.add(1).toNumber(), await payee1.getAddress()), - 'only callable by admin', - ) - }) - - it('reverts if called on an uncanceled upkeep', async () => { - await evmRevert( - registry.connect(admin).withdrawFunds(id, await payee1.getAddress()), - 'upkeep must be canceled', - ) - }) - - it('reverts if called with the 0 address', async () => { - await evmRevert( - registry.connect(admin).withdrawFunds(id, zeroAddress), - 'cannot send to zero address', - ) - }) - - describe('after the registration is cancelled', () => { - beforeEach(async () => { - await registry.connect(owner).cancelUpkeep(id) - }) - - it('moves the funds out and updates the balance', async () => { - const payee1Before = await linkToken.balanceOf( - await payee1.getAddress(), - ) - const registryBefore = await linkToken.balanceOf(registry.address) - - let registration = await registry.getUpkeep(id) - assert.isTrue(toWei('1').eq(registration.balance)) - - await registry - .connect(admin) - .withdrawFunds(id, await payee1.getAddress()) - - const payee1After = await linkToken.balanceOf(await payee1.getAddress()) - const registryAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(payee1Before.add(toWei('1')).eq(payee1After)) - assert.isTrue(registryBefore.sub(toWei('1')).eq(registryAfter)) - - registration = await registry.getUpkeep(id) - assert.equal(0, registration.balance.toNumber()) - }) - }) - }) - - describe('#cancelUpkeep', () => { - it('reverts if the ID is not valid', async () => { - await evmRevert( - registry.connect(owner).cancelUpkeep(id.add(1).toNumber()), - 'too late to cancel upkeep', - ) - }) - - it('reverts if called by a non-owner/non-admin', async () => { - await evmRevert( - registry.connect(keeper1).cancelUpkeep(id), - 'only owner or admin', - ) - }) - - describe('when called by the owner', async () => { - it('sets the registration to invalid immediately', async () => { - const tx = await registry.connect(owner).cancelUpkeep(id) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(id) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(owner).cancelUpkeep(id) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs(id, BigNumber.from(receipt.blockNumber)) - }) - - it('updates the canceled registrations list', async () => { - let canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([], canceled) - - await registry.connect(owner).cancelUpkeep(id) - - canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('immediately prevents upkeep', async () => { - await registry.connect(owner).cancelUpkeep(id) - - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('does not revert if reverts if called multiple times', async () => { - await registry.connect(owner).cancelUpkeep(id) - await evmRevert( - registry.connect(owner).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - - describe('when called by the owner when the admin has just canceled', () => { - let oldExpiration: BigNumber - - beforeEach(async () => { - await registry.connect(admin).cancelUpkeep(id) - const registration = await registry.getUpkeep(id) - oldExpiration = registration.maxValidBlocknumber - }) - - it('allows the owner to cancel it more quickly', async () => { - await registry.connect(owner).cancelUpkeep(id) - - const registration = await registry.getUpkeep(id) - const newExpiration = registration.maxValidBlocknumber - assert.isTrue(newExpiration.lt(oldExpiration)) - }) - }) - }) - - describe('when called by the admin', async () => { - const delay = 50 - - it('sets the registration to invalid in 50 blocks', async () => { - const tx = await registry.connect(admin).cancelUpkeep(id) - const receipt = await tx.wait() - const registration = await registry.getUpkeep(id) - assert.equal( - registration.maxValidBlocknumber.toNumber(), - receipt.blockNumber + 50, - ) - }) - - it('emits an event', async () => { - const tx = await registry.connect(admin).cancelUpkeep(id) - const receipt = await tx.wait() - await expect(tx) - .to.emit(registry, 'UpkeepCanceled') - .withArgs(id, BigNumber.from(receipt.blockNumber + delay)) - }) - - it('updates the canceled registrations list', async () => { - let canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([], canceled) - - await registry.connect(admin).cancelUpkeep(id) - - canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('immediately prevents upkeep', async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - await registry.connect(admin).cancelUpkeep(id) - await registry.connect(keeper2).performUpkeep(id, '0x') // still works - - for (let i = 0; i < delay; i++) { - await ethers.provider.send('evm_mine', []) - } - - await evmRevert( - registry.connect(keeper2).performUpkeep(id, '0x'), - 'invalid upkeep id', - ) - }) - - it('reverts if called again by the admin', async () => { - await registry.connect(admin).cancelUpkeep(id) - - await evmRevert( - registry.connect(admin).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - - it('does not revert or double add the cancellation record if called by the owner immediately after', async () => { - await registry.connect(admin).cancelUpkeep(id) - - await registry.connect(owner).cancelUpkeep(id) - - const canceled = await registry.callStatic.getCanceledUpkeepList() - assert.deepEqual([id], canceled) - }) - - it('reverts if called by the owner after the timeout', async () => { - await registry.connect(admin).cancelUpkeep(id) - - for (let i = 0; i < delay; i++) { - await ethers.provider.send('evm_mine', []) - } - - await evmRevert( - registry.connect(owner).cancelUpkeep(id), - 'too late to cancel upkeep', - ) - }) - }) - }) - - describe('#withdrawPayment', () => { - beforeEach(async () => { - await linkToken.connect(owner).approve(registry.address, toWei('100')) - await registry.connect(owner).addFunds(id, toWei('100')) - await registry.connect(keeper1).performUpkeep(id, '0x') - }) - - it('reverts if called by anyone but the payee', async () => { - await evmRevert( - registry - .connect(payee2) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ), - 'only callable by payee', - ) - }) - - it('reverts if called with the 0 address', async () => { - await evmRevert( - registry - .connect(payee2) - .withdrawPayment(await keeper1.getAddress(), zeroAddress), - 'cannot send to zero address', - ) - }) - - it('updates the balances', async () => { - const to = await nonkeeper.getAddress() - const keeperBefore = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const registrationBefore = (await registry.getUpkeep(id)).balance - const toLinkBefore = await linkToken.balanceOf(to) - const registryLinkBefore = await linkToken.balanceOf(registry.address) - - //// Do the thing - await registry - .connect(payee1) - .withdrawPayment(await keeper1.getAddress(), to) - - const keeperAfter = ( - await registry.getKeeperInfo(await keeper1.getAddress()) - ).balance - const registrationAfter = (await registry.getUpkeep(id)).balance - const toLinkAfter = await linkToken.balanceOf(to) - const registryLinkAfter = await linkToken.balanceOf(registry.address) - - assert.isTrue(keeperAfter.eq(BigNumber.from(0))) - assert.isTrue(registrationBefore.eq(registrationAfter)) - assert.isTrue(toLinkBefore.add(keeperBefore).eq(toLinkAfter)) - assert.isTrue(registryLinkBefore.sub(keeperBefore).eq(registryLinkAfter)) - }) - - it('emits a log announcing the withdrawal', async () => { - const balance = (await registry.getKeeperInfo(await keeper1.getAddress())) - .balance - const tx = await registry - .connect(payee1) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ) - await expect(tx) - .to.emit(registry, 'PaymentWithdrawn') - .withArgs( - await keeper1.getAddress(), - balance, - await nonkeeper.getAddress(), - await payee1.getAddress(), - ) - }) - }) - - describe('#transferPayeeship', () => { - it('reverts when called by anyone but the current payee', async () => { - await evmRevert( - registry - .connect(payee2) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ), - 'only callable by payee', - ) - }) - - it('reverts when transferring to self', async () => { - await evmRevert( - registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee1.getAddress(), - ), - 'cannot transfer to self', - ) - }) - - it('does not change the payee', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const info = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.equal(await payee1.getAddress(), info.payee) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferRequested') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does not emit an event when called with the same proposal', async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - - const tx = await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - const receipt = await tx.wait() - assert.equal(0, receipt.logs.length) - }) - }) - - describe('#acceptPayeeship', () => { - beforeEach(async () => { - await registry - .connect(payee1) - .transferPayeeship( - await keeper1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('reverts when called by anyone but the proposed payee', async () => { - await evmRevert( - registry.connect(payee1).acceptPayeeship(await keeper1.getAddress()), - 'only callable by proposed payee', - ) - }) - - it('emits an event announcing the new payee', async () => { - const tx = await registry - .connect(payee2) - .acceptPayeeship(await keeper1.getAddress()) - await expect(tx) - .to.emit(registry, 'PayeeshipTransferred') - .withArgs( - await keeper1.getAddress(), - await payee1.getAddress(), - await payee2.getAddress(), - ) - }) - - it('does change the payee', async () => { - await registry.connect(payee2).acceptPayeeship(await keeper1.getAddress()) - - const info = await registry.getKeeperInfo(await keeper1.getAddress()) - assert.equal(await payee2.getAddress(), info.payee) - }) - }) - - describe('#setConfig', () => { - const payment = BigNumber.from(1) - const flatFee = BigNumber.from(2) - const checks = BigNumber.from(3) - const staleness = BigNumber.from(4) - const ceiling = BigNumber.from(5) - const maxGas = BigNumber.from(6) - const fbGasEth = BigNumber.from(7) - const fbLinkEth = BigNumber.from(8) - - it('reverts when called by anyone but the proposed owner', async () => { - await evmRevert( - registry - .connect(payee1) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - gasCeilingMultiplier, - fbGasEth, - fbLinkEth, - ), - 'Only callable by owner', - ) - }) - - it('updates the config', async () => { - const old = await registry.getConfig() - const oldFlatFee = await registry.getFlatFee() - assert.isTrue(paymentPremiumPPB.eq(old.paymentPremiumPPB)) - assert.isTrue(flatFeeMicroLink.eq(oldFlatFee)) - assert.isTrue(blockCountPerTurn.eq(old.blockCountPerTurn)) - assert.isTrue(stalenessSeconds.eq(old.stalenessSeconds)) - assert.isTrue(gasCeilingMultiplier.eq(old.gasCeilingMultiplier)) - - await registry - .connect(owner) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - - const updated = await registry.getConfig() - const newFlatFee = await registry.getFlatFee() - assert.equal(updated.paymentPremiumPPB, payment.toNumber()) - assert.equal(newFlatFee, flatFee.toNumber()) - assert.equal(updated.blockCountPerTurn, checks.toNumber()) - assert.equal(updated.stalenessSeconds, staleness.toNumber()) - assert.equal(updated.gasCeilingMultiplier, ceiling.toNumber()) - assert.equal(updated.checkGasLimit, maxGas.toNumber()) - assert.equal(updated.fallbackGasPrice.toNumber(), fbGasEth.toNumber()) - assert.equal(updated.fallbackLinkPrice.toNumber(), fbLinkEth.toNumber()) - }) - - it('emits an event', async () => { - const tx = await registry - .connect(owner) - .setConfig( - payment, - flatFee, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - await expect(tx) - .to.emit(registry, 'ConfigSet') - .withArgs( - payment, - checks, - maxGas, - staleness, - ceiling, - fbGasEth, - fbLinkEth, - ) - }) - }) - - describe('#onTokenTransfer', () => { - const amount = toWei('1') - - it('reverts if not called by the LINK token', async () => { - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id.toNumber().toString()], - ) - - await evmRevert( - registry - .connect(keeper1) - .onTokenTransfer(await keeper1.getAddress(), amount, data), - 'only callable through LINK', - ) - }) - - it('reverts if not called with more or less than 32 bytes', async () => { - const longData = ethers.utils.defaultAbiCoder.encode( - ['uint256', 'uint256'], - ['33', '34'], - ) - const shortData = '0x12345678' - - await evmRevert( - linkToken - .connect(owner) - .transferAndCall(registry.address, amount, longData), - ) - await evmRevert( - linkToken - .connect(owner) - .transferAndCall(registry.address, amount, shortData), - ) - }) - - it('reverts if the upkeep is canceled', async () => { - await registry.connect(admin).cancelUpkeep(id) - await evmRevert( - registry.connect(keeper1).addFunds(id, amount), - 'upkeep must be active', - ) - }) - - it('updates the funds of the job id passed', async () => { - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id.toNumber().toString()], - ) - - const before = (await registry.getUpkeep(id)).balance - await linkToken - .connect(owner) - .transferAndCall(registry.address, amount, data) - const after = (await registry.getUpkeep(id)).balance - - assert.isTrue(before.add(amount).eq(after)) - }) - }) - - describe('#recoverFunds', () => { - const sent = toWei('7') - - beforeEach(async () => { - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - - // add funds to upkeep 1 and perform and withdraw some payment - const tx = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id1 = await getUpkeepID(tx) - await registry.connect(keeper1).addFunds(id1, toWei('5')) - await registry.connect(keeper1).performUpkeep(id1, '0x') - await registry.connect(keeper2).performUpkeep(id1, '0x') - await registry.connect(keeper3).performUpkeep(id1, '0x') - await registry - .connect(payee1) - .withdrawPayment( - await keeper1.getAddress(), - await nonkeeper.getAddress(), - ) - - // transfer funds directly to the registry - await linkToken.connect(keeper1).transfer(registry.address, sent) - - // add funds to upkeep 2 and perform and withdraw some payment - const tx2 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const id2 = await getUpkeepID(tx2) - await registry.connect(keeper1).addFunds(id2, toWei('5')) - await registry.connect(keeper1).performUpkeep(id2, '0x') - await registry.connect(keeper2).performUpkeep(id2, '0x') - await registry.connect(keeper3).performUpkeep(id2, '0x') - await registry - .connect(payee2) - .withdrawPayment( - await keeper2.getAddress(), - await nonkeeper.getAddress(), - ) - - // transfer funds using onTokenTransfer - const data = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [id2.toNumber().toString()], - ) - await linkToken - .connect(owner) - .transferAndCall(registry.address, toWei('1'), data) - - // remove a keeper - await registry - .connect(owner) - .setKeepers( - [await keeper1.getAddress(), await keeper2.getAddress()], - [await payee1.getAddress(), await payee2.getAddress()], - ) - - // withdraw some funds - await registry.connect(owner).cancelUpkeep(id1) - await registry.connect(admin).withdrawFunds(id1, await admin.getAddress()) - }) - - it('reverts if not called by owner', async () => { - await evmRevert( - registry.connect(keeper1).recoverFunds(), - 'Only callable by owner', - ) - }) - - it('allows any funds that have been accidentally transfered to be moved', async () => { - const balanceBefore = await linkToken.balanceOf(registry.address) - - await linkToken.balanceOf(registry.address) - - await registry.connect(owner).recoverFunds() - const balanceAfter = await linkToken.balanceOf(registry.address) - assert.isTrue(balanceBefore.eq(balanceAfter.add(sent))) - }) - }) - - describe('#pause', () => { - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).pause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as paused', async () => { - assert.isFalse(await registry.paused()) - - await registry.connect(owner).pause() - - assert.isTrue(await registry.paused()) - }) - }) - - describe('#unpause', () => { - beforeEach(async () => { - await registry.connect(owner).pause() - }) - - it('reverts if called by a non-owner', async () => { - await evmRevert( - registry.connect(keeper1).unpause(), - 'Only callable by owner', - ) - }) - - it('marks the contract as not paused', async () => { - assert.isTrue(await registry.paused()) - - await registry.connect(owner).unpause() - - assert.isFalse(await registry.paused()) - }) - }) - - describe('#getMaxPaymentForGas', () => { - const gasAmounts = [100000, 10000000] - const premiums = [0, 250000000] - const flatFees = [0, 1000000] - it('calculates the max fee approptiately', async () => { - for (let idx = 0; idx < gasAmounts.length; idx++) { - const gas = gasAmounts[idx] - for (let jdx = 0; jdx < premiums.length; jdx++) { - const premium = premiums[jdx] - for (let kdx = 0; kdx < flatFees.length; kdx++) { - const flatFee = flatFees[kdx] - await registry - .connect(owner) - .setConfig( - premium, - flatFee, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - const price = await registry.getMaxPaymentForGas(gas) - expect(price).to.equal(linkForGas(gas, premium, flatFee)) - } - } - } - }) - }) - - describe('#checkUpkeep / #performUpkeep', () => { - const performData = '0xc0ffeec0ffee' - const multiplier = BigNumber.from(10) - const flatFee = BigNumber.from('100000') //0.1 LINK - const callGasPrice = 1 - - it('uses the same minimum balance calculation [ @skip-coverage ]', async () => { - await registry - .connect(owner) - .setConfig( - paymentPremiumPPB, - flatFee, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - multiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - await linkToken.connect(owner).approve(registry.address, toWei('100')) - - const tx1 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const upkeepID1 = await getUpkeepID(tx1) - const tx2 = await registry - .connect(owner) - .registerUpkeep( - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - ) - const upkeepID2 = await getUpkeepID(tx2) - await mock.setCanCheck(true) - await mock.setCanPerform(true) - // upkeep 1 is underfunded, 2 is funded - const minBalance1 = (await registry.getMaxPaymentForGas(executeGas)).sub( - 1, - ) - const minBalance2 = await registry.getMaxPaymentForGas(executeGas) - await registry.connect(owner).addFunds(upkeepID1, minBalance1) - await registry.connect(owner).addFunds(upkeepID2, minBalance2) - // upkeep 1 check should revert, 2 should succeed - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(upkeepID1, await keeper1.getAddress(), { - gasPrice: callGasPrice, - }), - ) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(upkeepID2, await keeper1.getAddress(), { - gasPrice: callGasPrice, - }) - // upkeep 1 perform should revert, 2 should succeed - await evmRevert( - registry - .connect(keeper1) - .performUpkeep(upkeepID1, performData, { gasLimit: extraGas }), - 'insufficient funds', - ) - await registry - .connect(keeper1) - .performUpkeep(upkeepID2, performData, { gasLimit: extraGas }) - }) - }) - - describe('#getMinBalanceForUpkeep / #checkUpkeep', () => { - it('calculates the minimum balance appropriately', async () => { - const oneWei = BigNumber.from('1') - await linkToken.connect(keeper1).approve(registry.address, toWei('100')) - await mock.setCanCheck(true) - await mock.setCanPerform(true) - const minBalance = await registry.getMinBalanceForUpkeep(id) - const tooLow = minBalance.sub(oneWei) - await registry.connect(keeper1).addFunds(id, tooLow) - await evmRevert( - registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()), - 'insufficient funds', - ) - await registry.connect(keeper1).addFunds(id, oneWei) - await registry - .connect(zeroAddress) - .callStatic.checkUpkeep(id, await keeper1.getAddress()) - }) - }) -}) diff --git a/contracts/test/v0.7/Operator.test.ts b/contracts/test/v0.7/Operator.test.ts deleted file mode 100644 index 4af846576b3..00000000000 --- a/contracts/test/v0.7/Operator.test.ts +++ /dev/null @@ -1,3819 +0,0 @@ -import { ethers } from 'hardhat' -import { - publicAbi, - toBytes32String, - toWei, - stringToBytes, - increaseTime5Minutes, - getLog, -} from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractReceipt, - ContractTransaction, - Signer, -} from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { bigNumEquals, evmRevert } from '../test-helpers/matchers' -import type { providers } from 'ethers' -import { - convertCancelParams, - convertCancelByRequesterParams, - convertFufillParams, - convertFulfill2Params, - decodeRunRequest, - encodeOracleRequest, - encodeRequestOracleData, - RunRequest, -} from '../test-helpers/oracle' - -let v7ConsumerFactory: ContractFactory -let basicConsumerFactory: ContractFactory -let multiWordConsumerFactory: ContractFactory -let gasGuzzlingConsumerFactory: ContractFactory -let getterSetterFactory: ContractFactory -let maliciousRequesterFactory: ContractFactory -let maliciousConsumerFactory: ContractFactory -let maliciousMultiWordConsumerFactory: ContractFactory -let operatorFactory: ContractFactory -let forwarderFactory: ContractFactory -let linkTokenFactory: ContractFactory -const zeroAddress = ethers.constants.AddressZero - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - v7ConsumerFactory = await ethers.getContractFactory( - 'src/v0.7/tests/Consumer.sol:Consumer', - ) - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - ) - multiWordConsumerFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MultiWordConsumer.sol:MultiWordConsumer', - ) - gasGuzzlingConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/GasGuzzlingConsumer.sol:GasGuzzlingConsumer', - ) - getterSetterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/GetterSetter.sol:GetterSetter', - ) - maliciousRequesterFactory = await ethers.getContractFactory( - 'src/v0.4/tests/MaliciousRequester.sol:MaliciousRequester', - ) - maliciousConsumerFactory = await ethers.getContractFactory( - 'src/v0.4/tests/MaliciousConsumer.sol:MaliciousConsumer', - ) - maliciousMultiWordConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/MaliciousMultiWordConsumer.sol:MaliciousMultiWordConsumer', - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) -}) - -describe('Operator', () => { - let fHash: string - let specId: string - let to: string - let link: Contract - let operator: Contract - let forwarder1: Contract - let forwarder2: Contract - let owner: Signer - - beforeEach(async () => { - fHash = getterSetterFactory.interface.getSighash('requestedBytes32') - specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - to = '0x80e29acb842498fe6591f020bd82766dce619d43' - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - owner = roles.defaultAccount - operator = await operatorFactory - .connect(owner) - .deploy(link.address, await owner.getAddress()) - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(operator, [ - 'acceptAuthorizedReceivers', - 'acceptOwnableContracts', - 'cancelOracleRequest', - 'cancelOracleRequestByRequester', - 'distributeFunds', - 'fulfillOracleRequest', - 'fulfillOracleRequest2', - 'getAuthorizedSenders', - 'getChainlinkToken', - 'getExpiryTime', - 'isAuthorizedSender', - 'onTokenTransfer', - 'operatorRequest', - 'oracleRequest', - 'ownerForward', - 'ownerTransferAndCall', - 'setAuthorizedSenders', - 'setAuthorizedSendersOn', - 'transferOwnableContracts', - 'typeAndVersion', - 'withdraw', - 'withdrawable', - // Ownable methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the operator', async () => { - assert.equal(await operator.typeAndVersion(), 'Operator 1.0.0') - }) - }) - - describe('#transferOwnableContracts', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - }) - - describe('being called by the owner', () => { - it('cannot transfer to self', async () => { - await evmRevert( - operator - .connect(owner) - .transferOwnableContracts([forwarder1.address], operator.address), - 'Cannot transfer to self', - ) - }) - - it('emits an ownership transfer request event', async () => { - const tx = await operator - .connect(owner) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - await roles.oracleNode1.getAddress(), - ) - const receipt = await tx.wait() - assert.equal(receipt?.events?.length, 2) - const log1 = receipt?.events?.[0] - assert.equal(log1?.event, 'OwnershipTransferRequested') - assert.equal(log1?.address, forwarder1.address) - assert.equal(log1?.args?.[0], operator.address) - assert.equal(log1?.args?.[1], await roles.oracleNode1.getAddress()) - const log2 = receipt?.events?.[1] - assert.equal(log2?.event, 'OwnershipTransferRequested') - assert.equal(log2?.address, forwarder2.address) - assert.equal(log2?.args?.[0], operator.address) - assert.equal(log2?.args?.[1], await roles.oracleNode1.getAddress()) - }) - }) - - describe('being called by a non-owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .transferOwnableContracts( - [forwarder1.address], - await roles.oracleNode2.getAddress(), - ), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#acceptOwnableContracts', () => { - describe('being called by the owner', () => { - let operator2: Contract - let receipt: ContractReceipt - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - operator2.address, - ) - const tx = await operator2 - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address, forwarder2.address]) - receipt = await tx.wait() - }) - - it('sets the new owner on the forwarder', async () => { - assert.equal(await forwarder1.owner(), operator2.address) - }) - - it('emits ownership transferred events', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[0]?.args?.[0], forwarder1.address) - - assert.equal(receipt?.events?.[1]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[1]?.address, forwarder1.address) - assert.equal(receipt?.events?.[1]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[1]?.args?.[1], operator2.address) - - assert.equal(receipt?.events?.[2]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[2]?.args?.[0], forwarder2.address) - - assert.equal(receipt?.events?.[3]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[3]?.address, forwarder2.address) - assert.equal(receipt?.events?.[3]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[3]?.args?.[1], operator2.address) - }) - }) - - describe('being called by a non-owner authorized sender', () => { - it('does not revert', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode1.getAddress()]) - - await operator.connect(roles.oracleNode1).acceptOwnableContracts([]) - }) - }) - - describe('being called by a non owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .acceptOwnableContracts([await roles.oracleNode2.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#distributeFunds', () => { - describe('when called with empty arrays', () => { - it('reverts with invalid array message', async () => { - await evmRevert( - operator.connect(roles.defaultAccount).distributeFunds([], []), - 'Invalid array length(s)', - ) - }) - }) - - describe('when called with unequal array lengths', () => { - it('reverts with invalid array message', async () => { - const receivers = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const amounts = [1, 2, 3] - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds(receivers, amounts), - 'Invalid array length(s)', - ) - }) - }) - - describe('when called with not enough ETH', () => { - it('reverts with subtraction overflow message', async () => { - const amountToSend = toWei('2') - const ethSent = toWei('1') - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds( - [await roles.oracleNode2.getAddress()], - [amountToSend], - { - value: ethSent, - }, - ), - 'SafeMath: subtraction overflow', - ) - }) - }) - - describe('when called with too much ETH', () => { - it('reverts with too much ETH message', async () => { - const amountToSend = toWei('2') - const ethSent = toWei('3') - await evmRevert( - operator - .connect(roles.defaultAccount) - .distributeFunds( - [await roles.oracleNode2.getAddress()], - [amountToSend], - { - value: ethSent, - }, - ), - 'Too much ETH sent', - ) - }) - }) - - describe('when called with correct values', () => { - it('updates the balances', async () => { - const node2BalanceBefore = await roles.oracleNode2.getBalance() - const node3BalanceBefore = await roles.oracleNode3.getBalance() - const receivers = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const sendNode2 = toWei('2') - const sendNode3 = toWei('3') - const totalAmount = toWei('5') - const amounts = [sendNode2, sendNode3] - - await operator - .connect(roles.defaultAccount) - .distributeFunds(receivers, amounts, { value: totalAmount }) - - const node2BalanceAfter = await roles.oracleNode2.getBalance() - const node3BalanceAfter = await roles.oracleNode3.getBalance() - - assert.equal( - node2BalanceAfter.sub(node2BalanceBefore).toString(), - sendNode2.toString(), - ) - - assert.equal( - node3BalanceAfter.sub(node3BalanceBefore).toString(), - sendNode3.toString(), - ) - }) - }) - }) - - describe('#setAuthorizedSenders', () => { - let newSenders: string[] - let receipt: ContractReceipt - describe('when called by the owner', () => { - describe('setting 3 authorized senders', () => { - beforeEach(async () => { - newSenders = [ - await roles.oracleNode1.getAddress(), - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - const tx = await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - receipt = await tx.wait() - }) - - it('adds the authorized nodes', async () => { - const authorizedSenders = await operator.getAuthorizedSenders() - assert.equal(newSenders.length, authorizedSenders.length) - for (let i = 0; i < authorizedSenders.length; i++) { - assert.equal(authorizedSenders[i], newSenders[i]) - } - }) - - it('emits an event on the Operator', async () => { - assert.equal(receipt.events?.length, 1) - - const encodedSenders1 = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, await roles.defaultAccount.getAddress()], - ) - - const responseEvent1 = receipt.events?.[0] - assert.equal(responseEvent1?.event, 'AuthorizedSendersChanged') - assert.equal(responseEvent1?.data, encodedSenders1) - }) - - it('replaces the authorized nodes', async () => { - const originalAuthorization = await operator - .connect(roles.defaultAccount) - .isAuthorizedSender(await roles.oracleNode.getAddress()) - assert.isFalse(originalAuthorization) - }) - - after(async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode.getAddress()]) - }) - }) - - describe('setting 0 authorized senders', () => { - beforeEach(async () => { - newSenders = [] - }) - - it('reverts with a minimum senders message', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders), - 'Must have at least 1 sender', - ) - }) - }) - }) - - describe('when called by an authorized sender', () => { - beforeEach(async () => { - newSenders = [await roles.oracleNode1.getAddress()] - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders(newSenders) - }) - - it('succeeds', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.stranger.getAddress()]) - }) - }) - - describe('when called by a non-owner', () => { - it('cannot add an authorized node', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .setAuthorizedSenders([await roles.stranger.getAddress()]), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#setAuthorizedSendersOn', () => { - let newSenders: string[] - - beforeEach(async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSenders([await roles.oracleNode1.getAddress()]) - newSenders = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - - forwarder1 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(owner) - .deploy(link.address, operator.address, zeroAddress, '0x') - }) - - describe('when called by a non-authorized sender', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .setAuthorizedSendersOn(newSenders, [forwarder1.address]), - 'Cannot set authorized senders', - ) - }) - }) - - describe('when called by an owner', () => { - it('does not revert', async () => { - await operator - .connect(roles.defaultAccount) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - }) - - describe('when called by an authorized sender', () => { - it('does not revert', async () => { - await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - - it('does revert with 0 senders', async () => { - await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - }) - - it('emits a log announcing the change and who made it', async () => { - const targets = [forwarder1.address, forwarder2.address] - const tx = await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn(targets, newSenders) - - const receipt = await tx.wait() - const encodedArgs = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address[]', 'address'], - [targets, newSenders, await roles.oracleNode1.getAddress()], - ) - - const event1 = receipt.events?.[0] - assert.equal(event1?.event, 'TargetsUpdatedAuthorizedSenders') - assert.equal(event1?.address, operator.address) - assert.equal(event1?.data, encodedArgs) - }) - - it('updates the sender list on each of the targets', async () => { - const tx = await operator - .connect(roles.oracleNode1) - .setAuthorizedSendersOn( - [forwarder1.address, forwarder2.address], - newSenders, - ) - - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3, receipt.toString()) - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, operator.address], - ) - - const event1 = receipt.events?.[1] - assert.equal(event1?.event, 'AuthorizedSendersChanged') - assert.equal(event1?.address, forwarder1.address) - assert.equal(event1?.data, encodedSenders) - - const event2 = receipt.events?.[2] - assert.equal(event2?.event, 'AuthorizedSendersChanged') - assert.equal(event2?.address, forwarder2.address) - assert.equal(event2?.data, encodedSenders) - }) - }) - }) - - describe('#acceptAuthorizedReceivers', () => { - let newSenders: string[] - - describe('being called by the owner', () => { - let operator2: Contract - let receipt: ContractReceipt - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - forwarder2 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, zeroAddress, '0x') - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts( - [forwarder1.address, forwarder2.address], - operator2.address, - ) - newSenders = [ - await roles.oracleNode2.getAddress(), - await roles.oracleNode3.getAddress(), - ] - - const tx = await operator2 - .connect(roles.defaultAccount) - .acceptAuthorizedReceivers( - [forwarder1.address, forwarder2.address], - newSenders, - ) - receipt = await tx.wait() - }) - - it('sets the new owner on the forwarder', async () => { - assert.equal(await forwarder1.owner(), operator2.address) - }) - - it('emits ownership transferred events', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[0]?.args?.[0], forwarder1.address) - - assert.equal(receipt?.events?.[1]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[1]?.address, forwarder1.address) - assert.equal(receipt?.events?.[1]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[1]?.args?.[1], operator2.address) - - assert.equal(receipt?.events?.[2]?.event, 'OwnableContractAccepted') - assert.equal(receipt?.events?.[2]?.args?.[0], forwarder2.address) - - assert.equal(receipt?.events?.[3]?.event, 'OwnershipTransferred') - assert.equal(receipt?.events?.[3]?.address, forwarder2.address) - assert.equal(receipt?.events?.[3]?.args?.[0], operator.address) - assert.equal(receipt?.events?.[3]?.args?.[1], operator2.address) - - assert.equal( - receipt?.events?.[4]?.event, - 'TargetsUpdatedAuthorizedSenders', - ) - - const encodedSenders = ethers.utils.defaultAbiCoder.encode( - ['address[]', 'address'], - [newSenders, operator2.address], - ) - assert.equal(receipt?.events?.[5]?.event, 'AuthorizedSendersChanged') - assert.equal(receipt?.events?.[5]?.address, forwarder1.address) - assert.equal(receipt?.events?.[5]?.data, encodedSenders) - - assert.equal(receipt?.events?.[6]?.event, 'AuthorizedSendersChanged') - assert.equal(receipt?.events?.[6]?.address, forwarder2.address) - assert.equal(receipt?.events?.[6]?.data, encodedSenders) - }) - }) - - describe('being called by a non owner', () => { - it('reverts with message', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .acceptAuthorizedReceivers( - [forwarder1.address, forwarder2.address], - newSenders, - ), - 'Cannot set authorized senders', - ) - }) - }) - }) - - describe('#onTokenTransfer', () => { - describe('when called from any address but the LINK token', () => { - it('triggers the intended method', async () => { - const callData = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - await evmRevert( - operator.onTokenTransfer( - await roles.defaultAccount.getAddress(), - 0, - callData, - ), - ) - }) - }) - - describe('when called from the LINK token', () => { - it('triggers the intended method', async () => { - const callData = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - const tx = await link.transferAndCall(operator.address, 0, callData, { - value: 0, - }) - const receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - }) - - describe('with no data', () => { - it('reverts', async () => { - await evmRevert( - link.transferAndCall(operator.address, 0, '0x', { - value: 0, - }), - ) - }) - }) - }) - - describe('malicious requester', () => { - let mock: Contract - let requester: Contract - const paymentAmount = toWei('1') - - beforeEach(async () => { - mock = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(mock.address, paymentAmount) - }) - - it('cannot withdraw from oracle', async () => { - const operatorOriginalBalance = await link.balanceOf(operator.address) - const mockOriginalBalance = await link.balanceOf(mock.address) - - await evmRevert(mock.maliciousWithdraw()) - - const operatorNewBalance = await link.balanceOf(operator.address) - const mockNewBalance = await link.balanceOf(mock.address) - - bigNumEquals(operatorOriginalBalance, operatorNewBalance) - bigNumEquals(mockNewBalance, mockOriginalBalance) - }) - - describe('if the requester tries to create a requestId for another contract', () => { - it('the requesters ID will not match with the oracle contract', async () => { - const tx = await mock.maliciousTargetConsumer(to) - const receipt = await tx.wait() - - const mockRequestId = receipt.logs?.[0].data - const requestId = (receipt.events?.[0].args as any).requestId - assert.notEqual(mockRequestId, requestId) - }) - - it('the target requester can still create valid requests', async () => { - requester = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - await link.transfer(requester.address, paymentAmount) - await mock.maliciousTargetConsumer(requester.address) - await requester.requestEthereumPrice('USD', paymentAmount) - }) - }) - }) - - it('does not allow recursive calls of onTokenTransfer', async () => { - const requestPayload = encodeOracleRequest( - specId, - to, - fHash, - 0, - constants.HashZero, - ) - - const ottSelector = - operatorFactory.interface.getSighash('onTokenTransfer') - const header = - '000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef' + // to - '0000000000000000000000000000000000000000000000000000000000000539' + // amount - '0000000000000000000000000000000000000000000000000000000000000060' + // offset - '0000000000000000000000000000000000000000000000000000000000000136' // length - - const maliciousPayload = ottSelector + header + requestPayload.slice(2) - - await evmRevert( - link.transferAndCall(operator.address, 0, maliciousPayload, { - value: 0, - }), - ) - }) - }) - - describe('#oracleRequest', () => { - describe('when called through the LINK token', () => { - const paid = 100 - let log: providers.Log | undefined - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const args = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, paid, args) - receipt = await tx.wait() - assert.equal(3, receipt?.logs?.length) - - log = receipt.logs && receipt.logs[2] - }) - - it('logs an event', async () => { - assert.equal(operator.address, log?.address) - - assert.equal(log?.topics?.[1], specId) - - const req = decodeRunRequest(receipt?.logs?.[2]) - assert.equal(await roles.defaultAccount.getAddress(), req.requester) - bigNumEquals(paid, req.payment) - }) - - it('uses the expected event signature', async () => { - // If updating this test, be sure to update models.RunLogTopic. - const eventSignature = - '0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65' - assert.equal(eventSignature, log?.topics?.[0]) - }) - - it('does not allow the same requestId to be used twice', async () => { - const args2 = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args2)) - }) - - describe('when called with a payload less than 2 EVM words + function selector', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '0000000000000000000000000000000000000000000000000000000000000000000' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - - describe('when called with a payload between 3 and 9 EVM words', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - }) - - describe('when dataVersion is higher than 255', () => { - it('throws an error', async () => { - const paid = 100 - const args = encodeOracleRequest( - specId, - to, - fHash, - 1, - constants.HashZero, - 256, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args)) - }) - }) - - describe('when not called through the LINK token', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.oracleNode) - .oracleRequest( - '0x0000000000000000000000000000000000000000', - 0, - specId, - to, - fHash, - 1, - 1, - '0x', - ), - ) - }) - }) - }) - - describe('#operatorRequest', () => { - describe('when called through the LINK token', () => { - const paid = 100 - let log: providers.Log | undefined - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const args = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, paid, args) - receipt = await tx.wait() - assert.equal(3, receipt?.logs?.length) - - log = receipt.logs && receipt.logs[2] - }) - - it('logs an event', async () => { - assert.equal(operator.address, log?.address) - - assert.equal(log?.topics?.[1], specId) - - const req = decodeRunRequest(receipt?.logs?.[2]) - assert.equal(await roles.defaultAccount.getAddress(), req.requester) - bigNumEquals(paid, req.payment) - }) - - it('uses the expected event signature', async () => { - // If updating this test, be sure to update models.RunLogTopic. - const eventSignature = - '0xd8d7ecc4800d25fa53ce0372f13a416d98907a7ef3d8d3bdd79cf4fe75529c65' - assert.equal(eventSignature, log?.topics?.[0]) - }) - - it('does not allow the same requestId to be used twice', async () => { - const args2 = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args2)) - }) - - describe('when called with a payload less than 2 EVM words + function selector', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '0000000000000000000000000000000000000000000000000000000000000000000' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - - describe('when called with a payload between 3 and 9 EVM words', () => { - it('throws an error', async () => { - const funcSelector = - operatorFactory.interface.getSighash('oracleRequest') - const maliciousData = - funcSelector + - '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' - await evmRevert( - link.transferAndCall(operator.address, paid, maliciousData), - ) - }) - }) - }) - - describe('when dataVersion is higher than 255', () => { - it('throws an error', async () => { - const paid = 100 - const args = encodeRequestOracleData( - specId, - fHash, - 1, - constants.HashZero, - 256, - ) - await evmRevert(link.transferAndCall(operator.address, paid, args)) - }) - }) - - describe('when not called through the LINK token', () => { - it('reverts', async () => { - await evmRevert( - operator - .connect(roles.oracleNode) - .oracleRequest( - '0x0000000000000000000000000000000000000000', - 0, - specId, - to, - fHash, - 1, - 1, - '0x', - ), - ) - }) - }) - }) - - describe('#fulfillOracleRequest', () => { - const response = 'Hi Mom!' - let maliciousRequester: Contract - let basicConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyRequestEthereumPrice(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse event', async () => { - const fulfillParams = convertFufillParams(request, response) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - }) - - describe('when fulfilled with the wrong function', () => { - let v7Consumer - beforeEach(async () => { - v7Consumer = await v7ConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(v7Consumer.address, paymentAmount) - const currency = 'USD' - const tx = await v7Consumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const currentValue = await basicConsumer.currentPrice() - assert.equal(response, ethers.utils.parseBytes32String(currentValue)) - }) - - it('emits an OracleResponse event', async () => { - const fulfillParams = convertFufillParams(request, response) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal(response, ethers.utils.parseBytes32String(currentValue)) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest( - ...convertFufillParams(request, response, { - gasLimit: 70000, - }), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest( - ...convertFufillParams(request, response, { - gasLimit: defaultGasLimit, - }), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('cancelRequestOnFulfill(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - const mockBalance = await link.balanceOf(maliciousConsumer.address) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response2)), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, response)) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - - describe('when calling an owned contract', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, link.address, operator.address, '0x') - }) - - it('does not allow the contract to callback to owned contracts', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('whatever(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - let responseParams = convertFufillParams(request, response) - // set the params to be the owned address - responseParams[2] = forwarder1.address - - //accept ownership - await operator - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address]) - - // do the thing - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Cannot call owned contract', - ) - - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts([forwarder1.address], link.address) - //reverts for a different reason after transferring ownership - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Params do not match request ID', - ) - }) - }) - }) - }) - - describe('#fulfillOracleRequest2', () => { - describe('single word fulfils', () => { - const response = 'Hi mom!' - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - let maliciousRequester: Contract - let basicConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - let request2: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyRequestEthereumPrice(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal( - response, - ethers.utils.parseBytes32String(currentValue), - ) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - const response2Values = [toBytes32String(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - - const currentValue = await basicConsumer.currentPrice() - assert.equal( - response, - ethers.utils.parseBytes32String(currentValue), - ) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params(request, responseTypes, responseValues, { - gasLimit: defaultGasLimit, - }), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious oracle', () => { - beforeEach(async () => { - // Setup Request 1 - basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - // Setup Request 2 - await link.transfer(basicConsumer.address, paymentAmount) - const tx2 = await basicConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('cannot spoof requestId in response data by moving calldata offset', async () => { - // Malicious Oracle Fulfill 2 - const functionSelector = '0x6ae0bc76' // fulfillOracleRequest2 - const dataOffset = - '0000000000000000000000000000000000000000000000000000000000000100' // Moved to 0x0124 - const fillerBytes = - '0000000000000000000000000000000000000000000000000000000000000000' - const expectedCalldataStart = request.requestId.slice(2) // 0xe4, this is checked against requestId in validateMultiWordResponseId - const dataSize = - '0000000000000000000000000000000000000000000000000000000000000040' // Two 32 byte blocks - const maliciousCalldataId = request2.requestId.slice(2) // 0x0124, set to a different requestId - const calldataData = - '1122334455667788991122334455667788991122334455667788991122334455' // some garbage value as response value - - const data = - functionSelector + - /** Input Params - slice off 0x prefix and pad with 0's */ - request.requestId.slice(2) + - request.payment.slice(2).padStart(64, '0') + - request.callbackAddr.slice(2).padStart(64, '0') + - request.callbackFunc.slice(2).padEnd(64, '0') + - request.expiration.slice(2).padStart(64, '0') + - // calldata "data" - dataOffset + - fillerBytes + - expectedCalldataStart + - dataSize + - maliciousCalldataId + - calldataData - - await evmRevert( - operator.connect(roles.oracleNode).signer.sendTransaction({ - to: operator.address, - data, - }), - ) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [toBytes32String(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf(maliciousConsumer.address) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [toBytes32String(response2)] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - - describe('when calling an owned contract', () => { - beforeEach(async () => { - forwarder1 = await forwarderFactory - .connect(roles.defaultAccount) - .deploy(link.address, link.address, operator.address, '0x') - }) - - it('does not allow the contract to callback to owned contracts', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('whatever(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - let responseParams = convertFufillParams(request, response) - // set the params to be the owned address - responseParams[2] = forwarder1.address - - //accept ownership - await operator - .connect(roles.defaultAccount) - .acceptOwnableContracts([forwarder1.address]) - - // do the thing - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...responseParams), - 'Cannot call owned contract', - ) - - await operator - .connect(roles.defaultAccount) - .transferOwnableContracts([forwarder1.address], link.address) - //reverts for a different reason after transferring ownership - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...responseParams), - 'Params do not match request ID', - ) - }) - }) - }) - }) - - describe('multi word fulfils', () => { - describe('one bytes parameter', () => { - const response = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\ - Fusce euismod malesuada ligula, eget semper metus ultrices sit amet.' - const responseTypes = ['bytes'] - const responseValues = [stringToBytes(response)] - let maliciousRequester: Contract - let multiConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyMultiWordRequest(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - multiConsumer = await multiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(multiConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await multiConsumer.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it("matches the consumer's request ID", async () => { - const nonce = await multiConsumer.publicGetNextRequestCount() - const tx = await multiConsumer.requestEthereumPrice('USD', 0) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - const packed = ethers.utils.solidityPack( - ['address', 'uint256'], - [multiConsumer.address, nonce], - ) - const expected = ethers.utils.keccak256(packed) - assert.equal(expected, request.requestId) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = - ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const currentValue = await multiConsumer.currentPrice() - assert.equal(response, ethers.utils.toUtf8String(currentValue)) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response2 = response + ' && Hello World!!' - const response2Values = [stringToBytes(response2)] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - - const currentValue = await multiConsumer.currentPrice() - assert.equal(response, ethers.utils.toUtf8String(currentValue)) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: defaultGasLimit, - }, - ), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousMultiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [stringToBytes(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf( - maliciousConsumer.address, - ) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response2 = 'hack the planet 102' - const response2Values = [stringToBytes(response2)] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - response2Values, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - }) - }) - - describe('multiple bytes32 parameters', () => { - const response1 = '100' - const response2 = '7777777' - const response3 = 'forty two' - const responseTypes = ['bytes32', 'bytes32', 'bytes32'] - const responseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response3), - ] - let maliciousRequester: Contract - let multiConsumer: Contract - let maliciousConsumer: Contract - let gasGuzzlingConsumer: Contract - let request: ReturnType - - describe('gas guzzling consumer [ @skip-coverage ]', () => { - beforeEach(async () => { - gasGuzzlingConsumer = await gasGuzzlingConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(gasGuzzlingConsumer.address, paymentAmount) - const tx = - await gasGuzzlingConsumer.gassyMultiWordRequest(paymentAmount) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 1) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - }) - - describe('cooperative consumer', () => { - beforeEach(async () => { - multiConsumer = await multiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(multiConsumer.address, paymentAmount) - const currency = 'USD' - const tx = await multiConsumer.requestMultipleParameters( - currency, - paymentAmount, - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - describe('when called by an unauthorized node', () => { - beforeEach(async () => { - assert.equal( - false, - await operator.isAuthorizedSender( - await roles.stranger.getAddress(), - ), - ) - }) - - it('raises an error', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - }) - - describe('when called by an authorized node', () => { - it('raises an error if the request ID does not exist', async () => { - request.requestId = - ethers.utils.formatBytes32String('DOESNOTEXIST') - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ), - ) - }) - - it('sets the value on the requested contract', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const firstValue = await multiConsumer.usd() - const secondValue = await multiConsumer.eur() - const thirdValue = await multiConsumer.jpy() - assert.equal( - response1, - ethers.utils.parseBytes32String(firstValue), - ) - assert.equal( - response2, - ethers.utils.parseBytes32String(secondValue), - ) - assert.equal( - response3, - ethers.utils.parseBytes32String(thirdValue), - ) - }) - - it('emits an OracleResponse2 event', async () => { - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - const tx = await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams) - const receipt = await tx.wait() - assert.equal(receipt.events?.length, 3) - const responseEvent = receipt.events?.[0] - assert.equal(responseEvent?.event, 'OracleResponse') - assert.equal(responseEvent?.args?.[0], request.requestId) - }) - - it('does not allow a request to be fulfilled twice', async () => { - const response4 = response3 + ' && Hello World!!' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - - const firstValue = await multiConsumer.usd() - const secondValue = await multiConsumer.eur() - const thirdValue = await multiConsumer.jpy() - assert.equal( - response1, - ethers.utils.parseBytes32String(firstValue), - ) - assert.equal( - response2, - ethers.utils.parseBytes32String(secondValue), - ) - assert.equal( - response3, - ethers.utils.parseBytes32String(thirdValue), - ) - }) - }) - - describe('when the oracle does not provide enough gas', () => { - // if updating this defaultGasLimit, be sure it matches with the - // defaultGasLimit specified in store/tx_manager.go - const defaultGasLimit = 500000 - - beforeEach(async () => { - bigNumEquals(0, await operator.withdrawable()) - }) - - it('does not allow the oracle to withdraw the payment', async () => { - await evmRevert( - operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: 70000, - }, - ), - ), - ) - - bigNumEquals(0, await operator.withdrawable()) - }) - - it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await operator.connect(roles.oracleNode).fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - { - gasLimit: defaultGasLimit, - }, - ), - ) - - bigNumEquals(request.payment, await operator.withdrawable()) - }) - }) - }) - - describe('with a malicious requester', () => { - beforeEach(async () => { - const paymentAmount = toWei('1') - maliciousRequester = await maliciousRequesterFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousRequester.address, paymentAmount) - }) - - it('cannot cancel before the expiration', async () => { - await evmRevert( - maliciousRequester.maliciousRequestCancel( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ), - ) - }) - - it('cannot call functions on the LINK token through callbacks', async () => { - await evmRevert( - maliciousRequester.request( - specId, - link.address, - ethers.utils.toUtf8Bytes('transfer(address,uint256)'), - ), - ) - }) - - describe('requester lies about amount of LINK sent', () => { - it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await maliciousRequester.maliciousPrice(specId) - const receipt = await tx.wait() - const req = decodeRunRequest(receipt.logs?.[3]) - - assert(toWei('1').eq(req.payment)) - }) - }) - }) - - describe('with a malicious consumer', () => { - const paymentAmount = toWei('1') - - beforeEach(async () => { - maliciousConsumer = await maliciousMultiWordConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address) - await link.transfer(maliciousConsumer.address, paymentAmount) - }) - - describe('fails during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response4 = 'hack the planet 102' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - }) - }) - - describe('calls selfdestruct', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - await maliciousConsumer.remove() - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - }) - - describe('request is canceled during fulfillment', () => { - beforeEach(async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes( - 'cancelRequestOnFulfill(bytes32,bytes32)', - ), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - bigNumEquals(0, await link.balanceOf(maliciousConsumer.address)) - }) - - it('allows the oracle node to receive their payment', async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - const mockBalance = await link.balanceOf( - maliciousConsumer.address, - ) - bigNumEquals(mockBalance, 0) - - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(balance, 0) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), paymentAmount) - const newBalance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - bigNumEquals(paymentAmount, newBalance) - }) - - it("can't fulfill the data again", async () => { - const response4 = 'hack the planet 102' - const repeatedResponseValues = [ - toBytes32String(response1), - toBytes32String(response2), - toBytes32String(response4), - ] - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - repeatedResponseValues, - ), - ), - ) - }) - }) - - describe('tries to steal funds from node', () => { - it('is not successful with call', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with send', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - - it('is not successful with transfer', async () => { - const tx = await maliciousConsumer.requestData( - specId, - ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), - ) - const receipt = await tx.wait() - request = decodeRunRequest(receipt.logs?.[3]) - - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params( - request, - responseTypes, - responseValues, - ), - ) - bigNumEquals( - 0, - await ethers.provider.getBalance(maliciousConsumer.address), - ) - }) - }) - }) - }) - }) - - describe('when the response data is too short', () => { - const response = 'Hi mom!' - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - - it('reverts', async () => { - let basicConsumer = await basicConsumerFactory - .connect(roles.defaultAccount) - .deploy(link.address, operator.address, specId) - const paymentAmount = toWei('1') - await link.transfer(basicConsumer.address, paymentAmount) - const tx = await basicConsumer.requestEthereumPrice( - 'USD', - paymentAmount, - ) - const receipt = await tx.wait() - let request = decodeRunRequest(receipt.logs?.[3]) - - const fulfillParams = convertFulfill2Params( - request, - responseTypes, - responseValues, - ) - fulfillParams[5] = '0x' // overwrite the data to be of lenght 0 - await evmRevert( - operator - .connect(roles.oracleNode) - .fulfillOracleRequest2(...fulfillParams), - 'Response must be > 32 bytes', - ) - }) - }) - }) - - describe('#withdraw', () => { - describe('without reserving funds via oracleRequest', () => { - it('does nothing', async () => { - let balance = await link.balanceOf(await roles.oracleNode.getAddress()) - assert.equal(0, balance.toNumber()) - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), toWei('1')), - ) - balance = await link.balanceOf(await roles.oracleNode.getAddress()) - assert.equal(0, balance.toNumber()) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - - describe('reserving funds via oracleRequest', () => { - const payment = 15 - let request: ReturnType - - beforeEach(async () => { - const requester = await roles.defaultAccount.getAddress() - const args = encodeOracleRequest( - specId, - requester, - fHash, - 0, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, payment, args) - const receipt = await tx.wait() - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - }) - - describe('but not freeing funds w fulfillOracleRequest', () => { - it('does not transfer funds', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.oracleNode.getAddress(), payment), - ) - const balance = await link.balanceOf( - await roles.oracleNode.getAddress(), - ) - assert.equal(0, balance.toNumber()) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - - describe('and freeing funds', () => { - beforeEach(async () => { - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest( - ...convertFufillParams(request, 'Hello World!'), - ) - }) - - it('does not allow input greater than the balance', async () => { - const originalOracleBalance = await link.balanceOf(operator.address) - const originalStrangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - const withdrawalAmount = payment + 1 - - assert.isAbove(withdrawalAmount, originalOracleBalance.toNumber()) - await evmRevert( - operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), withdrawalAmount), - ) - - const newOracleBalance = await link.balanceOf(operator.address) - const newStrangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - - assert.equal( - originalOracleBalance.toNumber(), - newOracleBalance.toNumber(), - ) - assert.equal( - originalStrangerBalance.toNumber(), - newStrangerBalance.toNumber(), - ) - }) - - it('allows transfer of partial balance by owner to specified address', async () => { - const partialAmount = 6 - const difference = payment - partialAmount - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), partialAmount) - const strangerBalance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - const oracleBalance = await link.balanceOf(operator.address) - assert.equal(partialAmount, strangerBalance.toNumber()) - assert.equal(difference, oracleBalance.toNumber()) - }) - - it('allows transfer of entire balance by owner to specified address', async () => { - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.stranger.getAddress(), payment) - const balance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - assert.equal(payment, balance.toNumber()) - }) - - it('does not allow a transfer of funds by non-owner', async () => { - await evmRevert( - operator - .connect(roles.stranger) - .withdraw(await roles.stranger.getAddress(), payment), - ) - const balance = await link.balanceOf( - await roles.stranger.getAddress(), - ) - assert.isTrue(ethers.constants.Zero.eq(balance)) - }) - - describe('recovering funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('withdraws funds', async () => { - const operatorBalanceBefore = await link.balanceOf(operator.address) - const accountBalanceBefore = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - await operator - .connect(roles.defaultAccount) - .withdraw(await roles.defaultAccount.getAddress(), paid) - - const operatorBalanceAfter = await link.balanceOf(operator.address) - const accountBalanceAfter = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - - const accountDifference = - accountBalanceAfter.sub(accountBalanceBefore) - const operatorDifference = - operatorBalanceBefore.sub(operatorBalanceAfter) - - bigNumEquals(operatorDifference, paid) - bigNumEquals(accountDifference, paid) - }) - }) - }) - }) - }) - - describe('#withdrawable', () => { - let request: ReturnType - const amount = toWei('1') - - beforeEach(async () => { - const requester = await roles.defaultAccount.getAddress() - const args = encodeOracleRequest( - specId, - requester, - fHash, - 0, - constants.HashZero, - ) - const tx = await link.transferAndCall(operator.address, amount, args) - const receipt = await tx.wait() - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - await operator - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request, 'Hello World!')) - }) - - it('returns the correct value', async () => { - const withdrawAmount = await operator.withdrawable() - bigNumEquals(withdrawAmount, request.payment) - }) - - describe('funds that were mistakenly sent', () => { - const paid = 1 - beforeEach(async () => { - await link.transfer(operator.address, paid) - }) - - it('returns the correct value', async () => { - const withdrawAmount = await operator.withdrawable() - - const expectedAmount = amount.add(paid) - bigNumEquals(withdrawAmount, expectedAmount) - }) - }) - }) - - describe('#ownerTransferAndCall', () => { - let operator2: Contract - let args: string - let to: string - const startingBalance = 1000 - const payment = 20 - - beforeEach(async () => { - operator2 = await operatorFactory - .connect(roles.oracleNode2) - .deploy(link.address, await roles.oracleNode2.getAddress()) - to = operator2.address - args = encodeOracleRequest( - specId, - operator.address, - operatorFactory.interface.getSighash('fulfillOracleRequest'), - 1, - constants.HashZero, - ) - }) - - describe('when called by a non-owner', () => { - it('reverts with owner error message', async () => { - await link.transfer(operator.address, startingBalance) - await evmRevert( - operator - .connect(roles.stranger) - .ownerTransferAndCall(to, payment, args), - 'Only callable by owner', - ) - }) - }) - - describe('when called by the owner', () => { - beforeEach(async () => { - await link.transfer(operator.address, startingBalance) - }) - - describe('without sufficient funds in contract', () => { - it('reverts with funds message', async () => { - const tooMuch = startingBalance * 2 - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerTransferAndCall(to, tooMuch, args), - 'Amount requested is greater than withdrawable balance', - ) - }) - }) - - describe('with sufficient funds', () => { - let tx: ContractTransaction - let receipt: ContractReceipt - let requesterBalanceBefore: BigNumber - let requesterBalanceAfter: BigNumber - let receiverBalanceBefore: BigNumber - let receiverBalanceAfter: BigNumber - - before(async () => { - requesterBalanceBefore = await link.balanceOf(operator.address) - receiverBalanceBefore = await link.balanceOf(operator2.address) - tx = await operator - .connect(roles.defaultAccount) - .ownerTransferAndCall(to, payment, args) - receipt = await tx.wait() - requesterBalanceAfter = await link.balanceOf(operator.address) - receiverBalanceAfter = await link.balanceOf(operator2.address) - }) - - it('emits an event', async () => { - assert.equal(3, receipt.logs?.length) - const transferLog = await getLog(tx, 1) - const parsedLog = link.interface.parseLog({ - data: transferLog.data, - topics: transferLog.topics, - }) - await expect(parsedLog.name).to.equal('Transfer') - }) - - it('transfers the tokens', async () => { - bigNumEquals( - requesterBalanceBefore.sub(requesterBalanceAfter), - payment, - ) - bigNumEquals(receiverBalanceAfter.sub(receiverBalanceBefore), payment) - }) - }) - }) - }) - - describe('#cancelOracleRequestByRequester', () => { - const nonce = 17 - - describe('with no pending requests', () => { - it('fails', async () => { - const fakeRequest: RunRequest = { - requestId: ethers.utils.formatBytes32String('1337'), - payment: '0', - callbackFunc: - getterSetterFactory.interface.getSighash('requestedBytes32'), - expiration: '999999999999', - - callbackAddr: '', - data: Buffer.from(''), - dataVersion: 0, - specId: '', - requester: '', - topic: '', - } - await increaseTime5Minutes(ethers.provider) - - await evmRevert( - operator - .connect(roles.stranger) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(fakeRequest, nonce), - ), - ) - }) - }) - - describe('with a pending request', () => { - const startingBalance = 100 - let request: ReturnType - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const requestAmount = 20 - - await link.transfer(await roles.consumer.getAddress(), startingBalance) - - const args = encodeOracleRequest( - specId, - await roles.consumer.getAddress(), - fHash, - nonce, - constants.HashZero, - ) - const tx = await link - .connect(roles.consumer) - .transferAndCall(operator.address, requestAmount, args) - receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - - // pre conditions - const oracleBalance = await link.balanceOf(operator.address) - bigNumEquals(request.payment, oracleBalance) - - const consumerAmount = await link.balanceOf( - await roles.consumer.getAddress(), - ) - assert.equal( - startingBalance - Number(request.payment), - consumerAmount.toNumber(), - ) - }) - - describe('from a stranger', () => { - it('fails', async () => { - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ), - ) - }) - }) - - describe('from the requester', () => { - it('refunds the correct amount', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - const balance = await link.balanceOf( - await roles.consumer.getAddress(), - ) - - assert.equal(startingBalance, balance.toNumber()) // 100 - }) - - it('triggers a cancellation event', async () => { - await increaseTime5Minutes(ethers.provider) - const tx = await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - const receipt = await tx.wait() - - assert.equal(receipt.logs?.length, 2) - assert.equal(request.requestId, receipt.logs?.[0].topics[1]) - }) - - it('fails when called twice', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequestByRequester( - ...convertCancelByRequesterParams(request, nonce), - ) - - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequestByRequester(...convertCancelParams(request)), - ) - }) - }) - }) - }) - - describe('#cancelOracleRequest', () => { - describe('with no pending requests', () => { - it('fails', async () => { - const fakeRequest: RunRequest = { - requestId: ethers.utils.formatBytes32String('1337'), - payment: '0', - callbackFunc: - getterSetterFactory.interface.getSighash('requestedBytes32'), - expiration: '999999999999', - - callbackAddr: '', - data: Buffer.from(''), - dataVersion: 0, - specId: '', - requester: '', - topic: '', - } - await increaseTime5Minutes(ethers.provider) - - await evmRevert( - operator - .connect(roles.stranger) - .cancelOracleRequest(...convertCancelParams(fakeRequest)), - ) - }) - }) - - describe('with a pending request', () => { - const startingBalance = 100 - let request: ReturnType - let receipt: providers.TransactionReceipt - - beforeEach(async () => { - const requestAmount = 20 - - await link.transfer(await roles.consumer.getAddress(), startingBalance) - - const args = encodeOracleRequest( - specId, - await roles.consumer.getAddress(), - fHash, - 1, - constants.HashZero, - ) - const tx = await link - .connect(roles.consumer) - .transferAndCall(operator.address, requestAmount, args) - receipt = await tx.wait() - - assert.equal(3, receipt.logs?.length) - request = decodeRunRequest(receipt.logs?.[2]) - }) - - it('has correct initial balances', async () => { - const oracleBalance = await link.balanceOf(operator.address) - bigNumEquals(request.payment, oracleBalance) - - const consumerAmount = await link.balanceOf( - await roles.consumer.getAddress(), - ) - assert.equal( - startingBalance - Number(request.payment), - consumerAmount.toNumber(), - ) - }) - - describe('from a stranger', () => { - it('fails', async () => { - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)), - ) - }) - }) - - describe('from the requester', () => { - it('refunds the correct amount', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - const balance = await link.balanceOf( - await roles.consumer.getAddress(), - ) - - assert.equal(startingBalance, balance.toNumber()) // 100 - }) - - it('triggers a cancellation event', async () => { - await increaseTime5Minutes(ethers.provider) - const tx = await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - const receipt = await tx.wait() - - assert.equal(receipt.logs?.length, 2) - assert.equal(request.requestId, receipt.logs?.[0].topics[1]) - }) - - it('fails when called twice', async () => { - await increaseTime5Minutes(ethers.provider) - await operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)) - - await evmRevert( - operator - .connect(roles.consumer) - .cancelOracleRequest(...convertCancelParams(request)), - ) - }) - }) - }) - }) - - describe('#ownerForward', () => { - let bytes: string - let payload: string - let mock: Contract - - beforeEach(async () => { - bytes = ethers.utils.hexlify(ethers.utils.randomBytes(100)) - payload = getterSetterFactory.interface.encodeFunctionData( - getterSetterFactory.interface.getFunction('setBytes'), - [bytes], - ) - mock = await getterSetterFactory.connect(roles.defaultAccount).deploy() - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - operator.connect(roles.stranger).ownerForward(mock.address, payload), - ) - }) - }) - - describe('when called by owner', () => { - describe('when attempting to forward to the link token', () => { - it('reverts', async () => { - const sighash = linkTokenFactory.interface.getSighash('name') - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerForward(link.address, sighash), - 'Cannot call to LINK', - ) - }) - }) - - describe('when forwarding to any other address', () => { - it('forwards the data', async () => { - const tx = await operator - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - await tx.wait() - assert.equal(await mock.getBytes(), bytes) - }) - - it('reverts when sending to a non-contract address', async () => { - await evmRevert( - operator - .connect(roles.defaultAccount) - .ownerForward(zeroAddress, payload), - 'Must forward to a contract', - ) - }) - - it('perceives the message is sent by the Operator', async () => { - const tx = await operator - .connect(roles.defaultAccount) - .ownerForward(mock.address, payload) - const receipt = await tx.wait() - const log: any = receipt.logs?.[0] - const logData = mock.interface.decodeEventLog( - mock.interface.getEvent('SetBytes'), - log.data, - log.topics, - ) - assert.equal(ethers.utils.getAddress(logData.from), operator.address) - }) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/OperatorFactory.test.ts b/contracts/test/v0.7/OperatorFactory.test.ts deleted file mode 100644 index d2a24600e23..00000000000 --- a/contracts/test/v0.7/OperatorFactory.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { ethers } from 'hardhat' -import { evmWordToAddress, publicAbi } from '../test-helpers/helpers' -import { assert } from 'chai' -import { Contract, ContractFactory, ContractReceipt } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' - -let linkTokenFactory: ContractFactory -let operatorGeneratorFactory: ContractFactory -let operatorFactory: ContractFactory -let forwarderFactory: ContractFactory - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - operatorGeneratorFactory = await ethers.getContractFactory( - 'src/v0.7/OperatorFactory.sol:OperatorFactory', - roles.defaultAccount, - ) - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - forwarderFactory = await ethers.getContractFactory( - 'src/v0.7/AuthorizedForwarder.sol:AuthorizedForwarder', - roles.defaultAccount, - ) -}) - -describe('OperatorFactory', () => { - let link: Contract - let operatorGenerator: Contract - let operator: Contract - let forwarder: Contract - let receipt: ContractReceipt - let emittedOperator: string - let emittedForwarder: string - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - operatorGenerator = await operatorGeneratorFactory - .connect(roles.defaultAccount) - .deploy(link.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(operatorGenerator, [ - 'created', - 'deployNewOperator', - 'deployNewOperatorAndForwarder', - 'deployNewForwarder', - 'deployNewForwarderAndTransferOwnership', - 'getChainlinkToken', - 'typeAndVersion', - ]) - }) - - describe('#typeAndVersion', () => { - it('describes the authorized forwarder', async () => { - assert.equal( - await operatorGenerator.typeAndVersion(), - 'OperatorFactory 1.0.0', - ) - }) - }) - - describe('#deployNewOperator', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewOperator() - - receipt = await tx.wait() - emittedOperator = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[0]?.event, 'OperatorCreated') - assert.equal(emittedOperator, receipt.events?.[0].args?.[0]) - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[2], - ) - }) - - it('sets the correct owner', async () => { - operator = await operatorFactory - .connect(roles.defaultAccount) - .attach(emittedOperator) - const ownerString = await operator.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedOperator)) - }) - }) - - describe('#deployNewOperatorAndForwarder', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewOperatorAndForwarder() - - receipt = await tx.wait() - emittedOperator = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - emittedForwarder = evmWordToAddress(receipt.logs?.[3].topics?.[1]) - }) - - it('emits an event recording that the operator was deployed', async () => { - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal(receipt?.events?.[0]?.event, 'OperatorCreated') - assert.equal(receipt?.events?.[0]?.args?.[0], emittedOperator) - assert.equal( - receipt?.events?.[0]?.args?.[1], - await roles.oracleNode.getAddress(), - ) - assert.equal( - receipt?.events?.[0]?.args?.[2], - await roles.oracleNode.getAddress(), - ) - }) - - it('proposes the transfer of the forwarder to the operator', async () => { - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) - assert.equal( - receipt?.events?.[1]?.topics?.[0], - '0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278', //OwnershipTransferRequested(address,address) - ) - assert.equal( - evmWordToAddress(receipt?.events?.[1]?.topics?.[1]), - operatorGenerator.address, - ) - assert.equal( - evmWordToAddress(receipt?.events?.[1]?.topics?.[2]), - emittedOperator, - ) - - assert.equal( - receipt?.events?.[2]?.topics?.[0], - '0x4e1e878dc28d5f040db5969163ff1acd75c44c3f655da2dde9c70bbd8e56dc7e', //OwnershipTransferRequestedWithMessage(address,address,bytes) - ) - assert.equal( - evmWordToAddress(receipt?.events?.[2]?.topics?.[1]), - operatorGenerator.address, - ) - assert.equal( - evmWordToAddress(receipt?.events?.[2]?.topics?.[2]), - emittedOperator, - ) - }) - - it('emits an event recording that the forwarder was deployed', async () => { - assert.equal(receipt?.events?.[3]?.event, 'AuthorizedForwarderCreated') - assert.equal(receipt?.events?.[3]?.args?.[0], emittedForwarder) - assert.equal(receipt?.events?.[3]?.args?.[1], operatorGenerator.address) - assert.equal( - receipt?.events?.[3]?.args?.[2], - await roles.oracleNode.getAddress(), - ) - }) - - it('sets the correct owner on the operator', async () => { - operator = await operatorFactory - .connect(roles.defaultAccount) - .attach(receipt?.events?.[0]?.args?.[0]) - assert.equal(await roles.oracleNode.getAddress(), await operator.owner()) - }) - - it('sets the operator as the owner of the forwarder', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - assert.equal(operatorGenerator.address, await forwarder.owner()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedOperator)) - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) - - describe('#deployNewForwarder', () => { - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewForwarder() - - receipt = await tx.wait() - emittedForwarder = receipt.events?.[0].args?.[0] - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[0]?.event, 'AuthorizedForwarderCreated') - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[1], - ) // owner - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[0].args?.[2], - ) // sender - }) - - it('sets the caller as the owner', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - const ownerString = await forwarder.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) - - describe('#deployNewForwarderAndTransferOwnership', () => { - const message = '0x42' - - beforeEach(async () => { - const tx = await operatorGenerator - .connect(roles.oracleNode) - .deployNewForwarderAndTransferOwnership( - await roles.stranger.getAddress(), - message, - ) - receipt = await tx.wait() - - emittedForwarder = evmWordToAddress(receipt.logs?.[2].topics?.[1]) - }) - - it('emits an event', async () => { - assert.equal(receipt?.events?.[2]?.event, 'AuthorizedForwarderCreated') - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[2].args?.[1], - ) // owner - assert.equal( - await roles.oracleNode.getAddress(), - receipt.events?.[2].args?.[2], - ) // sender - }) - - it('sets the caller as the owner', async () => { - forwarder = await forwarderFactory - .connect(roles.defaultAccount) - .attach(emittedForwarder) - const ownerString = await forwarder.owner() - assert.equal(ownerString, await roles.oracleNode.getAddress()) - }) - - it('proposes a transfer to the recipient', async () => { - const emittedOwner = evmWordToAddress(receipt.logs?.[0].topics?.[1]) - assert.equal(emittedOwner, await roles.oracleNode.getAddress()) - const emittedRecipient = evmWordToAddress(receipt.logs?.[0].topics?.[2]) - assert.equal(emittedRecipient, await roles.stranger.getAddress()) - }) - - it('proposes a transfer to the recipient with the specified message', async () => { - const emittedOwner = evmWordToAddress(receipt.logs?.[1].topics?.[1]) - assert.equal(emittedOwner, await roles.oracleNode.getAddress()) - const emittedRecipient = evmWordToAddress(receipt.logs?.[1].topics?.[2]) - assert.equal(emittedRecipient, await roles.stranger.getAddress()) - - const encodedMessage = ethers.utils.defaultAbiCoder.encode( - ['bytes'], - [message], - ) - assert.equal(receipt?.logs?.[1]?.data, encodedMessage) - }) - - it('records that it deployed that address', async () => { - assert.isTrue(await operatorGenerator.created(emittedForwarder)) - }) - }) -}) diff --git a/contracts/test/v0.7/StalenessFlaggingValidator.test.ts b/contracts/test/v0.7/StalenessFlaggingValidator.test.ts deleted file mode 100644 index 8a5c4b67632..00000000000 --- a/contracts/test/v0.7/StalenessFlaggingValidator.test.ts +++ /dev/null @@ -1,632 +0,0 @@ -import { ethers } from 'hardhat' -import { - evmWordToAddress, - getLog, - getLogs, - numToBytes32, - publicAbi, -} from '../test-helpers/helpers' -import { assert, expect } from 'chai' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { Personas, getUsers } from '../test-helpers/setup' -import { evmRevert } from '../test-helpers/matchers' - -let personas: Personas -let validatorFactory: ContractFactory -let flagsFactory: ContractFactory -let acFactory: ContractFactory -let aggregatorFactory: ContractFactory - -before(async () => { - personas = (await getUsers()).personas - - validatorFactory = await ethers.getContractFactory( - 'src/v0.7/dev/StalenessFlaggingValidator.sol:StalenessFlaggingValidator', - personas.Carol, - ) - flagsFactory = await ethers.getContractFactory( - 'src/v0.6/Flags.sol:Flags', - personas.Carol, - ) - acFactory = await ethers.getContractFactory( - 'src/v0.6/SimpleWriteAccessController.sol:SimpleWriteAccessController', - personas.Carol, - ) - aggregatorFactory = await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - personas.Carol, - ) -}) - -describe('StalenessFlaggingValidator', () => { - let validator: Contract - let flags: Contract - let ac: Contract - - const flaggingThreshold1 = 10000 - const flaggingThreshold2 = 20000 - - beforeEach(async () => { - ac = await acFactory.connect(personas.Carol).deploy() - flags = await flagsFactory.connect(personas.Carol).deploy(ac.address) - validator = await validatorFactory - .connect(personas.Carol) - .deploy(flags.address) - - await ac.connect(personas.Carol).addAccess(validator.address) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(validator, [ - 'update', - 'check', - 'setThresholds', - 'setFlagsAddress', - 'threshold', - 'flags', - // Upkeep methods: - 'checkUpkeep', - 'performUpkeep', - // Owned methods: - 'acceptOwnership', - 'owner', - 'transferOwnership', - ]) - }) - - describe('#constructor', () => { - it('sets the arguments passed in', async () => { - assert.equal(await validator.flags(), flags.address) - }) - - it('sets the owner', async () => { - assert.equal(await validator.owner(), await personas.Carol.getAddress()) - }) - }) - - describe('#setFlagsAddress', () => { - const newFlagsAddress = '0x0123456789012345678901234567890123456789' - - it('changes the flags address', async () => { - assert.equal(flags.address, await validator.flags()) - - await validator.connect(personas.Carol).setFlagsAddress(newFlagsAddress) - - assert.equal(newFlagsAddress, await validator.flags()) - }) - - it('emits a log event only when actually changed', async () => { - const tx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsAddress) - await expect(tx) - .to.emit(validator, 'FlagsAddressUpdated') - .withArgs(flags.address, newFlagsAddress) - - const sameChangeTx = await validator - .connect(personas.Carol) - .setFlagsAddress(newFlagsAddress) - - await expect(sameChangeTx).to.not.emit(validator, 'FlagsAddressUpdated') - }) - - describe('when called by a non-owner', () => { - it('reverts', async () => { - await evmRevert( - validator.connect(personas.Neil).setFlagsAddress(newFlagsAddress), - 'Only callable by owner', - ) - }) - }) - }) - - describe('#setThresholds', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - - beforeEach(async () => { - const decimals = 8 - const initialAnswer = 10000000000 - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - }) - - describe('failure', () => { - beforeEach(() => { - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1] - }) - - it('reverts when called by a non-owner', async () => { - await evmRevert( - validator - .connect(personas.Neil) - .setThresholds(aggregators, thresholds), - 'Only callable by owner', - ) - }) - - it('reverts when passed uneven arrays', async () => { - await evmRevert( - validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds), - 'Different sized arrays', - ) - }) - }) - - describe('success', () => { - let tx: any - - beforeEach(() => { - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - }) - - describe('when called with 2 new thresholds', () => { - beforeEach(async () => { - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds) - }) - - it('sets the thresholds', async () => { - const first = await validator.threshold(agg1.address) - const second = await validator.threshold(agg2.address) - assert.equal(first.toString(), flaggingThreshold1.toString()) - assert.equal(second.toString(), flaggingThreshold2.toString()) - }) - - it('emits events', async () => { - const firstEvent = await getLog(tx, 0) - assert.equal(evmWordToAddress(firstEvent.topics[1]), agg1.address) - assert.equal(firstEvent.topics[3], numToBytes32(flaggingThreshold1)) - const secondEvent = await getLog(tx, 1) - assert.equal(evmWordToAddress(secondEvent.topics[1]), agg2.address) - assert.equal(secondEvent.topics[3], numToBytes32(flaggingThreshold2)) - }) - }) - - describe('when called with 2, but 1 has not changed', () => { - it('emits only 1 event', async () => { - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, thresholds) - - const newThreshold = flaggingThreshold2 + 1 - tx = await validator - .connect(personas.Carol) - .setThresholds(aggregators, [flaggingThreshold1, newThreshold]) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - const log = logs[0] - assert.equal(evmWordToAddress(log.topics[1]), agg2.address) - assert.equal(log.topics[2], numToBytes32(flaggingThreshold2)) - assert.equal(log.topics[3], numToBytes32(newThreshold)) - }) - }) - }) - }) - - describe('#check', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('returns an empty array', async () => { - const response = await validator.check(aggregators) - assert.equal(response.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('returns an empty array', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const response = await validator.check([agg3.address]) - assert.equal(response.length, 0) - }) - }) - - describe('when one of the aggregators is stale', () => { - it('returns an array with one stale aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - const response = await validator.check(aggregators) - - assert.equal(response.length, 1) - assert.equal(response[0], agg1.address) - }) - }) - - describe('When both aggregators are stale', () => { - it('returns an array with both aggregators', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const response = await validator.check(aggregators) - - assert.equal(response.length, 2) - assert.equal(response[0], agg1.address) - assert.equal(response[1], agg2.address) - }) - }) - }) - - describe('#update', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('does not raise a flag', async () => { - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('does not raise a flag', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const tx = await validator.update([agg3.address]) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when one is stale', () => { - it('raises a flag for that aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - }) - }) - - describe('when both are stale', () => { - it('raises 2 flags, one for each aggregator', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const tx = await validator.update(aggregators) - const logs = await getLogs(tx) - assert.equal(logs.length, 2) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - assert.equal(evmWordToAddress(logs[1].topics[1]), agg2.address) - }) - }) - }) - - describe('#checkUpkeep', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('returns false and an empty array', async () => { - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], false) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse[0].length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('returns flase and an empty array', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[agg3.address]], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], false) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - assert.equal(decodedResponse[0].length, 0) - }) - }) - - describe('when one of the aggregators is stale', () => { - it('returns true with an array with one stale aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], true) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - const decodedArray = decodedResponse[0] - assert.equal(decodedArray.length, 1) - assert.equal(decodedArray[0], agg1.address) - }) - }) - - describe('When both aggregators are stale', () => { - it('returns true with an array with both aggregators', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const response = await validator.checkUpkeep(bytesData) - - assert.equal(response[0], true) - const decodedResponse = ethers.utils.defaultAbiCoder.decode( - ['address[]'], - response?.[1], - ) - const decodedArray = decodedResponse[0] - assert.equal(decodedArray.length, 2) - assert.equal(decodedArray[0], agg1.address) - assert.equal(decodedArray[1], agg2.address) - }) - }) - }) - - describe('#performUpkeep', () => { - let agg1: Contract - let agg2: Contract - let aggregators: Array - let thresholds: Array - const decimals = 8 - const initialAnswer = 10000000000 - beforeEach(async () => { - agg1 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - agg2 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - aggregators = [agg1.address, agg2.address] - thresholds = [flaggingThreshold1, flaggingThreshold2] - await validator.setThresholds(aggregators, thresholds) - }) - - describe('when neither are stale', () => { - it('does not raise a flag', async () => { - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when threshold is not set in the validator', () => { - it('does not raise a flag', async () => { - const agg3 = await aggregatorFactory - .connect(personas.Carol) - .deploy(decimals, initialAnswer) - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [[agg3.address]], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - - describe('when one is stale', () => { - it('raises a flag for that aggregator', async () => { - const currentTimestamp = await agg1.latestTimestamp() - const staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 1) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - }) - }) - - describe('when both are stale', () => { - it('raises 2 flags, one for each aggregator', async () => { - let currentTimestamp = await agg1.latestTimestamp() - let staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold1 + 1), - ) - await agg1.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - currentTimestamp = await agg2.latestTimestamp() - staleTimestamp = currentTimestamp.sub( - BigNumber.from(flaggingThreshold2 + 1), - ) - await agg2.updateRoundData( - 99, - initialAnswer, - staleTimestamp, - staleTimestamp, - ) - - const bytesData = ethers.utils.defaultAbiCoder.encode( - ['address[]'], - [aggregators], - ) - const tx = await validator.performUpkeep(bytesData) - const logs = await getLogs(tx) - assert.equal(logs.length, 2) - assert.equal(evmWordToAddress(logs[0].topics[1]), agg1.address) - assert.equal(evmWordToAddress(logs[1].topics[1]), agg2.address) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts b/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts deleted file mode 100644 index 5ec9306c668..00000000000 --- a/contracts/test/v0.7/UpkeepRegistrationRequests.test.ts +++ /dev/null @@ -1,603 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { evmRevert } from '../test-helpers/matchers' -import { getUsers, Personas } from '../test-helpers/setup' -import { BigNumber, Signer } from 'ethers' -import { LinkToken__factory as LinkTokenFactory } from '../../typechain/factories/LinkToken__factory' -import { KeeperRegistry1_1__factory as KeeperRegistryFactory } from '../../typechain/factories/KeeperRegistry1_1__factory' -import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../typechain/factories/MockV3Aggregator__factory' -import { UpkeepRegistrationRequests__factory as UpkeepRegistrationRequestsFactory } from '../../typechain/factories/UpkeepRegistrationRequests__factory' -import { UpkeepMock__factory as UpkeepMockFactory } from '../../typechain/factories/UpkeepMock__factory' -import { KeeperRegistry1_1 as KeeperRegistry } from '../../typechain/KeeperRegistry1_1' -import { UpkeepRegistrationRequests } from '../../typechain/UpkeepRegistrationRequests' -import { MockV3Aggregator } from '../../typechain/MockV3Aggregator' -import { LinkToken } from '../../typechain/LinkToken' -import { UpkeepMock } from '../../typechain/UpkeepMock' - -let linkTokenFactory: LinkTokenFactory -let mockV3AggregatorFactory: MockV3AggregatorFactory -let keeperRegistryFactory: KeeperRegistryFactory -let upkeepRegistrationRequestsFactory: UpkeepRegistrationRequestsFactory -let upkeepMockFactory: UpkeepMockFactory - -let personas: Personas - -before(async () => { - personas = (await getUsers()).personas - - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - ) - mockV3AggregatorFactory = (await ethers.getContractFactory( - 'src/v0.7/tests/MockV3Aggregator.sol:MockV3Aggregator', - )) as unknown as MockV3AggregatorFactory - // @ts-ignore bug in autogen file - keeperRegistryFactory = await ethers.getContractFactory('KeeperRegistry1_1') - upkeepRegistrationRequestsFactory = await ethers.getContractFactory( - 'UpkeepRegistrationRequests', - ) - upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') -}) - -const errorMsgs = { - onlyOwner: 'revert Only callable by owner', - onlyAdmin: 'only admin / owner can cancel', - hashPayload: 'hash and payload do not match', - requestNotFound: 'request not found', -} - -describe('UpkeepRegistrationRequests', () => { - const upkeepName = 'SampleUpkeep' - - const linkEth = BigNumber.from(300000000) - const gasWei = BigNumber.from(100) - const executeGas = BigNumber.from(100000) - const source = BigNumber.from(100) - const paymentPremiumPPB = BigNumber.from(250000000) - const flatFeeMicroLink = BigNumber.from(0) - - const window_big = BigNumber.from(1000) - const window_small = BigNumber.from(2) - const threshold_big = BigNumber.from(1000) - const threshold_small = BigNumber.from(5) - - const blockCountPerTurn = BigNumber.from(3) - const emptyBytes = '0x00' - const stalenessSeconds = BigNumber.from(43820) - const gasCeilingMultiplier = BigNumber.from(1) - const maxCheckGas = BigNumber.from(20000000) - const fallbackGasPrice = BigNumber.from(200) - const fallbackLinkPrice = BigNumber.from(200000000) - const minLINKJuels = BigNumber.from('1000000000000000000') - const amount = BigNumber.from('5000000000000000000') - const amount1 = BigNumber.from('6000000000000000000') - - let owner: Signer - let admin: Signer - let someAddress: Signer - let registrarOwner: Signer - let stranger: Signer - - let linkToken: LinkToken - let linkEthFeed: MockV3Aggregator - let gasPriceFeed: MockV3Aggregator - let registry: KeeperRegistry - let mock: UpkeepMock - let registrar: UpkeepRegistrationRequests - - beforeEach(async () => { - owner = personas.Default - admin = personas.Neil - someAddress = personas.Ned - registrarOwner = personas.Nelly - stranger = personas.Nancy - - linkToken = await linkTokenFactory.connect(owner).deploy() - gasPriceFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(0, gasWei) - linkEthFeed = await mockV3AggregatorFactory - .connect(owner) - .deploy(9, linkEth) - registry = await keeperRegistryFactory - .connect(owner) - .deploy( - linkToken.address, - linkEthFeed.address, - gasPriceFeed.address, - paymentPremiumPPB, - flatFeeMicroLink, - blockCountPerTurn, - maxCheckGas, - stalenessSeconds, - gasCeilingMultiplier, - fallbackGasPrice, - fallbackLinkPrice, - ) - - mock = await upkeepMockFactory.deploy() - - registrar = await upkeepRegistrationRequestsFactory - .connect(registrarOwner) - .deploy(linkToken.address, minLINKJuels) - - await registry.setRegistrar(registrar.address) - }) - - describe('#typeAndVersion', () => { - it('uses the correct type and version', async () => { - const typeAndVersion = await registrar.typeAndVersion() - assert.equal(typeAndVersion, 'UpkeepRegistrationRequests 1.0.0') - }) - }) - - describe('#register', () => { - it('reverts if not called by the LINK token', async () => { - await evmRevert( - registrar - .connect(someAddress) - .register( - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ), - 'Must use LINK token', - ) - }) - - it('reverts if the amount passed in data mismatches actual amount sent', async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount1, - source, - ], - ) - - await evmRevert( - linkToken.transferAndCall(registrar.address, amount, abiEncodedBytes), - 'Amount mismatch', - ) - }) - - it('reverts if the admin address is 0x0000...', async () => { - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - '0x0000000000000000000000000000000000000000', - emptyBytes, - amount, - source, - ], - ) - - await evmRevert( - linkToken.transferAndCall(registrar.address, amount, abiEncodedBytes), - 'Unable to create request', - ) - }) - - it('Auto Approve ON - registers an upkeep on KeeperRegistry instantly and emits both RegistrationRequested and RegistrationApproved events', async () => { - //get current upkeep count - const upkeepCount = await registry.getUpkeepCount() - - //set auto approve ON with high threshold limits - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve ON - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - - //confirm if a new upkeep has been registered and the details are the same as the one just registered - const newupkeep = await registry.getUpkeep(upkeepCount) - assert.equal(newupkeep.target, mock.address) - assert.equal(newupkeep.admin, await admin.getAddress()) - assert.equal(newupkeep.checkData, emptyBytes) - assert.equal(newupkeep.balance.toString(), amount.toString()) - assert.equal(newupkeep.executeGas, executeGas.toNumber()) - - await expect(tx).to.emit(registrar, 'RegistrationRequested') - await expect(tx).to.emit(registrar, 'RegistrationApproved') - }) - - it('Auto Approve OFF - does not registers an upkeep on KeeperRegistry, emits only RegistrationRequested event', async () => { - //get upkeep count before attempting registration - const beforeCount = await registry.getUpkeepCount() - - //set auto approve OFF, threshold limits dont matter in this case - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - - //get upkeep count after attempting registration - const afterCount = await registry.getUpkeepCount() - //confirm that a new upkeep has NOT been registered and upkeep count is still the same - assert.deepEqual(beforeCount, afterCount) - - //confirm that only RegistrationRequested event is emitted and RegistrationApproved event is not - await expect(tx).to.emit(registrar, 'RegistrationRequested') - await expect(tx).not.to.emit(registrar, 'RegistrationApproved') - - const hash = receipt.logs[2].topics[1] - const pendingRequest = await registrar.getPendingRequest(hash) - assert.equal(await admin.getAddress(), pendingRequest[0]) - assert.ok(amount.eq(pendingRequest[1])) - }) - - it('Auto Approve ON - Throttle max approvals - does not registers an upkeep on KeeperRegistry beyond the throttle limit, emits only RegistrationRequested event after throttle starts', async () => { - //get upkeep count before attempting registration - const beforeCount = await registry.getUpkeepCount() - - //set auto approve on, with low threshold limits - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - true, - window_big, - threshold_small, - registry.address, - minLINKJuels, - ) - - let abiEncodedBytes = registrar.interface.encodeFunctionData('register', [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ]) - - //register within threshold, new upkeep should be registered - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const intermediateCount = await registry.getUpkeepCount() - //make sure 1 upkeep was registered - assert.equal(beforeCount.toNumber() + 1, intermediateCount.toNumber()) - - //try registering more than threshold(say 2x), new upkeeps should not be registered after the threshold amount is reached - for (let step = 0; step < threshold_small.toNumber() * 2; step++) { - abiEncodedBytes = registrar.interface.encodeFunctionData('register', [ - upkeepName, - emptyBytes, - mock.address, - executeGas.toNumber() + step, // make unique hash - await admin.getAddress(), - emptyBytes, - amount, - source, - ]) - - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - } - const afterCount = await registry.getUpkeepCount() - //count of newly registered upkeeps should be equal to the threshold set for auto approval - const newRegistrationsCount = - afterCount.toNumber() - beforeCount.toNumber() - assert( - newRegistrationsCount == threshold_small.toNumber(), - 'Registrations beyond threshold', - ) - }) - }) - - describe('#approve', () => { - let hash: string - - beforeEach(async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - hash = receipt.logs[2].topics[1] - }) - - it('reverts if not called by the owner', async () => { - const tx = registrar - .connect(stranger) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, 'Only callable by owner') - }) - - it('reverts if the hash does not exist', async () => { - const tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - '0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44', - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - - it('reverts if any member of the payload changes', async () => { - let tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - ethers.Wallet.createRandom().address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - 10000, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - ethers.Wallet.createRandom().address, - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - '0x1234', - hash, - ) - await evmRevert(tx, errorMsgs.hashPayload) - }) - - it('approves an existing registration request', async () => { - const tx = await registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await expect(tx).to.emit(registrar, 'RegistrationApproved') - }) - - it('deletes the request afterwards / reverts if the request DNE', async () => { - await registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - const tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - }) - - describe('#cancel', () => { - let hash: string - - beforeEach(async () => { - await registrar - .connect(registrarOwner) - .setRegistrationConfig( - false, - window_small, - threshold_big, - registry.address, - minLINKJuels, - ) - - //register with auto approve OFF - const abiEncodedBytes = registrar.interface.encodeFunctionData( - 'register', - [ - upkeepName, - emptyBytes, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - amount, - source, - ], - ) - const tx = await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - const receipt = await tx.wait() - hash = receipt.logs[2].topics[1] - // submit duplicate request (increase balance) - await linkToken.transferAndCall( - registrar.address, - amount, - abiEncodedBytes, - ) - }) - - it('reverts if not called by the admin / owner', async () => { - const tx = registrar.connect(stranger).cancel(hash) - await evmRevert(tx, errorMsgs.onlyAdmin) - }) - - it('reverts if the hash does not exist', async () => { - const tx = registrar - .connect(registrarOwner) - .cancel( - '0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44', - ) - await evmRevert(tx, 'request not found') - }) - - it('refunds the total request balance to the admin address', async () => { - const before = await linkToken.balanceOf(await admin.getAddress()) - const tx = await registrar.connect(admin).cancel(hash) - const after = await linkToken.balanceOf(await admin.getAddress()) - assert.isTrue(after.sub(before).eq(amount.mul(BigNumber.from(2)))) - await expect(tx).to.emit(registrar, 'RegistrationRejected') - }) - - it('deletes the request hash', async () => { - await registrar.connect(registrarOwner).cancel(hash) - let tx = registrar.connect(registrarOwner).cancel(hash) - await evmRevert(tx, errorMsgs.requestNotFound) - tx = registrar - .connect(registrarOwner) - .approve( - upkeepName, - mock.address, - executeGas, - await admin.getAddress(), - emptyBytes, - hash, - ) - await evmRevert(tx, errorMsgs.requestNotFound) - }) - }) -}) diff --git a/contracts/test/v0.7/VRFD20.test.ts b/contracts/test/v0.7/VRFD20.test.ts deleted file mode 100644 index f1e0e9ab0a8..00000000000 --- a/contracts/test/v0.7/VRFD20.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { - BigNumber, - constants, - Contract, - ContractFactory, - ContractTransaction, -} from 'ethers' -import { getUsers, Personas, Roles } from '../test-helpers/setup' -import { - evmWordToAddress, - getLog, - publicAbi, - toBytes32String, - toWei, - numToBytes32, - getLogs, -} from '../test-helpers/helpers' - -let roles: Roles -let personas: Personas -let linkTokenFactory: ContractFactory -let vrfCoordinatorMockFactory: ContractFactory -let vrfD20Factory: ContractFactory - -before(async () => { - const users = await getUsers() - - roles = users.roles - personas = users.personas - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) - vrfCoordinatorMockFactory = await ethers.getContractFactory( - 'src/v0.7/tests/VRFCoordinatorMock.sol:VRFCoordinatorMock', - roles.defaultAccount, - ) - vrfD20Factory = await ethers.getContractFactory( - 'src/v0.6/examples/VRFD20.sol:VRFD20', - roles.defaultAccount, - ) -}) - -describe('VRFD20', () => { - const deposit = toWei('1') - const fee = toWei('0.1') - const keyHash = toBytes32String('keyHash') - - let link: Contract - let vrfCoordinator: Contract - let vrfD20: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - vrfCoordinator = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - vrfD20 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await link.transfer(vrfD20.address, deposit) - }) - - it('has a limited public interface [ @skip-coverage ]', () => { - publicAbi(vrfD20, [ - // Owned - 'acceptOwnership', - 'owner', - 'transferOwnership', - //VRFConsumerBase - 'rawFulfillRandomness', - // VRFD20 - 'rollDice', - 'house', - 'withdrawLINK', - 'keyHash', - 'fee', - 'setKeyHash', - 'setFee', - ]) - }) - - describe('#withdrawLINK', () => { - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .withdrawLINK(await roles.stranger.getAddress(), deposit), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when not enough LINK in the contract', async () => { - const withdrawAmount = deposit.mul(2) - await expect( - vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK( - await roles.defaultAccount.getAddress(), - withdrawAmount, - ), - ).to.be.reverted - }) - }) - - describe('success', () => { - it('withdraws LINK', async () => { - const startingAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - const expectedAmount = BigNumber.from(startingAmount).add(deposit) - await vrfD20 - .connect(roles.defaultAccount) - .withdrawLINK(await roles.defaultAccount.getAddress(), deposit) - const actualAmount = await link.balanceOf( - await roles.defaultAccount.getAddress(), - ) - assert.equal(actualAmount.toString(), expectedAmount.toString()) - }) - }) - }) - - describe('#setKeyHash', () => { - const newHash = toBytes32String('newhash') - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setKeyHash(newHash), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the key hash', async () => { - await vrfD20.setKeyHash(newHash) - const actualHash = await vrfD20.keyHash() - assert.equal(actualHash, newHash) - }) - }) - }) - - describe('#setFee', () => { - const newFee = 1234 - - describe('failure', () => { - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20.connect(roles.stranger).setFee(newFee), - ).to.be.revertedWith('Only callable by owner') - }) - }) - - describe('success', () => { - it('sets the fee', async () => { - await vrfD20.setFee(newFee) - const actualFee = await vrfD20.fee() - assert.equal(actualFee.toString(), newFee.toString()) - }) - }) - }) - - describe('#house', () => { - describe('failure', () => { - it('reverts when dice not rolled', async () => { - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Dice not rolled') - }) - - it('reverts when dice roll is in progress', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.house(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Roll in progress') - }) - }) - - describe('success', () => { - it('returns the correct house', async () => { - const randomness = 98765 - const expectedHouse = 'Martell' - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - const eventRequestId = log?.topics?.[1] - await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - }) - }) - - describe('#rollDice', () => { - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - }) - - it('emits a RandomnessRequest event from the VRFCoordinator', async () => { - const log = await getLog(tx, 2) - const topics = log?.topics - assert.equal(evmWordToAddress(topics?.[1]), vrfD20.address) - assert.equal(topics?.[2], keyHash) - assert.equal(topics?.[3], constants.HashZero) - }) - }) - - describe('failure', () => { - it('reverts when LINK balance is zero', async () => { - const vrfD202 = await vrfD20Factory - .connect(roles.defaultAccount) - .deploy(vrfCoordinator.address, link.address, keyHash, fee) - await expect( - vrfD202.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Not enough LINK to pay fee') - }) - - it('reverts when called by a non-owner', async () => { - await expect( - vrfD20 - .connect(roles.stranger) - .rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Only callable by owner') - }) - - it('reverts when the roller rolls more than once', async () => { - await vrfD20.rollDice(await personas.Nancy.getAddress()) - await expect( - vrfD20.rollDice(await personas.Nancy.getAddress()), - ).to.be.revertedWith('Already rolled') - }) - }) - }) - - describe('#fulfillRandomness', () => { - const randomness = 98765 - const expectedModResult = (randomness % 20) + 1 - const expectedHouse = 'Martell' - let eventRequestId: string - beforeEach(async () => { - const tx = await vrfD20.rollDice(await personas.Nancy.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - }) - - describe('success', () => { - let tx: ContractTransaction - beforeEach(async () => { - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - }) - - it('emits a DiceLanded event', async () => { - const log = await getLog(tx, 0) - assert.equal(log?.topics[1], eventRequestId) - assert.equal(log?.topics[2], numToBytes32(expectedModResult)) - }) - - it('sets the correct dice roll result', async () => { - const response = await vrfD20.house(await personas.Nancy.getAddress()) - assert.equal(response.toString(), expectedHouse) - }) - - it('allows someone else to roll', async () => { - const secondRandomness = 55555 - tx = await vrfD20.rollDice(await personas.Ned.getAddress()) - const log = await getLog(tx, 3) - eventRequestId = log?.topics?.[1] - tx = await vrfCoordinator.callBackWithRandomness( - eventRequestId, - secondRandomness, - vrfD20.address, - ) - }) - }) - - describe('failure', () => { - it('does not fulfill when fulfilled by the wrong VRFcoordinator', async () => { - const vrfCoordinator2 = await vrfCoordinatorMockFactory - .connect(roles.defaultAccount) - .deploy(link.address) - - const tx = await vrfCoordinator2.callBackWithRandomness( - eventRequestId, - randomness, - vrfD20.address, - ) - const logs = await getLogs(tx) - assert.equal(logs.length, 0) - }) - }) - }) -}) diff --git a/contracts/test/v0.7/gasUsage.test.ts b/contracts/test/v0.7/gasUsage.test.ts deleted file mode 100644 index 97146622d06..00000000000 --- a/contracts/test/v0.7/gasUsage.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { ethers } from 'hardhat' -import { toBytes32String, toWei } from '../test-helpers/helpers' -import { Contract, ContractFactory } from 'ethers' -import { getUsers, Roles } from '../test-helpers/setup' -import { - convertFufillParams, - convertFulfill2Params, - decodeRunRequest, -} from '../test-helpers/oracle' -import { gasDiffLessThan } from '../test-helpers/matchers' - -let operatorFactory: ContractFactory -let oracleFactory: ContractFactory -let basicConsumerFactory: ContractFactory -let linkTokenFactory: ContractFactory - -let roles: Roles - -before(async () => { - const users = await getUsers() - - roles = users.roles - operatorFactory = await ethers.getContractFactory( - 'src/v0.7/Operator.sol:Operator', - roles.defaultAccount, - ) - oracleFactory = await ethers.getContractFactory( - 'src/v0.6/Oracle.sol:Oracle', - roles.defaultAccount, - ) - basicConsumerFactory = await ethers.getContractFactory( - 'src/v0.6/tests/BasicConsumer.sol:BasicConsumer', - roles.defaultAccount, - ) - linkTokenFactory = await ethers.getContractFactory( - 'src/v0.4/LinkToken.sol:LinkToken', - roles.defaultAccount, - ) -}) - -describe('Operator Gas Tests [ @skip-coverage ]', () => { - const specId = - '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' - let link: Contract - let oracle1: Contract - let operator1: Contract - let operator2: Contract - - beforeEach(async () => { - link = await linkTokenFactory.connect(roles.defaultAccount).deploy() - - operator1 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - await operator1.setAuthorizedSenders([await roles.oracleNode.getAddress()]) - - operator2 = await operatorFactory - .connect(roles.defaultAccount) - .deploy(link.address, await roles.defaultAccount.getAddress()) - await operator2.setAuthorizedSenders([await roles.oracleNode.getAddress()]) - - oracle1 = await oracleFactory - .connect(roles.defaultAccount) - .deploy(link.address) - await oracle1.setFulfillmentPermission( - await roles.oracleNode.getAddress(), - true, - ) - }) - - // Test Oracle.fulfillOracleRequest vs Operator.fulfillOracleRequest - describe('v0.6/Oracle vs v0.7/Operator #fulfillOracleRequest', () => { - const response = 'Hi Mom!' - let basicConsumer1: Contract - let basicConsumer2: Contract - - let request1: ReturnType - let request2: ReturnType - - beforeEach(async () => { - basicConsumer1 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, oracle1.address, specId) - basicConsumer2 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator1.address, specId) - - const paymentAmount = toWei('1') - const currency = 'USD' - - await link.transfer(basicConsumer1.address, paymentAmount) - const tx1 = await basicConsumer1.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt1 = await tx1.wait() - request1 = decodeRunRequest(receipt1.logs?.[3]) - - await link.transfer(basicConsumer2.address, paymentAmount) - const tx2 = await basicConsumer2.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('uses acceptable gas', async () => { - const tx1 = await oracle1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request1, response)) - const tx2 = await operator1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request2, response)) - const receipt1 = await tx1.wait() - const receipt2 = await tx2.wait() - // 38014 vs 40260 - gasDiffLessThan(3900, receipt1, receipt2) - }) - }) - - // Test Operator1.fulfillOracleRequest vs Operator2.fulfillOracleRequest2 - // with single word response - describe('Operator #fulfillOracleRequest vs #fulfillOracleRequest2', () => { - const response = 'Hi Mom!' - let basicConsumer1: Contract - let basicConsumer2: Contract - - let request1: ReturnType - let request2: ReturnType - - beforeEach(async () => { - basicConsumer1 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator1.address, specId) - basicConsumer2 = await basicConsumerFactory - .connect(roles.consumer) - .deploy(link.address, operator2.address, specId) - - const paymentAmount = toWei('1') - const currency = 'USD' - - await link.transfer(basicConsumer1.address, paymentAmount) - const tx1 = await basicConsumer1.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt1 = await tx1.wait() - request1 = decodeRunRequest(receipt1.logs?.[3]) - - await link.transfer(basicConsumer2.address, paymentAmount) - const tx2 = await basicConsumer2.requestEthereumPrice( - currency, - paymentAmount, - ) - const receipt2 = await tx2.wait() - request2 = decodeRunRequest(receipt2.logs?.[3]) - }) - - it('uses acceptable gas', async () => { - const tx1 = await operator1 - .connect(roles.oracleNode) - .fulfillOracleRequest(...convertFufillParams(request1, response)) - - const responseTypes = ['bytes32'] - const responseValues = [toBytes32String(response)] - const tx2 = await operator2 - .connect(roles.oracleNode) - .fulfillOracleRequest2( - ...convertFulfill2Params(request2, responseTypes, responseValues), - ) - - const receipt1 = await tx1.wait() - const receipt2 = await tx2.wait() - gasDiffLessThan(1240, receipt1, receipt2) - }) - }) -})