diff --git a/.gas-snapshot b/.gas-snapshot new file mode 100644 index 00000000..e2f7b875 --- /dev/null +++ b/.gas-snapshot @@ -0,0 +1,498 @@ +Account4337Test:test_addDeposit_creditsTheEntryPointRatherThanTheWallet() (gas: 970904) +Account4337Test:test_executeBatch_acceptsAnEmptyBatch() (gas: 926272) +Account4337Test:test_executeBatch_rejectsAnArbitraryCaller() (gas: 931666) +Account4337Test:test_executeBatch_revertsTheWholeBatchOnOneFailedCall() (gas: 1020821) +Account4337Test:test_executeBatch_runsEveryCall() (gas: 1003725) +Account4337Test:test_execute_passesBackTheTargetsRevertReason() (gas: 977646) +Account4337Test:test_execute_rejectsAnArbitraryCaller() (gas: 930957) +Account4337Test:test_execute_rejectsTheESIMWalletAdmin() (gas: 929039) +Account4337Test:test_isValidSignature_rejectsASignatureTooShortToParse() (gas: 924165) +Account4337Test:test_isValidSignature_rejectsAnUnknownSignatureVersion() (gas: 924525) +Account4337Test:test_validateUserOp_forwardsTheMissingPrefundToTheEntryPoint() (gas: 963010) +Account4337Test:test_validateUserOp_rejectsACallerOtherThanTheEntryPoint() (gas: 928679) +Account4337Test:test_validateUserOp_rejectsAnUnknownSignatureVersion() (gas: 928773) +Account4337Test:test_withdrawDepositTo_movesExactlyTheAmountWithdrawn() (gas: 1007530) +Account4337Test:test_withdrawDepositTo_rejectsACallerOtherThanTheWallet() (gas: 966467) +Account4337Test:test_withdrawDepositTo_rejectsAnAmountAboveTheDeposit() (gas: 970173) +Account4337Test:test_withdrawDepositTo_rejectsTheZeroAddress() (gas: 964277) +AdminRotationTest:test_acceptAdminUpdate() (gas: 49430) +AdminRotationTest:test_acceptAdminUpdate_afterRevoke() (gas: 50757) +AdminRotationTest:test_acceptAdminUpdate_clearsASuspension() (gas: 66071) +AdminRotationTest:test_acceptAdminUpdate_currentAdmin() (gas: 59726) +AdminRotationTest:test_acceptAdminUpdate_reachesEveryReader() (gas: 68090) +AdminRotationTest:test_acceptAdminUpdate_withoutRequest() (gas: 19775) +AdminRotationTest:test_disableAdmin_closesEveryGate() (gas: 1176230) +AdminRotationTest:test_disableAdmin_endsThePauseLoopAgainstACompromisedKey() (gas: 108885) +AdminRotationTest:test_disableAdmin_rejectsARepeat() (gas: 46606) +AdminRotationTest:test_disableAdmin_rejectsAnyoneButTheOwner() (gas: 38679) +AdminRotationTest:test_enableAdmin_leavesAnOutstandingHandoverAlone() (gas: 306061) +AdminRotationTest:test_enableAdmin_rejectsAnyoneButTheOwner() (gas: 56390) +AdminRotationTest:test_enableAdmin_rejectsWhenNotDisabled() (gas: 21023) +AdminRotationTest:test_enableAdmin_restoresTheGates() (gas: 305317) +AdminRotationTest:test_requestAdminUpdate() (gas: 54277) +AdminRotationTest:test_requestAdminUpdate_namingTheIncumbentLiftsASuspension() (gas: 40533) +AdminRotationTest:test_requestAdminUpdate_namingTheIncumbentRestoresItsPowers() (gas: 307940) +AdminRotationTest:test_requestAdminUpdate_rejectsAnyoneButTheOwner() (gas: 45792) +AdminRotationTest:test_requestAdminUpdate_revoke() (gas: 46417) +AdminRotationTest:test_requestAdminUpdate_stripsTheIncumbentImmediately() (gas: 1228348) +AdminRotationTest:test_requestAdminUpdate_zeroAddress() (gas: 19568) +AdminStorageLayoutTest:test_layout_aDoneOperationNeverReadsReady() (gas: 8042) +AdminStorageLayoutTest:test_layout_accessControlRolesReadSlotZero() (gas: 16215) +AdminStorageLayoutTest:test_layout_minDelayReadsSlotTwo() (gas: 7157) +AdminStorageLayoutTest:test_layout_operationTimestampsReadSlotOne() (gas: 7609) +AdminStorageLayoutTest:test_layout_readinessComesOnlyFromTheTimestamp() (gas: 8985) +AdminStorageLayoutTest:test_layout_theContractAddsNoStorageOfItsOwn() (gas: 7842) +AdminStorageLayoutTest:test_layout_theDelayFloorIsNotInStorage() (gas: 8675) +AdminStorageLayoutTest:test_layout_theGuardianRoleLivesInTheSameMappingAsTheRest() (gas: 17447) +AdminUpgradesTest:test_upgrade_beaconRejectsTheFormerOwnerOfTheFactory() (gas: 2200830) +AdminUpgradesTest:test_upgrade_canBeCancelledBeforeTheDelayExpires() (gas: 3686603) +AdminUpgradesTest:test_upgrade_deviceWalletBeaconReachesExistingWallets() (gas: 3688312) +AdminUpgradesTest:test_upgrade_eSIMWalletBeaconReachesExistingWallets() (gas: 2272470) +AdminUpgradesTest:test_upgrade_hasNoRouteThatSkipsTheDelay() (gas: 12289812) +AdminUpgradesTest:test_upgrade_lazyWalletRegistryThroughTheDelay() (gas: 3582065) +AdminUpgradesTest:test_upgrade_movesAllFourSingletonsInOneOperation() (gas: 12339027) +AdminUpgradesTest:test_upgrade_movesBothBeaconsInOneOperation() (gas: 5407536) +AdminUpgradesTest:test_upgrade_registryThroughTheDelay() (gas: 3719827) +AdminUpgradesTest:test_upgrade_rejectsAGuardianActingDirectly() (gas: 3659697) +AdminUpgradesTest:test_upgrade_rejectsAProposerActingDirectly() (gas: 3659719) +AdminUpgradesTest:test_upgrade_rejectsTheAccountThatUsedToOwnTheProxy() (gas: 3660088) +Deployer:test_deviceWalletFactory_ownable2Step() (gas: 44710) +Deployer:test_deviceWalletFactory_setUp() (gas: 53739) +Deployer:test_eSIMWalletFactory_ownable2Step() (gas: 44672) +Deployer:test_eSIMWalletFactory_setUp() (gas: 27298) +Deployer:test_lazyWalletRegistry_setUp() (gas: 20953) +Deployer:test_lazywalletRegistry_ownable2Step() (gas: 45678) +Deployer:test_registry_ownable2step() (gas: 47180) +Deployer:test_registry_setUp() (gas: 54769) +DeviceWalletESIMWalletsTest:test_addESIMWallet_afterRemoveESIMWallet_andETHCallback() (gas: 3662433) +DeviceWalletESIMWalletsTest:test_addESIMWallet_alreadyOwnedBySelf() (gas: 3398968) +DeviceWalletESIMWalletsTest:test_addESIMWallet_clearsTheStandbyMarkerRaisedByTheRelease() (gas: 3456974) +DeviceWalletESIMWalletsTest:test_addESIMWallet_unauthorised() (gas: 3400482) +DeviceWalletESIMWalletsTest:test_addESIMWallet_withoutTransferringOwnership() (gas: 3404711) +DeviceWalletESIMWalletsTest:test_bindESIMWallet_rejectsABindWhileOwnershipTransferIsPending() (gas: 3436728) +DeviceWalletESIMWalletsTest:test_bindESIMWallet_rejectsTheZeroAddress() (gas: 3406233) +DeviceWalletESIMWalletsTest:test_deployESIMWallet() (gas: 3393672) +DeviceWalletESIMWalletsTest:test_removeESIMWallet() (gas: 3409267) +DeviceWalletESIMWalletsTest:test_removeESIMWallet_keepsTheRegistrationAndStillAcceptsHistory() (gas: 3478416) +DeviceWalletESIMWalletsTest:test_removeESIMWallet_noETHToCallBack() (gas: 3399374) +DeviceWalletESIMWalletsTest:test_removeESIMWallet_reentrantCallbackCannotPullETH() (gas: 3694935) +DeviceWalletESIMWalletsTest:test_removeESIMWallet_siblingCannotRemoveAnother() (gas: 3395301) +DeviceWalletESIMWalletsTest:test_removeESIMWallet_unauthorised() (gas: 3400421) +DeviceWalletESIMWalletsTest:test_setESIMUniqueIdentifierForAnESIMWallet() (gas: 3466501) +DeviceWalletESIMWalletsTest:test_setESIMUniqueIdentifierForAnESIMWallet_deviceWallet() (gas: 3402811) +DeviceWalletESIMWalletsTest:test_setESIMUniqueIdentifierForAnESIMWallet_empty() (gas: 3405056) +DeviceWalletESIMWalletsTest:test_toggleESIMWalletStandbyStatus_letsTheOutgoingDeviceWalletLowerTheMarker() (gas: 3423622) +DeviceWalletESIMWalletsTest:test_toggleESIMWalletStandbyStatus_rejectsAReleaseFromADeviceWalletThatDoesNotHoldIt() (gas: 3408676) +DeviceWalletESIMWalletsTest:test_transferESIMWallet_frontrun() (gas: 3661843) +DeviceWalletETHTest:test_aFreshESIMWalletStartsWithNoETHAccess() (gas: 1270018) +DeviceWalletETHTest:test_addESIMWallet_cannotGrantETHAccessEvenFromTheWalletItself() (gas: 3398991) +DeviceWalletETHTest:test_deployESIMWallet_cannotGrantETHAccess() (gas: 3690079) +DeviceWalletETHTest:test_deviceWallet_rejectsACallToAFunctionItDoesNotHave() (gas: 3407752) +DeviceWalletETHTest:test_execute_stillMovesOwnerETHWhilePaused() (gas: 3460421) +DeviceWalletETHTest:test_getVaultAddress() (gas: 3401104) +DeviceWalletETHTest:test_getVaultAddress_followsTheRegistry() (gas: 3423385) +DeviceWalletETHTest:test_pullETH() (gas: 3410460) +DeviceWalletETHTest:test_pullETH_revertsWhilePaused() (gas: 3427111) +DeviceWalletETHTest:test_pullETH_revokedESIMWallet() (gas: 3400800) +DeviceWalletETHTest:test_pullETH_unauthorise() (gas: 3402500) +DeviceWalletETHTest:test_theAdminCannotUndoARevocationByDeployingAnotherWallet() (gas: 4062257) +DeviceWalletETHTest:test_theOwnerGrantsAfterBinding() (gas: 3920075) +DeviceWalletETHTest:test_toggleAccessToETH_grant_deviceWalletHasETH() (gas: 3569086) +DeviceWalletETHTest:test_toggleAccessToETH_grant_userHasETH() (gas: 3562740) +DeviceWalletETHTest:test_toggleAccessToETH_revoke_deviceWalletHasETH() (gas: 3405713) +DeviceWalletETHTest:test_toggleAccessToETH_revoke_eSIMWalletHasETH() (gas: 3514913) +DeviceWalletETHTest:test_toggleAccessToETH_revoke_userHasETH() (gas: 3522786) +DeviceWalletETHTest:test_toggleAccessToETH_unauthorised() (gas: 3401910) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers() (gas: 4550224) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_bindsAnESIMWalletToAnAlreadyRegisteredWallet() (gas: 936616) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_depositsAndRefundsExactly() (gas: 960588) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_existingIdentifierRefundsItsDeposit() (gas: 977295) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_rejectsAReplayOfTheSameEntry() (gas: 931587) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_rejectsARetryUnderANewSalt() (gas: 930178) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_survivesCreateAccountFrontRun() (gas: 946034) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_withoutAdminOrRegistry() (gas: 76760) +DeviceWalletFactoryBatchDeployTest:test_deployDeviceWalletForUsers_zeroDepositSkipsEntryPoint() (gas: 959421) +DeviceWalletFactoryConfigTest:test_addRegistryAddress_onlyOnce() (gas: 22234) +DeviceWalletFactoryConfigTest:test_addRegistryAddress_withoutAdmin() (gas: 37785) +DeviceWalletFactoryConfigTest:test_addRegistryAddress_withoutOwner() (gas: 21169) +DeviceWalletFactoryConfigTest:test_updateDeviceWalletImplementation() (gas: 4067215) +DeviceWalletFactoryConfigTest:test_updateDeviceWalletImplementation_admin() (gas: 36150) +DeviceWalletFactoryCreateAccountTest:test_createAccount() (gas: 565782) +DeviceWalletFactoryCreateAccountTest:test_createAccount_byArbitraryCaller() (gas: 382209) +DeviceWalletFactoryCreateAccountTest:test_createAccount_byArbitraryCaller_cannotCallPostCreateAccount() (gas: 363018) +DeviceWalletFactoryCreateAccountTest:test_createAccount_callTwice() (gas: 579399) +DeviceWalletFactoryCreateAccountTest:test_createAccount_forwardsValueToAnAlreadyDeployedWallet() (gas: 391096) +DeviceWalletFactoryCreateAccountTest:test_createAccount_fundsTheWalletAndNotTheDeposit() (gas: 369798) +DeviceWalletFactoryCreateAccountTest:test_createAccount_withoutValueLeavesTheWalletUnfunded() (gas: 351655) +DeviceWalletFactoryCreateAccountTest:test_getCounterFactualAddress() (gas: 927980) +DeviceWalletFactoryCreateAccountTest:test_postCreateAccount_revertsOnRegisteredIdentifier() (gas: 885298) +DeviceWalletFactoryCreateAccountTest:test_postCreateAccount_revertsOnRegisteredOwnerKey() (gas: 886712) +DeviceWalletFactoryGuardsTest:test_addRegistryAddress_rejectsTheZeroAddress() (gas: 3556193) +DeviceWalletFactoryGuardsTest:test_createAccount_rejectsAnEmptyDeviceIdentifier() (gas: 19689) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsADepositAboveTheETHSent() (gas: 53951) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAKnownIdentifierUnderADifferentKey() (gas: 937983) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAKnownKeyUnderADifferentIdentifier() (gas: 935884) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAShortDepositArray() (gas: 58016) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAShortKeyArray() (gas: 52062) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAShortSaltArray() (gas: 57179) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAnEmptyBatch() (gas: 35731) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_rejectsAnEmptyDeviceIdentifier() (gas: 57803) +DeviceWalletFactoryGuardsTest:test_deployDeviceWalletForUsers_revertsWhenTheRefundIsRefused() (gas: 975591) +DeviceWalletFactoryGuardsTest:test_eSIMWalletAdmin_isEmptyUntilTheRegistryIsAdded() (gas: 3565213) +DeviceWalletFactoryGuardsTest:test_initialize_rejectsAZeroESIMWalletFactory() (gas: 3141634) +DeviceWalletFactoryGuardsTest:test_initialize_rejectsAZeroEntryPoint() (gas: 3141332) +DeviceWalletFactoryGuardsTest:test_initialize_rejectsAZeroUpgradeManager() (gas: 3142122) +DeviceWalletFactoryGuardsTest:test_initialize_rejectsAZeroVerifier() (gas: 3141833) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_recordsAWalletWithMatchingArguments() (gas: 539310) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAKeyTheWalletDoesNotHold() (gas: 412153) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAWalletAlreadyRecorded() (gas: 917785) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAnAddressThatIsNotADeviceWallet() (gas: 88139) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAnAddressWithNoCode() (gas: 75365) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAnEmptyDeviceIdentifier() (gas: 42126) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAnIdentifierTheWalletDoesNotCarry() (gas: 408225) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsAnOffCurveOwnerKey() (gas: 368118) +DeviceWalletFactoryGuardsTest:test_postCreateAccount_rejectsTheWrongSalt() (gas: 391784) +DeviceWalletFactoryGuardsTest:test_preCreateAccountValidation_reportsTheWalletHoldingTheIdentifier() (gas: 914679) +DeviceWalletFactoryGuardsTest:test_preCreateAccountValidation_reportsTheWalletHoldingTheOwnerKey() (gas: 917774) +DeviceWalletFactoryGuardsTest:test_updateDeviceWalletImplementation_rejectsTheCurrentImplementation() (gas: 31755) +DeviceWalletFactoryGuardsTest:test_updateDeviceWalletImplementation_rejectsTheZeroAddress() (gas: 19599) +DeviceWalletFactoryOwnerKeysTest:test_createAccount_revertsOnOffCurveOwnerKey() (gas: 41069) +DeviceWalletFactoryOwnerKeysTest:test_createAccount_revertsOnOutOfFieldOwnerKey() (gas: 38596) +DeviceWalletFactoryOwnerKeysTest:test_createAccount_revertsOnZeroOwnerKeyComponent() (gas: 42931) +DeviceWalletFactoryOwnerKeysTest:test_deployDeviceWalletForUsers_revertsOnOffCurveOwnerKey() (gas: 84762) +DeviceWalletFactoryOwnerKeysTest:test_deployDeviceWalletForUsers_revertsOnZeroOwnerKey() (gas: 83885) +DeviceWalletFactoryOwnerKeysTest:test_preCreateAccountValidation_revertsOnOffCurveOwnerKey() (gas: 19804) +DeviceWalletFactoryOwnerKeysTest:test_preCreateAccountValidation_revertsOnZeroOwnerKey() (gas: 19342) +DeviceWalletGuardsTest:test_deployESIMWallet_rejectsACallerOtherThanTheAdmin() (gas: 953932) +DeviceWalletGuardsTest:test_init_rejectsAZeroESIMWalletFactory() (gas: 117215) +DeviceWalletGuardsTest:test_init_rejectsAZeroRegistry() (gas: 117788) +DeviceWalletGuardsTest:test_init_rejectsAnEmptyDeviceIdentifier() (gas: 115103) +DeviceWalletGuardsTest:test_pullETH_rejectsAZeroAmount() (gas: 954447) +DeviceWalletGuardsTest:test_removeESIMWallet_completesWhenTheETHCallbackReverts() (gas: 972004) +DeviceWalletGuardsTest:test_removeESIMWallet_refusedRemovalLeavesTheRealBindingIntact() (gas: 963209) +DeviceWalletGuardsTest:test_removeESIMWallet_rejectsAnUnknownESIMWallet() (gas: 956816) +DeviceWalletGuardsTest:test_setESIMUniqueIdentifierForAnESIMWallet_rejectsAnUnknownESIMWallet() (gas: 963042) +DeviceWalletGuardsTest:test_toggleAccessToETH_rejectsAnUnknownESIMWallet() (gas: 961115) +DeviceWalletOwnerKeyTest:test_transferOwnership_acceptsARotationOntoTheSameKey() (gas: 3417233) +DeviceWalletOwnerKeyTest:test_transferOwnership_freesTheRetiredKeyForANewWallet() (gas: 4431273) +DeviceWalletOwnerKeyTest:test_transferOwnership_movesTheRegistryBindingToTheNewKey() (gas: 3432999) +DeviceWalletOwnerKeyTest:test_transferOwnership_rejectsACallerOtherThanTheWalletItself() (gas: 3407460) +DeviceWalletOwnerKeyTest:test_transferOwnership_rejectsAKeyAnotherWalletHolds() (gas: 3418230) +DeviceWalletOwnerKeyTest:test_transferOwnership_rejectsAnOffCurveKey() (gas: 3407432) +DeviceWalletOwnerKeyTest:test_transferOwnership_rejectsAnOutOfFieldKey() (gas: 3407175) +DeviceWalletOwnerKeyTest:test_transferOwnership_rejectsTheZeroKey() (gas: 3407500) +DeviceWalletSignaturesTest:test_isValidSignature_acceptsAnAssertionSignedForThisMessage() (gas: 1131820) +DeviceWalletSignaturesTest:test_isValidSignature_rejectsASignatureMadeForAnotherWallet() (gas: 1461840) +DeviceWalletSignaturesTest:test_isValidSignature_rejectsATamperedValidUntil() (gas: 1152478) +DeviceWalletSignaturesTest:test_isValidSignature_rejectsAnAssertionPastItsExpiry() (gas: 1135063) +DeviceWalletSignaturesTest:test_isValidSignature_rejectsAnAssertionSignedForAnotherMessage() (gas: 1123474) +DeviceWalletSignaturesTest:test_validateUserOp_shortSignatureFailsGracefully() (gas: 3399867) +DeviceWalletSignaturesTest:test_verifySignature_acceptsACapturedDeviceAssertion() (gas: 30652) +ESIMWalletFactoryTest:test_addRegistryAddress_onlyOnce() (gas: 25701) +ESIMWalletFactoryTest:test_addRegistryAddress_rejectsTheZeroAddress() (gas: 2412257) +ESIMWalletFactoryTest:test_addRegistryAddress_withoutOwner() (gas: 21188) +ESIMWalletFactoryTest:test_deployESIMWallet() (gas: 343467) +ESIMWalletFactoryTest:test_deployESIMWallet_allowsDeviceWalletToDeployForItself() (gas: 837432) +ESIMWalletFactoryTest:test_deployESIMWallet_revertsWhenDeviceWalletNamesAnother() (gas: 1324425) +ESIMWalletFactoryTest:test_deployESIMWallet_revertsWhenTheSameOwnerReusesASalt() (gas: 331443) +ESIMWalletFactoryTest:test_deployESIMWallet_sameSaltDifferentOwnersDoNotCollide() (gas: 616811) +ESIMWalletFactoryTest:test_deployESIMWallet_unauthorised() (gas: 35337) +ESIMWalletFactoryTest:test_initialize_rejectsAZeroUpgradeManager() (gas: 2065344) +ESIMWalletFactoryTest:test_updateESIMWalletImplementation() (gas: 3215616) +ESIMWalletFactoryTest:test_updateESIMWalletImplementation_rejectsTheZeroAddress() (gas: 18246) +ESIMWalletFactoryTest:test_updateESIMWalletImplementation_unauthorised() (gas: 20990) +ESIMWalletGuardsTest:test_buyDataBundle_rejectsAZeroPrice() (gas: 985161) +ESIMWalletGuardsTest:test_buyDataBundle_rejectsAnEmptyDataBundleID() (gas: 985198) +ESIMWalletGuardsTest:test_initialize_rejectsAZeroDeviceWallet() (gas: 105517) +ESIMWalletGuardsTest:test_initialize_rejectsAZeroFactory() (gas: 104683) +ESIMWalletGuardsTest:test_populateHistory_rejectsACallerOtherThanTheRegistry() (gas: 979257) +ESIMWalletGuardsTest:test_sendETHToDeviceWallet_rejectsAnAmountAboveTheBalance() (gas: 977711) +ESIMWalletGuardsTest:test_sendETHToDeviceWallet_revertsWhenTheDeviceWalletRefusesTheETH() (gas: 1039023) +ESIMWalletGuardsTest:test_setDataBundlePriceCap_clearingItReturnsToTheRegistryDefault() (gas: 1143449) +ESIMWalletTest:test_acceptOwnershipTransfer() (gas: 2401233) +ESIMWalletTest:test_acceptOwnershipTransfer_addESIMWallet() (gas: 2465669) +ESIMWalletTest:test_acceptOwnershipTransfer_afterRevoke() (gas: 2432685) +ESIMWalletTest:test_acceptOwnershipTransfer_clearsTheOutgoingOwnersPriceCeiling() (gas: 2405400) +ESIMWalletTest:test_acceptOwnershipTransfer_currentOwner() (gas: 2414629) +ESIMWalletTest:test_acceptOwnershipTransfer_emitsNoCapUpdateWhenThereWasNoCeiling() (gas: 2400328) +ESIMWalletTest:test_acceptOwnershipTransfer_emitsOwnershipTransferredOnce() (gas: 2402483) +ESIMWalletTest:test_acceptOwnershipTransfer_withoutRequest() (gas: 1513371) +ESIMWalletTest:test_buyDataBundle_allFundsFromUser() (gas: 1653995) +ESIMWalletTest:test_buyDataBundle_followsTheRotatedAdmin() (gas: 1674980) +ESIMWalletTest:test_buyDataBundle_noFundsFromESIMWallet() (gas: 1681470) +ESIMWalletTest:test_buyDataBundle_partialFundsFromESIMWallet() (gas: 1682571) +ESIMWalletTest:test_buyDataBundle_partialFundsFromUserAndESIMWallet() (gas: 1695388) +ESIMWalletTest:test_buyDataBundle_recordsTheHistoryBeforeTheVaultIsPaid() (gas: 1843257) +ESIMWalletTest:test_buyDataBundle_rejectsAPriceAboveTheRegistryDefault() (gas: 1534726) +ESIMWalletTest:test_buyDataBundle_revertsAboveTheRegistryDefault() (gas: 1543589) +ESIMWalletTest:test_buyDataBundle_revertsAboveTheWalletCap() (gas: 1551225) +ESIMWalletTest:test_buyDataBundle_revertsWhilePaused() (gas: 1675238) +ESIMWalletTest:test_buyDataBundle_theNewOwnerIsNotBoundByTheOldOwnersCeiling() (gas: 2448441) +ESIMWalletTest:test_buyDataBundle_theWalletCapOverridesTheRegistryDefault() (gas: 1694349) +ESIMWalletTest:test_owner() (gas: 1508592) +ESIMWalletTest:test_populateHistory() (gas: 1807741) +ESIMWalletTest:test_populateHistory_appendsTheSecondBatchAfterTheFirst() (gas: 1929463) +ESIMWalletTest:test_populateHistory_reportsTheRunningTotal() (gas: 1854352) +ESIMWalletTest:test_requestTransferOwnership() (gas: 2407842) +ESIMWalletTest:test_requestTransferOwnership_reissuingTheSameRequest() (gas: 2428004) +ESIMWalletTest:test_requestTransferOwnership_retargetDropsTheOldNominee() (gas: 3249785) +ESIMWalletTest:test_requestTransferOwnership_retargetsAnOutstandingRequest() (gas: 3266288) +ESIMWalletTest:test_requestTransferOwnership_revoke() (gas: 2426203) +ESIMWalletTest:test_requestTransferOwnership_selfCancelRestoresTheBinding() (gas: 2437417) +ESIMWalletTest:test_requestTransferOwnership_toRandomAddress() (gas: 1520098) +ESIMWalletTest:test_requestTransferOwnership_withoutOwner() (gas: 1513534) +ESIMWalletTest:test_sendETHToDeviceWallet() (gas: 1520278) +ESIMWalletTest:test_sendETHToDeviceWallet_newDeviceWallet() (gas: 2481086) +ESIMWalletTest:test_sendETHToDeviceWallet_unauthorised() (gas: 1511504) +ESIMWalletTest:test_setDataBundlePriceCap_rejectsTheAdmin() (gas: 1515530) +ESIMWalletTest:test_setESIMUniqueIdentifier() (gas: 1534786) +ESIMWalletTest:test_setESIMUniqueIdentifier_callTwiceFail() (gas: 1512574) +ESIMWalletTest:test_setESIMUniqueIdentifier_unauthorised() (gas: 1511701) +ESIMWalletTest:test_transferOwnership() (gas: 1511374) +GuardianPowersTest:test_disableAdminInstantly_cannotChooseAReplacement() (gas: 36580) +GuardianPowersTest:test_disableAdminInstantly_cannotReinstate() (gas: 95335) +GuardianPowersTest:test_disableAdminInstantly_emitsTheSuspension() (gas: 56388) +GuardianPowersTest:test_disableAdminInstantly_endsThePauseLoop() (gas: 81174) +GuardianPowersTest:test_disableAdminInstantly_rejectsAnAccountWithoutTheGuardianRole() (gas: 35569) +GuardianPowersTest:test_disableAdminInstantly_suspendsWithoutWaiting() (gas: 60710) +GuardianPowersTest:test_disableAndNominate_rejectsEveryCallerButTheTimelock() (gas: 34201) +GuardianPowersTest:test_disableAndNominate_stripsAndNominatesOnceTheDelayIsServed() (gas: 101826) +GuardianPowersTest:test_execute_closesToOutsidersOnceOpenExecutionIsRevoked() (gas: 110056) +GuardianPowersTest:test_guardian_canStillExecuteOnceOpenExecutionIsClosed() (gas: 134092) +GuardianPowersTest:test_guardian_cannotCancel() (gas: 55808) +GuardianPowersTest:test_guardian_cannotGrantItselfAnythingDirectly() (gas: 18512) +GuardianPowersTest:test_guardian_cannotSchedule() (gas: 20992) +GuardianPowersTest:test_guardian_holdsNeitherOfTheRolesItActsAgainst() (gas: 20724) +GuardianPowersTest:test_revokeCancellersInstantly_emitsOnePerAccount() (gas: 31905) +GuardianPowersTest:test_revokeCancellersInstantly_leavesTheProposerRoleAlone() (gas: 64598) +GuardianPowersTest:test_revokeCancellersInstantly_mayLeaveNoCancellersAtAll() (gas: 86062) +GuardianPowersTest:test_revokeCancellersInstantly_rejectsACanceller() (gas: 18377) +GuardianPowersTest:test_revokeCancellersInstantly_rejectsARepeatedAccount() (gas: 22088) +GuardianPowersTest:test_revokeCancellersInstantly_rejectsAnAccountThatNeverHeldTheRole() (gas: 19534) +GuardianPowersTest:test_revokeCancellersInstantly_rejectsAnAccountWithoutTheGuardianRole() (gas: 18245) +GuardianPowersTest:test_revokeCancellersInstantly_revertsTheWholeBatchOnOneBadAccount() (gas: 30933) +GuardianPowersTest:test_revokeCancellersInstantly_stripsAWholeSignerSetAtOnce() (gas: 48729) +GuardianPowersTest:test_revokeCancellersInstantly_takesTheVetoAway() (gas: 68312) +GuardianPowersTest:test_unpauseInstantly_emitsTheRelease() (gas: 44916) +GuardianPowersTest:test_unpauseInstantly_isHarmlessWhenNothingIsPaused() (gas: 33384) +GuardianPowersTest:test_unpauseInstantly_reachesAContractAddedLater() (gas: 92864) +GuardianPowersTest:test_unpauseInstantly_rejectsACanceller() (gas: 17332) +GuardianPowersTest:test_unpauseInstantly_rejectsAnAccountWithoutTheGuardianRole() (gas: 18168) +GuardianPowersTest:test_unpauseInstantly_rejectsTheProposer() (gas: 17244) +GuardianPowersTest:test_unpauseInstantly_releasesAPauseWithoutWaiting() (gas: 45449) +GuardianPowersTest:test_unpauseInstantly_revertsOnATargetWithNoCode() (gas: 20165) +IdentifierCollisionTest:test_aReservedDeviceIdentifierSurvivesAnAdminDeployment() (gas: 1956767) +IdentifierCollisionTest:test_claimESIMIdentifier_isRefusedForAWalletTheCallerDoesNotOwn() (gas: 1755429) +IdentifierCollisionTest:test_claimESIMIdentifier_isRefusedFromANonDeviceWallet() (gas: 918123) +IdentifierCollisionTest:test_lazyDeployment_isNotBlockedByItsOwnReservation() (gas: 1230409) +IdentifierCollisionTest:test_populateHistory_refusesAnESIMIdentifierAlreadyLiveOnchain() (gas: 1009673) +IdentifierCollisionTest:test_postCreateAccount_cannotClaimAReservedDeviceIdentifier() (gas: 564120) +IdentifierCollisionTest:test_setESIMUniqueIdentifier_cannotClaimAnIdentifierReservedForALazyUser() (gas: 1111882) +IdentifierCollisionTest:test_theLazyRouteCanClaimTheIdentifierItReserved() (gas: 1234367) +IdentifierCollisionTest:test_twoWalletsCannotCarryTheSameESIMIdentifier() (gas: 1837421) +ImplementationLocksTest:test_DeviceWalletFactory_implementationCannotBeInitialized() (gas: 3065558) +ImplementationLocksTest:test_DeviceWallet_orphanedProxyCannotBeClaimed() (gas: 3555416) +ImplementationLocksTest:test_ESIMWalletFactory_implementationCannotBeInitialized() (gas: 1991966) +ImplementationLocksTest:test_ESIMWallet_implementationIsLockedAtTheMaximumVersion() (gas: 2100242) +ImplementationLocksTest:test_LazyWalletRegistry_implementationCannotBeInitialized() (gas: 3515024) +ImplementationLocksTest:test_Registry_implementationCannotBeInitialized() (gas: 3657301) +LazySaltCollisionTest:test_deployMoreLazyESIMWallets_probedWalletsAreStillBound() (gas: 2188703) +LazySaltCollisionTest:test_deployMoreLazyESIMWallets_survivesAnOccupiedSalt() (gas: 2178707) +LazySaltCollisionTest:test_getCounterFactualAddress_matchesTheDeployedAddress() (gas: 325274) +LazyWalletRegistryGuardsTest:test_batchPopulateHistory_rejectsAShortDataBundleArray() (gas: 35483) +LazyWalletRegistryGuardsTest:test_batchPopulateHistory_rejectsAShortESIMIdentifierArray() (gas: 34721) +LazyWalletRegistryGuardsTest:test_batchPopulateHistory_rejectsAnEmptyDeviceIdentifier() (gas: 35522) +LazyWalletRegistryGuardsTest:test_batchPopulateHistory_rejectsAnEmptyESIMIdentifier() (gas: 40905) +LazyWalletRegistryGuardsTest:test_batchPopulateHistory_rejectsUnpairedESIMsAndBundles() (gas: 40587) +LazyWalletRegistryGuardsTest:test_deployLazyWalletAndSetESIMIdentifier_rejectsABatchOutsideTheCap() (gas: 216431) +LazyWalletRegistryGuardsTest:test_deployLazyWalletAndSetESIMIdentifier_rejectsADepositThatDoesNotMatchTheETHSent() (gas: 209364) +LazyWalletRegistryGuardsTest:test_deployLazyWalletAndSetESIMIdentifier_rejectsASaltWithNoRoomForTheESIMWallets() (gas: 205104) +LazyWalletRegistryGuardsTest:test_deployMoreESIMWalletsForLazyDevice_rejectsADeviceItNeverDeployed() (gas: 199639) +LazyWalletRegistryGuardsTest:test_deployMoreESIMWalletsForLazyDevice_rejectsANonAdminCaller() (gas: 34218) +LazyWalletRegistryGuardsTest:test_initialize_rejectsAZeroRegistry() (gas: 3668545) +LazyWalletRegistryGuardsTest:test_initialize_rejectsAZeroUpgradeManager() (gas: 3669045) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_rejectsAnEmptyESIMIdentifier() (gas: 33251) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_rejectsAnEmptyNewDeviceIdentifier() (gas: 33367) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_rejectsAnOldDeviceOfADifferentLength() (gas: 198088) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_rejectsAnOldDeviceOfTheSameLength() (gas: 199278) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_rejectsSwitchingToTheSameDevice() (gas: 198566) +LazyWalletRegistryGuardsTest:test_switchESIMIdentifierToNewDeviceIdentifier_revertsWhenTheIdentifierIsNotFound() (gas: 84454) +LazyWalletRegistryTest:test_batchPopulateHistory() (gas: 3263318) +LazyWalletRegistryTest:test_batchPopulateHistory_acceptsAnIdentifierAtTheLimit() (gas: 205969) +LazyWalletRegistryTest:test_batchPopulateHistory_addNewData() (gas: 4491903) +LazyWalletRegistryTest:test_batchPopulateHistory_afterDeployment() (gas: 6329066) +LazyWalletRegistryTest:test_batchPopulateHistory_duplicateData() (gas: 2980008) +LazyWalletRegistryTest:test_batchPopulateHistory_followsTheRotatedAdmin() (gas: 3326768) +LazyWalletRegistryTest:test_batchPopulateHistory_incorrectIdentifier() (gas: 3357246) +LazyWalletRegistryTest:test_batchPopulateHistory_refusedWhileTheDeviceIsStillDeploying() (gas: 1616070) +LazyWalletRegistryTest:test_batchPopulateHistory_rejectsAnOverLongDeviceIdentifier() (gas: 46640) +LazyWalletRegistryTest:test_batchPopulateHistory_rejectsAnOverLongESIMIdentifier() (gas: 60851) +LazyWalletRegistryTest:test_batchPopulateHistory_withoutAdmin() (gas: 241250) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier() (gas: 6284948) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_carriesNoHistory() (gas: 1692571) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_fundedDeploySurvivesAFrontRun() (gas: 6149125) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_leavesTheDeviceUsableMidDeployment() (gas: 2217587) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_reachesFortyFiveESIMsOverBatches() (gas: 27491908) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_staysCheapAtAFullBatch() (gas: 12244308) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_unfundedDeploySurvivesAFrontRun() (gas: 6116716) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_withoutAdmin() (gas: 44658) +LazyWalletRegistryTest:test_deployLazyWalletAndSetESIMIdentifier_withoutESIMIdentifier() (gas: 51636) +LazyWalletRegistryTest:test_deployLazyWallet_spendsItsWholeDeposit() (gas: 6131973) +LazyWalletRegistryTest:test_deployMoreESIMWalletsForLazyDevice_clampsToWhatIsLeft() (gas: 3557238) +LazyWalletRegistryTest:test_deployMoreESIMWalletsForLazyDevice_refusesADeviceWithNoFirstBatch() (gas: 464723) +LazyWalletRegistryTest:test_deployMoreESIMWalletsForLazyDevice_revertsOnceEveryWalletExists() (gas: 2389219) +LazyWalletRegistryTest:test_isDeviceIdentifierAlreadyUsed() (gas: 6286545) +LazyWalletRegistryTest:test_isDeviceIdentifierAlreadyUsed_registeredIdentfier() (gas: 3268236) +LazyWalletRegistryTest:test_isDeviceIdentifierAlreadyUsed_registeredIdentfier_addNewData() (gas: 4496997) +LazyWalletRegistryTest:test_isDeviceIdentifierAlreadyUsed_unregisteredIdentfier() (gas: 17773) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_copiesTheWholeHistoryInOrder() (gas: 2070380) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_ignoresAWalletClaimingTheSameIdentifier() (gas: 2547905) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_rejectsABatchAboveTheCap() (gas: 1353723) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_rejectsACallerOtherThanTheAdmin() (gas: 1355434) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_rejectsAnESIMItNeverDeployed() (gas: 332400) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_rejectsAnEmptyBatch() (gas: 1353546) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_revertsOnceTheHistoryIsCopied() (gas: 1563344) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_staysCheapAtAFullBatch() (gas: 6896048) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_survivesAnOwnershipTransfer() (gas: 2797936) +LazyWalletRegistryTest:test_setHistoryForLazyWallet_worksWhileTheDeviceIsStillDeploying() (gas: 2067983) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier() (gas: 3370388) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier_refusedWhileTheDeviceIsStillDeploying() (gas: 1611075) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier_revertsWhenNewDeviceDeployed() (gas: 6311230) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier_revertsWhenOldDeviceDeployed() (gas: 6308337) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier_unregistered() (gas: 37030) +LazyWalletRegistryTest:test_switchESIMIdentifierToNewDeviceIdentifier_withoutAdmin() (gas: 32460) +OwnershipHandoverTest:test_accept_doesNothingForAnEmptyBatch() (gas: 2491201) +OwnershipHandoverTest:test_accept_emitsOncePerTarget() (gas: 2674724) +OwnershipHandoverTest:test_accept_isOpenToAnyoneOnceTheOfferIsMade() (gas: 2671619) +OwnershipHandoverTest:test_accept_rejectsATargetThatOfferedNothing() (gas: 2499982) +OwnershipHandoverTest:test_accept_takesNothingWhenOneTargetInTheBatchOfferedNothing() (gas: 2647413) +OwnershipHandoverTest:test_handover_cannotEndWithNoOwnerAtAll() (gas: 74845) +OwnershipHandoverTest:test_handover_leavesOwnershipInPlaceUntilTheDestinationAccepts() (gas: 292087) +OwnershipHandoverTest:test_handover_movesToAReplacementAdminContract() (gas: 2699107) +OwnershipHandoverTest:test_handover_movesToAnAccountHoldingTheOwnerSlotDirectly() (gas: 218252) +OwnershipHandoverTest:test_handover_rejectsATransferProposedByAnyoneButTheAdminContract() (gas: 32770) +OwnershipHandoverTest:test_pause_canAlsoBeReleasedThroughTheDelayIfNobodyIsInAHurry() (gas: 82967) +OwnershipHandoverTest:test_pause_staysWithTheAdminKeyWhileTheReleaseMovesToTheContract() (gas: 48344) +P256VerificationTest:test_verifySignature_acceptsARealAssertion() (gas: 32871) +P256VerificationTest:test_verifySignature_rejectsAnAssertionMadeForAnotherChallenge() (gas: 24383) +P256VerificationTest:test_verify_bothPathsAcceptAValidSignature() (gas: 236037) +P256VerificationTest:test_verify_bothPathsRejectACorruptedSignature() (gas: 239570) +P256VerificationTest:test_verify_theFallbackCostsFarMoreThanThePrecompile() (gas: 239733) +ProtocolAdminTest:test_cancel_leavesThePayloadSchedulableAgain() (gas: 93506) +ProtocolAdminTest:test_cancel_rejectsAnAccountHoldingNeitherRole() (gas: 56421) +ProtocolAdminTest:test_cancel_rejectsTheGuardian() (gas: 56377) +ProtocolAdminTest:test_cancel_worksForAProposerAndForACancellerAlike() (gas: 110044) +ProtocolAdminTest:test_construction_acceptsAnEmptyCancellerSet() (gas: 2396186) +ProtocolAdminTest:test_construction_grantsTheRolesItSaysItDoes() (gas: 40788) +ProtocolAdminTest:test_construction_keepsRoleAdministrationInsideTheContract() (gas: 28961) +ProtocolAdminTest:test_construction_leavesExecutionOpenToEveryone() (gas: 9266) +ProtocolAdminTest:test_construction_rejectsACancellerThatIsAlsoAProposer() (gas: 197517) +ProtocolAdminTest:test_construction_rejectsADelayUnderTheFloor() (gas: 202323) +ProtocolAdminTest:test_construction_rejectsAGuardianThatCanAlsoCancel() (gas: 224446) +ProtocolAdminTest:test_construction_rejectsAGuardianThatIsAlsoAProposer() (gas: 282452) +ProtocolAdminTest:test_construction_rejectsAnEmptyGuardianSet() (gas: 200461) +ProtocolAdminTest:test_construction_rejectsTheZeroAddressInEveryRoleList() (gas: 606219) +ProtocolAdminTest:test_construction_spreadsTheCancelPowerWiderThanTheProposerSet() (gas: 48453) +ProtocolAdminTest:test_constructor_rejectsAnEmptyProposerList() (gas: 99242) +ProtocolAdminTest:test_executeBatch_appliesEveryCallOrNone() (gas: 98481) +ProtocolAdminTest:test_executeBatch_rejectsMismatchedArrayLengths() (gas: 16864) +ProtocolAdminTest:test_executeBatch_revertsTheWholeBatchWhenOneCallFails() (gas: 99965) +ProtocolAdminTest:test_execute_allowsAnyoneOnceTheDelayHasPassed() (gas: 82863) +ProtocolAdminTest:test_execute_rejectsAnOperationStillInsideItsDelay() (gas: 56256) +ProtocolAdminTest:test_execute_rejectsAnOperationThatWasNeverScheduled() (gas: 18891) +ProtocolAdminTest:test_getMinDelay_startsAtTheConstructorValue() (gas: 9359) +ProtocolAdminTest:test_ownership_movesTheUpgradeAuthorityWithIt() (gas: 29383) +ProtocolAdminTest:test_ownership_reachesAllFourContracts() (gas: 53658) +ProtocolAdminTest:test_roles_cannotBeGrantedDirectlyByAnyone() (gas: 20630) +ProtocolAdminTest:test_roles_rotateWithoutTouchingTheProtocolContracts() (gas: 171475) +ProtocolAdminTest:test_schedule_rejectsAnAccountWithoutTheProposerRole() (gas: 17713) +ProtocolAdminTest:test_schedule_rejectsTheGuardian() (gas: 18692) +ProtocolAdminTest:test_updateDelay_appliesOnceItHasBeenScheduledAndRun() (gas: 66037) +ProtocolAdminTest:test_updateDelay_cannotBringTheDelayBelowTheFloor() (gas: 69021) +ProtocolAdminTest:test_updateDelay_rejectsACallerThatIsNotTheContractItself() (gas: 12136) +RegistryGuardsTest:test_addOrUpdateLazyWalletRegistryAddress_rejectsTheAdmin() (gas: 25377) +RegistryGuardsTest:test_addOrUpdateLazyWalletRegistryAddress_rejectsTheZeroAddress() (gas: 18719) +RegistryGuardsTest:test_bindESIMWallet_acceptsAWalletTheFactoryDeployed() (gas: 1271721) +RegistryGuardsTest:test_bindESIMWallet_rejectsACallerThatIsNotADeviceWallet() (gas: 20694) +RegistryGuardsTest:test_bindESIMWallet_rejectsAnAddressTheFactoryNeverDeployed() (gas: 1297406) +RegistryGuardsTest:test_deployLazyWallet_rejectsACallerOtherThanTheLazyWalletRegistry() (gas: 30777) +RegistryGuardsTest:test_deployLazyWallet_rejectsADeviceIdentifierThatAlreadyHasAWallet() (gas: 926856) +RegistryGuardsTest:test_deployMoreLazyESIMWallets_rejectsACallerOtherThanTheLazyRegistry() (gas: 23494) +RegistryGuardsTest:test_initialize_rejectsAZeroAdmin() (gas: 3780060) +RegistryGuardsTest:test_initialize_rejectsAZeroEntryPoint() (gas: 3779219) +RegistryGuardsTest:test_initialize_rejectsAZeroUpgradeManager() (gas: 3780085) +RegistryGuardsTest:test_initialize_rejectsAZeroVault() (gas: 3779163) +RegistryGuardsTest:test_populateLazyHistory_rejectsACallerOtherThanTheLazyWalletRegistry() (gas: 21047) +RegistryGuardsTest:test_populateLazyHistory_rejectsAnAddressThatIsNotAProtocolESIMWallet() (gas: 23387) +RegistryGuardsTest:test_setESIMUniqueIdentifierForAnESIMWallet_cannotReachAnImpostor() (gas: 2129221) +RegistryGuardsTest:test_toggleESIMWalletStandbyStatus_rejectsACallerThatIsNotADeviceWallet() (gas: 21578) +RegistryGuardsTest:test_updateDeviceWalletInfo_rejectsACallerOtherThanTheFactory() (gas: 30048) +RegistryGuardsTest:test_updateDeviceWalletOwnerKey_rejectsACallerThatIsNotADeviceWallet() (gas: 23390) +RegistryInitializeTest:test_initialize_recordsBothFactories() (gas: 20810) +RegistryInitializeTest:test_initialize_recordsThePriceCap() (gas: 15591) +RegistryInitializeTest:test_initialize_revertsWhenDeviceWalletFactoryIsZero() (gas: 3779590) +RegistryInitializeTest:test_initialize_revertsWhenESIMWalletFactoryIsZero() (gas: 3779323) +RegistryInitializeTest:test_initialize_revertsWhenPriceCapIsZero() (gas: 3778768) +RegistryInitializeTest:test_initialize_revertsWhenVaultIsZero() (gas: 3779357) +RegistryOwnerGuardsTest:test_bindESIMWallet_rejectsAFormerDeviceWallet() (gas: 1773292) +RegistryOwnerGuardsTest:test_removeESIMWallet_stillRaisesTheStandbyMarker() (gas: 922026) +RegistryOwnerGuardsTest:test_toggleESIMWalletStandbyStatus_rejectsAFormerDeviceWallet() (gas: 1771842) +RegistryTest:test_pause_onlyTheAdminCanTripIt() (gas: 64129) +RegistryTest:test_pause_survivesAnAdminRotation() (gas: 72243) +RegistryTest:test_requireNotPaused_revertsOnlyWhilePaused() (gas: 45515) +RegistryTest:test_setDefaultDataBundlePriceCap_rejectsTheAdmin() (gas: 40196) +RegistryTest:test_setDefaultDataBundlePriceCap_rejectsZero() (gas: 18182) +RegistryTest:test_unpause_onlyTheOwnerCanReleaseIt() (gas: 40880) +RegistryTest:test_updateVaultAddress() (gas: 30877) +RegistryTest:test_updateVaultAddress_rejectsTheAdmin() (gas: 39907) +RegistryTest:test_updateVaultAddress_rejectsTheCurrentAddress() (gas: 23246) +RegistryTest:test_updateVaultAddress_rejectsTheZeroAddress() (gas: 26009) +RenounceOwnershipTest:test_renounceOwnership_revertsOnDeviceWalletFactory() (gas: 30214) +RenounceOwnershipTest:test_renounceOwnership_revertsOnESIMWalletFactory() (gas: 29394) +RenounceOwnershipTest:test_renounceOwnership_revertsOnESIMWalletViaDeviceWallet() (gas: 923739) +RenounceOwnershipTest:test_renounceOwnership_revertsOnLazyWalletRegistry() (gas: 21941) +RenounceOwnershipTest:test_renounceOwnership_revertsOnRegistry() (gas: 22029) +RoleRecoveryTest:test_grantRole_keepsALaterGuardianExecutingOnceExecutionIsClosed() (gas: 236659) +RoleRecoveryTest:test_grantRole_pairsExecutionWithAGuardianGrantedLater() (gas: 122541) +RoleRecoveryTest:test_grantRole_rejectsMakingACancellerAGuardian() (gas: 71070) +RoleRecoveryTest:test_grantRole_rejectsMakingAGuardianACanceller() (gas: 71729) +RoleRecoveryTest:test_grantRole_rejectsMakingAProposerAGuardian() (gas: 68718) +RoleRecoveryTest:test_grantRole_stillAllowsAnUnrelatedGrant() (gas: 94715) +RoleRecoveryTest:test_recovery_aCompromisedCancellerCannotBlockItsOwnEviction() (gas: 86106) +RoleRecoveryTest:test_recovery_aCompromisedProposerIsEvictedByTheBackupProposer() (gas: 113088) +RoleRecoveryTest:test_recovery_aHostileGuardianIsStillEvictable() (gas: 109854) +RoleRecoveryTest:test_recovery_aKeyholderCanStepDownWithoutWaitingForAnyone() (gas: 30553) +RoleRecoveryTest:test_recovery_addingTheCancelPowerBackTakesTheFullDelay() (gas: 93066) +RoleRecoveryTest:test_recovery_anInstantRevocationBeatsAScheduledGrant() (gas: 94355) +RoleRecoveryTest:test_recovery_hasNoRouteAtAllWithASingleProposer() (gas: 2448776) +RoleRecoveryTest:test_recovery_leavesTheProtocolContractsUntouched() (gas: 145107) +RoleRecoveryTest:test_recovery_nobodyCanStepDownOnAnotherAccountsBehalf() (gas: 18981) +RoleRecoveryTest:test_revokeRole_evictsAGuardianCompletelyInOneBatch() (gas: 92280) +RoleRecoveryTest:test_revokeRole_leavesAnEvictedGuardianAbleToExecute() (gas: 76645) +StorageLayoutTest:test_layout_deviceWalletFactorySlotsAreUnchanged() (gas: 21377) +StorageLayoutTest:test_layout_deviceWalletSlotsAreUnchanged() (gas: 544298) +StorageLayoutTest:test_layout_eSIMWalletFactorySlotsAreUnchanged() (gas: 16007) +StorageLayoutTest:test_layout_eSIMWalletSlotsAreUnchanged() (gas: 853373) +StorageLayoutTest:test_layout_lazyWalletRegistrySlotsAreUnchanged() (gas: 34351) +StorageLayoutTest:test_layout_registrySlotsAreUnchanged() (gas: 50043) +UpgradeAuthorityTest:test_upgradeManager_acceptsAContractAsTheUpgradeAuthority() (gas: 3837671) +UpgradeAuthorityTest:test_upgradeManager_followsAnOwnershipTransferOnTheLazyWalletRegistry() (gas: 38661) +UpgradeAuthorityTest:test_upgradeManager_followsAnOwnershipTransferOnTheRegistry() (gas: 39684) +UpgradeAuthorityTest:test_upgradeManager_matchesTheOwner() (gas: 29287) +UpgradeAuthorityTest:test_upgradeManager_theRetiredOwnerCannotUpgrade() (gas: 3679076) +UserOpValidationTest:test_handleOps_acceptsAnOperationSignedByTheOwner() (gas: 975581) +UserOpValidationTest:test_handleOps_rejectsASignatureMadeForAnotherOperation() (gas: 967073) +UserOpValidationTest:test_handleOps_rejectsAnExpiredOperation() (gas: 976094) +UserOpValidationTest:test_handleOps_rejectsAnUnparseableSignature() (gas: 938108) +UserOpValidationTest:test_validateUserOp_acceptsANonZeroValidUntil() (gas: 975757) +UserOpValidationTest:test_validateUserOp_rejectsAZeroValidUntil() (gas: 947016) +WebAuthnTest:test_verifySignature_rejectsARegistrationCeremonyType() (gas: 20365) +WebAuthnTest:test_verifySignature_rejectsAbsentUserPresentFlag() (gas: 29039) +WebAuthnTest:test_verifySignature_rejectsAbsentUserVerifiedFlagWhenRequired() (gas: 28673) +WebAuthnTest:test_verifySignature_rejectsChallengeIndexPastEnd() (gas: 16673) +WebAuthnTest:test_verifySignature_rejectsChallengeValueFromAnotherField() (gas: 17793) +WebAuthnTest:test_verifySignature_rejectsChallengeValueStartingPastEnd() (gas: 17806) +WebAuthnTest:test_verifySignature_rejectsEmptyAuthenticatorData() (gas: 28549) +WebAuthnTest:test_verifySignature_rejectsShortAuthenticatorData() (gas: 28434) +WebAuthnTest:test_verifySignature_rejectsTypeIndexPastEnd() (gas: 16746) +WebAuthnTest:test_verifySignature_rejectsUnterminatedChallengeValue() (gas: 28874) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..37454c33 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [main, test-and-security] + pull_request: + workflow_dispatch: + +# A new push to the same branch makes the run in flight pointless. Cancelling it frees a runner and +# stops two results racing to report on the same branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + strategy: + fail-fast: true + + name: Build, test and baselines + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.7.1 + + # The signature tests shell out to test/utils/ffi/webauthn-signer.js, which imports ethers + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install node dependencies + run: npm ci + + - name: Run Forge build + run: | + forge --version + forge build --via-ir --sizes + id: build + + - name: Run Forge tests + run: | + forge test --via-ir -vvv + id: test + + # Fuzz and invariant runs draw a fresh seed each time, and the malleable twin case verifies a + # signature generated offchain per run, so all three report a different figure every time. + # They are left out of the baseline rather than given a tolerance, so what remains is checked + # exactly. test/foundry/gas is out because it keeps its own baseline under snapshots/, one + # figure per operation, and sweeping it in here would record the test bodies instead. + - name: Check gas snapshot + run: | + forge snapshot --check --via-ir \ + --no-match-path "test/foundry/{fuzz-testing,invariant-testing,fork,gas}/*" \ + --no-match-test "test_verifySignature_rejectsTheMalleableTwinOfARealAssertion" + id: snapshot + + # The per-operation baseline. forge test rewrites snapshots/ on every run, so a difference + # here means an operation moved rather than a test body moving around it. + - name: Check per-operation gas snapshots + run: | + git diff --exit-code -- snapshots/ + id: operation-snapshot + + # Four UUPS singletons and every wallet behind two beacons, so a slot that moves reaches + # live state. The generator strips ast ids, which renumber on edits that touch no storage, + # so a difference here is a real layout change. + - name: Check storage layouts + run: | + python3 scripts/checks/storage-layouts.py + git diff --exit-code -- storage-layouts/ + id: storage-layout diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 9282e829..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: test - -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/.gitignore b/.gitignore index ffbfcc8d..1c69c305 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,14 @@ artifacts/ cache/ out/ forge-cache/ + +# Written by every `forge script --broadcast` run. The deployment record kept on purpose is +# deployments/address.json, which the scripts write and later scripts read. +broadcast/ lcov.info .env -lib/ +.DS_Store + +# Editor settings are per developer. The remappings inside are generated from remappings.txt +# anyway, so a stale copy here is worse than no copy. +.vscode/ diff --git a/README.md b/README.md index 99bfcc4c..c968c263 100644 --- a/README.md +++ b/README.md @@ -2,73 +2,178 @@ ![](./resources/KokioSCWithBG.png) -The **eSIM Wallet Smart Contract Suite** is a comprehensive group of smart contracts designed to deploy, manage, and maintain eSIM-related data and functionalities on the blockchain. Through these contracts, users can subscribe to data bundles and utilize their smart wallets as primary hot wallets. This suite consists of multiple interconnected contracts that ensure seamless user experience: +Onchain wallets for eSIM data plans. A phone gets one smart wallet controlled by a passkey, and +every eSIM on that phone gets its own wallet that buys data bundles and keeps the record of what it bought. -### Key Components: +The wallets are ERC-4337 accounts. Signatures are WebAuthn assertions over a P256 key held in the +device's secure enclave, so no seed phrase appears anywhere in the flow. -- **Registry Contract**: - This contract deploys both the Device Wallet Factory and the eSIM Wallet Factory. It serves as a central registry, tracking all Device and eSIM Wallets, ensuring their validity. Any wallets deployed outside this suite are considered invalid. Users can interact with the Registry Contract to deploy their first Device and eSIM Wallets. Additionally, it coordinates with the Lazy Wallet Registry to allow deferred smart wallet deployment. +## Architecture -- **Lazy Wallet Registry**: - To optimize gas usage, the Lazy Wallet Registry batches and stores transactions for users who operate with fiat. Smart wallets are deployed only when users choose to interact with them, preloaded with transaction history, ensuring minimal gas expenditure. +Two proxy layers, and most of the risk sits in them. Four UUPS singletons hold protocol state and +upgrade one at a time. Every wallet is a beacon proxy, so one beacon call moves every wallet of that kind at once. There is no per-wallet opt-out, which makes any beacon change a protocol-wide upgrade. -- **Device Wallet Factory**: - This contract is responsible for deploying individual Device Wallet smart contracts and managing device-specific wallet data. +| Contract | What it does | Pattern | +|---|---|---| +| `Registry` | Central record of every device and eSIM wallet. Also holds the protocol admin, the vault, the pause switch and the data bundle price ceiling | UUPS proxy | +| `RegistryHelper` | The registry's storage and lazy deployment half | Inherited by `Registry` | +| `LazyWalletRegistry` | Holds data bundle history for users paying in fiat, keyed by device and eSIM identifier strings, until they ask for a wallet | UUPS proxy | +| `DeviceWalletFactory` | Deploys device wallets at deterministic CREATE2 addresses and owns their beacon | UUPS proxy | +| `DeviceWallet` | One per phone. Holds ETH, owns the eSIM wallets on that device, verifies the passkey | Beacon proxy | +| `ESIMWalletFactory` | Deploys eSIM wallets and owns their beacon | UUPS proxy | +| `ESIMWallet` | One per eSIM. Buys data bundles, keeps purchase history, pulls ETH from its device wallet | Beacon proxy | +| `Account4337` | The ERC-4337 `IAccount` and `IERC1271` base that `DeviceWallet` builds on | Inherited by `DeviceWallet` | +| `WebAuthn` | Verifies WebAuthn authentication assertions. Tries the RIP-7212 precompile first and falls back to FreshCryptoLib | Library | +| `P256Verifier` | One immutable address for accounts to verify through, wrapping the WebAuthn library | Plain contract | +| `ProtocolAdmin` | Timelock meant to own the four singletons. Adds a delay floor that `updateDelay` cannot go under, and a guardian role with exactly two powers. **Written, not deployed** | Plain contract | +| `Errors` | Every custom error in the suite | Library | +| `CustomStructs` | Structs shared across contracts | Types | +| `interfaces/` | `IPausable` and `IOwnable2Step`, the two calls `ProtocolAdmin` makes back into the protocol | Interfaces | + +A backend server generates the device and eSIM identifiers and writes them into the wallets. That is what ties an onchain wallet to a provisioned eSIM. + +## Quickstart + +Foundry 1.7.1, solc 0.8.36, `evm_version = "osaka"`. + +```bash +git clone --recurse-submodules https://github.com/Blockchain-Powered-eSIM/smart-contract-suite.git +cd smart-contract-suite +forge build --sizes +forge test +``` + +This repo does not compile without via-IR. `via_ir = true` is already in `foundry.toml`, so plain +`forge` commands pick it up. A cold build takes about 30 seconds. + +Dependencies are git submodules under `lib/`. +Hardhat is wired up through `@nomicfoundation/hardhat-foundry` and compiles the same sources, so `npx hardhat compile` produces byte-identical bytecode. +That parity depends on `bytecode_hash = "none"` in `foundry.toml` and `metadata.bytecodeHash: "none"` in `hardhat.config.js`. Removing either breaks it. + +## Testing + +573 tests across 57 suites, about seven minutes for a full run. + +| Suite | Files | What it covers | +|---|---|---| +| `test/foundry/unit-testing/` | 32 | Per-contract behaviour, access control, revert paths, storage layout and initialiser locks | +| `test/foundry/fuzz-testing/` | 8 | Identifier and array shapes, ETH amounts, signature shapes, a P256 differential against the precompile | +| `test/foundry/invariant-testing/` | 8 | Registry consistency, wallet ownership, ETH accounting, purchase history, timelock behaviour | +| `test/foundry/gas/` | 7 | Per-operation gas with every input pinned, offchain signatures included | +| `test/foundry/fork/` | 1 | A user operation through the deployed EntryPoint on both chains | + +Branch coverage is 94.27%, or 181 of 192 arms, with `WebAuthn` at 80% the lowest and +`RegistryHelper`, `Account4337` and `ProtocolAdmin` at 100%. +Measure it with `scripts/checks/branch-coverage.py` rather than reading forge's own percentage: +`forge coverage --ir-minimum` does not count `require(cond, "string")` as a branch, so those sites +report zero hits on both arms however often they run. The contracts use custom errors throughout, so nothing is currently excluded, but the raw number stops meaning anything the moment a `require` +string is added. + +```bash +forge coverage --ir-minimum --report lcov --report-file lcov.info \ + --no-match-path "test/foundry/{fork,invariant-testing}/*" +python3 scripts/checks/branch-coverage.py lcov.info +``` + +The fork tests read `ALCHEMY_OP_SEPOLIA_HTTPS` and `ALCHEMY_BASE_SEPOLIA_HTTPS`. They skip rather +than fail when those are unset, so the suite runs without credentials. + +The long invariant campaign is a separate profile, run before a release rather than on every change: + +```bash +FOUNDRY_PROFILE=campaign forge test --match-path "test/foundry/invariant-testing/*" +``` + +Two gas baselines, measuring different things. `.gas-snapshot` is the whole-test-body figure, useful as a regression tripwire and not as a protocol gas number, since a row can include whatever that test deployed. +`snapshots/*.json` is per operation, written by the tests under `test/foundry/gas/` and rewritten by any ordinary `forge test`. + +## Security + +**Audit.** Reviewed by CD Security in March 2025. The report is in [audits/2025-03-CDSecurity.pdf](./audits/2025-03-CDSecurity.pdf). + +**Formal verification.** Seven Certora specs, 60 rules. + +| Spec | Rules | Subject | +|---|---|---| +| `Registry.spec` | 11 | Wallet registration and association records | +| `ProtocolAdmin.spec` | 10 | The delay nothing gets around, and the two things a guardian can say | +| `ESIMWallet.spec` | 9 | Ownership state machine, price ceiling, deploying factory | +| `DeviceWalletFactory.spec` | 9 | Deterministic deployment and beacon control | +| `ESIMWalletFactory.spec` | 9 | Registry wiring, beacon control, the record of what it deployed | +| `DeviceWallet.spec` | 8 | Owner key, eSIM wallet set, ETH access flags | +| `RegistryCrossContract.spec` | 4 | `Registry`, `DeviceWallet` and `ESIMWallet` agreeing on who holds an eSIM wallet | + +Three caveats attach to every proof. Loops unroll three times, so a result covers batches of at most three rather than all batches. Hashing of unbounded arguments is assumed within 224 bytes, raised to 1600 in `ESIMWalletFactory.spec` so its CREATE2 address prediction stays reachable. External calls are summarised one signature at a time. -- **Device Wallet Smart Contract**: - Each mobile device is assigned a unique Device Wallet. The app generates P256 keys using passkeys to verify ownership. These wallets allow users to store ETH and ERC-20 tokens, offering full control over their assets and enabling data bundle purchases for their associated eSIMs. +`LazyWalletRegistry` has no spec, and the reason is every mapping in it is string-keyed, and Certora's storage analysis fails on any method taking a `string`. +Its properties are carried by Foundry invariants instead. + +**Static analysis.** Slither and Aderyn run before anything substantial is committed: + +```bash +slither . --filter-paths "test/,script/,lib/,node_modules/" +aderyn . +``` + +**Trust model, as it stands.** One EOA owns all four UUPS proxies and both factories that own the +beacons, on both chains. A single key compromise reaches every wallet in one transaction, and admin +transactions go into the public mempool with no private relay in front of them. `ProtocolAdmin` +exists to replace that with a two day timelock and it is not deployed yet. Read the testnet +deployment below with that in mind. -- **eSIM Wallet Factory**: - Responsible for deploying all valid eSIM Wallet contracts. Only wallets deployed through this factory are considered legitimate. +## Deployments -- **eSIM Wallet Smart Contract**: - Every eSIM is linked to a unique eSIM Wallet, providing an on-chain representation of the eSIM. Users can purchase data bundles through these wallets. For instance, if a user has three eSIMs, three corresponding eSIM Wallets will be deployed, each linked to a unique eSIM. The eSIM Wallets can pull ETH from the Device Wallet, offering a streamlined experience without requiring individual top-ups. Users retain full control over their eSIM Wallets and can revoke or update permissions for each one. +Testnet only. These were deployed from an earlier commit and bind the v0.7 EntryPoint, while the +current branch builds against v0.8, so the suite gets redeployed rather than upgraded. -For seamless synchronization, a backend server securely generates unique identifiers for the device and eSIMs. These identifiers are stored in the respective wallets, ensuring secure eSIM generation and accurate data bundle application. +| Contract | OP Sepolia | Base Sepolia | +|---|---|---| +| `RegistryProxy` | `0x96dA9cE92D2C09f7b3ADE01260608e9079f16d12` | `0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9` | +| `LazyWalletRegistryProxy` | `0x3F14D060074B174B0784056bDe5e0f8970D25ff1` | `0x8a1E53b903efcc6b252CE4bD3b255202318505Ef` | +| `DeviceWalletFactoryProxy` | `0x243cCdE6a56b0Ba740E067f39896772748E20fFD` | `0xB4473979ff8cE4e09161B08f74EEb66BD7718076` | +| `ESIMWalletFactoryProxy` | `0x8444bF9C39F01e4B092e42DC11695C61f8B93957` | `0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB` | +| `DeviceWalletImpl` | `0x22FCFa80868dc9F423873F9332817eDAe4483974` | `0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e` | +| `ESIMWalletImpl` | `0xf86FE9253b6ea9454abda657f47aE508B00c15C1` | `0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9` | +| `P256Verifier` | `0x3c15a78046838481788613A9F111F972B562623C` | `0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1` | +| `EntryPoint` (v0.7) | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | -- **P256 Verifier**: - A proxy contract that verifies WebAuthn signatures for passkeys using the WebAuthn library. This will serve as an internal P256 verifier for the Kokio app. +The full list, including the Ethereum Sepolia deployment, is in +[deployments/address.json](./deployments/address.json). -- **WebAuthn Library**: - A library built on Daimo’s work to verify WebAuthn Authentication Assertions. It attempts to use the RIP-7212 precompile for signature verification, with fallback to FreshCryptoLib if the precompile fails. +The two chains are not symmetric in one way that file does not record. The owner EOA carries an +EIP-7702 delegation on Base Sepolia and none on OP Sepolia, so the same address is a smart account +on one chain and a plain EOA on the other. -## Smart Contract Suite Specifications: +## Specifications -- [Registry Contract](./docs/Registry.md) +- [Registry](./docs/Registry.md) - [Registry Helper](./docs/RegistryHelper.md) - [Lazy Wallet Registry](./docs/LazyWalletRegistry.md) +- [Protocol Admin](./docs/admin/ProtocolAdmin.md) - [Device Wallet Factory](./docs/device-wallet/DeviceWalletFactory.md) - [Device Wallet](./docs/device-wallet/DeviceWallet.md) - [eSIM Wallet Factory](./docs/esim-wallet/ESIMWalletFactory.md) - [eSIM Wallet](./docs/esim-wallet/ESIMWallet.md) +- [Account4337](./docs/aa-helper/Account4337.md) +- [Upgradeable Beacon](./docs/UpgradableBeacon.md) +- [Custom Structs](./docs/CustomStructs.md) +- [Errors](./docs/Errors.md) - [eSIM Wallet Interface](./docs/interfaces/IOwnableESIMWallet.md) +- [Ownable Two-Step Interface](./docs/interfaces/IOwnable2Step.md) +- [Pausable Interface](./docs/interfaces/IPausable.md) - [P256 Verifier](./docs/P256Verifier.md) -- [Web Authentication Contract](./docs/WebAuthn.md) - -## User Flow: - -1. **Install the eSIM Wallet App**: - Users install the app and register their passkeys. Passkeys (P256 keys) derive their security from the device’s Secure Enclave. - -2. **Device and eSIM Wallet Deployment**: - For new devices, the app requests the Registry to deploy a Device Wallet and an associated eSIM Wallet. These are linked upon deployment. - -3. **Data Bundle Selection & Purchase**: - a. Users select a data bundle plan before eSIM generation. - b. The app initiates the purchase. If paid via crypto, both wallets are deployed immediately. For fiat transactions, users can deploy their wallets later if needed. - c. Upon successful purchase, the server initiates the eSIM and data bundle provisioning. - d. The server generates a unique eSIM identifier and updates the corresponding eSIM Wallet via the Device Wallet. - -4. **eSIM Activation**: - The app provides a QR code for eSIM activation, which users can scan to begin using the eSIM. - -5. **Primary Wallet Use**: - Users can also use the Device Wallet as their primary wallet and withdraw funds anytime. - -## Future Prospects: - -- **Asset Recovery**: - Currently, users own their Device and eSIM Wallets and can transfer ownership to another Ethereum address. In the future, recovery rights may be assigned to a secondary keystore, allowing users to recover wallets in case of device loss. - -- **Unique Device bound identity**: - Currently, the device unique identifier is generated using some device parameters, but it would be great if we could bound an identifier with the secure enclave (TEE) of the mobile devie and associate it with the smart wallet, thus making it a device bound smart wallet. +- [WebAuthn](./docs/WebAuthn.md) + +## User flow + +1. **Install the app and register a passkey.** The P256 key lives in the device's secure enclave and + never leaves it. +2. **Deploy the wallets.** For a new device, the app asks the registry for a device wallet and one + eSIM wallet, linked at deployment. +3. **Pick and buy a data bundle.** Paying in crypto deploys both wallets immediately. Paying in fiat + records the purchase in the lazy wallet registry, and the wallets are deployed later if the user + asks for them. +4. **Provision the eSIM.** The server generates the eSIM identifier, writes it into the eSIM wallet + through the device wallet, and returns a QR code for activation. +5. **Use the device wallet.** It holds ETH and ERC-20 tokens and can be used as an ordinary wallet. + Funds can be withdrawn at any time. diff --git a/certora/conf/CrossContract-probe.conf b/certora/conf/CrossContract-probe.conf new file mode 100644 index 00000000..0e544395 --- /dev/null +++ b/certora/conf/CrossContract-probe.conf @@ -0,0 +1,36 @@ +{ + "files": [ + "contracts/Registry.sol", + "contracts/device-wallet/DeviceWallet.sol", + "contracts/esim-wallet/ESIMWallet.sol" + ], + "verify": "Registry:certora/specs/RegistryCrossContract.spec", + "rule": ["aTransferRequestMeansTheHolderHasAlreadyLetGo"], + "method": "ESIMWallet.requestTransferOwnership(address)", + "link": [ + "DeviceWallet:registry=Registry", + "ESIMWallet:deviceWallet=DeviceWallet" + ], + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "optimistic_fallback": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "Transfer request rule against requestTransferOwnership only", + "function_finder_mode": "relaxed" +} diff --git a/certora/conf/DeviceWallet.conf b/certora/conf/DeviceWallet.conf new file mode 100644 index 00000000..cc4c90ef --- /dev/null +++ b/certora/conf/DeviceWallet.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/device-wallet/DeviceWallet.sol" + ], + "verify": "DeviceWallet:certora/specs/DeviceWallet.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "DeviceWallet eSIM wallet rights and owner key", + "function_finder_mode": "relaxed" +} diff --git a/certora/conf/DeviceWalletFactory.conf b/certora/conf/DeviceWalletFactory.conf new file mode 100644 index 00000000..4c11f8f2 --- /dev/null +++ b/certora/conf/DeviceWalletFactory.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/device-wallet/DeviceWalletFactory.sol" + ], + "verify": "DeviceWalletFactory:certora/specs/DeviceWalletFactory.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "DeviceWalletFactory wiring and deployment record", + "function_finder_mode": "relaxed" +} diff --git a/certora/conf/ESIMWallet.conf b/certora/conf/ESIMWallet.conf new file mode 100644 index 00000000..7097dd95 --- /dev/null +++ b/certora/conf/ESIMWallet.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/esim-wallet/ESIMWallet.sol" + ], + "verify": "ESIMWallet:certora/specs/ESIMWallet.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "ESIMWallet ownership state machine", + "function_finder_mode": "relaxed" +} diff --git a/certora/conf/ESIMWalletFactory.conf b/certora/conf/ESIMWalletFactory.conf new file mode 100644 index 00000000..88962a5b --- /dev/null +++ b/certora/conf/ESIMWalletFactory.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/esim-wallet/ESIMWalletFactory.sol" + ], + "verify": "ESIMWalletFactory:certora/specs/ESIMWalletFactory.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "1600", + "rule_sanity": "basic", + "function_finder_mode": "relaxed", + "wait_for_results": "all", + "msg": "eSIM wallet factory registry, beacon and deployment record rules" +} diff --git a/certora/conf/ProtocolAdmin.conf b/certora/conf/ProtocolAdmin.conf new file mode 100644 index 00000000..a9fc6771 --- /dev/null +++ b/certora/conf/ProtocolAdmin.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/admin/ProtocolAdmin.sol" + ], + "verify": "ProtocolAdmin:certora/specs/ProtocolAdmin.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "optimistic_fallback": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "ProtocolAdmin delay floor and guardian powers" +} diff --git a/certora/conf/Registry-bv.conf b/certora/conf/Registry-bv.conf new file mode 100644 index 00000000..d644ac35 --- /dev/null +++ b/certora/conf/Registry-bv.conf @@ -0,0 +1,29 @@ +{ + "files": [ + "contracts/Registry.sol" + ], + "verify": "Registry:certora/specs/Registry.spec", + "rule": ["acceptingTheAdminHandoverClearsTheNomination"], + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "function_finder_mode": "relaxed", + "prover_args": ["-smt_bitVectorTheory true"], + "wait_for_results": "all", + "msg": "Registry admin handover under bit-vector theory" +} diff --git a/certora/conf/Registry.conf b/certora/conf/Registry.conf new file mode 100644 index 00000000..90812a40 --- /dev/null +++ b/certora/conf/Registry.conf @@ -0,0 +1,27 @@ +{ + "files": [ + "contracts/Registry.sol" + ], + "verify": "Registry:certora/specs/Registry.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "function_finder_mode": "relaxed", + "wait_for_results": "all", + "msg": "Registry association rules" +} diff --git a/certora/conf/RegistryCrossContract.conf b/certora/conf/RegistryCrossContract.conf new file mode 100644 index 00000000..3a15150b --- /dev/null +++ b/certora/conf/RegistryCrossContract.conf @@ -0,0 +1,34 @@ +{ + "files": [ + "contracts/Registry.sol", + "contracts/device-wallet/DeviceWallet.sol", + "contracts/esim-wallet/ESIMWallet.sol" + ], + "verify": "Registry:certora/specs/RegistryCrossContract.spec", + "link": [ + "DeviceWallet:registry=Registry", + "ESIMWallet:deviceWallet=DeviceWallet" + ], + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "optimistic_loop": true, + "optimistic_fallback": true, + "loop_iter": "3", + "optimistic_hashing": true, + "hashing_length_bound": "224", + "rule_sanity": "basic", + "wait_for_results": "all", + "msg": "Registry and DeviceWallet agree on who holds an eSIM wallet", + "function_finder_mode": "relaxed" +} diff --git a/certora/conf/smoke.conf b/certora/conf/smoke.conf new file mode 100644 index 00000000..f7d00a75 --- /dev/null +++ b/certora/conf/smoke.conf @@ -0,0 +1,23 @@ +{ + "files": [ + "contracts/Registry.sol" + ], + "verify": "Registry:certora/specs/Smoke.spec", + "solc": "solc", + "solc_via_ir": true, + "solc_optimize": "10000000", + "solc_evm_version": "osaka", + "packages": [ + "@openzeppelin/contracts=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + "@openzeppelin/contracts-upgradeable=lib/openzeppelin-contracts-upgradeable/contracts", + "@account-abstraction=lib/account-abstraction", + "solady/utils=lib/solady/src/utils", + "FreshCryptoLib=lib/FreshCryptoLib/solidity/src", + "p256-verifier=lib/p256-verifier/src", + "forge-std=lib/forge-std/src" + ], + "rule_sanity": "basic", + "function_finder_mode": "relaxed", + "wait_for_results": "all", + "msg": "smoke: can the prover compile this codebase" +} diff --git a/certora/specs/DeviceWallet.spec b/certora/specs/DeviceWallet.spec new file mode 100644 index 00000000..9db641cf --- /dev/null +++ b/certora/specs/DeviceWallet.spec @@ -0,0 +1,214 @@ +/// DeviceWallet: the rights it hands to eSIM wallets, and the key that owns it. +/// +/// A device wallet holds user ETH and decides which eSIM wallets may reach it. Two mappings carry +/// that decision, `isValidESIMWallet` for membership and `canPullETH` for the spending right, and +/// the second is meaningless without the first: `pullETH` checks `canPullETH` on its own, so a +/// wallet that kept the right after being let go would still be able to spend. The rules below fix +/// the relation between the two, fix the one ETH path against the balance, and fix the P256 key +/// that authorises everything this wallet does. +/// +/// Scope. Calls out of this wallet, into the registry, the two factories and the eSIM wallets, are +/// summarised as NONDET, so what is proved is this contract's own storage. That is the same scope +/// the earlier milestones used and it carries the same limit: NONDET returns an arbitrary value and +/// writes nothing, so no rule here says anything about re-entrancy. The `nonReentrant` guard on the +/// path that makes an external call is what covers that, and it is covered by the unit tests rather +/// than here. +/// +/// A second consequence of those summaries is that every access check reading the registry becomes +/// arbitrary. `onlyESIMWalletAdmin` asks the registry who the admin is and +/// `onlyRegistryOrDeviceWalletFactoryOrOwner` asks it for the factory address, so the prover may +/// answer with `msg.sender` and walk in. That is the conservative direction, exploring callers the +/// real protocol would refuse, and a rule holding anyway holds for a stronger reason. It does mean +/// no rule here states who may call what. The one guard that survives untouched is `onlySelf`, which +/// compares against `address(this)` and reads no storage. +/// +/// `transferOwnership` and `toggleAccessToETH` are both `onlySelf`, so on chain they are reachable +/// only through `execute` with this wallet as the target, which needs a signature from the current +/// owner key. The rules drive them directly instead. That is the stronger statement about which +/// storage they write, and it says nothing about who could get there, which the paragraph above +/// already gave up. +/// +/// Two bounded assumptions carry over from the earlier milestones. Loops unroll three times, so +/// `executeBatch` is covered for batches of at most three. Hashing of unbounded arguments is assumed +/// within 224 bytes, which the device identifiers and signature blobs reaching this contract are. +/// +/// The curve check on a new owner key is summarised, because it is field arithmetic the prover would +/// spend the whole run on for an answer no rule reads. Stating it plainly: nothing here proves that +/// `transferOwnership` rejects an off-curve key. That one is in the unit tests. What the rules ask +/// is which storage a call may write, given that it got past whatever check it faced. +/// +/// One method is genuinely out of reach here and it is expected. `init` calls the inherited +/// `initialize`, and OpenZeppelin lets a nested initialiser through on +/// `initialized == 1 && address(this).code.length == 0`, the branch that recognises a constructor. +/// The prover models this contract as carrying code, so that branch is false and `init` always +/// reverts. It shows up as a `rule_not_vacuous` record reading verified on the one rule that does +/// not filter `init` out. The long note on R-17 has the rest of it. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// `Violated: --rule_not_vacuous` means the body was reachable, which is the outcome +/// wanted. A `rule_not_vacuous` record reading verified is the failure. Count the records carrying +/// no sanity suffix and ignore the fraction. + +methods { + function isValidESIMWallet(address) external returns (bool) envfree; + function canPullETH(address) external returns (bool) envfree; + function registry() external returns (address) envfree; + function eSIMWalletFactory() external returns (address) envfree; + + /// `bytes32[2] public owner` generates an indexed getter, not one returning the pair. + function owner(uint256) external returns (bytes32) envfree; + + /// Nothing outside this contract is in the scene. Without this the registry and eSIM wallet + /// calls would havoc this wallet's own storage and every rule below would fail for the wrong + /// reason. + unresolved external in _._ => DISPATCH [] default NONDET; + + function FCL_Elliptic_ZZ.ecAff_isOnCurve(uint256 x, uint256 y) internal returns (bool) => NONDET; +} + +/// R-13. A wallet that may pull ETH is a wallet this device wallet still recognises. +/// +/// `canPullETH` is checked on its own in both spending paths, without a membership check beside it, +/// so this relation is the whole of what keeps a released wallet away from the balance. +/// `removeESIMWallet` clears both, and this says nothing anywhere leaves them apart. +/// +/// A transition rule rather than an invariant because these wallets live behind a beacon proxy and +/// are set up in `init` rather than a constructor, so an invariant's base case would be arguing +/// about a state the proxy never occupies. +rule theRightToPullETHNeverOutlivesMembership(method f, address eSIMWallet) { + require canPullETH(eSIMWallet) => isValidESIMWallet(eSIMWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert canPullETH(eSIMWallet) => isValidESIMWallet(eSIMWallet), + "an eSIM wallet kept the right to pull ETH without being recognised"; +} + +/// R-14. The ETH path pays out no more than the wallet holds. +/// +/// The route ends in `_transferETH`, which compares against the live balance. Stated over the entry +/// point rather than over the internal function, so the guard is proved where a caller meets it. It +/// is not payable, so the balance the call reads is the balance read here. +rule pullingMoreETHThanTheWalletHoldsAlwaysReverts(uint256 amount) { + require amount > nativeBalances[currentContract]; + + env callEnv; + pullETH@withrevert(callEnv, amount); + + assert lastReverted, "an eSIM wallet pulled more ETH than the device wallet held"; +} + +/// R-15. The registry and the eSIM wallet factory are written once. +/// +/// Both are set in `init` and both are read as authority: the registry answers who the admin is and +/// which factory is real, the factory is called to deploy. A second write to either would let the +/// wallet be pointed at a contract that answers those questions differently. +rule theRegistryAndFactoryAreWriteOnce(method f) filtered { + f -> f.selector != sig:init(address, bytes32[2], string, address).selector +} { + address registryBefore = registry(); + address factoryBefore = eSIMWalletFactory(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert registry() == registryBefore, "the registry address was overwritten"; + assert eSIMWalletFactory() == factoryBefore, "the eSIM wallet factory address was overwritten"; +} + +/// R-16. Removal withdraws both rights in the same call. +/// +/// The call ends with a callback into the wallet being removed, which shares one upgradeable beacon +/// with every other eSIM wallet, so the logic that runs there is not fixed for the life of the +/// protocol. This says the two writes have landed before it runs, whatever it turns out to do. +rule removalWithdrawsMembershipAndTheRightToPullTogether(address eSIMWallet, bool callBackETH) { + env callEnv; + removeESIMWallet(callEnv, eSIMWallet, callBackETH); + + assert !isValidESIMWallet(eSIMWallet), "a removed eSIM wallet was still recognised"; + assert !canPullETH(eSIMWallet), "a removed eSIM wallet kept the right to pull ETH"; +} + +/// R-17. The owner key moves only through `transferOwnership`. +/// +/// The key is the whole of the wallet's authority: every signature this contract checks is checked +/// against it, and a key written by anything other than the one guarded path is a wallet taken +/// over. +/// +/// `init` is filtered out, being the function the deploy paths call to write the key in the first +/// place. Both deploy paths pass that call as the proxy's constructor argument, so a device wallet +/// is initialised in the same transaction that creates it and there is no window between the two. +/// The prover disagrees about reachability because of one line of OpenZeppelin's `initializer`, +/// `construction = initialized == 1 && address(this).code.length == 0`, which is how a nested +/// initialiser is allowed to run inside a constructor. The prover models this contract as already +/// carrying code, so that branch is false for it, which is why the reachability check reports `init` +/// unreachable in the rule above: the nested `initialize` inside it can never pass. +/// +/// This rule once needed a second filter. The inherited `initialize(bytes32[2])` was public with +/// nothing but the `initializer` modifier on it, so the prover reached it from a wallet that had +/// never been initialised and wrote the key straight in. Nothing about the function refused a +/// caller; what kept it closed was entirely the atomicity of the deploy paths. It is internal now, +/// so there is no selector left to filter. +rule theOwnerKeyMovesOnlyThroughItsOwnEntryPoint(method f) filtered { + f -> f.selector != sig:init(address, bytes32[2], string, address).selector +} { + bytes32 xBefore = owner(0); + bytes32 yBefore = owner(1); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert owner(0) != xBefore || owner(1) != yBefore => + f.selector == sig:transferOwnership(bytes32[2]).selector, + "the owner key moved through something other than transferOwnership"; +} + +/// Membership is never granted twice over. +/// +/// The add path refuses a wallet it already holds. That refusal is what stops a second grant quietly +/// resetting `canPullETH` on a wallet whose access the owner had already revoked, which would be a +/// revocation undone by a call that looks like it is only adding. +rule addingAWalletTwiceAlwaysReverts(address eSIMWallet, bool hasAccessToETH) { + require isValidESIMWallet(eSIMWallet); + + env callEnv; + addESIMWallet@withrevert(callEnv, eSIMWallet, hasAccessToETH); + + assert lastReverted, "an eSIM wallet already held by this device wallet was added again"; +} + +/// The toggle only ever moves a wallet this device wallet recognises. +/// +/// The other half of R-13. That rule says the right never outlives membership; this says the right +/// is never granted to a wallet that never had membership in the first place, which is the case +/// where the two would come apart at the moment of the grant rather than later. +rule togglingETHAccessOnAnUnknownWalletAlwaysReverts(address eSIMWallet, bool hasAccessToETH) { + require !isValidESIMWallet(eSIMWallet); + + env callEnv; + toggleAccessToETH@withrevert(callEnv, eSIMWallet, hasAccessToETH); + + assert lastReverted, "ETH access was toggled on a wallet this device wallet does not recognise"; +} + +/// The right to pull ETH is only ever granted by the owner. +/// +/// `toggleAccessToETH` is `onlySelf` and every bind path refuses the flag, so a revocation stands. +/// The admin used to undo one by deploying a second eSIM wallet with the flag set. +/// +/// Stated over the whole method set, so a later function that writes the flag has to answer it too. +rule onlyToggleAccessToETHGrantsETHAccess(method f, address eSIMWallet) { + require !canPullETH(eSIMWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert canPullETH(eSIMWallet) => f.selector == sig:toggleAccessToETH(address, bool).selector, + "the right to pull ETH was granted by something other than toggleAccessToETH"; +} diff --git a/certora/specs/DeviceWalletFactory.spec b/certora/specs/DeviceWalletFactory.spec new file mode 100644 index 00000000..1374f0d8 --- /dev/null +++ b/certora/specs/DeviceWalletFactory.spec @@ -0,0 +1,200 @@ +/// DeviceWalletFactory: the addresses it is wired to, and the record of what it deployed. +/// +/// This contract owns the beacon every device wallet runs on, so it is the single point from which +/// all of them can be moved at once. It also holds the registry it reports deployments to, and a +/// per-wallet flag saying that report was made. The vault that receives data bundle payment lives on +/// the registry, not here. Nothing here is a balance, and the rules are about which of those may move +/// and through what. +/// +/// Two rules the milestone list asked for are not here, and the reason is that the contract moved +/// on. R-19 and R-20 are about `eSIMWalletAdmin` and `newRequestedAdmin`, and neither is storage +/// this contract has any more. The admin is read off the registry on every call, through the +/// `eSIMWalletAdmin()` view at the top of this file, so that the factory cannot fall behind the rest +/// of the protocol after a rotation. The two-step handover those rules describe now happens in the +/// registry and is proved there. +/// +/// Scope. Calls out of this factory, into the registry, the eSIM wallet factory, the beacon and the +/// wallets it deploys, are summarised as NONDET. Same limit as the earlier milestones: NONDET +/// returns an arbitrary value and writes nothing, so nothing here is a statement about re-entrancy. +/// +/// The consequence to keep in mind while reading any result below is that `eSIMWalletAdmin()` reads +/// through the registry and therefore answers arbitrarily, so `onlyAdmin` and `onlyAdminOrRegistry` +/// admit any caller as far as the prover is concerned. `onlyOwner` is the one guard that survives, +/// since it reads local storage. Read every rule with that in mind: a rule holding over an arbitrary +/// caller holds for a stronger reason than the code gives, and no rule here says who may call what. +/// +/// The curve check on an owner key is summarised, the same as in the DeviceWallet spec, because it +/// is field arithmetic no rule below reads an answer from. +/// +/// Loops unroll three times, so every statement about `deployDeviceWalletForUsers` covers batches of +/// at most three. Hashing of unbounded arguments is assumed within 224 bytes. +/// +/// That hashing bound costs three methods outright, and it is worth knowing which. `createAccount`, +/// `getCounterFactualAddress` and `postCreateAccount` all hash `type(BeaconProxy).creationCode`, +/// which is thousands of bytes, so under the bound they always revert and no rule below says +/// anything about them. They show up as `rule_not_vacuous` records reading verified. None of them +/// writes any of the storage these rules are about, so what is lost is the confirmation rather than +/// the property, but the honest statement is that the counterfactual address derivation is not +/// covered here at all. Raising the bound to cover the creation code would make every other hash in +/// the contract more expensive, and the return on it is one confirmation, so it is left where it is. +/// +/// `postCreateAccount` joined that list when it started re-deriving the address it is being asked to +/// record, which is what binds the identifier and the owner key to the wallet. The consequence for +/// the rule below is specific: `reportingAWalletTwiceAlwaysReverts` still passes, but it now passes +/// because every call reverts rather than because the flag is checked, so it has stopped being +/// evidence of anything. The guard itself is carried by +/// `test_postCreateAccount_rejectsAWalletAlreadyRecorded` instead. +/// +/// `renounceOwnership` also reads unreachable, and correctly: it is overridden to revert. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// `Violated: --rule_not_vacuous` means the body was reachable, which is the outcome +/// wanted. A `rule_not_vacuous` record reading verified is the failure. Count the records carrying +/// no sanity suffix and ignore the fraction. + +methods { + function registry() external returns (address) envfree; + function beacon() external returns (address) envfree; + function eSIMWalletFactory() external returns (address) envfree; + function entryPoint() external returns (address) envfree; + function verifier() external returns (address) envfree; + function deviceWalletInfoAdded(address) external returns (bool) envfree; + function owner() external returns (address) envfree; + + unresolved external in _._ => DISPATCH [] default NONDET; + + function FCL_Elliptic_ZZ.ecAff_isOnCurve(uint256 x, uint256 y) internal returns (bool) => NONDET; +} + +/// R-21. The registry is written once. +/// +/// The registry answers who the admin is, so every admin-gated function here resolves through it. A +/// second write would let the whole admin surface be pointed at a contract that names a different +/// admin, and the guard on that write is `onlyOwner`, which is one key. +rule theRegistryIsWriteOnce(method f) { + address registryBefore = registry(); + require registryBefore != 0; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert registry() == registryBefore, "the registry address was overwritten"; +} + +/// The registry setter refuses a second call outright. +/// +/// The rule above says no method moves a registry already set. This says the one method that sets it +/// reverts rather than silently doing nothing, which is what makes a mistaken second call visible to +/// whoever sent it. +rule settingTheRegistryTwiceAlwaysReverts(address newRegistry) { + require registry() != 0; + + env callEnv; + addRegistryAddress@withrevert(callEnv, newRegistry); + + assert lastReverted, "the registry was set a second time"; +} + +/// R-22. A wallet's deployment record is never withdrawn. +/// +/// `deviceWalletInfoAdded` says this factory has already told the registry about a wallet. +/// `postCreateAccount` refuses a wallet carrying the flag, so clearing it would let the same wallet +/// be reported twice under a different identifier or a different owner key. +rule aDeploymentRecordIsNeverWithdrawn(method f, address deviceWallet) { + require deviceWalletInfoAdded(deviceWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert deviceWalletInfoAdded(deviceWallet), "a device wallet's deployment record was withdrawn"; +} + +/// Reporting the same wallet twice always reverts. +/// +/// The other half of R-22. The flag never comes down, and this says the flag being up is actually +/// checked, so the registry cannot be handed a second identifier or a second owner key for a wallet +/// it already has a record of. +rule reportingAWalletTwiceAlwaysReverts(address deviceWallet) { + require deviceWalletInfoAdded(deviceWallet); + + env callEnv; + string identifier; + bytes32[2] ownerKey; + uint256 salt; + postCreateAccount@withrevert(callEnv, deviceWallet, identifier, ownerKey, salt); + + assert lastReverted, "a device wallet already reported to the registry was reported again"; +} + +/// The beacon and the rest of the wiring are written once. +/// +/// The beacon is the one address that decides what every device wallet in the protocol executes. +/// `entryPoint`, `verifier` and `eSIMWalletFactory` are set beside it and read the same way. All four +/// are written in `initialize` and nothing is supposed to move them afterwards: moving the beacon +/// would leave every deployed wallet pointing at an object this factory no longer controls, and the +/// upgrade path that is supposed to be used goes through the beacon, not around it. +rule theBeaconAndTheWiringAreWriteOnce(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address).selector +} { + address beaconBefore = beacon(); + address entryPointBefore = entryPoint(); + address verifierBefore = verifier(); + address eSIMWalletFactoryBefore = eSIMWalletFactory(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert beacon() == beaconBefore, "the beacon was replaced"; + assert entryPoint() == entryPointBefore, "the entry point was replaced"; + assert verifier() == verifierBefore, "the signature verifier was replaced"; + assert eSIMWalletFactory() == eSIMWalletFactoryBefore, "the eSIM wallet factory was replaced"; +} + +/// Ownership is never given up. +/// +/// The owner is the only caller `_authorizeUpgrade` accepts, and this contract owns the beacon, so +/// the owner is also the only route to a device wallet implementation change. Renouncing would leave +/// every device wallet frozen on its current logic with no way back, which is why the function is +/// overridden to revert. Stated parametrically so a method added later that clears the owner without +/// going through the two-step handover fails here. +rule ownershipIsNeverGivenUp(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address).selector +} { + require owner() != 0; + + env callEnv; + calldataarg args; + + /// The zero address cannot originate a call. Without this the prover hands back + /// `acceptOwnership` called by zero against a pending owner of zero, which passes its own + /// `pendingOwner() == msg.sender` check and lands the owner at zero. Nothing on chain reaches + /// that state, so a zero-check on the accept path would defend against nothing. Note that the + /// outgoing owner really can name zero as the pending owner, since `Ownable2Step` does not + /// refuse it; what stops the handover completing is that no caller can answer to it. + require callEnv.msg.sender != 0; + + f(callEnv, args); + + assert owner() != 0, "the factory lost its owner"; +} + +/// The owner moves only through the two-step handover. +/// +/// `Ownable2Step` is used deliberately here: a one-step transfer to a wrong address would hand away +/// the only key that can move the beacon, with nobody able to accept it back. +rule theOwnerMovesOnlyThroughTheHandover(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address).selector +} { + address ownerBefore = owner(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert owner() != ownerBefore => f.selector == sig:acceptOwnership().selector, + "the factory owner moved through something other than acceptOwnership"; +} diff --git a/certora/specs/ESIMWallet.spec b/certora/specs/ESIMWallet.spec new file mode 100644 index 00000000..aec19e55 --- /dev/null +++ b/certora/specs/ESIMWallet.spec @@ -0,0 +1,249 @@ +/// ESIMWallet ownership state machine. +/// +/// An eSIM wallet holds five facts worth proving about: who owns it, which device wallet it belongs +/// to, who has been offered it, the identifier it was issued under, and its purchase history. The +/// first two are supposed to be the same address at all times and the rules below say so directly, +/// because a wallet whose owner and device wallet disagree is one neither side can act on. Four of +/// the five are covered here. Purchase history is not, for a prover limitation recorded at the foot +/// of this file. +/// +/// Scope. Calls out of this wallet into the device wallet, the registry and the vault are +/// summarised as NONDET, so the prover explores this contract's own storage rather than the whole +/// protocol. Sound for the properties here, all of which are about storage this contract writes +/// itself. It does mean a proof says nothing about whether the device wallet agreed: that +/// two-sided statement is the cross-contract rule in the later milestone. +/// +/// Two bounded assumptions, recorded because a result quoted without them says more than it proves. +/// The history loop unrolls three times, so `populateHistory` is covered for batches of at most +/// three. Hashing of unbounded arguments is assumed within 224 bytes, which the identifiers and +/// bundle IDs reaching this contract are; without it every method taking a `string` reports a +/// violation about the bound rather than about the contract. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// A record reading `Violated: --rule_not_vacuous` means the body was reachable, which +/// is the outcome wanted. A `rule_not_vacuous` record reading verified is the failure. The methods +/// that read verified here are `renounceOwnership()` and `transferOwnership(address)` on every rule, +/// both overridden to revert unconditionally so that no rule body can complete on them, plus +/// `setESIMUniqueIdentifier(string)` on `noMethodReopensTheIdentifier` alone, where the rule has +/// already issued an identifier and the setter is meant to refuse a second one. Count the records +/// carrying no sanity suffix and ignore the fraction. + +methods { + function owner() external returns (address) envfree; + function newRequestedOwner() external returns (address) envfree; + function deviceWallet() external returns (address) envfree; + function eSIMWalletFactory() external returns (address) envfree; + function dataBundlePriceCap() external returns (uint256) envfree; + + /// Nothing outside this contract is in the scene. Without this the device wallet and registry + /// calls would havoc this wallet's own storage and every rule below would fail for the wrong + /// reason. + unresolved external in _._ => DISPATCH [] default NONDET; +} + +/// The identifier is issued once and never reissued. +/// +/// Stated as two calls rather than as a comparison of the stored string, which CVL handles poorly. +/// Driving the setter twice is the stronger statement anyway: it covers a second call with any +/// argument at all, including the one already stored. +rule theIdentifierIsIssuedAtMostOnce() { + env firstCall; + env secondCall; + string first; + string second; + + setESIMUniqueIdentifier(firstCall, first); + setESIMUniqueIdentifier@withrevert(secondCall, second); + + assert lastReverted, "a second identifier was accepted after one was already issued"; +} + +/// R-07. No method reopens an identifier once one has been issued. +/// +/// Parametric, so a method added later has to satisfy it without anyone remembering to extend a +/// test. Written as issue, run anything, try to issue again, because CVL cannot read a stored +/// string to compare it. Driving the setter is the stronger statement in any case: it is the only +/// writer, so a method that cleared the identifier would show up here as the setter succeeding +/// twice, whatever it wrote. +rule noMethodReopensTheIdentifier(method f) { + env issuing; + string identifier; + setESIMUniqueIdentifier(issuing, identifier); + + env callEnv; + calldataarg args; + f(callEnv, args); + + env reissuing; + string another; + setESIMUniqueIdentifier@withrevert(reissuing, another); + + assert lastReverted, "a method reopened an identifier that had already been issued"; +} + +/// R-08. An owned wallet never becomes unowned. +/// +/// The original plan expected this to fail, catching the renounce path that leaves `owner()` at zero +/// while `deviceWallet` still names the old holder, stranding the wallet's ETH. `renounceOwnership` +/// has since been overridden to revert, so this rule now confirms that fix rather than finding it. +/// It is kept parametric so a future method that clears the owner fails here. +/// +/// A transition rule rather than an invariant on purpose: the owner is set in `initialize` rather +/// than a constructor, so an invariant would have to fail its base case before saying anything. +rule anOwnedWalletNeverBecomesUnowned(method f) { + require owner() != 0; + + env callEnv; + calldataarg args; + + /// The zero address cannot originate a call. Without this the prover hands back + /// `acceptOwnershipTransfer` called by zero with nothing offered, which passes its own + /// `msg.sender == newRequestedOwner` guard and lands the owner at zero. That state is + /// unreachable on chain, so a zero-check on the accept path would defend nothing. + require callEnv.msg.sender != 0; + + f(callEnv, args); + + assert owner() != 0, "an owned eSIM wallet lost its owner"; +} + +/// R-09. An outstanding offer never names the address that already holds the wallet. +/// +/// Offering a wallet to its own holder is how the code spells revoking the offer, so the state this +/// forbids is the one where a revoke was recorded as a transfer. +/// +/// `initialize` is filtered out because it is the one method that moves the owner without reading +/// the offer. Left in, the prover starts from an arbitrary state carrying an offer, initializes the +/// owner onto that same address and reports it. That prestate does not exist: the offer is written +/// only by `requestTransferOwnership`, which admits only the device wallet, and the device wallet is +/// named by this very function. +rule anOutstandingOfferNeverNamesTheCurrentOwner(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + require newRequestedOwner() == 0 || newRequestedOwner() != owner(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert newRequestedOwner() == 0 || newRequestedOwner() != owner(), + "an eSIM wallet was left offered to the device wallet already holding it"; +} + +/// R-10. Accepting an offer consumes it and lands the wallet on the address that accepted. +/// +/// An offer that survived being accepted could be replayed to take the wallet back after it had +/// moved on to somebody else. +rule acceptingAnOfferConsumesItAndMovesTheWallet() { + address requested = newRequestedOwner(); + + env callEnv; + acceptOwnershipTransfer(callEnv); + + assert newRequestedOwner() == 0, "the offer survived being accepted"; + assert owner() == requested, "the wallet landed somewhere other than the address that accepted it"; +} + +/// R-11. Ownership moves through one entry point and only onto the address that was offered it. +/// +/// `initialize` is filtered out because it is where the owner is first set. Everything else, +/// including the two OpenZeppelin entry points this contract overrides to revert, has to leave the +/// owner alone or go through the offer. +rule theOwnerMovesOnlyToTheOfferedAddress(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + address ownerBefore = owner(); + address offered = newRequestedOwner(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + address ownerAfter = owner(); + + assert ownerAfter != ownerBefore => f.selector == sig:acceptOwnershipTransfer().selector, + "ownership moved through something other than acceptOwnershipTransfer"; + + assert ownerAfter != ownerBefore => ownerAfter == offered, + "ownership moved to an address that was never offered the wallet"; +} + +/// The owner and the device wallet are the same address, always. +/// +/// Two slots holding one fact, written together in `initialize` and again in +/// `_secureTransferOwnership`. This is the rule that would catch them drifting apart, which is the +/// shape of defect that a separate registry mapping already produced once elsewhere in this +/// protocol. `sendETHToDeviceWallet` pays out to `owner()` while `onlyDeviceWallet` admits +/// `deviceWallet`, so a drift is directly a misdirected payment. +rule theOwnerIsAlwaysTheDeviceWallet(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + require owner() == deviceWallet(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert owner() == deviceWallet(), + "the owner and the device wallet came apart"; +} + +/// R-12. Purchase history is append-only. +/// +/// Two writers, `buyDataBundle` and the registry's `populateHistory`, and neither is allowed to +/// drop an entry. History is what a dispute is settled from, so losing one is not recoverable. +/// +/// Not stated here, because the prover cannot reach the array length on this contract. The storage +/// analysis fails on `setESIMUniqueIdentifier`, and every phrasing needs it: +/// +/// - reading `currentContract.transactionHistory.length` fails to compile against that analysis +/// - a ghost mirror fed by storage hooks is worse, since one hook anywhere makes the analysis run +/// over every method and errors out all the rules, not just this one +/// - the generated getter is not modelled as reverting out of bounds, so "an entry that was +/// readable stays readable" passes while proving nothing +/// +/// Lowering the optimizer to 200 does not shift it either. The property is real and stays owed, so +/// it is carried by a Foundry invariant instead of a rule here. + +/// The wallet's own price ceiling is only ever set by its setter, and only ever cleared by a +/// handover. +/// +/// Not in the original milestone list, since the ceiling postdates it. Worth stating because the +/// ceiling is what stops the admin naming its own price on `buyDataBundle`, and the guard on the +/// setter is the whole of that protection. +/// +/// The second assert is what makes the handover exception safe. A handover may only take the +/// ceiling to zero, which hands the wallet to the registry default rather than to a figure the +/// outgoing owner chose. Anything else on that path would be a second, unguarded writer. +rule thePriceCeilingIsSetOnlyByItsSetter(method f) { + uint256 capBefore = dataBundlePriceCap(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + uint256 capAfter = dataBundlePriceCap(); + + assert capAfter != capBefore => + (f.selector == sig:setDataBundlePriceCap(uint256).selector || + f.selector == sig:acceptOwnershipTransfer().selector), + "the price ceiling moved through something other than its setter or a handover"; + + assert (capAfter != capBefore && f.selector == sig:acceptOwnershipTransfer().selector) => + capAfter == 0, + "a handover left the wallet on a ceiling rather than on the registry default"; +} + +/// The factory that deployed this wallet is write-once. +rule theFactoryIsWriteOnce(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + address factoryBefore = eSIMWalletFactory(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert eSIMWalletFactory() == factoryBefore, "the deploying factory was overwritten"; +} diff --git a/certora/specs/ESIMWalletFactory.spec b/certora/specs/ESIMWalletFactory.spec new file mode 100644 index 00000000..bfd4397a --- /dev/null +++ b/certora/specs/ESIMWalletFactory.spec @@ -0,0 +1,218 @@ +/// ESIMWalletFactory: the addresses it is wired to, and the record of what it deployed. +/// +/// This contract owns the beacon every eSIM wallet runs on, so it is the single point from which all +/// of them can be moved onto new logic at once. Beside that it holds the registry every caller check +/// reads through, and a per-address flag saying a wallet came out of this factory. Nothing here is a +/// balance, and the rules are about which of those may move and through what. +/// +/// The registry rule is not the one the plan described. That text said the registry is set in +/// `initialize` and moved only by `addRegistryAddress`. It is not: `initialize` never touches it, and +/// `addRegistryAddress` is the only writer there has ever been. So the rule below is stated as +/// exactly one writer, written once, and never back to zero, which is what the code does. +/// +/// Scope. Calls out of this factory, into the registry and into the beacon, are summarised as NONDET, +/// one signature at a time. A wildcard on unresolved calls would not catch them: the selector is +/// known and the callee is not, since it comes off a storage field, so the prover would pick its own +/// summary and havoc every other contract in the scene instead. NONDET returns an arbitrary value and +/// writes nothing, so nothing here is a statement about re-entrancy. +/// +/// The consequence to carry into every rule below is that the caller gate on `deployESIMWallet` reads +/// through the registry and therefore answers arbitrarily, so +/// `onlyRegistryOrDeviceWalletFactoryOrDeviceWallet` admits any caller as far as the prover is +/// concerned, and so does the `OnlyDeployForSelf` check inside the body. `onlyOwner` is the one guard +/// that survives, since it reads local storage. A rule holding over an arbitrary caller holds for a +/// stronger reason than the code gives; no rule here says who may deploy a wallet. +/// +/// The beacon's implementation is summarised for the same reason, and that is why no rule reads it. +/// `getCurrentESIMWalletImplementation` answers arbitrarily, so a rule comparing it across a call +/// would fail on the summary rather than on the contract. What is stated instead is that the beacon +/// address never moves and that the one method able to call `upgradeTo` on it refuses a non-owner. +/// Since this factory is the beacon's owner, those two together say the implementation moves only on +/// the owner's word, which is the property the plan asked for. +/// +/// Loops unroll three times. Hashing of unbounded arguments is assumed within 1600 bytes, and that +/// bound is deliberately not the 224 the other six confs use. `deployESIMWallet` hashes +/// `type(BeaconProxy).creationCode` to predict its CREATE2 address, which is 1252 bytes, plus 192 of +/// encoded constructor arguments. Under 224 the assumption is false, the method always reverts, and +/// the two rules about the deployment record would be proved over every method except the only one +/// that writes it. Raising the bound costs nothing here because the CREATE2 prediction is the only +/// hash of an unbounded argument in the contract. The same call in `DeviceWalletFactory` was left +/// uncovered rather than raised, and correctly, since that contract hashes in several other places +/// and the bound is global. +/// +/// `renounceOwnership` reads unreachable, and correctly: it is overridden to revert. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// `Violated: --rule_not_vacuous` means the body was reachable, which is the outcome +/// wanted. A `rule_not_vacuous` record reading verified is the failure. Count the records carrying no +/// sanity suffix and ignore the fraction. + +methods { + function registry() external returns (address) envfree; + function beacon() external returns (address) envfree; + function isESIMWalletDeployed(address) external returns (bool) envfree; + function owner() external returns (address) envfree; + function pendingOwner() external returns (address) envfree; + + unresolved external in _._ => DISPATCH [] default NONDET; + + function _.deviceWalletFactory() external => NONDET; + function _.isDeviceWalletValid(address) external => NONDET; + function _.implementation() external => NONDET; + function _.upgradeTo(address) external => NONDET; +} + +/// The registry is written once. +/// +/// Every caller check in this contract resolves through the registry, three reads of it in the one +/// modifier. A second write would point all of them at a contract naming a different device wallet +/// factory and a different set of valid device wallets, and the guard on that write is `onlyOwner`, +/// which is one key. +rule theRegistryIsWriteOnce(method f) { + address registryBefore = registry(); + require registryBefore != 0; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert registry() == registryBefore, "the registry address was overwritten"; +} + +/// The registry setter refuses a second call outright. +/// +/// The rule above says no method moves a registry already set. This says the one method that sets it +/// reverts rather than silently doing nothing, which is what makes a mistaken second call visible to +/// whoever sent it. +rule settingTheRegistryTwiceAlwaysReverts(address newRegistry) { + require registry() != 0; + + env callEnv; + addRegistryAddress@withrevert(callEnv, newRegistry); + + assert lastReverted, "the registry was set a second time"; +} + +/// The registry is never set to zero. +/// +/// A zero registry does not fail closed. `deployESIMWallet` would call into an address with no code, +/// and the one write this contract accepts would be spent on a value that can never be corrected, +/// since the setter is write-once. +rule theRegistryIsNeverSetToZero(address newRegistry) { + env callEnv; + addRegistryAddress@withrevert(callEnv, newRegistry); + + assert !lastReverted => registry() != 0, "the registry was set to the zero address"; +} + +/// The beacon is written once. +/// +/// The beacon is the one address deciding what every eSIM wallet in the protocol executes. It is +/// created in `initialize` and nothing is supposed to move it afterwards. Moving it would leave every +/// deployed wallet reading its logic from an object this factory no longer owns, and would strand the +/// upgrade path, which goes through the beacon rather than around it. +rule theBeaconIsWriteOnce(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + address beaconBefore = beacon(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert beacon() == beaconBefore, "the beacon was replaced"; +} + +/// A wallet's deployment record is never withdrawn. +/// +/// `isESIMWalletDeployed` is what the rest of the protocol asks to tell a wallet this factory made +/// from an address that merely claims to be one. The registry reads it before binding a wallet to a +/// device wallet, so clearing it would strand a live wallet outside the protocol with no way back: +/// the flag is only ever set by a deployment, and the address is already taken. +rule aDeploymentRecordIsNeverWithdrawn(method f, address eSIMWallet) { + require isESIMWalletDeployed(eSIMWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert isESIMWalletDeployed(eSIMWallet), "an eSIM wallet's deployment record was withdrawn"; +} + +/// Only a deployment adds a deployment record. +/// +/// The other half of the rule above. The flag never comes down, and this says it only goes up through +/// the method that actually creates the wallet, so no method can vouch for an address that was never +/// deployed here. Stated parametrically so a method added later that writes the map directly fails +/// here rather than in review. +rule onlyADeploymentAddsARecord(method f, address eSIMWallet) { + require !isESIMWalletDeployed(eSIMWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert isESIMWalletDeployed(eSIMWallet) => f.selector == sig:deployESIMWallet(address, uint256).selector, + "an eSIM wallet was recorded as deployed by something other than a deployment"; +} + +/// The eSIM wallet implementation moves only on the owner's word. +/// +/// This factory owns the beacon, so `updateESIMWalletImplementation` is the only route to +/// `upgradeTo`, and one call there moves every eSIM wallet in the protocol at once with no per-wallet +/// opt-out. `onlyOwner` reads local storage rather than the registry, so unlike the deployment gate it +/// is a guard the prover can actually see. +rule onlyTheOwnerCanMoveTheImplementation(address newImplementation) { + env callEnv; + require callEnv.msg.sender != owner(); + + updateESIMWalletImplementation@withrevert(callEnv, newImplementation); + + assert lastReverted, "a non-owner moved the eSIM wallet implementation"; +} + +/// Ownership is never given up. +/// +/// The owner is the only caller `_authorizeUpgrade` accepts, and this contract owns the beacon, so +/// the owner is also the only route to an eSIM wallet implementation change. Renouncing would leave +/// every eSIM wallet frozen on its current logic with no way back, which is why the function is +/// overridden to revert. Stated parametrically so a method added later that clears the owner without +/// going through the two-step handover fails here. +rule ownershipIsNeverGivenUp(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + require owner() != 0; + + env callEnv; + calldataarg args; + + /// The zero address cannot originate a call. Without this the prover hands back + /// `acceptOwnership` called by zero against a pending owner of zero, which passes its own + /// `pendingOwner() == msg.sender` check and lands the owner at zero. Nothing on chain reaches + /// that state, so a zero-check on the accept path would defend against nothing. Note that the + /// outgoing owner really can name zero as the pending owner, since `Ownable2Step` does not refuse + /// it; what stops the handover completing is that no caller can answer to it. + require callEnv.msg.sender != 0; + + f(callEnv, args); + + assert owner() != 0, "the factory lost its owner"; +} + +/// The owner moves only through the two-step handover. +/// +/// `Ownable2Step` is used deliberately here: a one-step transfer to a wrong address would hand away +/// the only key that can move the beacon, with nobody able to accept it back. +rule theOwnerMovesOnlyThroughTheHandover(method f) filtered { + f -> f.selector != sig:initialize(address, address).selector +} { + address ownerBefore = owner(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert owner() != ownerBefore => f.selector == sig:acceptOwnership().selector, + "the factory owner moved through something other than acceptOwnership"; +} diff --git a/certora/specs/ProtocolAdmin.spec b/certora/specs/ProtocolAdmin.spec new file mode 100644 index 00000000..cdd88bba --- /dev/null +++ b/certora/specs/ProtocolAdmin.spec @@ -0,0 +1,312 @@ +/// ProtocolAdmin: the delay nothing gets around, and the two sentences a guardian can say. +/// +/// This contract is the owner of the four upgradeable protocol contracts, and both wallet beacons +/// sit under two of them, so every power it holds reaches every wallet. What makes that safe is not +/// who holds a key but that every route into the protocol waits, and that the one role which does +/// not wait can express exactly two things. The rules below fix both halves: the delay floor binds +/// whatever `updateDelay` was last given, an operation never completes before the clock it was +/// scheduled against, and the guardian fast paths write nothing but the cancel power. +/// +/// Scope. Every call leaving this contract is summarised, `unpause` and the two `Ownable2Step` +/// methods by signature and everything else by the unresolved default. The low level call inside +/// `_execute` reaches an arbitrary target with arbitrary calldata, so its selector is genuinely +/// unresolved and the default applies to it. NONDET writes nothing, so nothing here says anything +/// about a target that calls back in. The one call that would matter is a scheduled operation +/// pointing at this contract, which is how `updateDelay` and every role change are reached on chain. +/// Those are driven directly instead, which is the stronger statement about which storage they +/// write and says nothing about the route to them. +/// +/// Two assumptions about the role table, both true of the deployed shape and both preserved by the +/// code rather than merely asserted at construction. The prover starts from an arbitrary state, so +/// they have to be written down. +/// +/// First, no account other than this contract holds `DEFAULT_ADMIN_ROLE`. The base constructor +/// grants it to `address(this)` and this contract passes zero for the optional admin, so there is no +/// second holder, and granting one is itself an operation that has to be scheduled and served. +/// Without this the prover hands `DEFAULT_ADMIN_ROLE` to an arbitrary caller and every role rule +/// fails for a state the deployment cannot reach. +/// +/// Second, every role's admin is `DEFAULT_ADMIN_ROLE`. `_setRoleAdmin` is internal and no function +/// in `AccessControl` or `TimelockController` calls it, so the mapping is whatever construction left +/// and construction never writes it. The prover would otherwise pick an arbitrary admin role per +/// role. +/// +/// Loops unroll three times, so `revokeCancellersInstantly`, `acceptOwnershipBatch`, +/// `scheduleBatch` and `executeBatch` are covered for batches of at most three. Hashing of unbounded +/// arguments is assumed within 224 bytes, which the operation payloads reaching this contract are +/// not necessarily: an upgrade payload carrying an initializer call can exceed that. The rules below +/// are stated over `getTimestamp` on an arbitrary identifier rather than over a payload, so none of +/// them recompute an operation hash and the bound does not weaken what they say. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// `Violated: --rule_not_vacuous` means the body was reachable, which is the outcome +/// wanted. A `rule_not_vacuous` record reading verified is the failure. Count the records carrying +/// no sanity suffix and ignore the fraction. + +methods { + function GUARDIAN_ROLE() external returns (bytes32) envfree; + function PROPOSER_ROLE() external returns (bytes32) envfree; + function CANCELLER_ROLE() external returns (bytes32) envfree; + function EXECUTOR_ROLE() external returns (bytes32) envfree; + function DEFAULT_ADMIN_ROLE() external returns (bytes32) envfree; + + function hasRole(bytes32, address) external returns (bool) envfree; + function getRoleAdmin(bytes32) external returns (bytes32) envfree; + + function minDelayFloor() external returns (uint256) envfree; + function getMinDelay() external returns (uint256) envfree; + function getTimestamp(bytes32) external returns (uint256) envfree; + + /// The two `Ownable2Step` methods and the pause release all have resolved selectors and an + /// unknown callee, so the unresolved default below never applies to them and the prover would + /// pick its own summary. Naming each one is what keeps this contract's own storage alone. + function _.unpause() external => NONDET; + function _.pendingOwner() external => NONDET; + function _.acceptOwnership() external => NONDET; + + /// The arbitrary call inside `_execute`, whose selector really is unresolved. + unresolved external in _._ => DISPATCH [] default NONDET; +} + +/// This contract is the only holder of `DEFAULT_ADMIN_ROLE`, and every role is administered by it. +/// See the header for why both hold on chain and why the prover has to be told. +function theRoleTableIsAsDeployed(env callEnv, bytes32 role) { + require callEnv.msg.sender != currentContract => + !hasRole(DEFAULT_ADMIN_ROLE(), callEnv.msg.sender); + require getRoleAdmin(role) == DEFAULT_ADMIN_ROLE(); +} + +/// A-01. The floor binds whatever the stored delay says. +/// +/// `updateDelay` accepts any value including zero and is reachable by scheduling a call to this +/// contract like any other, so without the clamp one served operation turns the timelock into a +/// plain multisig and nothing after it ever waits again. `getMinDelay` is what `_schedule` measures +/// against, so clamping there is what makes the floor bind every new operation rather than only the +/// setter. +rule theDelayIsNeverBelowTheFloor(method f) { + env callEnv; + calldataarg args; + f(callEnv, args); + + assert getMinDelay() >= minDelayFloor(), + "the effective delay fell below the floor"; +} + +/// A-02. Nothing is scheduled to mature inside the floor. +/// +/// The other half of A-01, stated where it lands rather than where it is read. `_schedule` is the +/// only writer that raises a timestamp, and it writes `block.timestamp + delay` having already +/// refused a delay under `getMinDelay`. Parametric rather than over `schedule`, so `scheduleBatch` +/// is covered by the same statement and so a future writer cannot slip a timestamp in beside them. +rule nothingIsScheduledToMatureInsideTheFloor(method f, bytes32 id) { + uint256 timestampBefore = getTimestamp(id); + + env callEnv; + calldataarg args; + f(callEnv, args); + + uint256 timestampAfter = getTimestamp(id); + + assert timestampAfter > timestampBefore => + timestampAfter >= callEnv.block.timestamp + minDelayFloor(), + "an operation was scheduled to become ready sooner than the floor allows"; +} + +/// A-03. An operation never completes before the clock it was scheduled against. +/// +/// The whole point of the contract in one line. `_beforeCall` refuses an operation that is not +/// ready, and ready means the stored timestamp has passed. Stated over every method so that no path +/// other than `execute` and `executeBatch` can mark an operation done either. +/// Both halves of the precondition are load-bearing. Waiting and done are told apart by the stored +/// timestamp alone, done being the literal 1, so `getTimestamp(id) > block.timestamp` does not on its +/// own exclude a done operation: at `block.timestamp == 0` it admits a timestamp of 1, which is done +/// already, and the rule then fails on every method including pure views. Pending is exactly +/// `getTimestamp(id) > 1`, which is what separates the two. +/// +/// Stated through `getTimestamp` rather than through `isOperationPending` and `isOperationDone` +/// because those two read the clock to tell a waiting operation from a ready one, which no rule here +/// asks about, and reading it makes them ineligible for `envfree`. The stored timestamp answers both +/// questions on its own. +rule nothingCompletesBeforeItsTimestamp(method f, bytes32 id) { + env callEnv; + calldataarg args; + + require getTimestamp(id) > 1; + require getTimestamp(id) > callEnv.block.timestamp; + + f(callEnv, args); + + assert getTimestamp(id) != 1, + "an operation completed before its delay had been served"; +} + +/// A-04. A completed operation is never reopened. +/// +/// Done is written as the timestamp 1, which `_schedule` reads as an operation already existing and +/// `cancel` reads as not pending. Both refusals are what stops a served payload being replayed under +/// its own identifier, and the identifier is a hash of the payload, so replaying one means running +/// the same calls again with no new wait. +rule aCompletedOperationIsNeverReopened(method f, bytes32 id) { + require getTimestamp(id) == 1; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert getTimestamp(id) == 1, + "a completed operation was reopened"; +} + +/// A-05. The delay moves only through a served operation. +/// +/// `updateDelay` compares the caller against `address(this)`, which reads no role table and no +/// storage, so this is one of the few guards in the whole protocol that survives every summary +/// above intact. Reaching it means scheduling a call to this contract and waiting the current delay +/// out, so a delay change announces itself for as long as the delay it is changing. +rule theDelayMovesOnlyThroughAServedOperation(method f) { + uint256 delayBefore = getMinDelay(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert getMinDelay() != delayBefore => + f.selector == sig:updateDelay(uint256).selector && callEnv.msg.sender == currentContract, + "the delay moved through something other than a served operation"; +} + +/// A-06. Every role change waits, renounces, or is the guardian taking the cancel power. +/// +/// The role table is the contract's own authority, so this is the statement that says the fast path +/// is exactly as wide as it was meant to be. Three ways a role moves and no fourth. A grant or a +/// revocation comes from this contract itself, meaning it was scheduled and served. An account may +/// always drop a role it holds. And a guardian may strip `CANCELLER_ROLE`, which A-07 then pins to +/// that role alone. +/// +/// The guardian exception is what stops a compromised canceller being permanent: evicting any role +/// holder is a scheduled operation, and any canceller can cancel a scheduled operation, so without +/// an instant route a compromised canceller cancels its own eviction forever. +rule everyRoleChangeIsServedRenouncedOrTheGuardianException(method f, bytes32 role, address account) { + env callEnv; + theRoleTableIsAsDeployed(callEnv, role); + + bool heldBefore = hasRole(role, account); + + calldataarg args; + f(callEnv, args); + + assert hasRole(role, account) != heldBefore => ( + callEnv.msg.sender == currentContract + || f.selector == sig:renounceRole(bytes32, address).selector + || f.selector == sig:revokeCancellersInstantly(address[]).selector + ), "a role moved without being scheduled, renounced, or stripped by a guardian"; +} + +/// A-07. The guardian's instant path takes the cancel power and nothing else. +/// +/// The half of A-06 that its third branch leaves open. A guardian that could reach any other role +/// through this function would be a general fast path wearing a narrow name: `PROPOSER_ROLE` most +/// of all, because reaching zero proposers is unrecoverable. Re-granting any role needs a scheduled +/// operation, scheduling needs a proposer, and `Registry.unpause` is owner only, so a bricked admin +/// plus a pause is a pause nobody can ever release. +/// +/// Note the direction as well as the role: this path only ever takes away. It cannot hand the cancel +/// power to an account that did not have it, which is what would let a guardian install its own +/// veto. +rule theGuardianTakesNothingButTheCancelPower(bytes32 role, address account) { + env callEnv; + theRoleTableIsAsDeployed(callEnv, role); + + bool heldBefore = hasRole(role, account); + + address[] accounts; + revokeCancellersInstantly(callEnv, accounts); + + bool heldAfter = hasRole(role, account); + + assert heldAfter != heldBefore => (heldBefore && role == CANCELLER_ROLE()), + "the guardian's instant path moved a role other than the cancel power, or granted one"; +} + +/// A-08. Both instant paths refuse an account without the guardian role. +/// +/// `onlyRole` reads the role table on this contract and calls nothing out, so unlike every access +/// check elsewhere in the protocol this one survives the summaries above and can actually be +/// stated. The two rules are separate because they are separate powers and a future edit could +/// loosen one without the other. +rule releasingAPauseWithoutTheGuardianRoleAlwaysReverts(address target) { + env callEnv; + require !hasRole(GUARDIAN_ROLE(), callEnv.msg.sender); + + unpauseInstantly@withrevert(callEnv, target); + + assert lastReverted, "a pause was released by an account holding no guardian role"; +} + +rule strippingACancellerWithoutTheGuardianRoleAlwaysReverts() { + env callEnv; + require !hasRole(GUARDIAN_ROLE(), callEnv.msg.sender); + + address[] accounts; + revokeCancellersInstantly@withrevert(callEnv, accounts); + + assert lastReverted, "the cancel power was stripped by an account holding no guardian role"; +} + +/// A-09. Taking ownership writes nothing here. +/// +/// `acceptOwnershipBatch` is permissionless, which is safe only because it takes ownership the +/// current owner already offered and changes nothing about who controls this contract. That second +/// half is what this says: an open function on the owner of the whole protocol touches no role, no +/// operation and no delay. +rule acceptingOwnershipWritesNoAdminState(bytes32 role, address account, bytes32 id) { + env callEnv; + theRoleTableIsAsDeployed(callEnv, role); + + bool heldBefore = hasRole(role, account); + uint256 timestampBefore = getTimestamp(id); + uint256 delayBefore = getMinDelay(); + + address[] targets; + acceptOwnershipBatch(callEnv, targets); + + assert hasRole(role, account) == heldBefore, "taking ownership moved a role"; + assert getTimestamp(id) == timestampBefore, "taking ownership moved an operation"; + assert getMinDelay() == delayBefore, "taking ownership moved the delay"; +} + +/// A-10. A guardian never also holds the cancel power, whatever call sequence ran. +/// +/// The constructor refuses this overlap and used to be the only place that did: an account holding +/// both could revoke every other canceller, become the only one, and cancel its own eviction +/// forever. `grantRole` now refuses it too, so this restates A-06's guardian exception as a standing +/// invariant of the role table rather than only a constructor-time check. +/// +/// This rule is expected to report one violation on `grantRole` under via-IR, on a counterexample +/// where `account` is the zero address. It is a prover artifact, not a defect: the same source and +/// the same rule verify when the contract is compiled without via-IR, job +/// `bd9b8400f48045538134cbae342d9eb5`. The override at `ProtocolAdmin.grantRole` reverts on exactly +/// the state the counterexample reaches, so no call can produce it. +/// +/// The counterexample state is also inert, which is worth stating because the zero address means +/// two different things in this contract. `GUARDIAN_ROLE`, `CANCELLER_ROLE` and `PROPOSER_ROLE` are +/// all read through `onlyRole`, which compares the holder against `msg.sender`, and no transaction +/// has a sender of zero. Zero holding one of those roles therefore empowers nobody, and zero +/// holding both empowers nobody twice. `EXECUTOR_ROLE` is the single exception: it is read through +/// `onlyRoleOrOpenRole`, which treats a grant to the zero address as a sentinel meaning execution +/// is open to everyone. That reading applies at two call sites in `TimelockController`, both for +/// `EXECUTOR_ROLE`, and nowhere else. +/// +/// Do not narrow this rule with `require account != 0` to make the via-IR job read clean. That +/// would hide a genuine overlap at any other account just as effectively as it hides this one. +rule theGuardianAndCancellerRolesNeverOverlap(method f, address account) { + require !(hasRole(GUARDIAN_ROLE(), account) && hasRole(CANCELLER_ROLE(), account)); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert !(hasRole(GUARDIAN_ROLE(), account) && hasRole(CANCELLER_ROLE(), account)), + "an account ended up holding both the guardian role and the cancel power"; +} diff --git a/certora/specs/Registry.spec b/certora/specs/Registry.spec new file mode 100644 index 00000000..194fafaa --- /dev/null +++ b/certora/specs/Registry.spec @@ -0,0 +1,363 @@ +/// Registry association rules. +/// +/// The registry holds four facts about every wallet the protocol knows: whether a device wallet is +/// valid, which device wallet an eSIM wallet is registered to, whether that eSIM wallet is in +/// transit between devices, and which device wallet a P256 key is registered to. Every rule here is +/// about how those four move under any call sequence, including ones no current caller makes. +/// +/// The association and the transit marker are deliberately independent. The association is a +/// registration that also names the last device wallet to hold the eSIM wallet, and it stays put +/// through a transfer; the marker says a transfer is outstanding. Rules that tie them to each other +/// are not just unprovable, they are wrong about the design. +/// +/// Scope and what it costs. Calls out of the registry into wallets and factories are summarised as +/// NONDET, so the prover explores this contract's own storage rather than the whole protocol. That +/// is sound for reads. It is not sound for one call: `deployLazyWallet` reaches the device wallet +/// factory, which calls back into `updateDeviceWalletInfo`, and a NONDET summary removes that +/// return path. Rules quantifying over every method therefore prove less about that one function +/// than about the rest, and the cross-contract statement is the separate Milestone 3 work. +/// +/// The dispatch list is empty, so the one delegate call in this contract, `upgradeToAndCall` +/// reaching `Address.functionDelegateCall`, always takes the NONDET default. Nothing below says +/// anything about what a new implementation's initializer does to registry storage on the way +/// through an upgrade. That is deliberate, since the implementation is not known here, but it means +/// the write-once rules cover every caller except the one that replaces the code. +/// +/// Two bounded assumptions, both recorded because a result quoted without them says more than it +/// proves. Loops unroll three times, so a proof covers batches of at most three rather than all +/// batches. Hashing of unbounded-length arguments is assumed to stay inside 224 bytes, which the +/// string identifiers and eSIM string arrays reaching this contract do; without it every method +/// taking a `string` reports a violation that is about the bound rather than about the contract. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` adds `assert false` +/// to the end of each rule and reports the verdict of that modified rule rather than a verdict on +/// the check. A record reading `Violated: --rule_not_vacuous` means the body was +/// reachable, which is the outcome wanted. The inverse, a `rule_not_vacuous` record reading +/// verified, is the one that says the rule proved nothing for that method. +/// +/// `renounceOwnership()` is the only method that reads verified there, and it is right to: it is +/// overridden to revert unconditionally, so no rule body can complete on it. Every other method +/// reaches the body. The headline count adds the reachability records to the assert failures and so +/// reports a clean run as mostly violated. Count the records without a sanity suffix instead. + +methods { + function isDeviceWalletValid(address) external returns (bool) envfree; + function isESIMWalletValid(address) external returns (address) envfree; + function isESIMWalletOnStandby(address) external returns (bool) envfree; + function registeredP256Keys(bytes32) external returns (address) envfree; + function uniqueIdentifierToDeviceWallet(string) external returns (address) envfree; + /// Keyed by hash rather than by the string, so a rule can quantify over the key without the + /// string hashing bound the header records applying to it. + function claimedESIMIdentifiers(bytes32) external returns (address) envfree; + + function vault() external returns (address) envfree; + /// Derived, but from storage alone and never from the environment, so it passes the envfree + /// static check. `adminOfRecord` is the address itself and this is whether it may act. + function eSIMWalletAdmin() external returns (address) envfree; + function adminOfRecord() external returns (address) envfree; + function adminDisabled() external returns (bool) envfree; + function newRequestedAdmin() external returns (address) envfree; + function entryPoint() external returns (address) envfree; + function deviceWalletFactory() external returns (address) envfree; + + /// Nothing outside this contract is in the scene. Without this every external call would havoc + /// the registry's own storage and every rule below would be unprovable for the wrong reason. + unresolved external in _._ => DISPATCH [] default NONDET; +} + +/// R-02, first part. A registration is never withdrawn. +/// +/// The association and the standby flag are unrelated facts, so the rules here are about each on +/// its own rather than about the two agreeing. An earlier version of this spec stated R-02 as +/// `onStandby <=> valid == 0`, taken from the mapping's own comment. That comment was wrong about +/// the design and has been corrected: the association names the device wallet that last held the +/// eSIM wallet and keeps naming it through a transfer, so the two are true at once by design. +/// +/// This is the property everything else leans on. Zero is how the registry spells an address it +/// never heard of, so a wallet that has been registered must never read it again. +rule aRegistrationIsNeverWithdrawn(method f, address e) { + require isESIMWalletValid(e) != 0; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert isESIMWalletValid(e) != 0, "a registered eSIM wallet lost its registration"; +} + +/// R-02, second part. The association moves through one entry point, and only onto its caller. +/// +/// Parametric, so an entry point added later has to satisfy it without anyone remembering to +/// extend a test. +rule theAssociationMovesOnlyThroughBind(method f, address e) { + address heldBefore = isESIMWalletValid(e); + + env callEnv; + calldataarg args; + f(callEnv, args); + + address heldAfter = isESIMWalletValid(e); + + assert heldAfter != heldBefore => + f.selector == sig:bindESIMWallet(address, address).selector, + "the association moved through something other than bindESIMWallet"; + + assert heldAfter != heldBefore => heldAfter == callEnv.msg.sender, + "a device wallet took on an eSIM wallet for somebody else"; +} + +/// R-02, third part. The marker is raised only by the release, and lowered only by those two. +/// +/// Two writers rather than one, which is why this is stated as a rule over every method instead of +/// left to the access control on either. +rule theStandbyMarkerHasExactlyTwoWriters(method f, address e) { + bool markedBefore = isESIMWalletOnStandby(e); + + env callEnv; + calldataarg args; + f(callEnv, args); + + bool markedAfter = isESIMWalletOnStandby(e); + + assert !markedBefore && markedAfter => + f.selector == sig:toggleESIMWalletStandbyStatus(address, bool).selector, + "an eSIM wallet was marked in transit by something other than the release"; + + assert markedBefore && !markedAfter => + f.selector == sig:toggleESIMWalletStandbyStatus(address, bool).selector || + f.selector == sig:bindESIMWallet(address, address).selector, + "the transit marker was lowered by something that neither binds nor releases"; +} + +/// R-02, fourth part. The two facts are independent, so no call moves both. +/// +/// `bindESIMWallet` is the one exception and it is named here rather than hidden: taking a wallet +/// on is the single moment both change, which is the whole reason it is one call and not two. +rule nothingButBindMovesBothFacts(method f, address e) { + address heldBefore = isESIMWalletValid(e); + bool markedBefore = isESIMWalletOnStandby(e); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert (isESIMWalletValid(e) != heldBefore && isESIMWalletOnStandby(e) != markedBefore) => + f.selector == sig:bindESIMWallet(address, address).selector, + "a call moved the association and the transit marker together"; +} + +/// R-01. An eSIM wallet is only ever associated with a device wallet the registry considers valid. +/// +/// Stated as a transition rule rather than an invariant. The invariant form held over a state +/// nothing had to reach, so it said the association was consistent without ever watching one move. +/// The rule form asserts against the value the call actually wrote, which is the statement worth +/// having and is what the induction step was standing in for. +rule everyAssociationNamesAValidDeviceWallet(method f, address e) { + address heldBefore = isESIMWalletValid(e); + + env callEnv; + calldataarg args; + f(callEnv, args); + + address heldAfter = isESIMWalletValid(e); + + assert heldAfter != heldBefore => isDeviceWalletValid(heldAfter), + "an eSIM wallet was associated with something the registry does not consider a device wallet"; +} + +/// R-03. A registered P256 key only ever names a device wallet the registry considers valid. +/// +/// Same reformulation as R-01, for the same reason. +rule everyRegisteredKeyNamesAValidDeviceWallet(method f, bytes32 keyHash) { + address namedBefore = registeredP256Keys(keyHash); + + env callEnv; + calldataarg args; + f(callEnv, args); + + address namedAfter = registeredP256Keys(keyHash); + + assert namedAfter != namedBefore && namedAfter != 0 => isDeviceWalletValid(namedAfter), + "a P256 key was registered to something the registry does not consider a device wallet"; +} + +/// R-04. A device wallet becomes valid through one entry point and one caller. +/// +/// Parametric, so it covers entry points added later as well as the ones there today. That is worth +/// more than the specific fact: the check is `onlyDeviceWalletFactory` today, and a second write +/// added anywhere later fails here without anyone remembering to extend a test. +rule aDeviceWalletBecomesValidOnlyThroughTheFactory(method f, address d) { + require !isDeviceWalletValid(d); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert isDeviceWalletValid(d) => + f.selector == sig:updateDeviceWalletInfo(address, string, bytes32[2]).selector, + "a device wallet was made valid by something other than updateDeviceWalletInfo"; + + assert isDeviceWalletValid(d) => callEnv.msg.sender == deviceWalletFactory(), + "a device wallet was made valid by a caller that is not the device wallet factory"; +} + +/// R-05. Validity is one-way. Nothing revokes a device wallet, which is why every other rule here +/// can lean on `isDeviceWalletValid` without asking when it was set. +rule aValidDeviceWalletNeverBecomesInvalid(method f, address d) { + require isDeviceWalletValid(d); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert isDeviceWalletValid(d), "a device wallet lost its validity"; +} + +/// R-06, first half. The entry point is set once and never moves. +/// +/// Split from the admin below because the plan states all three as initialize-only and that is +/// wrong for two of them. This one is genuinely write-once. +rule theEntryPointMovesOnlyAtInitialization(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address, address, uint256).selector +} { + address entryPointBefore = entryPoint(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert entryPoint() == entryPointBefore, "the entry point moved outside initialization"; +} + +/// The vault moves only through its own setter. +/// +/// The vault is where every data bundle payment lands, and it is read here on each purchase rather +/// than cached anywhere, so this one write reaches every wallet. It used to sit on the device wallet +/// factory as well, where nothing on the payment path read it, which left the only rotatable copy +/// pointing somewhere the money never went. +rule theVaultMovesOnlyThroughItsSetter(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address, address, uint256).selector +} { + address vaultBefore = vault(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert vault() != vaultBefore => f.selector == sig:updateVaultAddress(address).selector, + "the vault address moved through something other than its setter"; +} + +/// The vault is never set to the zero address. +/// +/// A zero vault would send every subsequent data bundle payment to an address nobody holds. The +/// setter checks for it and this says the check has no way around it. +rule theVaultIsNeverSetToZero(address newVault) { + require vault() != 0; + + env callEnv; + updateVaultAddress@withrevert(callEnv, newVault); + + assert !lastReverted => vault() != 0, "the vault was set to the zero address"; +} + +/// R-06, second half. The admin is not write-once, and stating it as such would have proved +/// nothing. It moves through a two-step handover, and the property worth having is that the +/// handover is the only route and that it lands on the address that was nominated. +/// +/// Stated over `adminOfRecord` rather than the accessor. The two are different facts now: the +/// address on the books moves only by acceptance, while the power attached to it also goes away +/// on a suspension and on a nomination. Reading the accessor here would make the rule fail on +/// `disableAdmin` for a reason that has nothing to do with the address moving. +rule theAdminMovesOnlyToTheNominatedAddress(method f) filtered { + f -> f.selector != sig:initialize(address, address, address, address, address, address, uint256).selector +} { + address adminBefore = adminOfRecord(); + address nominated = newRequestedAdmin(); + + env callEnv; + calldataarg args; + f(callEnv, args); + + address adminAfter = adminOfRecord(); + + assert adminAfter != adminBefore => f.selector == sig:acceptAdminUpdate().selector, + "the admin moved through something other than acceptAdminUpdate"; + + assert adminAfter != adminBefore => adminAfter == nominated, + "the admin moved to an address that was never nominated"; +} + +/// The power and the address are separate facts, and the power never lands anywhere the address +/// is not. Without this, a suspension or a handover could in principle be read as handing the role +/// to some third address rather than to nobody. +rule onlyTheRecordedAdminEverHoldsThePower(method f) { + env callEnv; + calldataarg args; + f(callEnv, args); + + address acting = eSIMWalletAdmin(); + + assert acting == 0 || acting == adminOfRecord(), + "an address that is not on the books can act as admin"; +} + +/// A suspended admin and one with a handover outstanding are both powerless, whatever else is +/// true. This is what every gate in the protocol relies on: they compare `msg.sender` against the +/// accessor, and no transaction arrives from the zero address, so a zero here closes all of them. +rule aSuspendedOrHandedOverAdminCannotAct(method f) { + env callEnv; + calldataarg args; + f(callEnv, args); + + assert (adminDisabled() || newRequestedAdmin() != 0) => eSIMWalletAdmin() == 0, + "a suspended or handed-over admin can still act"; +} + +/// R-06, third half. Accepting the handover clears the nomination, so one nomination cannot be +/// replayed to take the role back after it has been passed on again. +rule acceptingTheAdminHandoverClearsTheNomination() { + env callEnv; + acceptAdminUpdate(callEnv); + + assert newRequestedAdmin() == 0, "the nomination survived being accepted"; +} + +/// An eSIM identifier is claimed once and never moves. +/// +/// The point of the record is that an identifier answers with one wallet, and a second write puts +/// it back where two could carry the same one. The claim survives an ownership transfer, since the +/// eSIM belongs to the wallet rather than to whichever device holds it, which is why this is stated +/// over every method rather than left to the one entry point. +rule theESIMIdentifierClaimIsWriteOnce(method f, bytes32 identifierHash) { + address holderBefore = claimedESIMIdentifiers(identifierHash); + require holderBefore != 0; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert claimedESIMIdentifiers(identifierHash) == holderBefore, + "an eSIM identifier changed hands after it was claimed"; +} + +/// Only a device wallet claims an eSIM identifier, and only through the one entry point. +/// +/// Parametric for the same reason as R-04. The claim is reachable directly, not only through +/// `DeviceWallet`, so its own gate is the whole of the access control and a second writer added +/// anywhere later fails here. +rule onlyADeviceWalletClaimsAnESIMIdentifier(method f, bytes32 identifierHash) { + require claimedESIMIdentifiers(identifierHash) == 0; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert claimedESIMIdentifiers(identifierHash) != 0 => + f.selector == sig:claimESIMIdentifier(string, address).selector, + "an eSIM identifier was claimed through something other than claimESIMIdentifier"; + + assert claimedESIMIdentifiers(identifierHash) != 0 => + isDeviceWalletValid(callEnv.msg.sender), + "an eSIM identifier was claimed by a caller the registry does not consider a device wallet"; +} diff --git a/certora/specs/RegistryCrossContract.spec b/certora/specs/RegistryCrossContract.spec new file mode 100644 index 00000000..2f884fd3 --- /dev/null +++ b/certora/specs/RegistryCrossContract.spec @@ -0,0 +1,330 @@ +/// Registry, DeviceWallet and ESIMWallet: whether the three agree on who holds an eSIM wallet. +/// +/// Every other spec in this directory proves one contract's storage against itself. This one is the +/// only cross-contract statement, and it is the one worth having, because the fact it fixes is split +/// across three contracts and no single one of them can be read to check it. A device wallet says +/// which eSIM wallets it holds, in `isValidESIMWallet`. The registry says which device wallet an +/// eSIM wallet belongs to, in `isESIMWalletValid`. The eSIM wallet itself says who owns it, through +/// `Ownable`. All three are written by different calls, and the ownership transfer moves them one at +/// a time rather than together. +/// +/// The scene therefore holds three linked instances rather than one contract and a wall of NONDET. +/// That matters more here than anywhere else: the guard that keeps a device wallet from binding an +/// eSIM wallet it does not own reads `ESIMWallet.owner()`, so under a NONDET summary the guard +/// answers arbitrarily and any device wallet walks in. The rules below would then fail for a reason +/// that exists only in the model. With the eSIM wallet in the scene the call resolves and the guard +/// is the one the chain runs. +/// +/// The price is that the statement is about one linked triple rather than every triple. A rule here +/// says: this device wallet, this eSIM wallet and this registry never come apart, over every method +/// of all three contracts. It does not quantify over other device wallets, and the second rule is +/// where that shows, since a second device wallet is exactly the party the guard is against. The +/// note on that rule says what is and is not covered. +/// +/// The direction is one way, and this is the correction rather than an omission. The two mappings +/// were once thought to be halves of one fact, and the milestone list asked for a biconditional: +/// `isESIMWalletValid[e] == d` if and only if `d.isValidESIMWallet[e]`. That is false, and it is +/// false on the normal path. `isESIMWalletValid` is a registration that also names the last device +/// wallet to hold the wallet, and it is never cleared, so after a release the registry still names +/// the previous holder while that holder's own mapping reads false. The direction that does hold is +/// the one that matters: a device wallet that currently holds an eSIM wallet is the device wallet +/// the registry names. +/// +/// Two of the three addresses are linked in the conf rather than merely required to match: +/// `DeviceWallet.registry` to the registry in the scene and `ESIMWallet.deviceWallet` to the device +/// wallet. A `require` fixes what an address equals, which is not the same as making the call at +/// that address run the callee's code, and without the link `bindESIMWallet` is summarised and the +/// registry side of every rule below is arbitrary. +/// +/// Scope. Calls out of the three contracts, into the two factories, the beacon and the entry point, +/// are still summarised as NONDET, so nothing here is a statement about re-entrancy. One consequence +/// worth naming: `deployESIMWallet` gets the address of the new wallet back from a summarised +/// factory, so the prover may hand it any address, this scene's eSIM wallet included. That is the +/// conservative direction and the add path checks ownership either way. The curve check +/// on an owner key is summarised for the same reason as in the other specs: it is field arithmetic +/// no rule reads an answer from. Loops unroll three times and hashing of unbounded arguments is +/// assumed within 224 bytes. +/// +/// A call leaving the scene is the one thing this spec has to be careful about, and the other specs +/// in this directory do not. They verify one contract, so a call out of it is havoced everywhere +/// else and the rule never notices. Here the rules read three contracts, so a havoc scoped to +/// everything except the caller rewrites the two a rule reads but does not call, and the assert +/// fails for a reason that exists only in the model. The methods block names every such signature +/// so none of them is left to the prover's own choice, and the conf assumes an unresolved +/// contract's fallback has no side effects for the same reason. +/// +/// Reading the result, which the headline count gets backwards. `rule_sanity` appends `assert false` +/// to each rule and the log carries the verdict of that modified rule, not a verdict on the check. +/// `Violated: --rule_not_vacuous` means the body was reachable, which is the outcome +/// wanted. A `rule_not_vacuous` record reading verified is the failure. Count the records carrying +/// no sanity suffix and ignore the fraction. + +using DeviceWallet as deviceWallet; +using ESIMWallet as eSIMWallet; + +methods { + function isESIMWalletValid(address) external returns (address) envfree; + function isESIMWalletOnStandby(address) external returns (bool) envfree; + function isDeviceWalletValid(address) external returns (bool) envfree; + + function deviceWallet.isValidESIMWallet(address) external returns (bool) envfree; + function deviceWallet.registry() external returns (address) envfree; + + function eSIMWallet.owner() external returns (address) envfree; + function eSIMWallet.newRequestedOwner() external returns (address) envfree; + function eSIMWallet.deviceWallet() external returns (address) envfree; + + /// The calls between these three contracts run real code, and nothing else does. + /// + /// The other specs in this directory summarise every external call, which is right when a rule + /// reads one contract's storage and nothing else. Here it is wrong: the calls between these + /// three are the whole subject, and a summarised call is havoced everywhere except the contract + /// under verification, so a summarised `bindESIMWallet` would leave the registry side of every + /// rule arbitrary. The first run of this spec did exactly that and every method containing an + /// external call failed while every view method passed. + /// + /// Resolution is per signature rather than per contract, and the difference is not cosmetic. A + /// list naming whole contracts applies to every unresolved call in the scene, so the call to the + /// entry point inside `addDeposit` becomes a candidate for resolution into one of these three, + /// and a method that touches none of this storage fails anyway. That is what the second run + /// reported. Naming signatures means a call is resolved only where the callee really is one of + /// these contracts, and everything else stays summarised. + /// + /// Each of the six is a call made on an address taken from a parameter rather than from storage. + /// The two made through a storage field, `DeviceWallet.registry` and `ESIMWallet.deviceWallet`, + /// need nothing here because the conf links them. + function _.owner() external => DISPATCHER(true); + function _.newRequestedOwner() external => DISPATCHER(true); + function _.sendETHToDeviceWallet(uint256 amount) external => DISPATCHER(true); + function _.setESIMUniqueIdentifier(string identifier) external => DISPATCHER(true); + function _.addESIMWallet(address walletAddress, bool hasAccessToETH) external => DISPATCHER(true); + function _.setESIMUniqueIdentifierForAnESIMWallet(address walletAddress, string identifier) + external => DISPATCHER(true); + /// The registry reads this off `msg.sender` while a device wallet is claiming an eSIM + /// identifier, so the callee is the scene's device wallet whenever that wallet is the caller. + function _.deviceUniqueIdentifier() external => DISPATCHER(true); + function _.populateHistory(ESIMWallet.DataBundleDetails[] bundles) external => DISPATCHER(true); + + /// Every call that leaves the three contracts, named one signature at a time. + /// + /// The `unresolved external` line below does not reach these. It catches a call whose selector + /// the prover cannot work out; here the selector is known and only the callee address is, since + /// it comes from a storage field pointing at a contract outside the scene. The prover then picks + /// its own summary, and the one it picks havocs every contract except the caller. That rewrites + /// the two contracts a rule reads but does not call, so `addDeposit`, whose whole body is one + /// entry point call, failed a rule about who owns an eSIM wallet. The call trace named it: an + /// `AUTO havoc` on storage path `DeviceWallet.entryPoint`, scoped to everything except + /// `DeviceWallet`. + /// + /// NONDET rather than a link because none of these four is part of the statement being proved. + /// The entry point holds gas deposits and the factories hand back addresses, and a rule here + /// reads neither. Handing back an arbitrary address is the conservative direction anyway: the + /// add path checks ownership whatever address it is given. + function _.balanceOf(address account) external => NONDET; + function _.depositTo(address account) external => NONDET; + function _.withdrawTo(address withdrawAddress, uint256 amount) external => NONDET; + function _.deployESIMWallet(address deviceWalletAddress, uint256 salt) external => NONDET; + /// `bindESIMWallet` asks the factory whether it deployed the wallet. NONDET is the conservative + /// direction: a rule here is about who ends up holding the wallet, and an arbitrary answer + /// leaves both the accepting and the refusing path open. + function _.isESIMWalletDeployed(address eSIMWalletAddress) external => NONDET; + function _.deployDeviceWalletForUsers( + string[] deviceUniqueIdentifiers, + bytes32[2][] ownerKeys, + uint256[] salts, + uint256[] depositAmounts + ) external => NONDET; + /// The two reservation reads the registry makes on the lazy wallet registry, whose address it + /// holds in a storage field. Out of the scene, so the same trap applies as to the four above: a + /// selector the prover knows with a callee it does not, which the `unresolved external` line + /// never reaches. NONDET leaves both the reserved and the free answer open, which is the + /// conservative direction for a rule about who holds an eSIM wallet. + function _.isDeviceIdentifierReserved(string deviceUniqueIdentifier) external => NONDET; + function _.eSIMIdentifierToDeviceIdentifier(string eSIMUniqueIdentifier) external => NONDET; + + /// The calls on the ownership path, and only those. + /// + /// The default case runs no code, so a state-changing call left out becomes a no-op that + /// reports success. `removeESIMWallet` was missing and the removal wrote nothing, which read as + /// a device wallet still holding a wallet it had just let go. + /// + /// The list stops here rather than covering every call between the three contracts. A complete + /// list of twenty-five signatures turned every call inside `execute`, whose target and calldata + /// are both arbitrary, into a case split over all of them: the run went from twenty-five minutes + /// to a hundred and three and died on a segfault with no report. + unresolved external in _._ => DISPATCH [ + DeviceWallet.registry(), + DeviceWallet.isValidESIMWallet(address), + DeviceWallet.addESIMWallet(address, bool), + DeviceWallet.removeESIMWallet(address, bool), + Registry.isDeviceWalletValid(address) + ] default NONDET; + + function FCL_Elliptic_ZZ.ecAff_isOnCurve(uint256 x, uint256 y) internal returns (bool) => NONDET; +} + +/// The three initialisers, filtered out of every rule below. +/// +/// Each writes the storage the rules are about, straight in, with no guard but the `initializer` +/// modifier. That is a state no deployment reaches: all three contracts are set up in the same +/// transaction that creates the proxy, with the initialiser passed as the constructor argument. The +/// prover disagrees because OpenZeppelin's `initializer` recognises a constructor by +/// `initialized == 1 && address(this).code.length == 0`, and the prover models these contracts as +/// carrying code, so it starts from an uninitialised wallet no proxy ever is. +/// +/// The inherited `initialize(bytes32[2])` used to need a fourth entry here. It is internal now, so +/// the prover no longer enumerates it as a method at all. +definition isInitialiser(method f) returns bool = + f.selector == sig:DeviceWallet.init(address, bytes32[2], string, address).selector + || f.selector == sig:ESIMWallet.initialize(address, address).selector + || f.selector == sig:Registry.initialize(address, address, address, address, address, address, uint256).selector; + +/// The three instances in the scene are wired to each other. +/// +/// Without this the prover is free to hand back a device wallet pointing at some other registry, or +/// a registry that has never heard of the device wallet, and every rule below fails on a triple that +/// no deployment produces. The device wallet's registry address is written once at `init` and the +/// registry flag is set when the factory reports the deployment, so both are facts about any wallet +/// that reached the protocol at all. +function requireLinkedScene() { + require deviceWallet.registry() == currentContract; + /// The conf links this field, which binds the address but does not stop the prover summarising + /// a call made through it. Without this the eSIM wallet's own device wallet is free to be some + /// other address, so `requestTransferOwnership` removes the wallet from a contract no rule + /// reads while the scene's device wallet keeps holding it. + require eSIMWallet.deviceWallet() == deviceWallet; + require isDeviceWalletValid(deviceWallet); + require deviceWallet != eSIMWallet; + require deviceWallet != currentContract; + require eSIMWallet != currentContract; +} + +/// An outstanding transfer request means the holder has already let go. +/// +/// Proved first because the three rules below carry it as a precondition, and it is the fact that +/// makes `acceptOwnershipTransfer` safe. That function moves `owner()` to the requested address +/// without consulting either wallet's mapping, so if a device wallet could still be holding the +/// eSIM wallet while a request stood, acceptance would hand ownership away from underneath it. +/// +/// What rules that out is the order inside `requestTransferOwnership`: it removes the eSIM wallet +/// from the current holder, if it is still held, before it writes `newRequestedOwner`, so the flag +/// is already down by the time the request exists. Re-targeting an outstanding request takes the +/// skipped branch and finds the flag down from the first request. The revoke path writes zero and +/// touches no mapping, and the re-add path runs after acceptance has cleared the request, so +/// neither reopens the gap. +rule aTransferRequestMeansTheHolderHasAlreadyLetGo(method f) filtered { f -> !isInitialiser(f) } { + requireLinkedScene(); + require eSIMWallet.newRequestedOwner() != 0 => !deviceWallet.isValidESIMWallet(eSIMWallet); + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert eSIMWallet.newRequestedOwner() != 0 => !deviceWallet.isValidESIMWallet(eSIMWallet), + "a transfer was requested while the device wallet still held the eSIM wallet"; +} + +/// A device wallet that holds an eSIM wallet is the owner of it. +/// +/// This is the precondition the registry rule below needs, and it is worth proving on its own: it is +/// the fact that keeps the two ownership records from drifting. `_addESIMWallet` refuses a wallet +/// whose `owner()` is not the calling device wallet, and ownership only moves through +/// `requestTransferOwnership`, which takes the wallet off the current holder before it writes +/// anything. So the holding flag and the ownership always move in the same call. +/// +/// Stated as a transition rule rather than an invariant for the same reason as everywhere else in +/// this directory: these contracts sit behind beacon proxies and are set up in an initialiser rather +/// than a constructor, so an invariant's base case would be arguing about a state the proxy never +/// occupies. +rule aHeldESIMWalletIsOwnedByTheDeviceWalletHoldingIt(method f) filtered { f -> !isInitialiser(f) } { + requireLinkedScene(); + require deviceWallet.isValidESIMWallet(eSIMWallet) => eSIMWallet.owner() == deviceWallet; + + /// Carried from the rule above rather than assumed. `acceptOwnershipTransfer` moves `owner()` + /// with no reference to either mapping, so without this it lands on a state where the device + /// wallet is still holding a wallet whose ownership has just moved. That state is unreachable + /// and the rule above is what says so. + require eSIMWallet.newRequestedOwner() != 0 => !deviceWallet.isValidESIMWallet(eSIMWallet); + + /// The zero address cannot originate a call. Without this the prover accepts an ownership + /// transfer requested of nobody: `acceptOwnershipTransfer` compares `msg.sender` against + /// `newRequestedOwner` and both being zero passes, which walks the eSIM wallet's owner to zero + /// while the device wallet still holds it. Nothing on chain reaches that, so a zero-check on the + /// accept path would defend against nothing. The same artifact shows up on `Ownable2Step` in the + /// factory spec. + env callEnv; + require callEnv.msg.sender != 0; + + calldataarg args; + f(callEnv, args); + + assert deviceWallet.isValidESIMWallet(eSIMWallet) => eSIMWallet.owner() == deviceWallet, + "a device wallet held an eSIM wallet it does not own"; +} + +/// The registry names the device wallet that currently holds an eSIM wallet. +/// +/// The point of the whole spec. `isValidESIMWallet` on the device wallet is what every ETH path +/// checks; `isESIMWalletValid` on the registry is what the rest of the protocol reads to decide +/// whether an eSIM wallet belongs to anyone. If the two disagree, an eSIM wallet is being spent +/// against by one device wallet while the registry attributes it to another. +/// +/// Only one direction is asserted, and the reverse is not an omission. The registry entry is a +/// permanent registration that also names the last holder, so after `removeESIMWallet` it still +/// names the previous device wallet while that wallet's own flag has already gone down. That gap is +/// the transfer window and it is intended. +/// +/// The rule carries the ownership fact above as a precondition rather than restating it, which is +/// what makes the binding guard readable: `bindESIMWallet` accepts a caller that either owns the +/// eSIM wallet or is already the associated device wallet, and the precondition is what rules out a +/// second device wallet satisfying the first of those while this one still holds the wallet. +/// +/// What is not covered, stated plainly. The scene has one device wallet, so a second device wallet +/// calling `bindESIMWallet` is modelled as an arbitrary address rather than as a real instance. Its +/// call still has to get past the same two guards, both of which read storage that is in the scene, +/// so the guard itself is exercised; what is missing is any statement about that second wallet's own +/// mapping. Covering it needs two device wallet instances, which doubles the parametric instances +/// for a fact the first rule already fixes on each of them separately. +rule theRegistryNamesTheDeviceWalletThatHoldsTheESIMWallet(method f) filtered { f -> !isInitialiser(f) } { + requireLinkedScene(); + require deviceWallet.isValidESIMWallet(eSIMWallet) => eSIMWallet.owner() == deviceWallet; + require deviceWallet.isValidESIMWallet(eSIMWallet) => + isESIMWalletValid(eSIMWallet) == deviceWallet; + + env callEnv; + calldataarg args; + f(callEnv, args); + + assert deviceWallet.isValidESIMWallet(eSIMWallet) => + isESIMWalletValid(eSIMWallet) == deviceWallet, + "the registry named a different device wallet than the one holding the eSIM wallet"; +} + +/// Releasing an eSIM wallet moves both records in one call. +/// +/// The release is the one operation that writes a mapping in each contract, and it writes them +/// through two different contracts in one transaction: the device wallet lowers its own holding flag +/// and then calls the registry to raise the transit marker. Either write landing without the other +/// is a wallet that reads as held by nobody with no transfer outstanding, or as in transit while its +/// holder still spends against it. +/// +/// This began as a parametric rule saying a held wallet is never on the marker, and the prover was +/// right to refuse it. `toggleESIMWalletStandbyStatus` is public and asks only that the caller is +/// the associated device wallet, so a holder can raise the marker on a wallet it has not released. +/// The natspec on that function says the same thing in words. A parametric rule forbidding the pair +/// would have been a rule about a design the protocol does not have, which is the mistake the +/// association rules already made once. +/// +/// Stated as a direct call rather than parametrically, because the claim is about one operation +/// being atomic across two contracts and not about every method preserving something. +rule releasingAnESIMWalletMovesBothRecordsTogether(address wallet, bool callBackETH) { + requireLinkedScene(); + + env callEnv; + deviceWallet.removeESIMWallet(callEnv, wallet, callBackETH); + + assert !deviceWallet.isValidESIMWallet(wallet), + "a released eSIM wallet was still held by the device wallet"; + assert isESIMWalletOnStandby(wallet), + "a released eSIM wallet was not put on the transit marker"; +} diff --git a/certora/specs/Smoke.spec b/certora/specs/Smoke.spec new file mode 100644 index 00000000..ea78adcb --- /dev/null +++ b/certora/specs/Smoke.spec @@ -0,0 +1,35 @@ +/// Smoke spec. It exists to answer one question: can the prover compile and reason about this +/// codebase end to end. It asserts nothing about the protocol. +/// +/// Every external method is run symbolically and asked to be reachable. A method that cannot be +/// reached means the prover choked on it, which is what we want to find out before writing specs +/// that depend on it. + +methods { + function owner() external returns (address) envfree; +} + +/// Every method is reachable under some input. +/// +/// renounceOwnership is excluded because it reverts on every input by design: the override at +/// Registry.sol:97 rejects the call outright, since the owner is the only caller _authorizeUpgrade +/// accepts and renouncing would freeze the contract on its current logic. Unreachable is the +/// correct answer for it, so asking the question is noise. +rule everyMethodIsReachable(method f) filtered { + f -> f.selector != sig:renounceOwnership().selector +} { + env e; + calldataarg args; + + f(e, args); + + satisfy true; +} + +/// The owner is a stable value across a call that does not touch ownership +rule ownerIsReadable() { + address before = owner(); + address after = owner(); + + assert before == after, "owner() is not deterministic"; +} diff --git a/contracts/CustomStructs.sol b/contracts/CustomStructs.sol index 11c45265..36535614 100644 --- a/contracts/CustomStructs.sol +++ b/contracts/CustomStructs.sol @@ -1,6 +1,5 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; /// @notice Data Bundle related details stored in the eSIM wallet struct DataBundleDetails { @@ -14,6 +13,9 @@ struct Wallets { address eSIMWallet; } +/// @notice One WebAuthn assertion, as the authenticator produced it +/// @dev Decoded from calldata by `WebAuthn.tryDecodeSignature`, which zeroes the whole struct on a +/// malformed body rather than reverting. A zeroed struct fails verification. struct WebAuthnSignature { bytes authenticatorData; // The WebAuthn authenticator data. // See https://www.w3.org/TR/webauthn-2/#dom-authenticatorassertionresponse-authenticatordata. @@ -25,6 +27,7 @@ struct WebAuthnSignature { uint256 s; // The s value of secp256r1 signature } +/// @notice One call an account makes on its owner's behalf struct Call { address dest; uint256 value; diff --git a/contracts/Errors.sol b/contracts/Errors.sol index 4b7e81dd..4df2fc92 100644 --- a/contracts/Errors.sol +++ b/contracts/Errors.sol @@ -1,38 +1,118 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity 0.8.36; +/// @notice Every custom error the protocol reverts with, in one place +/// @dev An interface rather than a library so each contract reaches them as `Errors.Name` without +/// inheriting anything. Grouped by the contract that raises them; several are shared, and the +/// comment above each group names who uses it. interface Errors { + // Any contract rejecting a zero address argument. The parameter name identifies which + // argument was zero, since a single function often checks several. + error ZeroAddress(string parameter); + + // Every Ownable contract: Registry, LazyWalletRegistry, DeviceWalletFactory, + // ESIMWalletFactory and ESIMWallet + error OwnershipCannotBeRenounced(); + // Registry and ESIMWallet error OnlyDeviceWallet(); // Registry error OnlyDeviceWalletFactory(); + error OnlyRequestedAdmin(address requestedAdmin); + error NotTheESIMWalletOwnerOrItsDeviceWallet(address eSIMWallet); + error ESIMWalletOwnershipTransferPending(address eSIMWallet, address newRequestedOwner); + error NotTheAssociatedDeviceWallet(address eSIMWallet, address associatedDeviceWallet); + error AdminAlreadyDisabled(); + error AdminNotDisabled(); + + // Registry, DeviceWallet and ESIMWallet + error ProtocolPaused(); // RegistryHelper error OnlyLazyWalletRegistry(); + error DeviceIdentifierAlreadyRegistered(string deviceIdentifier); + error OwnerKeyAlreadyRegistered(bytes32 ownerKeyHash); + error SaltTooHigh(uint256 salt, uint256 count); + error DeviceWalletAlreadyExists(string deviceIdentifier, address deviceWallet); + error NotAProtocolESIMWallet(address eSIMWallet); + error DeviceIdentifierReservedForLazyWallet(string deviceIdentifier); + error ESIMIdentifierReservedForLazyWallet(string eSIMIdentifier); + error ESIMIdentifierAlreadyClaimed(string eSIMIdentifier, address eSIMWallet); + + // Any contract rejecting an identifier it was handed empty + error EmptyDeviceIdentifier(); + error EmptyESIMIdentifier(); + + // Any contract taking parallel arrays: LazyWalletRegistry and DeviceWalletFactory + error ArrayLengthMismatch(uint256 expected, uint256 actual); + + // LazyWalletRegistry + error LazyWalletAlreadyDeployed(string deviceIdentifier); + error IdentifierTooLong(string identifier, uint256 maxLength); + error DepositDoesNotMatchValue(uint256 depositAmount, uint256 value); + error NoESIMIdentifiersForDevice(string deviceIdentifier); + error UnknownESIMIdentifier(string eSIMIdentifier); + error ESIMBoundToADifferentDevice(string eSIMIdentifier, string boundDeviceIdentifier); + error ESIMIdentifierNotFound(string eSIMIdentifier, string deviceIdentifier); + error CannotSwitchToTheSameDevice(string deviceIdentifier); + error ESIMWalletNotLazyDeployed(string eSIMIdentifier); + error HistoryAlreadyCopied(string eSIMIdentifier); + error TooManyHistoryEntries(uint256 requested, uint256 maxPerCall); + error LazyWalletNotDeployed(string deviceIdentifier); + error AllESIMWalletsDeployed(string deviceIdentifier); + error TooManyESIMWallets(uint256 requested, uint256 maxPerCall); // ESIMWalletFactory error OnlyRegistryOrDeviceWalletFactoryOrDeviceWallet(); + error OnlyDeployForSelf(); + error SaltAlreadyUsed(address deviceWallet, uint256 salt); + + // Any factory holding a beacon: ESIMWalletFactory and DeviceWalletFactory + error RegistryAlreadySet(address registry); + error ImplementationUnchanged(address implementation); // DeviceWalletFactory error OnlyAdmin(); error OnlyAdminOrRegistry(); error OnlyEntryPoint(); + error InvalidDeviceWalletOwnerKey(); + error VaultUnchanged(address vault); + error EmptyBatch(); + error DeviceWalletInfoAlreadyAdded(address deviceWallet); + error DeviceWalletMismatch(address deviceWallet, address derived); + error DeviceWalletNotDeployed(address deviceWallet); + + // Account4337, and so DeviceWallet through it + error OnlySelf(); + error OnlyEntryPointOrSelf(); // ESIMWallet and DeviceWallet error FailedToTransfer(); + error InsufficientBalance(uint256 balance, uint256 amount); // ESIMWallet error OnlyRegistry(); - error OnlyESIMWalletAdminOrESIMWalletfactoryOrDeviceWallet(); error OnlyDeviceWalletOrESIMWalletAdmin(); + error DataBundlePriceAboveCap(uint256 price, uint256 cap); + error ESIMIdentifierAlreadySet(string eSIMUniqueIdentifier); + error EmptyDataBundleID(); + error ZeroDataBundlePrice(); + error ZeroDataBundlePriceCap(); + error NotADeviceWallet(address account); + error OnlyRequestedOwner(address newRequestedOwner); + error UseAcceptOwnershipTransfer(); // DeviceWallet + error UnknownESIMWallet(address eSIMWallet); + error ZeroAmount(); + error ETHAccessRevoked(address eSIMWallet); + error ETHAccessNotGrantableAtBind(address eSIMWallet); + error ESIMWalletAlreadyAdded(address eSIMWallet); + error ESIMWalletNotOwnedByThisDeviceWallet(address eSIMWallet, address eSIMWalletOwner); error OnlyRegistryOrDeviceWalletFactoryOrOwner(); error OnlySelfOrAssociatedESIMWallet(); error OnlyESIMWalletAdminOrRegistry(); - error OnlyESIMWalletAdminOrDeviceWalletOwner(); - error OnlyESIMWalletAdminOrDeviceWalletFactory(); error OnlyAssociatedESIMWallets(); error OnlyESIMWalletAdmin(); } diff --git a/contracts/LazyWalletRegistry.sol b/contracts/LazyWalletRegistry.sol index 24cc4d2c..0f2e7532 100644 --- a/contracts/LazyWalletRegistry.sol +++ b/contracts/LazyWalletRegistry.sol @@ -1,17 +1,103 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Libraries +import {Errors} from "./Errors.sol"; + +// Types +import {DataBundleDetails} from "./CustomStructs.sol"; +// Contracts import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; - import {Registry} from "./Registry.sol"; -import "./CustomStructs.sol"; -/// @notice Contract for deploying the factory contracts and maintaining registry +/// @notice Holds what a fiat user bought before they had a wallet, then deploys the wallets and +/// copies the record onto them +/// @dev Everything here is keyed by string identifiers rather than by address, because a lazy user +/// has no address yet. Deployment and the history copy are both batched and both carry their +/// own cursor in storage, so a dropped transaction is retried by repeating the same call. Each +/// batch loop reverts on its terminal condition rather than returning quietly, which is what +/// lets a caller loop until it stops. contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable { + /// @notice Longest device or eSIM identifier accepted when a new binding is created + /// @dev An eSIM identifier is a UUID v4 in string form, so 36 bytes. This leaves room for a + /// longer device identifier while keeping both inside two storage words, which bounds the + /// keccak cost of the linear scan the switch path runs over the whole list. + uint256 private constant MAX_IDENTIFIER_LENGTH = 64; + + /// @notice Most purchase history entries `setHistoryForLazyWallet` will copy in one call + /// @dev Each entry costs roughly 50,000 gas to write into the wallet, so a full batch is around + /// 2,500,000. The limit is about keeping a failed batch cheap to retry rather than about + /// the block limit, which is 30,000,000 at its tightest across the deployment chains. + /// Refused rather than clamped, so a caller never believes it wrote more than it did. + uint256 public constant MAX_HISTORY_ENTRIES_PER_CALL = 50; + + /// @notice Most eSIM wallets a single call will deploy for one device + /// @dev A deployment costs roughly 500,000 gas per eSIM wallet, so a full batch is around + /// 10,000,000. As with the history cap this is set for retry cost rather than the block + /// limit: a batch that runs out of gas is paid for and thrown away, and a device with forty + /// eSIMs should not lose a whole block's worth of gas to one bad estimate. It also leaves + /// room for `forge coverage --ir-minimum`, which inflates the same call by about a fifth. + /// Refused rather than clamped, so a caller never believes it deployed more than it did. + uint256 public constant MAX_ESIM_WALLETS_PER_CALL = 20; + + /// @dev Slot that used to hold a copy of the upgrade authority. It was written once in + /// `initialize` and had no setter, so it kept naming the deploy-time address once + /// ownership moved on. Kept so nothing below it shifts on the live proxies. Never read; + /// `upgradeManager()` returns `owner()` instead. + /// + /// Slither raises `unused-state` and `constable-states` here. Both are false: occupying + /// the slot is the whole job, and either change takes it out of storage and moves every + /// variable below it. + address private _retiredUpgradeManager; + + /// @notice Registry contract instance + Registry public registry; + + /// @notice eSIM identifiers and their details associated with the device identifiers + mapping(string deviceIdentifier => mapping(string eSIMIdentifier => DataBundleDetails[] dataBundleDetails)) public deviceIdentifierToESIMDetails; + + /// @notice Mapping from eSIM unique identifier to device unique identifier + /// @dev A device identifier can have multiple associated eSIM identifiers. + /// But an eSIM identifier can have only a single device identifier. + mapping(string eSIMIdentifier => string deviceIdentifier) public eSIMIdentifierToDeviceIdentifier; + + /// @notice List of eSIM identifiers associated with the device identifiers + mapping(string deviceIdentifier => string[] associatedESIMIdentifiers) public eSIMIdentifiersAssociatedWithDeviceIdentifier; + + /// @notice How many of an eSIM's stored purchase entries have already reached its wallet + /// @dev The wallet appends whatever batch it is handed, so this is the only thing stopping a + /// repeated call from writing the same entries twice. Reading it rather than taking start + /// and end indexes from the caller also makes two admin transactions in flight at once + /// safe: the second reads the position the first left. + mapping(string eSIMIdentifier => uint256 copied) public historyEntriesCopied; + + /// @notice The eSIM wallet this contract deployed for an eSIM identifier + /// @dev Nothing enforces that an eSIM identifier is unique across eSIM wallets, so without this + /// record a wallet deployed through the ordinary route could claim an identifier that + /// already belongs to a lazy user and receive their purchase history. Written from the + /// addresses the deployment returns, and unaffected by any later ownership transfer, so + /// the copy follows the wallet rather than whichever device is holding it. + mapping(string eSIMIdentifier => address eSIMWallet) public lazyDeployedESIMWallet; + + /// @notice How many of a device's eSIM wallets this contract has already deployed + /// @dev Also the marker for the lazy route itself. The first batch always deploys at least one + /// wallet, so a non-zero value means this contract set the device up. Reading the registry + /// for a device wallet instead would accept one deployed through the ordinary route under + /// an identifier a lazy user's eSIMs are already bound to, and hand that device their + /// wallets. + mapping(string deviceIdentifier => uint256 deployed) public eSIMWalletsDeployed; + + /// @notice Salt the device's first deployment batch started from + /// @dev Every later batch derives its salts from this, so the sequence continues rather than + /// restarting on an address that already holds a wallet. Stored rather than taken from the + /// caller again, because a value that disagrees with the first batch is not something the + /// contract can detect: it just produces different addresses. + mapping(string deviceIdentifier => uint256 baseSalt) public lazyDeploymentSalt; + /// @notice Emitted when data related to a device is updated event DataUpdatedForDevice( string _deviceUniqueIdentifier, string[] _eSIMUniqueIdentifiers, DataBundleDetails[] _dataBundleDetails @@ -21,14 +107,38 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra event ESIMBindedWithDevice(string _eSIMUniqueIdentifier, string _deviceUniqueIdentifier); /// @notice Emitted when the Lazy wallet is deployed + /// @dev The device wallet is indexed so an indexer can follow one device without reading every + /// log. The two string arrays are left unindexed on purpose: indexing a dynamic type stores + /// its hash instead of its value, which no consumer of these can use. event LazyWalletDeployed( bytes32[2] _deviceOwnerPublicKey, - address deviceWallet, + address indexed deviceWallet, string _deviceUniqueIdentifier, address[] eSIMWallets, string[] _eSIMUniqueIdentifiers ); + /// @notice Emitted for every batch of eSIM wallets deployed for a device, including the first. + /// `_remaining` reaching zero is what says the device is fully deployed. + /// @dev `LazyWalletDeployed` fires once, when the device wallet itself is created, and carries + /// only the first batch. Anything waiting for the whole set has to follow this instead. + event LazyESIMWalletsDeployed( + string _deviceUniqueIdentifier, + address indexed _deviceWallet, + address[] _eSIMWallets, + string[] _eSIMUniqueIdentifiers, + uint256 _remaining + ); + + /// @notice Emitted for every batch of purchase history copied into a deployed eSIM wallet. + /// `_remaining` reaching zero is what says the copy is finished. + event LazyHistoryCopied( + string _eSIMIdentifier, + address indexed _eSIMWallet, + uint256 _copied, + uint256 _remaining + ); + /// @notice Emitted when the user switches eSIM to a new device event ESIMIdentifierSwitchedToNewDeviceIdentifier( string _eSIMIdentifier, @@ -48,7 +158,7 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra DataBundleDetails[] _newDataBundleDetails ); - /// @notice Emitted when teh Data bundle related details are deleted from the old device identifer + /// @notice Emitted when the data bundle details are deleted from the old device identifier event DataBundleDetailsDeletedFromOldDeviceIdentifier( string _oldDeviceIdentifier, string _eSIMIdentifier @@ -56,8 +166,8 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra /// @notice Emitted when an eSIM identifier is removed from a device identifier's list event ESIMIdentifierRemovedFromOldDeviceIdentifier( - string _oldDeviceIdentifier, - string _eSIMIdentifier, + string _oldDeviceIdentifier, + string _eSIMIdentifier, string[] _eSIMIdentifierOfOldDevice ); @@ -68,63 +178,49 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra string[] _eSIMIdentifierOfNewDevice ); - /// @notice Address (owned/controlled by eSIM wallet project) that can upgrade contracts - address public upgradeManager; - - /// @notice Registry contract instance - Registry public registry; - - /// @notice eSIM identifiers and their details associated with the device identifiers - mapping(string deviceIdentifier => mapping(string eSIMIdentifier => DataBundleDetails[] dataBundleDetails)) public deviceIdentifierToESIMDetails; - - /// @notice Mapping from eSIM unique identifier to device unique identifier - /// @dev A device identifier can have multiple associated eSIM identifiers. - /// But an eSIM identifier can have only a single device identifier. - mapping(string eSIMIdentifier => string deviceIdentifier) public eSIMIdentifierToDeviceIdentifier; - - /// @notice List of eSIM identifiers associated with the device identifiers - mapping(string deviceIdentifier => string[] associatedESIMIdentifiers) public eSIMIdentifiersAssociatedWithDeviceIdentifier; - + /// @notice Restricts a call to the eSIM wallet admin + /// @dev Read from the registry on every call, so a rotation there takes effect immediately. + /// Every state-changing function in this contract sits behind it. modifier onlyESIMWalletAdmin() { - require(msg.sender == registry.eSIMWalletAdmin(), "Only eSIM wallet admin"); + if(msg.sender != registry.eSIMWalletAdmin()) revert Errors.OnlyESIMWalletAdmin(); _; } - // /// @custom:oz-upgrades-unsafe-allow constructor - // constructor() initializer {} + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- - /// @dev Owner based upgrades - function _authorizeUpgrade(address newImplementation) - internal - onlyOwner - override - {} + /// @dev Locks the implementation contract itself. Without this, anyone can call initialize + /// directly on the implementation and own it. The proxy is unaffected either way, but an + /// owned implementation is a trap for any later upgrade that adds an outward call. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /// @notice Points this contract at the registry and hands ownership to the upgrade manager + /// @param _registry Registry this contract reads the admin from and deploys wallets through + /// @param _upgradeManager Admin address responsible for upgrading contracts function initialize( address _registry, address _upgradeManager ) external initializer { - require(_registry != address(0), "Registry 0"); - require(_upgradeManager != address(0), "Manager 0"); - + if(_registry == address(0)) revert Errors.ZeroAddress("_registry"); + if(_upgradeManager == address(0)) revert Errors.ZeroAddress("_upgradeManager"); + registry = Registry(_registry); - upgradeManager = _upgradeManager; __Ownable2Step_init(); __Ownable_init(_upgradeManager); } - /// @notice Function to check if a lazy wallet has been deployed or not - /// @return Boolean. True if deployed, false otherwise - function isLazyWalletDeployed(string calldata _deviceUniqueIdentifier) public view returns (bool) { - if(registry.uniqueIdentifierToDeviceWallet(_deviceUniqueIdentifier) != address(0)) { - return true; - } - - return false; - } + // --------------------------------------------------------------------------------------------- + // Recording purchases made before deployment + // --------------------------------------------------------------------------------------------- /// @notice Function to populate all the device and eSIM related data along with the data bundles + /// @dev Refused for any device that already has a wallet, which is what freezes a device's eSIM + /// list and its history for the whole time a deployment is walking them. /// @param _deviceUniqueIdentifiers List of device unique identifiers associated with the eSIM related data /// @param _eSIMUniqueIdentifiers 2D array of all the eSIMs corresponding to their device identifiers. /// @param _dataBundleDetails 2D array of all the new data bundles bought for the respective eSIMs @@ -134,81 +230,337 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra DataBundleDetails[][] calldata _dataBundleDetails ) external onlyESIMWalletAdmin { uint256 len = _deviceUniqueIdentifiers.length; - require(len == _eSIMUniqueIdentifiers.length, "Unequal array provided"); - require(len == _dataBundleDetails.length, "Unequal array provided"); + if(len != _eSIMUniqueIdentifiers.length) { + revert Errors.ArrayLengthMismatch(len, _eSIMUniqueIdentifiers.length); + } + if(len != _dataBundleDetails.length) { + revert Errors.ArrayLengthMismatch(len, _dataBundleDetails.length); + } for(uint256 i=0; i 0, "No eSIM identifier found"); + // The whole salt range is reserved here rather than one batch at a time, because every later + // batch continues from this salt. A range that overflows partway would leave a device that + // cannot be finished and cannot be redeployed either. + if(total + _salt >= type(uint256).max) revert Errors.SaltTooHigh(_salt, total); - address[] memory eSIMWallets = new address[](eSIMUniqueIdentifiers.length); - DataBundleDetails[][] memory listOfDataBundleDetails = new DataBundleDetails[][](eSIMUniqueIdentifiers.length); + uint256 batchSize = _boundedBatchSize(_maxWallets, total); + string[] memory batchIdentifiers = _readIdentifiers(allESIMIdentifiers, 0, batchSize); - for(uint256 i=0; i MAX_HISTORY_ENTRIES_PER_CALL) { + revert Errors.TooManyHistoryEntries(_maxEntries, MAX_HISTORY_ENTRIES_PER_CALL); + } + + // This lookup is the whole authorisation. An identifier only has an entry here if this + // contract deployed a wallet for it, so history cannot be aimed at a wallet somebody else + // created under the same identifier. + address eSIMWallet = lazyDeployedESIMWallet[_eSIMIdentifier]; + if(eSIMWallet == address(0)) revert Errors.ESIMWalletNotLazyDeployed(_eSIMIdentifier); + + string memory deviceIdentifier = eSIMIdentifierToDeviceIdentifier[_eSIMIdentifier]; + DataBundleDetails[] storage history = deviceIdentifierToESIMDetails[deviceIdentifier][_eSIMIdentifier]; + + uint256 alreadyCopied = historyEntriesCopied[_eSIMIdentifier]; + uint256 outstanding = history.length - alreadyCopied; + if(outstanding == 0) revert Errors.HistoryAlreadyCopied(_eSIMIdentifier); + + copied = outstanding > _maxEntries ? _maxEntries : outstanding; + remaining = outstanding - copied; + + DataBundleDetails[] memory batch = new DataBundleDetails[](copied); + for(uint256 i=0; i= 1, "Device identifier 0"); - require(isLazyWalletDeployed(_deviceUniqueIdentifier) == false, "Already deployed"); - + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); + _requireBoundedIdentifier(_deviceUniqueIdentifier); + if(registry.isDeviceIdentifierAlreadyUsed(_deviceUniqueIdentifier)) { + revert Errors.LazyWalletAlreadyDeployed(_deviceUniqueIdentifier); + } + uint256 len = _eSIMUniqueIdentifiers.length; - require(len == _dataBundleDetails.length, "Unequal array provided"); + if(len != _dataBundleDetails.length) { + revert Errors.ArrayLengthMismatch(len, _dataBundleDetails.length); + } for(uint256 i=0; i= 1, "eSIM identifier 0"); + if(bytes(eSIMUniqueIdentifier).length == 0) revert Errors.EmptyESIMIdentifier(); string memory deviceUniqueIdentifier = eSIMIdentifierToDeviceIdentifier[eSIMUniqueIdentifier]; if(bytes(deviceUniqueIdentifier).length == 0) { + _requireBoundedIdentifier(eSIMUniqueIdentifier); + + // A wallet already holds this eSIM onchain, so a lazy record under it would be + // purchases nobody can ever reach: the deployment refuses to hand a second wallet + // the same identifier. Only new bindings are checked, because once one exists the + // registry refuses the claim from any other device. + address holder = registry.eSIMWalletForIdentifier(eSIMUniqueIdentifier); + if(holder != address(0)) { + revert Errors.ESIMIdentifierAlreadyClaimed(eSIMUniqueIdentifier, holder); + } + eSIMIdentifierToDeviceIdentifier[eSIMUniqueIdentifier] = _deviceUniqueIdentifier; string[] storage associatedESIMIdentifiers = eSIMIdentifiersAssociatedWithDeviceIdentifier[_deviceUniqueIdentifier]; @@ -217,7 +569,9 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra emit ESIMBindedWithDevice(eSIMUniqueIdentifier, _deviceUniqueIdentifier); } else { - require(keccak256(bytes(deviceUniqueIdentifier)) == keccak256(bytes(_deviceUniqueIdentifier)), "Invalid _deviceUniqueIdentifier"); + if(keccak256(bytes(deviceUniqueIdentifier)) != keccak256(bytes(_deviceUniqueIdentifier))) { + revert Errors.ESIMBoundToADifferentDevice(eSIMUniqueIdentifier, deviceUniqueIdentifier); + } } DataBundleDetails[] storage dataBundleDetails = deviceIdentifierToESIMDetails[_deviceUniqueIdentifier][eSIMUniqueIdentifier]; @@ -231,47 +585,14 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra emit DataUpdatedForDevice(_deviceUniqueIdentifier, _eSIMUniqueIdentifiers, _dataBundleDetails); } - /// @notice This function should be called when the fiat user wants to switch their eSIM to a new device - /// @param _eSIMIdentifier unique eSIM identifier that needs to be switched to a new device - /// @param _oldDeviceIdentifier device identifier that the eSIM is currently associated with - /// @param _newDeviceIdentifier new device identifier that the eSIM needs to be switched to - /// @return bool Returns `true` if the switching of eSIM was successful - function switchESIMIdentifierToNewDeviceIdentifier( - string calldata _eSIMIdentifier, - string calldata _oldDeviceIdentifier, - string calldata _newDeviceIdentifier - ) external onlyESIMWalletAdmin returns (bool) { - require(bytes( _eSIMIdentifier).length > 0, "_eSIMIdentifier 0"); - require(bytes( _newDeviceIdentifier).length > 0, "_newDeviceIdentifier 0"); - - string memory currentDeviceIdentifier = eSIMIdentifierToDeviceIdentifier[_eSIMIdentifier]; - require(bytes(currentDeviceIdentifier).length > 0, "Unknown _eSIMIdentifier"); - require( - bytes(currentDeviceIdentifier).length == bytes(_oldDeviceIdentifier).length, - "Incorrect device identifier" - ); - require( - keccak256(bytes(currentDeviceIdentifier)) == keccak256(bytes(_oldDeviceIdentifier)), - "Incorrect device identifier" - ); - require( - keccak256(bytes(_newDeviceIdentifier)) != keccak256(bytes(currentDeviceIdentifier)), - "Cannot switch to same device" - ); - - eSIMIdentifierToDeviceIdentifier[_eSIMIdentifier] = _newDeviceIdentifier; - emit NewDeviceIdentifierAssociatedWithESIMIdentifier(_eSIMIdentifier, currentDeviceIdentifier, _newDeviceIdentifier); - - _updateDeviceIdentifierToESIMDetails(_eSIMIdentifier, _oldDeviceIdentifier, _newDeviceIdentifier); - _updateESIMIdentifiersAssociatedWithDeviceIdentifier(_eSIMIdentifier, _oldDeviceIdentifier, _newDeviceIdentifier); - - emit ESIMIdentifierSwitchedToNewDeviceIdentifier(_eSIMIdentifier, _oldDeviceIdentifier, currentDeviceIdentifier); - - return true; - } - - /// @dev Internal function to update the eSIM related details when switching to a new device identifier - function _updateDeviceIdentifierToESIMDetails( + /// @notice Moves what an eSIM bought to the device taking it over + /// @dev Carries the purchase entries themselves. Its counterpart + /// `_moveESIMIdentifierBetweenDeviceLists` carries the membership record saying the eSIM + /// exists at all, and a switch needs both. + /// @param _eSIMIdentifier eSIM being switched + /// @param _oldDeviceIdentifier Device it is leaving + /// @param _newDeviceIdentifier Device it is joining + function _moveESIMPurchaseHistory( string calldata _eSIMIdentifier, string calldata _oldDeviceIdentifier, string calldata _newDeviceIdentifier @@ -279,7 +600,10 @@ contract LazyWalletRegistry is Initializable, UUPSUpgradeable, Ownable2StepUpgra DataBundleDetails[] storage dataBundleDetails = deviceIdentifierToESIMDetails[_oldDeviceIdentifier][_eSIMIdentifier]; // Transfer history of the eSIM identifier to the new device identifier DataBundleDetails[] storage newDataBundleDetails = deviceIdentifierToESIMDetails[_newDeviceIdentifier][_eSIMIdentifier]; - for(uint256 i=0; i MAX_ESIM_WALLETS_PER_CALL) { + revert Errors.TooManyESIMWallets(_requested, MAX_ESIM_WALLETS_PER_CALL); + } + + return _requested > _outstanding ? _outstanding : _requested; + } + + /// @notice Copies one batch of identifiers out of a device's list + /// @dev Reads only the slice the batch needs. Copying the whole list into memory first would put + /// the cost this split exists to bound back into every call. + /// @param _allESIMIdentifiers The device's full identifier list + /// @param _startIndex Position this batch starts at + /// @param _batchSize How many to read + /// @return The batch's identifiers, in list order + function _readIdentifiers( + string[] storage _allESIMIdentifiers, + uint256 _startIndex, + uint256 _batchSize + ) private view returns (string[] memory) { + string[] memory batchIdentifiers = new string[](_batchSize); + + for(uint256 i=0; i<_batchSize; ++i) { + batchIdentifiers[i] = _allESIMIdentifiers[_startIndex + i]; + } + + return batchIdentifiers; + } + + /// @notice Binds each identifier in a batch to the wallet deployed for it + /// @dev The deployment returns the wallets in the order it was given the identifiers, which is + /// what makes this pairing sound. It is the only proof later on that a wallet claiming an + /// eSIM identifier is the one this contract deployed for it. This cannot run before the + /// deployment, unlike the cursor, because the addresses do not exist until then. + /// @param _batchIdentifiers The batch's eSIM identifiers + /// @param _eSIMWallets The wallets deployed for them, in the same order + function _recordDeployedESIMWallets( + string[] memory _batchIdentifiers, + address[] memory _eSIMWallets + ) private { + for(uint256 i=0; i<_batchIdentifiers.length; ++i) { + lazyDeployedESIMWallet[_batchIdentifiers[i]] = _eSIMWallets[i]; + } + } + + /// @notice Rejects an identifier longer than the protocol accepts + /// @param _identifier Device or eSIM identifier about to create a new binding + function _requireBoundedIdentifier(string calldata _identifier) private pure { + if(bytes(_identifier).length > MAX_IDENTIFIER_LENGTH) { + revert Errors.IdentifierTooLong(_identifier, MAX_IDENTIFIER_LENGTH); + } + } + + /// @notice Address (owned/controlled by eSIM wallet project) that can upgrade contracts + /// @dev Reads through to the owner rather than holding its own copy. `_authorizeUpgrade` is + /// gated on `onlyOwner`, so the owner is the upgrade authority by definition and a second + /// copy could only ever disagree with it. + function upgradeManager() public view returns (address) { + return owner(); + } + + /// @notice Whether a device identifier has purchases recorded against it here + /// @dev The ordinary deployment route asks this before taking an identifier, since a wallet + /// created under a reserved one strands every eSIM bound to it: the deploy, the history + /// copy and the device switch all refuse an identifier that has a wallet. + /// + /// Stays true once the lazy deployment finishes. Harmless, since the registry's own + /// identifier check refuses the second claim by then, and clearing it would mean walking + /// the whole list. + /// @param _deviceUniqueIdentifier Device identifier being checked + /// @return True if a lazy user is waiting on this identifier + function isDeviceIdentifierReserved(string calldata _deviceUniqueIdentifier) public view returns (bool) { + return eSIMIdentifiersAssociatedWithDeviceIdentifier[_deviceUniqueIdentifier].length != 0; + } + + /// @notice Whether an eSIM identifier is bound to a device here + /// @dev The registry refuses a claim on a reserved identifier from any device but the one that + /// reserved it, and reads `eSIMIdentifierToDeviceIdentifier` itself to make that + /// comparison. This is the plain question, for a caller that only wants the fact. + /// @param _eSIMUniqueIdentifier eSIM identifier being checked + /// @return True if a lazy user is waiting on this identifier + function isESIMIdentifierReserved(string calldata _eSIMUniqueIdentifier) public view returns (bool) { + return bytes(eSIMIdentifierToDeviceIdentifier[_eSIMUniqueIdentifier]).length != 0; + } } diff --git a/contracts/P256Verifier.sol b/contracts/P256Verifier.sol index a5642f45..3608891c 100644 --- a/contracts/P256Verifier.sol +++ b/contracts/P256Verifier.sol @@ -1,13 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.25; +pragma solidity 0.8.36; -import "./CustomStructs.sol"; -import "./WebAuthn.sol"; +// Libraries +import {WebAuthn} from "./WebAuthn.sol"; -// Source: https://github.com/daimo-eth/daimo/blob/master/packages/contract/src/DaimoVerifier.sol -// Proxies a webAuthnSignature verification call to the Webauthn library. +// Types +import {WebAuthnSignature} from "./CustomStructs.sol"; + +/// @notice Thin contract wrapper around the WebAuthn verification library +/// @dev Adapted from Daimo's DaimoVerifier: +/// https://github.com/daimo-eth/daimo/blob/master/packages/contract/src/DaimoVerifier.sol +/// It exists as a contract so accounts hold one immutable address to verify through, rather +/// than linking the library into every implementation. contract P256Verifier { + /// @notice Verifies a WebAuthn assertion against a P256 public key + /// @param message Raw challenge bytes expected inside the assertion's clientDataJSON + /// @param requireUserVerification True to demand the authenticator's user verification flag + /// @param webAuthnSignature The assertion to check + /// @param x X co-ordinate of the P256 public key + /// @param y Y co-ordinate of the P256 public key + /// @return True when the assertion is valid for that key function verifySignature( bytes memory message, bool requireUserVerification, diff --git a/contracts/Registry.sol b/contracts/Registry.sol index cd38f45c..4a24adf0 100644 --- a/contracts/Registry.sol +++ b/contracts/Registry.sol @@ -1,59 +1,142 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Interfaces +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {IPausable} from "./interfaces/IPausable.sol"; +import {IRegistryAdmin} from "./interfaces/IRegistryAdmin.sol"; +// Contracts import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; - import {RegistryHelper} from "./RegistryHelper.sol"; +import {LazyWalletRegistry} from "./LazyWalletRegistry.sol"; import {DeviceWalletFactory} from "./device-wallet/DeviceWalletFactory.sol"; +import {DeviceWallet} from "./device-wallet/DeviceWallet.sol"; import {ESIMWalletFactory} from "./esim-wallet/ESIMWalletFactory.sol"; import {ESIMWallet} from "./esim-wallet/ESIMWallet.sol"; -import {P256Verifier} from "./P256Verifier.sol"; import {Errors} from "./Errors.sol"; -import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; - -/// @notice Contract for deploying the factory contracts and maintaining registry -contract Registry is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, RegistryHelper { +/// @notice Single source of truth for who is who in the protocol, and the switchboard the wallets +/// read on every guarded path +/// @dev Holds the admin address, the vault, the pause flag and the price ceiling in one place. +/// Device wallets and eSIM wallets are beacon proxies tracked by mappings with no enumerable +/// list, so there is no way to write a value into each of them: one write here is how a change +/// reaches all of them in the same transaction. +/// +/// `IPausable` and `IRegistryAdmin` are declared so the compiler checks the signatures +/// `ProtocolAdmin` calls through them. A guardian acts with no delay, so a drift between the +/// two would only show as a revert during an incident. What each interface leaves out is +/// deliberate: `pause()` is the hot admin key's lever while releasing it is the timelock's, +/// and `enableAdmin()` is absent for the same reason in reverse, so nothing invites a fast +/// path for handing a suspended key its powers back. +contract Registry is + Initializable, + UUPSUpgradeable, + Ownable2StepUpgradeable, + RegistryHelper, + IPausable, + IRegistryAdmin +{ /// @notice Entry point contract address (one entryPoint per chain) IEntryPoint public entryPoint; - ///@notice eSIM wallet project admin address - address public eSIMWalletAdmin; + /// @notice Address holding the admin role, whether or not its powers are currently live + /// @dev The only copy in the protocol. `DeviceWalletFactory`, `DeviceWallet`, `ESIMWallet` and + /// `LazyWalletRegistry` all read it from here, so rotating it below reaches every one of + /// them in the same transaction. Holding it in more than one place is what previously let + /// a rotation update some readers and leave the rest authorising the retired key. + /// + /// Read `eSIMWalletAdmin()` rather than this to find out who may act: this is the address + /// on the books, and it keeps naming a suspended admin so the suspension can be lifted + /// without anyone having to remember who it was. + address public adminOfRecord; /// @notice Address of the vault that receives payments for the eSIM data bundles address public vault; - /// @notice Address (owned/controlled by eSIM wallet project) that can upgrade contracts - address public upgradeManager; + /// @dev Slot that used to hold a copy of the upgrade authority. It was written once in + /// `initialize` and had no setter, so it kept naming the deploy-time address once + /// ownership moved on. Kept so nothing below it shifts on the live proxies. Never read; + /// `upgradeManager()` returns `owner()` instead. + /// + /// Slither raises `unused-state` and `constable-states` here. Both are false: occupying + /// the slot is the whole job, and either change takes it out of storage and moves every + /// variable below it. + address private _retiredUpgradeManager; + + /// @notice Address of the admin to be appointed + /// @dev Only the owner can request the transfer. The nominated address has to accept it, and + /// this resets once they do. While it is set the incumbent has no powers, so a handover + /// that is never accepted leaves the role dormant rather than shared. + address public newRequestedAdmin; + + /// @notice True while the ETH-moving paths are stopped protocol-wide + /// @dev Held here for the same reason the admin address is: device wallets and eSIM wallets are + /// beacon proxies tracked by a mapping with no enumerable list, so there is no way to + /// write a flag into each of them. Both already read this contract on their guarded paths, + /// so one write here reaches every wallet in the same transaction. + bool public paused; + + /// @notice True while the admin's powers are suspended, leaving the address on the books + /// @dev Packs into the spare bytes beside `paused`, so it costs no slot of its own. Suspension + /// is the lever against a compromised admin key: it is instant through a guardian, while + /// lifting it is an owner action and therefore waits. A key that could restore itself as + /// fast as it was suspended would leave the two sides trading transactions forever. + bool public adminDisabled; + /// @notice Most an eSIM wallet may be charged for one data bundle unless it sets its own limit + /// @dev Held here rather than only on each wallet because a wallet deployed before this existed + /// reads zero, and there is no enumerable list to write a value into. Never zero: `initialize` + /// and `setDefaultDataBundlePriceCap` both reject it, since a zero here or on a wallet's own + /// cap reads as "no ceiling" in `ESIMWallet._requirePriceWithinCap`. + uint256 public defaultDataBundlePriceCap; + + /// @notice Restricts a call to a device wallet this registry has recorded modifier onlyDeviceWallet() { - if(isDeviceWalletValid[msg.sender] != true) revert Errors.OnlyDeviceWallet(); + if(!isDeviceWalletValid[msg.sender]) revert Errors.OnlyDeviceWallet(); _; } + /// @notice Restricts a call to the device wallet factory modifier onlyDeviceWalletFactory() { if(msg.sender != address(deviceWalletFactory)) revert Errors.OnlyDeviceWalletFactory(); _; } - // /// @custom:oz-upgrades-unsafe-allow constructor - // constructor() initializer {} + /// @notice Restricts a call to the current eSIM wallet admin + /// @dev The hot key the backend signs with, not the owner. It can trip the pause but not + /// release it, and cannot upgrade anything. Reads the accessor rather than the stored + /// address, so a suspended admin is refused here for the same reason it is refused + /// everywhere else. + modifier onlyESIMWalletAdmin() { + if(msg.sender != eSIMWalletAdmin()) revert Errors.OnlyAdmin(); + _; + } - /// @dev Owner based upgrades - function _authorizeUpgrade(address newImplementation) - internal - onlyOwner - override - {} + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + + /// @dev Locks the implementation contract itself. Without this, anyone can call initialize + /// directly on the implementation and own it. The proxy is unaffected either way, but an + /// owned implementation is a trap for any later upgrade that adds an outward call. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /// @notice Wires the registry to the two factories and sets the protocol's addresses /// @param _eSIMWalletAdmin Admin address of the eSIM wallet project /// @param _vault Address of the vault that receives payments for the data bundles /// @param _upgradeManager Admin address responsible for upgrading contracts + /// @param _deviceWalletFactory Factory that deploys device wallets + /// @param _eSIMWalletFactory Factory that deploys eSIM wallets + /// @param _entryPoint ERC-4337 EntryPoint singleton for this chain + /// @param _defaultDataBundlePriceCap Starting price ceiling. Must be non-zero: a zero cap, here + /// or on a wallet's own, reads as "no ceiling" in `ESIMWallet._requirePriceWithinCap`. function initialize( address _eSIMWalletAdmin, address _vault, @@ -61,17 +144,24 @@ contract Registry is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, Re address _deviceWalletFactory, address _eSIMWalletFactory, IEntryPoint _entryPoint, - P256Verifier _verifier + uint256 _defaultDataBundlePriceCap ) external initializer { - require(_eSIMWalletAdmin != address(0), "_eSIMWalletAdmin 0"); - require(_vault != address(0), "_vault 0"); - require(_upgradeManager != address(0), "_upgradeManager 0"); - require(address(_entryPoint) != address(0), "_entryPoint 0"); + if(_eSIMWalletAdmin == address(0)) revert Errors.ZeroAddress("_eSIMWalletAdmin"); + if(_vault == address(0)) revert Errors.ZeroAddress("_vault"); + if(_upgradeManager == address(0)) revert Errors.ZeroAddress("_upgradeManager"); + if(address(_entryPoint) == address(0)) revert Errors.ZeroAddress("_entryPoint"); + // Neither factory has a setter anywhere in the protocol, so a zero here is permanent. + // It would leave deployLazyWalletAndSetESIMIdentifier calling into address(0), + // onlyDeviceWalletFactory unable to match any sender, and the factory branch of + // ESIMWalletFactory's caller check dead, recoverable only by an upgrade. + if(_deviceWalletFactory == address(0)) revert Errors.ZeroAddress("_deviceWalletFactory"); + if(_eSIMWalletFactory == address(0)) revert Errors.ZeroAddress("_eSIMWalletFactory"); + if(_defaultDataBundlePriceCap == 0) revert Errors.ZeroDataBundlePriceCap(); + adminOfRecord = _eSIMWalletAdmin; entryPoint = _entryPoint; - eSIMWalletAdmin = _eSIMWalletAdmin; vault = _vault; - upgradeManager = _upgradeManager; + defaultDataBundlePriceCap = _defaultDataBundlePriceCap; deviceWalletFactory = DeviceWalletFactory(_deviceWalletFactory); eSIMWalletFactory = ESIMWalletFactory(_eSIMWalletFactory); @@ -80,58 +170,185 @@ contract Registry is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, Re __Ownable_init(_upgradeManager); emit RegistryInitialized( - _eSIMWalletAdmin, - _vault, - _upgradeManager, - address(deviceWalletFactory), - address(eSIMWalletFactory), - address(_verifier) + _eSIMWalletAdmin, + _vault, + _upgradeManager, + address(deviceWalletFactory), + address(eSIMWalletFactory) ); + emit DefaultDataBundlePriceCapUpdated(_defaultDataBundlePriceCap); } - /// @notice Function to add or update the lazy wallet registry address - function addOrUpdateLazyWalletRegistryAddress( - address _lazyWalletRegistry - ) public onlyOwner returns (address) { - require(_lazyWalletRegistry != address(0), "_lazyWalletRegistry 0"); + // --------------------------------------------------------------------------------------------- + // Admin handover + // --------------------------------------------------------------------------------------------- - lazyWalletRegistry = _lazyWalletRegistry; + /// @notice Nominates the next eSIM wallet admin, who then has to accept + /// @dev Owner and not the admin, deliberately. An admin that had to nominate its own + /// replacement could not be removed once its key was in someone else's hands, and the + /// pause is the admin's own lever, so a compromised key could hold the protocol stopped + /// for as long as it liked and no other key could end it. + /// + /// Nominating strips the incumbent at once, through the accessor rather than through a + /// write: a handover in flight leaves the role dormant until the nominee accepts, so the + /// two never hold it at the same time. A rotation therefore has a gap in it, and the + /// nomination and the acceptance belong close together. + /// + /// Deliberately does not check for an existing request, so an unintended nomination is + /// overridden by calling this again. Naming the incumbent withdraws the request and hands + /// the powers back, which also lifts a suspension, so one call undoes either mistake. + /// @param _newAdmin Address of the recipient to receive the admin role + function requestAdminUpdate(address _newAdmin) external onlyOwner { + if(_newAdmin == address(0)) revert Errors.ZeroAddress("_newAdmin"); - emit UpdatedLazyWalletRegistryAddress(_lazyWalletRegistry); + if(_newAdmin == adminOfRecord) { + address revokedAddress = newRequestedAdmin; + newRequestedAdmin = address(0); + adminDisabled = false; + emit AdminUpdateRevoked(msg.sender, revokedAddress); + } + else { + newRequestedAdmin = _newAdmin; + emit AdminUpdateRequested(adminOfRecord, _newAdmin); + } + } - return lazyWalletRegistry; + /// @notice Takes up the admin role, callable only by the nominated address + /// @dev Clears the suspension as well as the request. The suspension names a key, not the + /// role, so a fresh key accepting is the end of the incident rather than something that + /// has to be lifted separately afterwards. + /// @return Address of the new admin + function acceptAdminUpdate() external returns (address) { + if(msg.sender != newRequestedAdmin) revert Errors.OnlyRequestedAdmin(newRequestedAdmin); + + adminOfRecord = msg.sender; + + // Reset the requested admin to address(0) for further role transfer + newRequestedAdmin = address(0); + adminDisabled = false; + + emit AdminUpdated(msg.sender); + + return msg.sender; } - function updateDeviceWalletAssociatedWithESIMWallet( - address _eSIMWalletAddress, - address _deviceWalletAddress - ) external onlyDeviceWallet { - require( - ESIMWallet(payable(_eSIMWalletAddress)).owner() == msg.sender || - isESIMWalletValid[_eSIMWalletAddress] == msg.sender, - "Unauthorise caller or already assigned" - ); - // address(0) => owner removed eSIM wallet from device wallet - // msg.sender => new device wallet added the eSIM wallet - // any other address => Unauthorised: user is trying to change owner without initiating transfer of ownership - require( - _deviceWalletAddress == address(0) || _deviceWalletAddress == msg.sender, - "Transfer ownership first" - ); - // Owner cannot change device wallet address in the middle of ownership transfer - require( - ESIMWallet(payable(_eSIMWalletAddress)).newRequestedOwner() == address(0), - "Unauthorised action" - ); + /// @notice Suspends the admin's powers protocol-wide, leaving its address on the books + /// @dev Every gate in the protocol reads `eSIMWalletAdmin()`, which answers zero from here on, + /// and no transaction can arrive from the zero address, so one write closes all of them + /// in the same transaction. The address itself is kept so the suspension can be lifted + /// without anyone having to supply it again. + /// + /// Owner gated, which is what lets `ProtocolAdmin` offer a guardian an instant route to + /// it. Refuses a repeat rather than passing quietly: a guardian doing this during an + /// incident should not be left believing it acted when it did not. + function disableAdmin() external onlyOwner { + if(adminDisabled) revert Errors.AdminAlreadyDisabled(); - isESIMWalletValid[_eSIMWalletAddress] = _deviceWalletAddress; - emit UpdatedDeviceWalletassociatedWithESIMWallet(_eSIMWalletAddress, _deviceWalletAddress); + adminDisabled = true; + emit AdminDisabled(adminOfRecord, msg.sender); + } + + /// @notice Hands a suspended admin its powers back + /// @dev Owner only, with no instant route for anyone. Suspending is instant and restoring + /// waits, so a compromised key cannot undo its own suspension as fast as it is applied. + /// Reversing that would recreate the deadlock the suspension exists to break. + /// + /// Does nothing for an outstanding handover, which keeps the incumbent powerless on its + /// own. Withdraw that with `requestAdminUpdate` naming the incumbent. + function enableAdmin() external onlyOwner { + if(!adminDisabled) revert Errors.AdminNotDisabled(); + + adminDisabled = false; + emit AdminEnabled(adminOfRecord, msg.sender); + } + + /// @notice Admin address every gated call in the protocol is checked against + /// @dev Zero while the admin is suspended or while a handover is outstanding, which is how + /// both states close every gate at once: `msg.sender` is never zero, so no caller matches. + /// `adminOfRecord` holds the address itself either way. + /// @return The address that may act as admin right now, or zero if nobody may + function eSIMWalletAdmin() public view returns (address) { + if(adminDisabled || newRequestedAdmin != address(0)) return address(0); + + return adminOfRecord; + } + + // --------------------------------------------------------------------------------------------- + // Vault + // --------------------------------------------------------------------------------------------- + + /// @notice Points every data bundle payment at a different vault + /// @dev Owner and not admin, deliberately. This is the destination of every payment the protocol + /// collects, so moving it is a fund-flow change and belongs behind the same delay as an + /// upgrade rather than on the hot key that signs backend batches all day. + /// + /// Device wallets read `vault` here on every purchase instead of caching it, so one write + /// reaches all of them in the same transaction. This used to live on `DeviceWalletFactory`, + /// which nothing on the payment path ever read, so rotating the vault there changed nothing + /// and the real address could not be moved at all. + /// @param _newVaultAddress Address that receives payments for the data bundles from now on + /// @return The vault address now in force + function updateVaultAddress(address _newVaultAddress) external onlyOwner returns (address) { + if(_newVaultAddress == address(0)) revert Errors.ZeroAddress("_newVaultAddress"); + if(vault == _newVaultAddress) revert Errors.VaultUnchanged(vault); + + vault = _newVaultAddress; + emit VaultAddressUpdated(vault); + + return vault; + } + + // --------------------------------------------------------------------------------------------- + // Pause and price ceiling + // --------------------------------------------------------------------------------------------- + + /// @notice Stops the ETH-moving paths on every device wallet and eSIM wallet + /// @dev The admin trips this and the owner clears it. The admin key signs backend batches all + /// day and is the one watching, so it needs to act without waiting; giving it the release + /// as well would let a single hot key hold user funds indefinitely. Neither key can reach + /// an owner's own `execute`, so a pause never stops someone spending their own ETH. + function pause() external onlyESIMWalletAdmin { + paused = true; + emit Paused(msg.sender); + } + + /// @notice Releases the pause + /// @dev Owner only, see `pause` + function unpause() external onlyOwner { + paused = false; + emit Unpaused(msg.sender); + } + + /// @notice Reverts while the protocol is paused + /// @dev Device wallets and eSIM wallets call this rather than reading `paused` and reverting + /// themselves, so the revert reason is the same wherever it comes from. + function requireNotPaused() external view { + if(paused) revert Errors.ProtocolPaused(); + } + + /// @notice Sets the price ceiling eSIM wallets fall back to when they hold none of their own + /// @dev Owner and not admin, deliberately. The admin is the party this ceiling constrains, so + /// letting it raise its own limit would leave the ceiling meaningless. Zero is refused: + /// it would read as "no ceiling" in `ESIMWallet._requirePriceWithinCap` for every wallet + /// that has not set its own. + /// @param _cap Maximum price in wei, non-zero + function setDefaultDataBundlePriceCap(uint256 _cap) external onlyOwner { + if(_cap == 0) revert Errors.ZeroDataBundlePriceCap(); + + defaultDataBundlePriceCap = _cap; + emit DefaultDataBundlePriceCapUpdated(_cap); } - /// @dev For all the device wallets deployed by the esim wallet admin using the device wallet factory, - /// update the mappings + // --------------------------------------------------------------------------------------------- + // Device wallet registration + // --------------------------------------------------------------------------------------------- + + /// @notice Records a device wallet the factory has just deployed + /// @dev Factory only. Writes the identifier, the address and the owner key together, so the + /// three stay consistent with each other. /// @param _deviceWallet Address of the device wallet /// @param _deviceUniqueIdentifier String unique identifier associated with the device wallet + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning the wallet function updateDeviceWalletInfo( address _deviceWallet, string calldata _deviceUniqueIdentifier, @@ -140,16 +357,207 @@ contract Registry is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable, Re _updateDeviceWalletInfo(_deviceWallet, _deviceUniqueIdentifier, _deviceWalletOwnerKey); } - /// @notice Update eSIM standby status when being moved from one device wallet to another + /// @notice Called by a device wallet when the P256 key that owns it is replaced + /// @dev Only the wallet itself can move its own bindings, so `msg.sender` is the subject + /// rather than a parameter. Without this the registry keeps naming the retired key after + /// a rotation, and the key taking over stays unregistered and can be claimed by a second + /// wallet, which breaks the one key to one wallet rule the deploy paths enforce. + /// @param _newOwnerKey X,Y co-ordinates of the P256 key taking over + function updateDeviceWalletOwnerKey(bytes32[2] memory _newOwnerKey) external onlyDeviceWallet { + _updateDeviceWalletOwnerKey(msg.sender, _newOwnerKey); + } + + /// @notice Refuses a device identifier a fiat user's eSIMs are already waiting on + /// @dev The ordinary deployment route calls this. Taking such an identifier used to succeed and + /// strand the lazy user: the history copy, the wallet deployment and the device switch all + /// refuse an identifier that has a wallet. + /// + /// Passes while `lazyWalletRegistry` is unset, the window between deploying this contract + /// and wiring the two together. Nothing can be reserved before the contract holding + /// reservations exists, so the window is empty rather than unguarded. + /// @param _deviceUniqueIdentifier Identifier the caller is about to take + function requireDeviceIdentifierNotReserved(string calldata _deviceUniqueIdentifier) external view { + if(lazyWalletRegistry == address(0)) return; + + if(LazyWalletRegistry(lazyWalletRegistry).isDeviceIdentifierReserved(_deviceUniqueIdentifier)) { + revert Errors.DeviceIdentifierReservedForLazyWallet(_deviceUniqueIdentifier); + } + } + + // --------------------------------------------------------------------------------------------- + // eSIM wallet binding + // --------------------------------------------------------------------------------------------- + + /// @notice Binds an eSIM wallet to the calling device wallet and settles any outstanding transfer + /// @dev The association is a registration: once the registry has named a device wallet for an + /// eSIM wallet it always names one, and this is the only place it moves. Zero is refused + /// for that reason, so releasing an eSIM wallet raises the standby flag through + /// `toggleESIMWalletStandbyStatus` and leaves the association naming the last device + /// wallet that held it. + /// + /// Authorization reads `ESIMWallet.owner()` rather than the association above, because the + /// association can still name a former device wallet after an ownership transfer has been + /// accepted and never bound back through `addESIMWallet`. + /// + /// Taking a wallet on is the one moment both facts change together, which is why the flag + /// is cleared here rather than in a second call. Nothing else in this function reads it. + /// @param _eSIMWalletAddress Address of the eSIM wallet + /// @param _deviceWalletAddress The device wallet taking it on, which must be the caller + function bindESIMWallet( + address _eSIMWalletAddress, + address _deviceWalletAddress + ) external onlyDeviceWallet { + if(_deviceWalletAddress == address(0)) revert Errors.ZeroAddress("_deviceWalletAddress"); + + // The two checks below ask the eSIM wallet about itself, and any contract can answer them + // with whatever a caller needs. The factory is the one party that can state this, and + // without it a device wallet registers an address of its choosing as protocol eSIM wallet. + if(!eSIMWalletFactory.isESIMWalletDeployed(_eSIMWalletAddress)) { + revert Errors.NotAProtocolESIMWallet(_eSIMWalletAddress); + } + + if(ESIMWallet(payable(_eSIMWalletAddress)).owner() != msg.sender) { + revert Errors.NotTheESIMWalletOwnerOrItsDeviceWallet(_eSIMWalletAddress); + } + + // A device wallet can only bind an eSIM wallet to itself. Naming any other address is an + // attempt to move it without going through the ownership transfer. + if(_deviceWalletAddress != msg.sender) { + revert Errors.NotTheAssociatedDeviceWallet(_eSIMWalletAddress, _deviceWalletAddress); + } + + // Owner cannot change device wallet address in the middle of ownership transfer + address pendingOwner = ESIMWallet(payable(_eSIMWalletAddress)).newRequestedOwner(); + if(pendingOwner != address(0)) { + revert Errors.ESIMWalletOwnershipTransferPending(_eSIMWalletAddress, pendingOwner); + } + + isESIMWalletValid[_eSIMWalletAddress] = _deviceWalletAddress; + emit UpdatedDeviceWalletassociatedWithESIMWallet(_eSIMWalletAddress, _deviceWalletAddress); + + // Only written on a change, so a wallet that was never released emits nothing here + if(isESIMWalletOnStandby[_eSIMWalletAddress]) { + isESIMWalletOnStandby[_eSIMWalletAddress] = false; + emit ESIMWalletSetOnStandby(_eSIMWalletAddress, false, msg.sender); + } + } + + /// @notice Records that an eSIM wallet now holds an eSIM identifier, refusing a second holder + /// @dev The guard lives here rather than in `DeviceWallet` because a device wallet can reach + /// this directly through `execute`, which would skip anything sitting on the wallet side. + /// For the same reason the caller's device identifier is read from it rather than taken as + /// an argument. + /// + /// A reservation is compared against the caller's own identifier rather than refused + /// outright, since the lazy route reaches this while deploying against its own. + /// @param _eSIMUniqueIdentifier Identifier being claimed + /// @param _eSIMWalletAddress Wallet claiming it, which must be one the caller owns + function claimESIMIdentifier( + string calldata _eSIMUniqueIdentifier, + address _eSIMWalletAddress + ) external onlyDeviceWallet { + if(bytes(_eSIMUniqueIdentifier).length == 0) revert Errors.EmptyESIMIdentifier(); + + if(ESIMWallet(payable(_eSIMWalletAddress)).owner() != msg.sender) { + revert Errors.NotTheESIMWalletOwnerOrItsDeviceWallet(_eSIMWalletAddress); + } + + bytes32 identifierHash = keccak256(bytes(_eSIMUniqueIdentifier)); + address holder = claimedESIMIdentifiers[identifierHash]; + if(holder != address(0)) { + revert Errors.ESIMIdentifierAlreadyClaimed(_eSIMUniqueIdentifier, holder); + } + + _requireESIMIdentifierNotReservedElsewhere(_eSIMUniqueIdentifier); + + claimedESIMIdentifiers[identifierHash] = _eSIMWalletAddress; + + emit ESIMIdentifierClaimed(identifierHash, _eSIMUniqueIdentifier, _eSIMWalletAddress); + } + + /// @notice Marks an eSIM wallet as being moved from one device wallet to another, or cancels that + /// @dev Only the flag moves here. The association is a separate fact and keeps naming the device + /// wallet that last held the eSIM wallet, so raising standby on a wallet this caller still + /// holds is the ordinary case rather than a contradiction. + /// + /// Authorization reads `ESIMWallet.owner()` rather than the association, for the same reason + /// as `bindESIMWallet`: the association can still name a former device wallet after an + /// accepted transfer that was never bound back. /// @param _eSIMWalletAddress Address of the eSIM wallet - /// @param _isOnStandby Set to true when no device wallet is associated, false otherwise + /// @param _isOnStandby True while a transfer is outstanding, false once it is settled or revoked function toggleESIMWalletStandbyStatus( address _eSIMWalletAddress, bool _isOnStandby ) public onlyDeviceWallet { - require(isESIMWalletValid[_eSIMWalletAddress] == msg.sender, "Unauthorised caller"); + if(ESIMWallet(payable(_eSIMWalletAddress)).owner() != msg.sender) { + revert Errors.NotTheESIMWalletOwnerOrItsDeviceWallet(_eSIMWalletAddress); + } isESIMWalletOnStandby[_eSIMWalletAddress] = _isOnStandby; emit ESIMWalletSetOnStandby(_eSIMWalletAddress, _isOnStandby, msg.sender); } + + // --------------------------------------------------------------------------------------------- + // Ownership, wiring and upgrades + // --------------------------------------------------------------------------------------------- + + /// @notice Points the registry at the lazy wallet registry, which is deployed after it + /// @param _lazyWalletRegistry Address of the lazy wallet registry + /// @return The address now in force + function addOrUpdateLazyWalletRegistryAddress( + address _lazyWalletRegistry + ) public onlyOwner returns (address) { + if(_lazyWalletRegistry == address(0)) revert Errors.ZeroAddress("_lazyWalletRegistry"); + + lazyWalletRegistry = _lazyWalletRegistry; + + emit UpdatedLazyWalletRegistryAddress(_lazyWalletRegistry); + + return lazyWalletRegistry; + } + + /// @notice Refuses an eSIM identifier a fiat user has reserved against a different device + /// @dev The device wallet's own identifier is fetched only once the identifier turns out to be + /// reserved, so an ordinary claim pays for one call returning an empty string. + /// + /// Passes while `lazyWalletRegistry` is unset, for the same reason as + /// `requireDeviceIdentifierNotReserved`: nothing can be reserved before the contract that + /// holds reservations exists. + /// @param _eSIMUniqueIdentifier Identifier the caller is about to claim + function _requireESIMIdentifierNotReservedElsewhere(string calldata _eSIMUniqueIdentifier) private view { + if(lazyWalletRegistry == address(0)) return; + + string memory reservedFor = + LazyWalletRegistry(lazyWalletRegistry).eSIMIdentifierToDeviceIdentifier(_eSIMUniqueIdentifier); + if(bytes(reservedFor).length == 0) return; + + string memory claimant = DeviceWallet(payable(msg.sender)).deviceUniqueIdentifier(); + if(keccak256(bytes(reservedFor)) != keccak256(bytes(claimant))) { + revert Errors.ESIMIdentifierReservedForLazyWallet(_eSIMUniqueIdentifier); + } + } + + /// @notice Ownership of this contract is never renounced + /// @dev The owner is the only caller _authorizeUpgrade accepts, and there is no other route to + /// replace this implementation. Renouncing would freeze the contract on its current logic + /// permanently. + function renounceOwnership() public pure override { + revert Errors.OwnershipCannotBeRenounced(); + } + + /// @notice Restricts UUPS upgrades to the owner + /// @param newImplementation Address of the implementation being moved to + function _authorizeUpgrade(address newImplementation) + internal + onlyOwner + override + {} + + /// @notice Address (owned/controlled by eSIM wallet project) that can upgrade contracts + /// @dev Reads through to the owner rather than holding its own copy. `_authorizeUpgrade` is + /// gated on `onlyOwner`, so the owner is the upgrade authority by definition and a second + /// copy could only ever disagree with it. + function upgradeManager() public view returns (address) { + return owner(); + } } diff --git a/contracts/RegistryHelper.sol b/contracts/RegistryHelper.sol index f7d75ef3..98a0e3b9 100644 --- a/contracts/RegistryHelper.sol +++ b/contracts/RegistryHelper.sol @@ -1,55 +1,28 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Libraries +import {Errors} from "./Errors.sol"; + +// Types +import {DataBundleDetails, Wallets} from "./CustomStructs.sol"; -import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +// Contracts import {DeviceWalletFactory} from "./device-wallet/DeviceWalletFactory.sol"; import {ESIMWalletFactory} from "./esim-wallet/ESIMWalletFactory.sol"; import {DeviceWallet} from "./device-wallet/DeviceWallet.sol"; import {ESIMWallet} from "./esim-wallet/ESIMWallet.sol"; -import {P256Verifier} from "./P256Verifier.sol"; -import {Errors} from "./Errors.sol"; -import "./CustomStructs.sol"; +/// @notice Storage and the lazy deployment paths that `Registry` inherits +/// @dev Split out so `Registry` holds the admin and pause logic while the mappings and the calls +/// into the two factories live here. Only the lazy wallet registry reaches the functions in +/// this file; everything else goes through `Registry` itself. contract RegistryHelper { - event LazyWalletDeployed( - address indexed _deviceWallet, - string _deviceUniqueIdentifier, - address indexed _eSIMWallet, - string _eSIMUniqueIdentifier - ); - - event DeviceWalletInfoUpdated( - address indexed _deviceWallet, - string _deviceUniqueIdentifier, - bytes32[2] _deviceWalletOwnerKey - ); - - event UpdatedDeviceWalletassociatedWithESIMWallet( - address indexed _eSIMWalletAddress, - address indexed _deviceWalletAddress - ); - - event UpdatedLazyWalletRegistryAddress( - address indexed _lazyWalletRegistry - ); - - event RegistryInitialized( - address _eSIMWalletAdmin, - address _vault, - address indexed _upgradeManager, - address indexed _deviceWalletFactory, - address indexed _eSIMWalletFactory, - address _verifier - ); - - event ESIMWalletSetOnStandby( - address indexed _eSIMWalletAddress, - bool _isOnStandby, - address indexed _deviceWalletAddress - ); + /// @notice Most alternative salts tried before a lazy deployment gives up + /// @dev Each attempt costs one address derivation and no storage. Bounded because an unbounded + /// loop would let one occupied range make the whole batch unpriceable. + uint256 private constant MAX_SALT_PROBES = 8; /// @notice Address of the Lazy wallet registry address public lazyWalletRegistry; @@ -77,37 +50,159 @@ contract RegistryHelper { mapping(address deviceWalletAddress => bool valid) public isDeviceWalletValid; /// @notice All the eSIM wallets deployed using this registry are valid and mapped to their owner device wallet + /// @dev This is the registration record. A non-zero entry means the protocol deployed this eSIM + /// wallet, and it stays non-zero for the rest of the wallet's life. Mid-transfer it names + /// the device wallet that last held it, so it is never zero to mean "released". + /// `bindESIMWallet` is the only writer and it checks the deployment with the factory, which + /// is what makes the first sentence true rather than assumed. mapping(address eSIMWalletAddress => address deviceWalletAddress) public isESIMWalletValid; /// @notice If an existing eSIM wallet is in the process of being transferred from one device wallet to another - /// If bool is `true`, it means that the eSIM wallet has no device wallet associated to it yet + /// @dev If bool is `true`, the eSIM wallet is in a transient state. `isESIMWalletValid` still + /// points at the old device wallet. Do not use this mapping to check whether an eSIM + /// wallet belongs to the protocol; that is what `isESIMWalletValid` is for. Its job is to + /// hold transactions on this eSIM wallet until it reads false again, meaning the new + /// device wallet has accepted it. mapping(address eSIMWalletAddress => bool isOnStandby) public isESIMWalletOnStandby; - // Reserved storage gap for future upgrades + /// @notice The eSIM wallet holding each eSIM identifier, protocol-wide + /// @dev An eSIM wallet's own identifier slot is set once, but nothing stopped two wallets from + /// being set to the same identifier, one per deployment route. This is what makes the + /// identifier answer with a single wallet. Keyed by hash for the same reason + /// `registeredP256Keys` is: `eSIMWalletForIdentifier` takes the string. + /// + /// Written once and never cleared, including through an ownership transfer, because the + /// eSIM belongs to the wallet rather than to whichever device is holding it. + mapping(bytes32 hashOfESIMIdentifier => address eSIMWallet) public claimedESIMIdentifiers; + + /// @dev Registry inherits this contract and its own state begins directly after this gap, so + /// anything added above shifts every Registry variable. That is why the gap is here and + /// not at the end of `Registry` itself. uint256[50] private __gap; + /// @notice Emitted for each eSIM wallet deployed on behalf of the lazy wallet registry + event LazyWalletDeployed( + address indexed _deviceWallet, + string _deviceUniqueIdentifier, + address indexed _eSIMWallet, + string _eSIMUniqueIdentifier + ); + + /// @notice Emitted when a device wallet is first recorded, with its identifier and owner key + event DeviceWalletInfoUpdated( + address indexed _deviceWallet, + string _deviceUniqueIdentifier, + bytes32[2] _deviceWalletOwnerKey + ); + + /// @notice Emitted when a device wallet rotates the P256 key that owns it + event DeviceWalletOwnerKeyUpdated( + address indexed _deviceWallet, + bytes32[2] _oldOwnerKey, + bytes32[2] _newOwnerKey + ); + + /// @notice Emitted the first and only time an eSIM identifier is bound to an eSIM wallet + /// @dev The identifier is carried unindexed as well as hashed, because indexing a dynamic type + /// stores its hash and no consumer can read the value back out of that. + event ESIMIdentifierClaimed( + bytes32 indexed _hashOfESIMIdentifier, + string _eSIMUniqueIdentifier, + address indexed _eSIMWallet + ); + + /// @notice Emitted when an eSIM wallet is bound to a device wallet + event UpdatedDeviceWalletassociatedWithESIMWallet( + address indexed _eSIMWalletAddress, + address indexed _deviceWalletAddress + ); + + /// @notice Emitted when the owner points the registry at the lazy wallet registry + event UpdatedLazyWalletRegistryAddress( + address indexed _lazyWalletRegistry + ); + + /// @notice Emitted once, when the registry is initialised + event RegistryInitialized( + address _eSIMWalletAdmin, + address _vault, + address indexed _upgradeManager, + address indexed _deviceWalletFactory, + address indexed _eSIMWalletFactory + ); + + /// @notice Emitted when the owner nominates a new address for the admin role + /// @dev The incumbent is powerless from here until the nominee accepts, so a reader following + /// the admin has to treat this as the moment the role went dormant. + event AdminUpdateRequested(address indexed eSIMWalletAdmin, address indexed _newAdmin); + + /// @notice Emitted when the newly requested admin accepts the role + event AdminUpdated(address indexed _newAdmin); + + /// @notice Emitted when the owner withdraws an outstanding nomination + event AdminUpdateRevoked(address indexed _caller, address indexed _revokedAddress); + + /// @notice Emitted when the admin's powers are suspended, naming the address left on the books + event AdminDisabled(address indexed _adminOfRecord, address indexed _caller); + + /// @notice Emitted when a suspended admin is given its powers back + event AdminEnabled(address indexed _adminOfRecord, address indexed _caller); + + /// @notice Emitted when the owner points data bundle payments at a different vault + event VaultAddressUpdated(address indexed _updatedVaultAddress); + + /// @notice Emitted when the admin stops the ETH-moving paths protocol-wide + event Paused(address indexed _admin); + + /// @notice Emitted when the owner releases the pause + event Unpaused(address indexed _owner); + + /// @notice Emitted when the owner changes the price ceiling eSIM wallets fall back to + event DefaultDataBundlePriceCapUpdated(uint256 _cap); + + /// @notice Emitted when an eSIM wallet's outstanding transfer is raised or settled + event ESIMWalletSetOnStandby( + address indexed _eSIMWalletAddress, + bool _isOnStandby, + address indexed _deviceWalletAddress + ); + + /// @notice Restricts a call to the lazy wallet registry modifier onlyLazyWalletRegistry() { if(msg.sender != lazyWalletRegistry) revert Errors.OnlyLazyWalletRegistry(); _; } - /// @notice Allow LazyWalletRegistry to deploy a device wallet and an eSIM wallet on behalf of a user + // --------------------------------------------------------------------------------------------- + // Lazy wallet deployment + // --------------------------------------------------------------------------------------------- + + /// @notice Allow LazyWalletRegistry to deploy a device wallet and its first eSIM wallets + /// @dev Deploys the wallets and sets their identifiers only. Purchase history is copied in + /// afterwards through `populateLazyHistory`, because carrying it here made one transaction + /// grow with the eSIM count and each eSIM's history at the same time. + /// + /// `_eSIMUniqueIdentifiers` is the first batch rather than the device's whole list, and any + /// identifier past it reaches `deployMoreLazyESIMWallets`. The lazy wallet registry owns + /// the cursor deciding where one batch ends and the next begins, and it reserves the whole + /// salt range before this runs, so no bound on the salt is needed here. /// @param _deviceWalletOwnerKey P256 public key of user /// @param _deviceUniqueIdentifier Unique device identifier associated with the device - /// @return Return device wallet address and list of addresses of all the eSIM wallets + /// @param _salt CREATE2 salt the device wallet and its first eSIM wallet are deployed at + /// @param _eSIMUniqueIdentifiers First batch of eSIM identifiers, in the order the full list holds them + /// @param _depositAmount ETH forwarded to the new device wallet + /// @return Return device wallet address and the eSIM wallet addresses this call deployed function deployLazyWallet( bytes32[2] memory _deviceWalletOwnerKey, string calldata _deviceUniqueIdentifier, uint256 _salt, - string[] memory _eSIMUniqueIdentifiers, - DataBundleDetails[][] memory _dataBundleDetails, + string[] calldata _eSIMUniqueIdentifiers, uint256 _depositAmount ) external payable onlyLazyWalletRegistry returns (address, address[] memory) { - require(_eSIMUniqueIdentifiers.length + _salt < type(uint256).max, "Salt value too high"); - require( - uniqueIdentifierToDeviceWallet[_deviceUniqueIdentifier] == address(0), - "Device wallet already exists" - ); + address existing = uniqueIdentifierToDeviceWallet[_deviceUniqueIdentifier]; + if(existing != address(0)) { + revert Errors.DeviceWalletAlreadyExists(_deviceUniqueIdentifier, existing); + } string[] memory deviceUniqueIdentifier = new string[](1); bytes32[2][] memory deviceWalletOwnersKey = new bytes32[2][](1); @@ -131,7 +226,7 @@ contract RegistryHelper { address deviceWallet = wallet[0].deviceWallet; address firstESIMWallet = wallet[0].eSIMWallet; address[] memory eSIMWallets = new address[](_eSIMUniqueIdentifiers.length); - + // Tracks the eSIMWallets array index uint256 i = 0; @@ -139,45 +234,208 @@ contract RegistryHelper { eSIMWallets[i] = firstESIMWallet; // deployDeviceWalletForUsers doesn't set the eSIM identifer, hence updating it here for the 1st eSIM wallet DeviceWallet(payable(deviceWallet)).setESIMUniqueIdentifierForAnESIMWallet(firstESIMWallet, _eSIMUniqueIdentifiers[i]); - // Populate data bundle purchase details for the eSIM wallet - ESIMWallet(payable(firstESIMWallet)).populateHistory(_dataBundleDetails[i]); - // Increase the index to deploy, set identifier and populate history for the remaining _eSIMUniqueIdentifiers + // Increase the index to deploy and set the identifier for the remaining _eSIMUniqueIdentifiers i++; for(; i<_eSIMUniqueIdentifiers.length; ++i) { // increase salt for subsequent eSIM wallet deployments - address eSIMWallet = eSIMWalletFactory.deployESIMWallet(deviceWallet, (_salt + i)); + eSIMWallets[i] = _deployLazyESIMWallet( + deviceWallet, + _deviceUniqueIdentifier, + _salt + i, + _eSIMUniqueIdentifiers[i] + ); + } + + return (deviceWallet, eSIMWallets); + } - // Updates the Device wallet storage variables as well as for the registry - DeviceWallet(payable(deviceWallet)).addESIMWallet(eSIMWallet, true); + /// @notice Deploys the next batch of eSIM wallets for a device the lazy registry already set up + /// @dev Separate from `deployLazyWallet` because that call deploys the device wallet itself, and + /// the owner key, salt and deposit it takes describe a one-time act. Reaching a device this + /// way needs none of them, and repeating them would either be ignored or checked against a + /// key the owner is free to rotate between batches. + /// + /// The salt continues from where the first batch stopped rather than starting over, because + /// the eSIM wallet factory salts CREATE2 with it and a repeat would land on an address that + /// already holds a wallet. + /// @param _deviceWallet Device wallet the new eSIM wallets are bound to + /// @param _deviceUniqueIdentifier Device identifier the wallets belong to + /// @param _baseSalt Salt the device's deployment started from + /// @param _startIndex Position of this batch's first identifier in the device's full list + /// @param _eSIMUniqueIdentifiers This batch's identifiers, in the order the full list holds them + /// @return Addresses of the eSIM wallets this call deployed + function deployMoreLazyESIMWallets( + address _deviceWallet, + string calldata _deviceUniqueIdentifier, + uint256 _baseSalt, + uint256 _startIndex, + string[] calldata _eSIMUniqueIdentifiers + ) external onlyLazyWalletRegistry returns (address[] memory) { + uint256 batchSize = _eSIMUniqueIdentifiers.length; + address[] memory eSIMWallets = new address[](batchSize); + + for(uint256 i=0; i= clientDataJSONLength || + webAuthnSignature.challengeIndex >= clientDataJSONLength + ) { + return false; + } + // 11. Verify that the value of C.type is the string webauthn.get. - // bytes("type":"webauthn.get").length = 21 - string memory _type = webAuthnSignature.clientDataJSON.slice(webAuthnSignature.typeIndex, webAuthnSignature.typeIndex + 21); + string memory _type = webAuthnSignature.clientDataJSON.slice( + webAuthnSignature.typeIndex, + webAuthnSignature.typeIndex + _TYPE_FIELD_LENGTH + ); if (keccak256(bytes(_type)) != _EXPECTED_TYPE_HASH) { return false; } @@ -114,9 +204,20 @@ library WebAuthn { // `webAuthnSignature.clientDataJSON` (after off-chain fix for Problem 1) contains: // "challenge":"" - // `challengeIndex` points to the 'c' in "challenge": - // So, "challenge":" is 13 characters long. - uint256 challengeValueStartIndexInJson = webAuthnSignature.challengeIndex + 13; // Start of the Base64URL string + // challengeIndex must actually point at the challenge key. Without this the bytes are + // skipped blindly, so an index aimed at any other quoted field is accepted, and a signature + // made over a different challenge passes whenever that field happens to contain the + // expected base64url value before its closing quote. The origin is the obvious carrier. + string memory challengeKey = webAuthnSignature.clientDataJSON.slice( + webAuthnSignature.challengeIndex, + webAuthnSignature.challengeIndex + _CHALLENGE_KEY_LENGTH + ); + if (keccak256(bytes(challengeKey)) != _EXPECTED_CHALLENGE_KEY_HASH) { + return false; + } + + // `challengeIndex` points to the opening quote of "challenge": + uint256 challengeValueStartIndexInJson = webAuthnSignature.challengeIndex + _CHALLENGE_KEY_LENGTH; // Start of the Base64URL string if (challengeValueStartIndexInJson >= bytes(webAuthnSignature.clientDataJSON).length) { return false; @@ -150,14 +251,21 @@ library WebAuthn { // Skip 13., 14., 15. + // The flags byte has to exist before it can be read. Indexing a shorter authenticatorData + // panics, which reverts validation instead of failing the signature. + if (webAuthnSignature.authenticatorData.length <= _AUTH_DATA_FLAGS_OFFSET) { + return false; + } + bytes1 flags = webAuthnSignature.authenticatorData[_AUTH_DATA_FLAGS_OFFSET]; + // 16. Verify that the UP bit of the flags in authData is set. - if (webAuthnSignature.authenticatorData[32] & _AUTH_DATA_FLAGS_UP != _AUTH_DATA_FLAGS_UP) { + if (flags & _AUTH_DATA_FLAGS_UP != _AUTH_DATA_FLAGS_UP) { return false; } // 17. If user verification is required for this assertion, verify that the User Verified bit of the flags in // authData is set. - if (requireUV && (webAuthnSignature.authenticatorData[32] & _AUTH_DATA_FLAGS_UV) != _AUTH_DATA_FLAGS_UV) { + if (requireUV && (flags & _AUTH_DATA_FLAGS_UV) != _AUTH_DATA_FLAGS_UV) { return false; } diff --git a/contracts/aa-helper/Account4337.sol b/contracts/aa-helper/Account4337.sol index 615d18e5..3038aec7 100644 --- a/contracts/aa-helper/Account4337.sol +++ b/contracts/aa-helper/Account4337.sol @@ -1,63 +1,95 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; -// import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import "@account-abstraction/contracts/interfaces/IAccount.sol"; -import "@account-abstraction/contracts/core/Helpers.sol"; -import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; -import "@account-abstraction/contracts/core/UserOperationLib.sol"; -// To allow the smart wallet to handle ERC20 and ERC721 tokens -import {TokenCallbackHandler} from "@account-abstraction/contracts/samples/callback/TokenCallbackHandler.sol"; -import {P256Verifier} from "../P256Verifier.sol"; +// Libraries +import {UserOperationLib} from "@account-abstraction/contracts/core/UserOperationLib.sol"; +import {SIG_VALIDATION_FAILED, _packValidationData} from "@account-abstraction/contracts/core/Helpers.sol"; import {WebAuthn} from "../WebAuthn.sol"; -import "../CustomStructs.sol"; +import {Errors} from "../Errors.sol"; + +// Types +import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol"; +import {Call, WebAuthnSignature} from "../CustomStructs.sol"; + +// Interfaces +import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol"; +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +// Contracts +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +// Lets the wallet receive ERC-721 and ERC-1155 transfers +import {TokenCallbackHandler} from "@account-abstraction/contracts/accounts/callback/TokenCallbackHandler.sol"; +import {P256Verifier} from "../P256Verifier.sol"; + +/// @notice ERC-4337 account owned by a P256 key rather than by an address +/// @dev The owner never sends a transaction itself. It signs a WebAuthn assertion, and either +/// the EntryPoint or this account calling into itself turns that into a call. Two entry +/// points read signatures, `validateUserOp` for user operations and `isValidSignature` for +/// ERC-1271, and each hashes a different precursor, so a signature made for one is not +/// accepted by the other. contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 { using UserOperationLib for PackedUserOperation; - using MessageHashUtils for bytes32; - using ECDSA for bytes32; - bytes32[2] public owner; + /// @dev The EIP-191 prefix, "\x19Ethereum Signed Message:\n" + string private constant EIP191_PREFIX = "\x19Ethereum Signed Message:\n"; + + /// @dev Byte length of the user operation precursor: + /// version (1) + validUntil (6) + userOpHash (32). + /// A string because it is concatenated after the prefix before hashing. + string private constant USEROP_PRECURSOR_LENGTH = "39"; + + /// @dev Byte length of the ERC-1271 precursor: + /// version (1) + validUntil (6) + chain id (32) + wallet (20) + message hash (32). + string private constant ERC1271_PRECURSOR_LENGTH = "91"; + + /// @dev The fixed header on every signature this account accepts. + /// version (uint8) + validUntil (uint48) + uint256 private constant SIGNATURE_HEADER_LENGTH = 7; - /// The ERC-4337 entry point singleton + /// @notice The ERC-4337 EntryPoint singleton this account answers to + /// @dev Immutable to keep validation cheap, which means moving to a new EntryPoint version + /// is a new implementation rather than a setter call. IEntryPoint public immutable entryPoint; - /// Signature verifier contract + /// @notice Contract that verifies every WebAuthn assertion for this account P256Verifier public immutable verifier; - /// "\x19Ethereum Signed Message:\n" - string private constant EIP191_PREFIX = "\x19Ethereum Signed Message:\n"; - /// Length of the packed data: version (1) + validUntil (6) + userOpHash (32) = 39 - /// Defined as string because it is concatenated along with EIP191_PREFIX before hashing - string private constant USEROP_PRECURSOR_LENGTH = "39"; - /// version (uint8) + validUntil (uint48) - uint256 private constant SIGNATURE_HEADER_LENGTH = 7; + /// @notice X and Y co-ordinates of the P256 key that owns this account + /// @dev DeviceWallet inherits this contract, and base storage comes first, so its own + /// variables begin immediately after this one. A state variable added here moves all of + /// them on wallets that are already deployed, which then read back as zero. Anything + /// this contract needs later belongs in its own ERC-7201 namespace, not in a slot + /// following `owner`. + bytes32[2] public owner; + /// @notice Emitted once, when the account's owner key is first set event Account4337Initialized(IEntryPoint indexed entryPoint, bytes32[2] owner); + /// @notice Emitted when the owner key is replaced event AccountOwnershipTransferred(bytes32[2] newOwner); + /// @notice Restricts a call to the account itself + /// @dev The only way to satisfy this from outside is `execute` or `executeBatch` targeting this + /// address, which the owner key has to have signed for. modifier onlySelf() { - require(msg.sender == address(this), "Only self"); + if(msg.sender != address(this)) revert Errors.OnlySelf(); _; } + /// @notice Restricts a call to the EntryPoint singleton modifier onlyEntryPoint() { - require(msg.sender == address(entryPoint), "Only entry point"); + if(msg.sender != address(entryPoint)) revert Errors.OnlyEntryPoint(); _; } - modifier onlyOwnerOrEntryPoint() { - _requireFromEntryPointOrOwner(); - _; - } + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + /// @param _entryPoint EntryPoint singleton this account validates against + /// @param _verifier Contract used to verify WebAuthn assertions + /// @custom:oz-upgrades-unsafe-allow constructor constructor( IEntryPoint _entryPoint, P256Verifier _verifier @@ -67,40 +99,43 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 _disableInitializers(); } - /** - * @dev The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint, - * a new implementation of SimpleAccount must be deployed with the new EntryPoint address, then upgrading - * the implementation by calling `upgradeTo()` - */ - function initialize(bytes32[2] memory anOwner) public virtual initializer { + /// @notice Sets the owner key on a freshly deployed account + /// @dev Internal on purpose. A public setup function guarded only by `initializer` names no + /// caller, so a proxy created without its init call in the same transaction could be + /// claimed by anyone with an owner key of their choosing and none of the protocol wiring. + /// Internal keeps the subclass path working and leaves no other way in. + /// @param anOwner X,Y co-ordinates of the P256 key taking ownership + function initialize(bytes32[2] memory anOwner) internal virtual initializer { _initialize(anOwner); } + /// @notice Writes the owner key without the initializer guard + /// @dev Split out so a subclass can reuse the write from its own initializer. + /// @param anOwner X,Y co-ordinates of the P256 key taking ownership function _initialize(bytes32[2] memory anOwner) internal virtual { owner = anOwner; emit Account4337Initialized(entryPoint, owner); } - function transferOwnership(bytes32[2] memory newOwner) onlySelf public returns (bytes32[2] memory) { - owner = newOwner; - emit AccountOwnershipTransferred(newOwner); - return owner; - } + // --------------------------------------------------------------------------------------------- + // Execution + // --------------------------------------------------------------------------------------------- - /** - * execute a transaction (called directly from owner, or by entryPoint) - */ + /// @notice Makes one call from this account + /// @dev Callable by the EntryPoint or by this account. There is no path here for the P256 key + /// directly: it holds no address, so it reaches this only by signing a user operation. + /// @param call Target, value and calldata of the call to make function execute( Call calldata call ) external { _requireFromEntryPointOrOwner(); _call(call.dest, call.value, call.data); } - - /** - * execute a sequence of transactions - * @dev to reduce gas consumption for trivial case (no value), use a zero-length array to mean zero value - */ + + /// @notice Makes a sequence of calls from this account, reverting all of them if one fails + /// @dev Same callers as `execute`. Each entry carries its own value, so a batch that moves no + /// ETH simply leaves every value at zero. + /// @param calls Targets, values and calldata, executed in order function executeBatch( Call[] calldata calls ) external { @@ -111,16 +146,18 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 } } - /** - * @notice Validates a signature according to EIP-1271 using the WebAuthn verifier. - * @dev Assumes the `_signature` bytes were encoded off-chain using the `_encodeSignature` - * TypeScript function format: `abi.encodePacked(version, validUntil, abi.encode(WebAuthnSigData))`. - * Assumes the `_messageHash` provided is the EIP-191 digest that was embedded as the challenge - * in the `clientDataJSON` during off-chain signing (i.e., `_messageHash = hashMessage(originalMessage)`). - * @param _messageHash The EIP-191 digest of the original message (`keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)`). - * @param _signature The packed signature bytes including version, validUntil, and ABI-encoded WebAuthn data. - * @return magicValue `0x1626ba7e` if the signature is valid and timely, `0xffffffff` otherwise. - */ + // --------------------------------------------------------------------------------------------- + // Signature validation + // --------------------------------------------------------------------------------------------- + + /// @notice Validates a signature over an arbitrary message, per ERC-1271 + /// @dev The challenge inside `clientDataJSON` is not `_messageHash`. It is the EIP-191 digest + /// over version, validUntil, chain id, this address and `_messageHash`, so an offchain + /// signer needs all five. Signature layout is version (1 byte) then validUntil (6 bytes) + /// then the ABI-encoded WebAuthn assertion. + /// @param _messageHash EIP-191 digest of the original message + /// @param _signature Packed version, validUntil and WebAuthn assertion + /// @return magicValue `0x1626ba7e` when the signature is valid and unexpired, `0xffffffff` otherwise function isValidSignature( bytes32 _messageHash, bytes calldata _signature @@ -130,18 +167,36 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 uint8 version = uint8(_signature[0]); if(version == 1) { - // Version 1: version (1 byte) | validUntil (6 bytes) | abi.encode(WebAuthnSignature) uint48 validUntil = uint48(bytes6(_signature[1:SIGNATURE_HEADER_LENGTH])); - // ABI encoded WebAuthnSignature bytes bytes calldata webAuthnSignatureBytes = bytes(_signature[SIGNATURE_HEADER_LENGTH:]); - // Its Okay to access TIMESTAMP here, as it is only restricted for validateUserOp + // TIMESTAMP is only barred inside validateUserOp, so reading it here is fine if(block.timestamp > validUntil) { return 0xffffffff; } - // The challenge expected by WebAuthn.sol is the EIP-191 digest itself in bytes format - bytes memory challengeBytes = abi.encodePacked(_messageHash); + // Everything the caller can see has to be inside what was signed, otherwise it can be + // edited after the fact. validUntil is checked just above but was not part of the + // challenge, so an expired signature could be revived by rewriting those six bytes. + // The chain id and this address are here because neither is implied by the message: + // wallets sit at the same CREATE2 address on every chain, and createAccount will deploy + // a second wallet at another salt holding the same owner key. + bytes memory precursorBytes = abi.encodePacked( + version, + validUntil, + block.chainid, + address(this), + _messageHash + ); + bytes32 challengeDigest = keccak256( + abi.encodePacked( + EIP191_PREFIX, + ERC1271_PRECURSOR_LENGTH, + precursorBytes + ) + ); + // WebAuthn.sol expects the challenge as bytes + bytes memory challengeBytes = abi.encodePacked(challengeDigest); if(_validateSignature(challengeBytes, webAuthnSignatureBytes)) { return IERC1271(this).isValidSignature.selector; // magic value: `0x1626ba7e` } @@ -149,43 +204,129 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 return 0xffffffff; } + /// @inheritdoc IAccount + /// @dev Must stay within the ERC-4337 validation rules: no banned opcodes, no external calls + /// to other contracts, no TIMESTAMP. Expiry is handed to the EntryPoint through the packed + /// return value instead of being checked here. function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external virtual override onlyEntryPoint returns (uint256 validationData) { - // Note: `forge coverage` incorrectly marks this function and downstream - // as non-covered. + // `forge coverage` incorrectly marks this function and everything downstream as uncovered validationData = _validateUserOpSignature(userOp, userOpHash); _payPrefund(missingAccountFunds); } - /** - * @dev Validates the signature of a UserOperation based on the custom versioning scheme using WebAuthn. - * @param userOp The packed user operation. - * @param userOpHash The hash calculated by the EntryPoint according to ERC-4337 rules. - * @return validationData Packed validAfter (0) and validUntil timestamps, or SIG_VALIDATION_FAILED. - */ + // --------------------------------------------------------------------------------------------- + // Ownership handover + // --------------------------------------------------------------------------------------------- + + /// @notice Replaces the P256 key that owns this account + /// @dev Reachable only through `execute` or `executeBatch` with this account as the target, so + /// the current owner has to sign for it. Nothing outside this contract is told: a + /// subclass holding its own record of the owner has to override this and keep that record + /// in step. + /// @param newOwner X,Y co-ordinates of the P256 key taking over + /// @return The owner key now in force + function transferOwnership(bytes32[2] memory newOwner) onlySelf public virtual returns (bytes32[2] memory) { + owner = newOwner; + emit AccountOwnershipTransferred(newOwner); + return owner; + } + + // --------------------------------------------------------------------------------------------- + // EntryPoint deposit + // --------------------------------------------------------------------------------------------- + + /// @notice This account's gas deposit held by the EntryPoint + function getDeposit() public view returns (uint256) { + return entryPoint.balanceOf(address(this)); + } + + /// @notice Tops up this account's gas deposit at the EntryPoint + /// @dev Open to anyone, since paying another account's gas costs the payer and nobody else. + function addDeposit() public payable { + entryPoint.depositTo{value: msg.value}(address(this)); + } + + /// @notice Withdraws part of this account's gas deposit from the EntryPoint + /// @param withdrawAddress Recipient of the withdrawn ETH + /// @param amount Amount to withdraw + function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public onlySelf { + if(withdrawAddress == address(0)) revert Errors.ZeroAddress("withdrawAddress"); + entryPoint.withdrawTo(withdrawAddress, amount); + } + + // --------------------------------------------------------------------------------------------- + // Caller checks and outward calls + // --------------------------------------------------------------------------------------------- + + /// @notice Reverts unless the caller is the EntryPoint or this account itself + /// @dev "Owner" in the name means `address(this)`, not the P256 key, which has no address to + /// call from. + function _requireFromEntryPointOrOwner() internal view { + if(msg.sender != address(entryPoint) && msg.sender != address(this)) { + revert Errors.OnlyEntryPointOrSelf(); + } + } + + /// @notice Calls a target and bubbles its revert data unchanged + /// @param target Address to call + /// @param value ETH to send with the call + /// @param data Calldata for the call + function _call( + address target, + uint256 value, + bytes memory data + ) internal { + (bool success, bytes memory result) = target.call{value: value}(data); + if (!success) { + // Assembly because Solidity has no way to revert with an existing bytes buffer. It + // returns the callee's own revert reason instead of a generic failure, which matters + // when the call ran inside a batch signed offchain. `result` is memory returned by + // `call`, so its first word is the length and the payload starts 32 bytes in. + assembly { + revert(add(result, 32), mload(result)) + } + } + } + + // --------------------------------------------------------------------------------------------- + // Signature verification and prefund + // --------------------------------------------------------------------------------------------- + + /// @notice Verifies the signature carried by a user operation + /// @dev The challenge is the EIP-191 digest over version, validUntil and `userOpHash`. The + /// user operation's own fields need no separate binding because the EntryPoint already + /// folds them into `userOpHash`. A zero validUntil is refused outright, since the + /// EntryPoint reads it as never-expires. + /// @param userOp The packed user operation + /// @param userOpHash Hash the EntryPoint computed for it + /// @return validationData Packed validAfter (0) and validUntil, or SIG_VALIDATION_FAILED for a + /// signature that fails to verify or carries a zero validUntil function _validateUserOpSignature( PackedUserOperation calldata userOp, bytes32 userOpHash ) private view returns (uint256 validationData) { bytes calldata signature = userOp.signature; uint256 sigLength = signature.length; - if(sigLength <= SIGNATURE_HEADER_LENGTH + 32) return 0xffffffff; + // Not 0xffffffff. This is packed validationData, not a bytes4, and the EntryPoint reads + // its low 160 bits as an authorizer. 0xffffffff decodes as an aggregator address that + // does not exist, so the whole bundle reverts instead of this one operation failing. + if(sigLength <= SIGNATURE_HEADER_LENGTH + 32) return SIG_VALIDATION_FAILED; uint8 version = uint8(signature[0]); if(version == 1) { - // Version 1: version (1 byte) | validUntil (6 bytes) | abi.encode(WebAuthnSignature) uint48 validUntil = uint48(bytes6(signature[1:SIGNATURE_HEADER_LENGTH])); - // ABI encoded WebAuthnSignature bytes + // Zero is "no expiry" to the EntryPoint and "already expired" to isValidSignature, and + // the two paths read the same six bytes. Refusing it here is what keeps one header + // from meaning two things. + if(validUntil == 0) return SIG_VALIDATION_FAILED; bytes calldata webAuthnSignatureBytes = bytes(signature[SIGNATURE_HEADER_LENGTH:]); - // Reconstructing the exact bytes that were hashed with EIP-191 prefix off-chain bytes memory precursorBytes = abi.encodePacked(version, validUntil, userOpHash); - // Calculating the EIP-191 digest of the precursor bytes - // keccak256("\x19Ethereum Signed Message:\n39" + precursorBytes) bytes32 challengeDigest = keccak256( abi.encodePacked( EIP191_PREFIX, @@ -193,13 +334,12 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 precursorBytes ) ); - // The challenge expected by the WebAuthn.sol is in bytes format + // WebAuthn.sol expects the challenge as bytes bytes memory challengeBytes = abi.encodePacked(challengeDigest); - + if(_validateSignature(challengeBytes, webAuthnSignatureBytes)) { - // Since TIMESTAMP is a blocked opcode in validateUserOp, _packValidationData asks the entryPoint - // to check for validUntil and validAfter (0 in our case) - // False because signature is valid + // TIMESTAMP is a banned opcode here, so validUntil goes back to the EntryPoint to + // enforce. validAfter is 0, and false means the signature itself checked out. return _packValidationData(false, validUntil, 0); } else { @@ -209,28 +349,21 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 return SIG_VALIDATION_FAILED; } - // Require the function call went through EntryPoint or owner - function _requireFromEntryPointOrOwner() internal view { - require( - msg.sender == address(entryPoint) || msg.sender == address(this), - "account: not Owner or EntryPoint" - ); - } - - /** - * @dev Internal function to validate a signature using the P256Verifier -> WebAuthn library. - * @param challenge The raw bytes expected to be found (Base64Url encoded) in the - * `challenge` field of the `clientDataJSON` within the `webAuthnSignatureBytes`. - * @param webAuthnSignatureBytes The ABI-encoded WebAuthnSigData tuple containing authenticatorData, - * clientDataJSON, indices, r, and s. - * @return True if the WebAuthn assertion is valid according to the WebAuthn library's checks. - */ + /// @notice Verifies a WebAuthn assertion against the owner key + /// @param challenge Raw bytes that must appear, Base64Url encoded, in the assertion's + /// `clientDataJSON` challenge field + /// @param webAuthnSignatureBytes ABI-encoded WebAuthn assertion + /// @return True when the assertion is valid for the owner key function _validateSignature( bytes memory challenge, bytes calldata webAuthnSignatureBytes ) private view returns (bool) { - // Decoding the WebAuthnSignature struct from the provided ABI-encoded bytes - WebAuthnSignature memory sig = abi.decode(webAuthnSignatureBytes, (WebAuthnSignature)); + // Decoded rather than abi.decode'd, because a malformed body has to be rejected here and + // not reverted on. This runs inside ERC-4337 validation, where a revert fails the whole + // bundle rather than the one operation, and behind isValidSignature, where it reaches the + // integrating contract as an error. A failed decode leaves the struct zeroed, which + // verifySignature returns false for. + WebAuthnSignature memory sig = WebAuthn.tryDecodeSignature(webAuthnSignatureBytes); return verifier.verifySignature({ message: challenge, @@ -241,19 +374,11 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 }); } - function _call( - address target, - uint256 value, - bytes memory data - ) internal { - (bool success, bytes memory result) = target.call{value: value}(data); - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - + /// @notice Repays the EntryPoint for gas it fronted on this account's behalf + /// @dev Only ever called from `validateUserOp`, so `msg.sender` is the EntryPoint. The result + /// is deliberately ignored: the EntryPoint checks the balance it ended up with, and + /// reverting here would fail validation for a shortfall it is about to catch anyway. + /// @param missingAccountFunds Amount the EntryPoint asked for, zero when the deposit covers it function _payPrefund(uint256 missingAccountFunds) private { if (missingAccountFunds != 0) { (bool success, ) = payable(msg.sender).call{ @@ -264,33 +389,7 @@ contract Account4337 is IAccount, Initializable, TokenCallbackHandler, IERC1271 } } - /** - * check current account deposit in the entryPoint - */ - function getDeposit() public view returns (uint256) { - return entryPoint.balanceOf(address(this)); - } - - /** - * deposit more funds for this account in the entryPoint - */ - function addDeposit() public payable { - entryPoint.depositTo{value: msg.value}(address(this)); - } - - /** - * withdraw value from the account's deposit - * @param withdrawAddress target to send to - * @param amount to withdraw - */ - function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public onlySelf { - require(withdrawAddress != address(0), "Cannot withdraw to address(0)"); - entryPoint.withdrawTo(withdrawAddress, amount); - } - + /// @notice Accepts plain ETH transfers // solhint-disable-next-line no-empty-blocks receive() external payable {} - - // solhint-disable-next-line no-empty-blocks - fallback() external payable {} } diff --git a/contracts/admin/ProtocolAdmin.sol b/contracts/admin/ProtocolAdmin.sol new file mode 100644 index 00000000..d01d33d6 --- /dev/null +++ b/contracts/admin/ProtocolAdmin.sol @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Interfaces +import {IOwnable2Step} from "../interfaces/IOwnable2Step.sol"; +import {IPausable} from "../interfaces/IPausable.sol"; +import {IRegistryAdmin} from "../interfaces/IRegistryAdmin.sol"; + +// Contracts +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +/// @notice Owner of the four upgradeable protocol contracts, with a delay on every change +/// @dev Replaces the single externally owned account that owns `Registry`, `LazyWalletRegistry`, +/// `DeviceWalletFactory` and `ESIMWalletFactory` today. Both wallet beacons sit under the two +/// factories, so owning the factories reaches every device wallet and every eSIM wallet. +/// +/// Everything this contract can do to the protocol goes one way: a proposer schedules it, the +/// delay elapses, and anyone at all executes it. Execution is open on purpose. The +/// announcement is what the delay buys, and once the wait is served there is no reason to make +/// the protocol depend on one key still being available to press the button. +/// +/// A guardian does not get a general fast path. It can say exactly three things, and each is +/// written here as its own function rather than as a payload, so no fourth sentence is +/// expressible however the role is held: +/// +/// 1. Release a pause. An upgrade that waits is reviewable; an outage that waits is an outage. +/// 2. Take `CANCELLER_ROLE` away from an account. +/// 3. Suspend the protocol's admin key. +/// +/// All three take something away and none of them grants anything, which is what keeps the +/// role away from user funds. Releasing a pause cannot move ETH, stripping a canceller cannot, +/// and a suspended admin is an admin that has stopped being able to spend rather than one +/// chosen by the guardian. Restoring any of the three is an owner action and waits, so the +/// side taking power away always wins the race against the side handing it back. A guardian +/// able to reinstate an admin would lose that, and a guardian able to appoint one would reach +/// `ESIMWallet.buyDataBundle` and every wallet holding ETH access through it. +/// +/// The second one exists because without it a compromised canceller is permanent. Evicting any +/// role holder means scheduling `revokeRole`, a scheduled operation can be cancelled by any +/// canceller, and so a compromised canceller cancels its own eviction forever. Nothing else can +/// break that loop, because the delay is what every other route waits on. +/// +/// Two limits on that power carry the whole recovery argument, and both are enforced rather +/// than documented. A guardian may not hold `CANCELLER_ROLE` itself, or it could revoke every +/// other canceller, become the only one, and cancel its own eviction. And it may not touch +/// `PROPOSER_ROLE`, because reaching zero proposers is unrecoverable: re-granting any role +/// needs a scheduled operation, scheduling needs a proposer, and `Registry.unpause` is owner +/// only, so a bricked admin plus a pause is a pause nobody can ever release. Reaching zero +/// cancellers is fine by comparison. It costs the veto, and a proposer can schedule it back. +/// +/// Not upgradeable, deliberately. An upgradeable owner of upgradeable contracts moves the +/// trust to whoever can upgrade it, and there is no delay left to protect that step. +contract ProtocolAdmin is TimelockController { + + /// @notice Releases a pause and strips a canceller, both without waiting for the delay + /// @dev Always granted `EXECUTOR_ROLE` alongside it, which keeps the role useful if open + /// execution is ever closed off. Deliberately not granted `CANCELLER_ROLE`; see the note on + /// the contract for why that pairing is what makes a guardian un-evictable. + bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); + + /// @notice Shortest delay this contract will ever accept, whatever `updateDelay` was given + /// @dev `updateDelay` takes any value including zero, and it is reachable by scheduling a call + /// to this contract like any other. Without a floor, one scheduled operation turns the + /// timelock into a plain multisig and nothing after it ever waits again. Read through + /// `getMinDelay`, which is what `schedule` measures against. + uint256 public immutable minDelayFloor; + + /// @notice A guardian released a pause + event PauseReleased(address indexed target, address indexed guardian); + + /// @notice A guardian took the cancel power away from an account + event CancellerRevoked(address indexed account, address indexed guardian); + + /// @notice A guardian suspended a protocol contract's admin key + event AdminDisabled(address indexed target, address indexed guardian); + + /// @notice A scheduled operation suspended an admin key and nominated its replacement + event AdminDisabledAndNominated(address indexed target, address indexed newAdmin); + + /// @notice Ownership of a protocol contract was accepted + event OwnershipAccepted(address indexed target); + + /// @notice The initial delay was below the floor + error DelayBelowFloor(uint256 delay, uint256 floor); + + /// @notice No guardian was named at construction + error NoGuardians(); + + /// @notice No proposer was named at construction + error NoProposers(); + + /// @notice A zero address appeared in one of the constructor role lists + error ZeroAddress(string parameter); + + /// @notice An account was named in two role lists that have to stay separate + error RolesMustNotOverlap(address account); + + /// @notice The account named does not hold the cancel power + error NotACanceller(address account); + + /// @notice The contract was not offered ownership of this target + error OwnershipNotOffered(address target); + + /// @param _initialDelay Delay new operations wait before they can be executed + /// @param _minDelayFloor Shortest delay `updateDelay` can ever bring the contract down to + /// @param _proposers Accounts that may schedule operations, and that may also cancel them + /// @param _cancellers Further accounts that may cancel, holding no other role + /// @param _guardians Accounts that may release a pause and strip a canceller + /// @dev No admin account. The zero passed to `TimelockController` leaves this contract holding + /// its own `DEFAULT_ADMIN_ROLE`, so granting or revoking any role is itself an operation + /// that has to be scheduled and waited out. Rotating a signer set is a role change here and + /// never touches the protocol contracts, whose owner stays this address. + /// + /// The base constructor gives every proposer `CANCELLER_ROLE` as well, which is kept. + /// `_cancellers` is for the accounts that cancel and do nothing else, typically the + /// individual keys behind a proposer multisig, so that one key can veto on its own without + /// being able to schedule anything on its own. + /// + /// `EXECUTOR_ROLE` goes to the zero address, which `onlyRoleOrOpenRole` reads as open to + /// everyone. See the note on the contract for why. + /// + /// An empty `_cancellers` is allowed, since the proposers already carry the role. An empty + /// `_guardians` is not: there would be no way to add one without the delay it exists to + /// skip, and no way at all to break a compromised canceller loose. + constructor( + uint256 _initialDelay, + uint256 _minDelayFloor, + address[] memory _proposers, + address[] memory _cancellers, + address[] memory _guardians + ) TimelockController(_initialDelay, _proposers, new address[](0), address(0)) { + if(_initialDelay < _minDelayFloor) revert DelayBelowFloor(_initialDelay, _minDelayFloor); + if(_guardians.length == 0) revert NoGuardians(); + if(_proposers.length == 0) revert NoProposers(); + + for(uint256 i = 0; i < _proposers.length; ++i) { + if(_proposers[i] == address(0)) revert ZeroAddress("_proposers"); + } + + for(uint256 i = 0; i < _cancellers.length; ++i) { + address canceller = _cancellers[i]; + if(canceller == address(0)) revert ZeroAddress("_cancellers"); + if(hasRole(PROPOSER_ROLE, canceller)) revert RolesMustNotOverlap(canceller); + + _grantRole(CANCELLER_ROLE, canceller); + } + + for(uint256 i = 0; i < _guardians.length; ++i) { + address guardian = _guardians[i]; + if(guardian == address(0)) revert ZeroAddress("_guardians"); + if(hasRole(CANCELLER_ROLE, guardian)) revert RolesMustNotOverlap(guardian); + + _grantRole(GUARDIAN_ROLE, guardian); + _grantRole(EXECUTOR_ROLE, guardian); + } + + minDelayFloor = _minDelayFloor; + _grantRole(EXECUTOR_ROLE, address(0)); + } + + // --------------------------------------------------------------------------------------------- + // Delay floor + // --------------------------------------------------------------------------------------------- + + /// @inheritdoc TimelockController + /// @dev Held at the floor whatever `updateDelay` last wrote. `schedule` reads this rather than + /// the stored value, so the floor binds every new operation without needing to intercept + /// the setter. + /// + /// Anything reporting the delay should call this rather than follow `MinDelayChange`, + /// which carries the value `updateDelay` stored and not the floor that overrides it. + function getMinDelay() public view virtual override returns (uint256) { + uint256 delay = super.getMinDelay(); + + return delay < minDelayFloor ? minDelayFloor : delay; + } + + // --------------------------------------------------------------------------------------------- + // Role overlap + // --------------------------------------------------------------------------------------------- + + /// @inheritdoc AccessControl + /// @dev The constructor refuses these overlaps and nothing else did. Granting is a scheduled + /// operation like any other, so without this a single proposer can schedule the pairing + /// that makes a guardian un-evictable and anyone can execute it once the delay is served. + /// + /// Authority is checked first, exactly where the base checks it, so a caller with no right + /// to grant the role still gets `AccessControlUnauthorizedAccount`. Answering that caller + /// with an overlap instead would name a problem it never reached. + /// + /// The constructor's other rule, that `_cancellers` and `_proposers` do not intersect, is + /// deliberately not repeated here. That one shapes the deployment, keeping a veto key off + /// the schedule path. It is not a safety property, and pairing the two roles later is a + /// legitimate decision for a scheduled operation to make. + /// + /// A guardian granted here picks up `EXECUTOR_ROLE` with it, as the constructor does, so + /// the two ways of installing one leave the same state behind. The reverse is not paired: + /// `revokeRole` cannot tell that grant from an independent one, so evicting a guardian + /// means scheduling both revocations in one batch. + function grantRole(bytes32 role, address account) + public + virtual + override + onlyRole(getRoleAdmin(role)) + { + if(role == CANCELLER_ROLE || role == PROPOSER_ROLE) { + if(hasRole(GUARDIAN_ROLE, account)) revert RolesMustNotOverlap(account); + } + else if(role == GUARDIAN_ROLE) { + if(hasRole(CANCELLER_ROLE, account) || hasRole(PROPOSER_ROLE, account)) { + revert RolesMustNotOverlap(account); + } + + _grantRole(EXECUTOR_ROLE, account); + } + + _grantRole(role, account); + } + + // --------------------------------------------------------------------------------------------- + // Guardian powers + // --------------------------------------------------------------------------------------------- + + /// @notice Releases a pause on a protocol contract immediately + /// @dev The selector is fixed in the interface rather than passed in, so this cannot be pointed + /// at anything else on the target. Whatever a guardian does here, the worst outcome + /// reachable is that something is unpaused which someone wanted paused, and the key that + /// applies a pause is not this one and can simply apply it again. + /// @param target Contract to unpause + function unpauseInstantly(address target) external onlyRole(GUARDIAN_ROLE) { + emit PauseReleased(target, _msgSender()); + + IPausable(target).unpause(); + } + + /// @notice Takes the cancel power away from accounts immediately + /// @dev Only `CANCELLER_ROLE`, never anything else, and adding it back is an ordinary scheduled + /// operation. A batch because the account being evicted is usually one signer set holding + /// the role several times over, and doing that in one transaction rather than several is + /// the difference between the eviction landing and the operation it is racing landing + /// first. + /// + /// All or nothing, and an account that does not hold the role reverts rather than passing + /// quietly. A guardian doing this is acting on a named list during an incident, and a + /// silent no-op would leave it believing a veto is gone while the veto is still there. + /// @param accounts Accounts to strip + function revokeCancellersInstantly(address[] calldata accounts) external onlyRole(GUARDIAN_ROLE) { + for(uint256 i = 0; i < accounts.length; ++i) { + address account = accounts[i]; + + if(!_revokeRole(CANCELLER_ROLE, account)) revert NotACanceller(account); + + emit CancellerRevoked(account, _msgSender()); + } + } + + /// @notice Suspends a protocol contract's admin key immediately + /// @dev The lever against a compromised hot key. That key holds `Registry.pause`, so leaving + /// its removal to the delay would mean an outage running for the whole wait while the key + /// re-applies the pause after every release. Suspending it is what makes + /// `unpauseInstantly` stick. + /// + /// Takes powers away and hands none out. The suspended address stays on the target's + /// books, and only the owner can reinstate it or name a replacement, both of which wait. + /// Whatever a guardian does here, the worst outcome reachable is a stopped backend, which + /// the owner ends. + /// @param target Contract whose admin is being suspended + function disableAdminInstantly(address target) external onlyRole(GUARDIAN_ROLE) { + emit AdminDisabled(target, _msgSender()); + + IRegistryAdmin(target).disableAdmin(); + } + + // --------------------------------------------------------------------------------------------- + // Admin handover + // --------------------------------------------------------------------------------------------- + + /// @notice Suspends the current admin and nominates its replacement in one operation + /// @dev Scheduled like anything else, so this is not a fast path and holds no role of its own. + /// It exists because the two effects belong in one transaction: the target strips the + /// incumbent the moment a nomination is outstanding, so scheduling the nomination alone + /// already suspends the old key, and naming the compound effect is the difference between + /// a reviewer reading the intent off the operation and having to infer it from a payload. + /// + /// The nominee still has to accept, so this cannot hand the role to an address that + /// cannot act, and the role stays dormant until it does. + /// @param target Contract whose admin is being replaced + /// @param newAdmin Address nominated to take the role + function disableAndNominate(address target, address newAdmin) external { + address sender = _msgSender(); + if(sender != address(this)) revert TimelockUnauthorizedCaller(sender); + + emit AdminDisabledAndNominated(target, newAdmin); + + IRegistryAdmin(target).requestAdminUpdate(newAdmin); + } + + // --------------------------------------------------------------------------------------------- + // Ownership handover + // --------------------------------------------------------------------------------------------- + + /// @notice Completes the handover of every contract that has offered this one its ownership + /// @dev Permissionless, and safe to be: it only takes ownership that the current owner already + /// offered, and the offer is the decision. Scheduling it instead would mean waiting out + /// the delay before this contract could own anything, including during the deployment it + /// is being installed by. + /// + /// Each event is emitted ahead of the call it describes, so a target that emits its own + /// events on handover cannot interleave them out of order. A failing handover takes the + /// whole batch down with it, so no event here can outlive the call it announced. + /// @param targets Contracts whose `pendingOwner` is this address + function acceptOwnershipBatch(address[] calldata targets) external { + for(uint256 i = 0; i < targets.length; ++i) { + address target = targets[i]; + + if(IOwnable2Step(target).pendingOwner() != address(this)) { + revert OwnershipNotOffered(target); + } + + emit OwnershipAccepted(target); + IOwnable2Step(target).acceptOwnership(); + } + } +} diff --git a/contracts/device-wallet/DeviceWallet.sol b/contracts/device-wallet/DeviceWallet.sol index e6b0c266..b139ad21 100644 --- a/contracts/device-wallet/DeviceWallet.sol +++ b/contracts/device-wallet/DeviceWallet.sol @@ -1,27 +1,45 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +// Libraries +import {FCL_Elliptic_ZZ} from "FreshCryptoLib/FCL_elliptic.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {Errors} from "../Errors.sol"; -import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +// Interfaces +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; +// Contracts +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {Account4337} from "../aa-helper/Account4337.sol"; import {Registry} from "../Registry.sol"; -import {DeviceWalletFactory} from "./DeviceWalletFactory.sol"; import {ESIMWalletFactory} from "../esim-wallet/ESIMWalletFactory.sol"; import {ESIMWallet} from "../esim-wallet/ESIMWallet.sol"; -import {Account4337} from "../aa-helper/Account4337.sol"; import {P256Verifier} from "../P256Verifier.sol"; -import {Errors} from "../Errors.sol"; +/// @notice A user's device: an ERC-4337 account that owns the eSIM wallets bought for that device +/// @dev A beacon proxy deployed by `DeviceWalletFactory`, owned by a P256 key the user holds. It +/// funds its eSIM wallets, decides which of them may pull ETH, and is the only party that can +/// move one to another device. Its own owner key rotates through `transferOwnership`, which +/// also tells the registry so the two records cannot drift apart. contract DeviceWallet is Initializable, ReentrancyGuardUpgradeable, Account4337 { using Address for address; - /// @notice Emitted when the contract pays ETH for data bundle - event ETHPaidForDataBundle(address indexed _vault, address indexed _eSIMWallet, uint256 indexed _amount); + /// @notice Registry contract instance + Registry public registry; + + /// @notice eSIM wallet factory address + ESIMWalletFactory public eSIMWalletFactory; + + /// @notice String identifier to uniquely identify user's device + string public deviceUniqueIdentifier; + + /// @notice Set to true if the eSIM wallet belongs to this device wallet + mapping(address eSIMWalletAddress => bool isValid) public isValidESIMWallet; + + /// @notice Tracks if an associated eSIM wallet can pull ETH or not + mapping(address eSIMWalletAddress => bool isAllowedToPullETH) public canPullETH; /// @notice Emitted when owner updates ETH access to a particular eSIM wallet event ETHAccessUpdated(address indexed _eSIMWalletAddress, bool _hasAccessToETH); @@ -36,115 +54,149 @@ contract DeviceWallet is Initializable, ReentrancyGuardUpgradeable, Account4337 /// @notice Emitted when the eSIM wallet is removed from this Device Wallet event ESIMWalletRemoved(address indexed _eSIMWalletAddress, address indexed _deviceWalletAddress, address indexed _caller); - /// @notice Emitted when the eSIM being remvoved has no ETH to call back to this device wallet + /// @notice Emitted when the eSIM wallet being removed has no ETH to call back event NoETHToCallback(); /// @notice Emitted when the eSIM being removed sends back ETH to this device wallet event ETHCalledBack(uint256 _amount); - /// @notice Registry contract instance - Registry public registry; - - /// @notice eSIM wallet factory address - ESIMWalletFactory public eSIMWalletFactory; - - /// @notice String identifier to uniquely identify user's device - string public deviceUniqueIdentifier; - - /// @notice Set to true if the eSIM wallet belongs to this device wallet - mapping(address eSIMWalletAddress => bool isValid) public isValidESIMWallet; - - /// @notice Tracks if an associated eSIM wallet can pull ETH or not - mapping(address eSIMWalletAddress => bool isAllowedToPullETH) public canPullETH; - - function _onlyRegistryOrDeviceWalletFactoryOrOwner() private view { + /// @notice Reverts unless the caller is the registry, the device wallet factory, this wallet + /// itself, or the eSIM wallet the registry still names this device wallet as holding + /// @dev Private rather than inline in the modifier, so the check is emitted once instead of at + /// every use site. Keep each of these next to the modifier that calls it. + /// + /// The eSIM wallet branch is checked against the registry's own association rather than + /// the caller's own owner(), because a caller naming itself as `_eSIMWalletAddress` + /// controls what its own owner() returns. The registry association can only reach this + /// device wallet's address through a prior bindESIMWallet call, which already required + /// real ownership at that time and is not something a caller can forge. + function _onlyRegistryOrDeviceWalletFactoryOrOwner(address _eSIMWalletAddress) private view { if( msg.sender != address(registry) && msg.sender != address(registry.deviceWalletFactory()) && - msg.sender != address(this) + msg.sender != address(this) && + !(msg.sender == _eSIMWalletAddress && registry.isESIMWalletValid(_eSIMWalletAddress) == address(this)) ) { revert Errors.OnlyRegistryOrDeviceWalletFactoryOrOwner(); } } - modifier onlyRegistryOrDeviceWalletFactoryOrOwner() { - _onlyRegistryOrDeviceWalletFactoryOrOwner(); + /// @notice Restricts a call to the registry, the device wallet factory, this wallet itself, or + /// the named eSIM wallet re-adding itself + modifier onlyRegistryOrDeviceWalletFactoryOrOwner(address _eSIMWalletAddress) { + _onlyRegistryOrDeviceWalletFactoryOrOwner(_eSIMWalletAddress); _; } - function _onlySelfOrAssociatedESIMWallet() private view { + /// @notice Reverts unless the caller is this wallet itself or the eSIM wallet being removed + /// @dev An eSIM wallet may only name itself. Accepting any associated wallet let one of them + /// unbind a sibling, strip its ETH access, put it on standby and force its balance back to + /// the device wallet. Association of the named wallet is still established by the caller, + /// which requires isValidESIMWallet before doing anything. + function _onlySelfOrESIMWalletBeingRemoved(address _eSIMWalletAddress) private view { if( msg.sender != address(this) && - !isValidESIMWallet[msg.sender] + msg.sender != _eSIMWalletAddress ) { revert Errors.OnlySelfOrAssociatedESIMWallet(); } } - modifier onlySelfOrAssociatedESIMWallet() { - _onlySelfOrAssociatedESIMWallet(); + /// @notice Restricts a call to this wallet itself or to the eSIM wallet being removed + modifier onlySelfOrESIMWalletBeingRemoved(address _eSIMWalletAddress) { + _onlySelfOrESIMWalletBeingRemoved(_eSIMWalletAddress); _; } + /// @notice Reverts unless the caller is the registry or the eSIM wallet admin + /// @dev The registry is checked first because its address is already in a warm slot, while + /// reading the admin off it costs a cold proxy hop. The registry is also the caller that + /// reaches here most, through the lazy wallet deployment path, so short-circuiting on it + /// skips the hop entirely on the common case. function _onlyESIMWalletAdminOrRegistry() private view { if ( - msg.sender != registry.deviceWalletFactory().eSIMWalletAdmin() && - msg.sender != address(registry) + msg.sender != address(registry) && + msg.sender != registry.eSIMWalletAdmin() ) { revert Errors.OnlyESIMWalletAdminOrRegistry(); } } + /// @notice Restricts a call to the registry or the eSIM wallet admin modifier onlyESIMWalletAdminOrRegistry() { _onlyESIMWalletAdminOrRegistry(); _; } + /// @notice Reverts unless the caller is an eSIM wallet this device wallet holds function _onlyAssociatedESIMWallets() private view { if (!isValidESIMWallet[msg.sender]) revert Errors.OnlyAssociatedESIMWallets(); } + /// @notice Restricts a call to an eSIM wallet this device wallet holds modifier onlyAssociatedESIMWallets() { _onlyAssociatedESIMWallets(); _; } + /// @notice Restricts a call to the eSIM wallet admin + /// @dev Read from the registry on every call, so a rotation there takes effect immediately. modifier onlyESIMWalletAdmin() { - if( - msg.sender != registry.deviceWalletFactory().eSIMWalletAdmin() - ) { + if(msg.sender != registry.eSIMWalletAdmin()) { revert Errors.OnlyESIMWalletAdmin(); } _; } + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + + /// @param anEntryPoint EntryPoint singleton this wallet validates against + /// @param _verifier Contract used to verify WebAuthn assertions /// @custom:oz-upgrades-unsafe-allow constructor constructor( IEntryPoint anEntryPoint, P256Verifier _verifier ) Account4337(anEntryPoint, _verifier) {} - /// @notice Initialises the device wallet and deploys eSIM wallets for any already existing eSIMs + /// @notice Wires the wallet to the registry and the factory, and sets its owner key + /// @dev Called as the beacon proxy's constructor argument, so it always runs in the same + /// transaction as the deployment. `Account4337.initialize` is internal, and this is the + /// only path to it. + /// @param _registry Registry contract this wallet reads the admin, vault and pause flag from + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning this wallet + /// @param _deviceUniqueIdentifier Identifier the device is reached by + /// @param _eSIMWalletFactory Factory this wallet deploys its eSIM wallets through function init( address _registry, bytes32[2] memory _deviceWalletOwnerKey, string memory _deviceUniqueIdentifier, address _eSIMWalletFactory ) external initializer { - require(_registry != address(0), "Registry contract cannot be zero"); - require(bytes(_deviceUniqueIdentifier).length != 0, "Device identifier cannot be zero"); + if(_registry == address(0)) revert Errors.ZeroAddress("_registry"); + if(_eSIMWalletFactory == address(0)) revert Errors.ZeroAddress("_eSIMWalletFactory"); + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); registry = Registry(_registry); deviceUniqueIdentifier = _deviceUniqueIdentifier; eSIMWalletFactory = ESIMWalletFactory(_eSIMWalletFactory); - + initialize(_deviceWalletOwnerKey); __ReentrancyGuard_init(); } - /// @notice Allow eSIMWalletAdmin to deploy new eSIM wallet whenever new eSIM is installed - /// @dev Don't forget to call setESIMUniqueIdentifierForAnESIMWallet function after deploying eSIM wallet - /// @param _hasAccessToETH Set to true if the eSIM wallet is allowed to pull ETH from this wallet. + // --------------------------------------------------------------------------------------------- + // eSIM wallet deployment + // --------------------------------------------------------------------------------------------- + + /// @notice Deploys an eSIM wallet for this device and binds it + /// @dev The new wallet has no eSIM identifier yet. That arrives through + /// `setESIMUniqueIdentifierForAnESIMWallet` once the eSIM itself has been created. + /// + /// ETH access is granted only afterwards, by the owner, with `toggleAccessToETH`. + /// @param _hasAccessToETH Must be false + /// @param _salt CREATE2 salt for the new eSIM wallet /// @return eSIM wallet address function deployESIMWallet( bool _hasAccessToETH, @@ -157,18 +209,74 @@ contract DeviceWallet is Initializable, ReentrancyGuardUpgradeable, Account4337 return eSIMWalletAddress; } + // --------------------------------------------------------------------------------------------- + // ETH movement + // --------------------------------------------------------------------------------------------- + + /// @notice Allow the eSIM wallets associated with this device wallet to pull ETH (for data bundles) + /// @dev Refused while the protocol is paused, and refused for a wallet whose ETH access the + /// owner has revoked. + /// @param _amount Amount of ETH to pull + /// @return The amount pulled + function pullETH(uint256 _amount) external onlyAssociatedESIMWallets nonReentrant returns (uint256) { + registry.requireNotPaused(); + if(_amount == 0) revert Errors.ZeroAmount(); + if(!canPullETH[msg.sender]) revert Errors.ETHAccessRevoked(msg.sender); + + _transferETH(msg.sender, _amount); + + return _amount; + } + + // --------------------------------------------------------------------------------------------- + // Owner key rotation + // --------------------------------------------------------------------------------------------- + + /// @inheritdoc Account4337 + /// @dev The registry holds its own record of which key owns this wallet, and the deploy paths + /// keep one key to one wallet. Rotating without telling it leaves the retired key named + /// as the owner and leaves the key taking over unregistered, free for a second wallet to + /// claim. `super` runs after the key check because it carries the `onlySelf` guard and + /// because the registry call is an external one, so the local write has to land before it. + /// + /// A key that cannot verify a signature bricks the wallet for good: this function is + /// reachable only through `execute`, which needs a signature, so there is no rotating + /// back and no reaching the balance. The deploy paths reject such a key and this path + /// writes the same storage, so it has to reject it too. + function transferOwnership( + bytes32[2] memory newOwner + ) public override returns (bytes32[2] memory) { + _requireValidOwnerKey(newOwner); + + bytes32[2] memory updatedOwner = super.transferOwnership(newOwner); + registry.updateDeviceWalletOwnerKey(newOwner); + + return updatedOwner; + } + + // --------------------------------------------------------------------------------------------- + // eSIM wallet management + // --------------------------------------------------------------------------------------------- + /// @notice Allow wallet owner or admin to set unique identifier for their eSIM wallet - /// @dev Allow lazy wallet registry to call the function for fiat users who later decided to get a smart wallet + /// @dev The registry is also a caller, which is how a wallet deployed on the lazy path gets its + /// identifier in the same transaction as its deployment. + /// + /// The claim goes in before the wallet is written, and the order matters: the wallet's own + /// slot is set once and for good, so a claim that failed afterwards would leave a wallet + /// holding an identifier the registry does not record. /// @param _eSIMWalletAddress Address of the eSIM wallet smart contract /// @param _eSIMUniqueIdentifier String unique identifier for the eSIM wallet + /// @return The identifier now written on the eSIM wallet function setESIMUniqueIdentifierForAnESIMWallet( address _eSIMWalletAddress, string calldata _eSIMUniqueIdentifier ) public onlyESIMWalletAdminOrRegistry returns (string memory) { - require( - registry.isESIMWalletValid(_eSIMWalletAddress) != address(0), - "Unknown eSIM wallet address" - ); + if(registry.isESIMWalletValid(_eSIMWalletAddress) == address(0)) { + revert Errors.UnknownESIMWallet(_eSIMWalletAddress); + } + + registry.claimESIMIdentifier(_eSIMUniqueIdentifier, _eSIMWalletAddress); ESIMWallet eSIMWallet = ESIMWallet(payable(_eSIMWalletAddress)); eSIMWallet.setESIMUniqueIdentifier(_eSIMUniqueIdentifier); @@ -176,107 +284,54 @@ contract DeviceWallet is Initializable, ReentrancyGuardUpgradeable, Account4337 return eSIMWallet.eSIMUniqueIdentifier(); } - /// @notice Allow the eSIM wallets associated with this device wallet to pay ETH for data bundles - /// @dev Instead of pulling the ETH into the eSIM wallet and then sending to the vault, - /// the eSIM wallet can directly request the device wallet to pay ETH for the data bundles - /// NOTE This function is not yet being used by the eSIM wallet. If not needed, this might be removed in future - /// @param _amount Amount of ETH to pull - function payETHForDataBundles(uint256 _amount) external onlyAssociatedESIMWallets nonReentrant returns (uint256) { - require(_amount > 0, "_amount 0"); - require(canPullETH[msg.sender] == true, "Access revoked"); - - address vault = getVaultAddress(); - _transferETH(vault, _amount); - - emit ETHPaidForDataBundle(vault, msg.sender, _amount); - - return _amount; - } - - /// @notice Allow the eSIM wallets associated with this device wallet to pull ETH (for data bundles) - /// @param _amount Amount of ETH to pull - function pullETH(uint256 _amount) external onlyAssociatedESIMWallets nonReentrant returns (uint256) { - require(_amount > 0, "_amount 0"); - require(canPullETH[msg.sender] == true, "Access revoked"); - - _transferETH(msg.sender, _amount); - - return _amount; - } - - /// @notice Fetches the vault address (that receives payment for data bundles) from the device wallet factory - /// @dev Mostly used by the associated eSIM wallets for reference - function getVaultAddress() public view returns (address) { - return registry.vault(); - } - /// @notice Allow owner to revoke or give access to any associated eSIM wallet for pulling ETH + /// @dev The only way ETH access is ever granted. Binding a wallet never carries it, so a + /// revocation stands until the owner signs a grant. /// @param _eSIMWalletAddress Address of the eSIM wallet to toggle ETH access for /// @param _hasAccessToETH Set to true to give access, false to revoke access function toggleAccessToETH(address _eSIMWalletAddress, bool _hasAccessToETH) public onlySelf { - require(isValidESIMWallet[_eSIMWalletAddress], "Unknown _eSIMWalletAddress"); + if(!isValidESIMWallet[_eSIMWalletAddress]) revert Errors.UnknownESIMWallet(_eSIMWalletAddress); canPullETH[_eSIMWalletAddress] = _hasAccessToETH; emit ETHAccessUpdated(_eSIMWalletAddress, _hasAccessToETH); } - function _transferETH(address _recipient, uint256 _amount) internal virtual { - require(_amount <= address(this).balance, "Not enough ETH"); - require(_recipient != address(0), "_recipient 0"); - - if (_amount > 0) { - (bool success,) = _recipient.call{value: _amount}(""); - if (!success) revert Errors.FailedToTransfer(); - else emit ETHSent(_recipient, _amount); - } - } - /// @notice Allow the device wallet factory or the wallet owner to add new eSIM wallet to this device wallet /// @param _eSIMWalletAddress Address of the eSIM wallet to be added - /// @param _hasAccessToETH `true` if the eSIM wallet is allowed to pull ETH from this device wallet, `false` otherwise + /// @param _hasAccessToETH Must be false. ETH access is granted only through `toggleAccessToETH` function addESIMWallet( address _eSIMWalletAddress, bool _hasAccessToETH - ) public onlyRegistryOrDeviceWalletFactoryOrOwner { + ) public onlyRegistryOrDeviceWalletFactoryOrOwner(_eSIMWalletAddress) { _addESIMWallet(_eSIMWalletAddress, _hasAccessToETH); } - /// @notice Internal function for binding eSIM wallet with the device wallet - function _addESIMWallet( - address _eSIMWalletAddress, - bool _hasAccessToETH - ) internal { - require(isValidESIMWallet[_eSIMWalletAddress] == false, "ESIM wallet already owned"); - // If the eSIM wallet is a newly deployed one, then the owner will definitely be set - // during initialisation. This device wallet will be the owner. - // If the eSIM wallet already existed, then the previous owner (device wallet) - // must transfer the ownership to the eSIM wallet, and mark its status as standby. - // And this device wallet must accept the ownership before calling the addESIMWallet function - require(ESIMWallet(payable(_eSIMWalletAddress)).owner() == address(this), "Accept ownership first"); - - isValidESIMWallet[_eSIMWalletAddress] = true; - canPullETH[_eSIMWalletAddress] = _hasAccessToETH; - - // Inform and update the registry about the newly added eSIM wallet to this device wallet - registry.updateDeviceWalletAssociatedWithESIMWallet(_eSIMWalletAddress, address(this)); - // Since the eSIM wallet now has a device wallet, remove it from standby - if(registry.isESIMWalletOnStandby(_eSIMWalletAddress)) { - registry.toggleESIMWalletStandbyStatus(_eSIMWalletAddress, false); - } - - emit ESIMWalletAdded(_eSIMWalletAddress, _hasAccessToETH, msg.sender); - } - /// @notice Allow the device wallet owner or the eSIM wallet to remove any eSIM wallet bound with this device wallet /// @param _eSIMWalletAddress Address of the eSIM wallet to be removed /// @param _callBackETH `true` if any remaining ETH needs to be called back from the ESIM wallet to this device wallet, `false` otherwise function removeESIMWallet( address _eSIMWalletAddress, bool _callBackETH - ) public onlySelfOrAssociatedESIMWallet { - require(isValidESIMWallet[_eSIMWalletAddress] == true, "Unknown eSIM wallet"); + ) public onlySelfOrESIMWalletBeingRemoved(_eSIMWalletAddress) nonReentrant { + if(!isValidESIMWallet[_eSIMWalletAddress]) revert Errors.UnknownESIMWallet(_eSIMWalletAddress); + isValidESIMWallet[_eSIMWalletAddress] = false; + canPullETH[_eSIMWalletAddress] = false; + + // Inform the registry that this eSIM wallet has been let go. Only the flag moves: the + // registry keeps naming this device wallet as the last one to hold it, which is what tells + // the protocol the wallet is still one of its own while the transfer is outstanding. The + // authority this device wallet had over it is withdrawn by the two writes above, not by + // anything in the registry. + registry.toggleESIMWalletStandbyStatus(_eSIMWalletAddress, true); + + emit ESIMWalletRemoved(_eSIMWalletAddress, address(this), msg.sender); + + // The callback runs last. All eSIM wallets share one upgradeable beacon, so the logic + // reached here is not fixed for the life of the protocol. By this point the wallet has + // already lost canPullETH and its registry association, so a handler that re-enters + // cannot use the rights it is in the middle of losing. if(_callBackETH) { try ESIMWallet(payable(_eSIMWalletAddress)).sendETHToDeviceWallet(_eSIMWalletAddress.balance) returns (uint256 _amount) { emit ETHCalledBack(_amount); @@ -285,19 +340,88 @@ contract DeviceWallet is Initializable, ReentrancyGuardUpgradeable, Account4337 emit NoETHToCallback(); } } + } - isValidESIMWallet[_eSIMWalletAddress] = false; + /// @notice Binds an eSIM wallet to this device wallet and records it with the registry + /// @dev Refuses a wallet this device wallet does not already own, so binding cannot run ahead + /// of the ownership handover. + /// + /// A bind never carries ETH access. `toggleAccessToETH` is `onlySelf` and the only writer + /// of a `true`, so no bind can undo the owner's revocation. Asking for access here reverts + /// rather than being downgraded in silence. + /// @param _eSIMWalletAddress Address of the eSIM wallet to bind + /// @param _hasAccessToETH Must be false + function _addESIMWallet( + address _eSIMWalletAddress, + bool _hasAccessToETH + ) internal { + if(_hasAccessToETH) revert Errors.ETHAccessNotGrantableAtBind(_eSIMWalletAddress); + if(isValidESIMWallet[_eSIMWalletAddress]) revert Errors.ESIMWalletAlreadyAdded(_eSIMWalletAddress); + // If the eSIM wallet is a newly deployed one, then the owner will definitely be set + // during initialisation. This device wallet will be the owner. + // If the eSIM wallet already existed, then the previous owner (device wallet) + // must transfer the ownership to the eSIM wallet, and mark its status as standby. + // And this device wallet must accept the ownership before calling the addESIMWallet function + address eSIMWalletOwner = ESIMWallet(payable(_eSIMWalletAddress)).owner(); + if(eSIMWalletOwner != address(this)) { + revert Errors.ESIMWalletNotOwnedByThisDeviceWallet(_eSIMWalletAddress, eSIMWalletOwner); + } + + isValidESIMWallet[_eSIMWalletAddress] = true; + // Already false on arrival, since `removeESIMWallet` zeroes it. Written anyway so the + // property is readable here. canPullETH[_eSIMWalletAddress] = false; - // Inform and update the registry about the existingd eSIM wallet being removed from this device wallet - registry.toggleESIMWalletStandbyStatus(_eSIMWalletAddress, true); - registry.updateDeviceWalletAssociatedWithESIMWallet(_eSIMWalletAddress, address(0)); + // Inform the registry that this device wallet now holds the eSIM wallet. The call writes the + // association and, if a release was outstanding, lowers the transit marker. The two records + // are independent and this is the only call that touches both. + registry.bindESIMWallet(_eSIMWalletAddress, address(this)); - emit ESIMWalletRemoved(_eSIMWalletAddress, address(this), msg.sender); + emit ESIMWalletAdded(_eSIMWalletAddress, false, msg.sender); + } + + // --------------------------------------------------------------------------------------------- + // ETH transfers and key checks + // --------------------------------------------------------------------------------------------- + + /// @notice Sends ETH out of this wallet, reverting if the call fails + /// @dev A zero amount is a no-op rather than a revert. + /// @param _recipient Address receiving the ETH + /// @param _amount Amount in wei + function _transferETH(address _recipient, uint256 _amount) internal virtual { + uint256 balance = address(this).balance; + if(_amount > balance) revert Errors.InsufficientBalance(balance, _amount); + if(_recipient == address(0)) revert Errors.ZeroAddress("_recipient"); + + if (_amount > 0) { + (bool success,) = _recipient.call{value: _amount}(""); + if (!success) revert Errors.FailedToTransfer(); + else emit ETHSent(_recipient, _amount); + } + } + + /// @notice Rejects a P256 public key that is not a point on the curve + /// @dev Same predicate the three deploy paths apply, repeated here because the factory holds + /// its own copy privately and this contract is not in its inheritance chain. Sharing one + /// copy would mean an external call on a path that must stay self-contained. The + /// predicate also covers a key outside the field and the point at infinity. + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key to check + function _requireValidOwnerKey(bytes32[2] memory _deviceWalletOwnerKey) private pure { + if( + !FCL_Elliptic_ZZ.ecAff_isOnCurve( + uint256(_deviceWalletOwnerKey[0]), + uint256(_deviceWalletOwnerKey[1]) + ) + ) revert Errors.InvalidDeviceWalletOwnerKey(); + } + + /// @notice Fetches the vault address that receives payment for data bundles + /// @dev Read through to the registry rather than cached, so a vault change reaches every + /// wallet at once. The associated eSIM wallets call this before paying. + /// @return The vault address + function getVaultAddress() public view returns (address) { + return registry.vault(); } - // receive function already exists in the Account4337.sol - // receive() external payable { - // receive ETH - // } + // ETH is received through the receive function Account4337 declares } diff --git a/contracts/device-wallet/DeviceWalletFactory.sol b/contracts/device-wallet/DeviceWalletFactory.sol index 4a6ec497..b67dc00b 100644 --- a/contracts/device-wallet/DeviceWalletFactory.sol +++ b/contracts/device-wallet/DeviceWalletFactory.sol @@ -1,40 +1,62 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Libraries +import {FCL_Elliptic_ZZ} from "FreshCryptoLib/FCL_elliptic.sol"; +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Errors} from "../Errors.sol"; + +// Types +import {Wallets} from "../CustomStructs.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +// Interfaces +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; + +// Contracts import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; - -import "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; -import "@openzeppelin/contracts/utils/Create2.sol"; -import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - import {Registry} from "../Registry.sol"; import {DeviceWallet} from "./DeviceWallet.sol"; import {ESIMWalletFactory} from "../esim-wallet/ESIMWalletFactory.sol"; import {P256Verifier} from "../P256Verifier.sol"; -import {Errors} from "../Errors.sol"; -import "../CustomStructs.sol"; -/// @notice Contract for deploying a new eSIM wallet +/// @notice Deploys device wallets at deterministic addresses and owns the beacon they all point at +/// @dev A UUPS singleton with two deployment routes. The admin batch route deploys a wallet, its +/// first eSIM wallet and the registry records together. The EntryPoint route, `createAccount`, +/// writes no external storage at all, so a wallet created that way is registered afterwards +/// through `postCreateAccount`. Both land on the same CREATE2 address for the same inputs. contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable { - /// @notice Emitted when factory is deployed and admin is set + /// @notice Upgradeable beacon that points to correct Device wallet implementation + /// @dev Every device wallet is a beacon proxy reading its implementation from here, so one + /// update moves all of them at once and none can decline it. + UpgradeableBeacon public beacon; + + /// @notice ERC-4337 EntryPoint singleton passed into every device wallet implementation + IEntryPoint public entryPoint; + + /// @notice Contract the device wallets verify WebAuthn assertions through + P256Verifier public verifier; + + ///@notice Registry contract instance + Registry public registry; + + /// @notice eSIM wallet factory contract instance + ESIMWalletFactory public eSIMWalletFactory; + + /// @notice Tracks all the device wallets that have their data added into the registry upon deployment + mapping(address deviceWallet => bool isAdded) public deviceWalletInfoAdded; + + /// @notice Emitted when factory is deployed event DeviceWalletFactoryDeployed( - address _admin, - address _vault, address indexed _upgradeManager, address indexed _deviceWalletImplementation, address indexed _beacon ); - /// @notice Emitted when the Vault address is updated - event VaultAddressUpdated(address indexed _updatedVaultAddress); - /// @notice Emitted when a new device wallet is deployed event DeviceWalletDeployed( address indexed _deviceWalletAddress, @@ -42,104 +64,62 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr bytes32[2] _deviceWalletOwnerKey ); - /// @notice Emitted when the current admin requests to transfer admin role to a new address - event AdminUpdateRequested(address indexed eSIMWalletAdmin, address indexed _newAdmin); - - /// @notice Emitted when the newly requested admin accepts the role - event AdminUpdated(address indexed _newAdmin); - - /// @notice Emitted when the current admin revokes the transfer of ownership - event AdminUpdateRevoked(address indexed _currentAdmin, address indexed _revokedAddress); - /// @notice Emitted when the device wallet implementation is updated event DeviceWalletImplementationUpdated(address indexed _newDeviceImplementation); /// @notice Emitted when the registry is added to the factory contract event AddedRegistry(address indexed registry); - /// @notice Upgradeable beacon that points to correct Device wallet implementation - /// @dev Just updating the device wallet implementation address in this contract resolves - /// the issue of manually updating each device wallet proxy with a new implementation - UpgradeableBeacon public beacon; - - IEntryPoint public entryPoint; - - P256Verifier public verifier; - - ///@notice Registry contract instance - Registry public registry; - - /// @notice eSIM wallet factory contract instance - ESIMWalletFactory public eSIMWalletFactory; - - /// @notice Admin address of the eSIM wallet project - address public eSIMWalletAdmin; - - /// @notice Vault address that receives payments for eSIM data bundles - address public vault; - - /// @notice Address of the admin to be appointed - /// @dev Only the current admin can send the request to transfer admin role - /// The new admin should accept the role, once accepted, this variable should be reset - address public newRequestedAdmin; - - /// @notice Tracks all the device wallets that have their data added into the registry upon deployment - mapping(address deviceWallet => bool isAdded) public deviceWalletInfoAdded; - - function _onlyAdmin() private view { - if (msg.sender != eSIMWalletAdmin) revert Errors.OnlyAdmin(); - } - - modifier onlyAdmin() { - _onlyAdmin(); - _; - } - + /// @notice Reverts unless the caller is the eSIM wallet admin or the registry + /// @dev Private rather than inline in the modifier, so the check is emitted once instead of at + /// every use site. Keep each of these next to the modifier that calls it. function _onlyAdminOrRegistry() private view { if ( - msg.sender != eSIMWalletAdmin && + msg.sender != eSIMWalletAdmin() && msg.sender != address(registry) ) revert Errors.OnlyAdminOrRegistry(); } + /// @notice Restricts a call to the eSIM wallet admin or the registry modifier onlyAdminOrRegistry() { _onlyAdminOrRegistry(); _; } - modifier onlyEntryPoint() { - if(msg.sender != address(entryPoint)) revert Errors.OnlyEntryPoint(); - _; + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + + /// @dev Locks the implementation contract itself. Without this, anyone can call initialize + /// directly on the implementation, own it, and make it deploy a beacon it controls. The + /// proxy is unaffected either way, but an owned implementation is a trap for any later + /// upgrade that adds an outward call. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); } - // /// @custom:oz-upgrades-unsafe-allow constructor - // constructor() initializer {} - - /// @dev Owner based upgrades - function _authorizeUpgrade(address newImplementation) - internal - override - onlyOwner - {} - - /// @param _eSIMWalletAdmin Admin address of the eSIM wallet project - /// @param _vault Address of the vault that receives payments for the data bundles + /// @notice Deploys the beacon and hands ownership of this factory to the upgrade manager + /// @dev Neither the admin nor the vault is taken here. Both come from the registry, which is + /// added afterwards through addRegistryAddress, so admin functions stay closed until that + /// is done and every payment reads one address. + /// @param _deviceWalletImplementation First device wallet logic contract the beacon points at /// @param _upgradeManager Admin address responsible for upgrading contracts + /// @param _eSIMWalletFactoryAddress Factory the device wallets deploy their eSIM wallets through + /// @param _entryPoint ERC-4337 EntryPoint singleton for this chain + /// @param _verifier Contract the device wallets verify WebAuthn assertions through function initialize( address _deviceWalletImplementation, - address _eSIMWalletAdmin, - address _vault, address _upgradeManager, address _eSIMWalletFactoryAddress, IEntryPoint _entryPoint, P256Verifier _verifier ) external initializer { - require(_eSIMWalletAdmin != address(0), "Admin cannot be zero address"); - require(_vault != address(0), "Vault address cannot be zero"); - require(_upgradeManager != address(0), "_upgradeManager cannot be zero"); + if(_upgradeManager == address(0)) revert Errors.ZeroAddress("_upgradeManager"); + if(address(_entryPoint) == address(0)) revert Errors.ZeroAddress("_entryPoint"); + if(address(_verifier) == address(0)) revert Errors.ZeroAddress("_verifier"); + if(_eSIMWalletFactoryAddress == address(0)) revert Errors.ZeroAddress("_eSIMWalletFactoryAddress"); - eSIMWalletAdmin = _eSIMWalletAdmin; - vault = _vault; entryPoint = _entryPoint; verifier = _verifier; eSIMWalletFactory = ESIMWalletFactory(_eSIMWalletFactoryAddress); @@ -148,24 +128,31 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr beacon = new UpgradeableBeacon(_deviceWalletImplementation, address(this)); emit DeviceWalletFactoryDeployed( - _eSIMWalletAdmin, - _vault, _upgradeManager, getCurrentDeviceWalletImplementation(), address(beacon) ); - + __Ownable_init(_upgradeManager); __Ownable2Step_init(); __UUPSUpgradeable_init(); } - /// @notice Allow admin to add registry contract after it has been deployed + // --------------------------------------------------------------------------------------------- + // Registry wiring and beacon + // --------------------------------------------------------------------------------------------- + + /// @notice Allow the owner to add the registry contract after it has been deployed + /// @dev Write-once, and the owner rather than the admin, because the admin is read from the + /// registry and there is no admin to check against until this call lands. Matches + /// ESIMWalletFactory, which has always gated its own version on the owner. + /// @param _registryContractAddress Address of the registry + /// @return The registry address now in force function addRegistryAddress( address _registryContractAddress - ) external onlyAdmin returns (address) { - require(_registryContractAddress != address(0), "_registryContractAddress 0"); - require(address(registry) == address(0), "Already added"); + ) external onlyOwner returns (address) { + if(_registryContractAddress == address(0)) revert Errors.ZeroAddress("_registryContractAddress"); + if(address(registry) != address(0)) revert Errors.RegistryAlreadySet(address(registry)); registry = Registry(_registryContractAddress); emit AddedRegistry(address(registry)); @@ -173,59 +160,18 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr return address(registry); } - /// @notice Function to update vault address. - /// @dev Can only be called by the admin - /// @param _newVaultAddress New vault address - function updateVaultAddress(address _newVaultAddress) public onlyAdmin returns (address) { - require(vault != _newVaultAddress, "Cannot update to same address"); - require(_newVaultAddress != address(0), "Vault address cannot be zero"); - - vault = _newVaultAddress; - emit VaultAddressUpdated(vault); - - return vault; - } - - /// @notice 2-step admin update function. Current admin sends request to for the new admin to accept the role - /// @dev The function deliberately doesn't check for any existing requests - /// In case the current admin sends request to an unintended address, the admin can override - /// the request to a new (intended) address by calling this function again. - /// @param _newAdmin Address of the recipient to recieve the admin role - function requestAdminUpdate(address _newAdmin) external onlyAdmin { - require(_newAdmin != address(0), "Admin address cannot be zero"); - - if(_newAdmin == eSIMWalletAdmin) { - address revokedAdmin = newRequestedAdmin; - newRequestedAdmin = address(0); - emit AdminUpdateRevoked(msg.sender, revokedAdmin); - } - else { - newRequestedAdmin = _newAdmin; - emit AdminUpdateRequested(eSIMWalletAdmin, _newAdmin); - } - } - - /// @notice Function to update admin address - /// @return Address of the new admin - function acceptAdminUpdate() external returns (address) { - require(msg.sender == newRequestedAdmin, "Unauthorised"); - - eSIMWalletAdmin = msg.sender; - emit AdminUpdated(msg.sender); - - // Reset the requested admin to address(0) for further role transfer - newRequestedAdmin = address(0); - - return eSIMWalletAdmin; - } - /// @notice Function to update the device wallet implementation + /// @dev Moves every device wallet in the protocol at once. Treat any change here as a + /// protocol-wide upgrade, since no wallet can decline it. /// @param _newDeviceImpl Address of the new device implementation contract + /// @return The implementation now in force function updateDeviceWalletImplementation( address _newDeviceImpl ) external onlyOwner returns (address) { - require(_newDeviceImpl != address(0), "_newDeviceImpl 0"); - require(_newDeviceImpl != getCurrentDeviceWalletImplementation(), "Existing implementation"); + if(_newDeviceImpl == address(0)) revert Errors.ZeroAddress("_newDeviceImpl"); + if(_newDeviceImpl == getCurrentDeviceWalletImplementation()) { + revert Errors.ImplementationUnchanged(_newDeviceImpl); + } beacon.upgradeTo(_newDeviceImpl); @@ -234,90 +180,256 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr return getCurrentDeviceWalletImplementation(); } + // --------------------------------------------------------------------------------------------- + // Device wallet deployment + // --------------------------------------------------------------------------------------------- + /// @notice To deploy multiple device wallets at once + /// @dev Each entry deploys a device wallet, its first eSIM wallet and the registry records in + /// one go. ETH left over once the batch has been funded is returned to the caller. /// @param _deviceUniqueIdentifiers Array of unique device identifiers for each device wallet /// @param _deviceWalletOwnersKey Array of P256 public keys of owners of the respective device wallets - /// @param _depositAmounts Array of all the ETH to be deposited into each of the device wallets + /// @param _salts Array of CREATE2 salts, one per device wallet + /// @param _depositAmounts Array of all the ETH to be deposited into each of the device wallets /// @return Array of deployed device wallet address function deployDeviceWalletForUsers( - string[] memory _deviceUniqueIdentifiers, - bytes32[2][] memory _deviceWalletOwnersKey, + string[] calldata _deviceUniqueIdentifiers, + bytes32[2][] calldata _deviceWalletOwnersKey, uint256[] calldata _salts, uint256[] calldata _depositAmounts ) external payable onlyAdminOrRegistry returns (Wallets[] memory) { uint256 numberOfDeviceWallets = _deviceUniqueIdentifiers.length; - require(numberOfDeviceWallets != 0, "Array cannot be empty"); - require(numberOfDeviceWallets == _deviceWalletOwnersKey.length, "Array mismatch"); - require(numberOfDeviceWallets == _salts.length, "Array mismatch"); - require(numberOfDeviceWallets == _depositAmounts.length, "Array mismatch"); + if(numberOfDeviceWallets == 0) revert Errors.EmptyBatch(); + if(numberOfDeviceWallets != _deviceWalletOwnersKey.length) { + revert Errors.ArrayLengthMismatch(numberOfDeviceWallets, _deviceWalletOwnersKey.length); + } + if(numberOfDeviceWallets != _salts.length) { + revert Errors.ArrayLengthMismatch(numberOfDeviceWallets, _salts.length); + } + if(numberOfDeviceWallets != _depositAmounts.length) { + revert Errors.ArrayLengthMismatch(numberOfDeviceWallets, _depositAmounts.length); + } // Track the available ETH to spend uint256 availableETH = msg.value; Wallets[] memory walletsDeployed = new Wallets[](numberOfDeviceWallets); + // The lazy route reaches this through the registry and is deploying against its own + // reservation, so only a direct admin batch is checked. Read once rather than per entry. + bool checkReservations = msg.sender != address(registry); + for (uint256 i = 0; i < numberOfDeviceWallets; ++i) { - require(_depositAmounts[i] <= availableETH, "Out of ETH"); - - walletsDeployed[i] = _deployDeviceWallet( + if(_depositAmounts[i] > availableETH) { + revert Errors.InsufficientBalance(availableETH, _depositAmounts[i]); + } + + if(checkReservations) { + registry.requireDeviceIdentifierNotReserved(_deviceUniqueIdentifiers[i]); + } + + uint256 spentETH; + (walletsDeployed[i], spentETH) = _deployDeviceWallet( _deviceUniqueIdentifiers[i], _deviceWalletOwnersKey[i], _salts[i], _depositAmounts[i] ); - availableETH -= _depositAmounts[i]; + // Charge the budget for what was forwarded, not what was requested. An entry that + // resolves to an existing wallet forwards nothing, and that ETH must stay refundable. + availableETH -= spentETH; } // return unused ETH if(availableETH > 0) { (bool success,) = msg.sender.call{value: availableETH}(""); - require(success, "ETH return failed"); + if(!success) revert Errors.FailedToTransfer(); } return walletsDeployed; } - /// @dev Internal function to allow admin to deploy a device wallet (and an eSIM wallet) for given unique device identifiers + /// @notice Records a wallet the EntryPoint deployed through createAccount + /// @dev Not needed on the admin batch route, which writes the registry itself. Callable by the + /// admin directly and by the registry on the lazy deployment path. + /// + /// The wallet was not deployed in this call, so nothing binds the arguments to it. The + /// re-derivation does: the key and the identifier are proxy constructor arguments, so an + /// address matching the derivation and holding code was deployed here with exactly those. + /// @param _deviceWallet Wallet that was deployed + /// @param _deviceUniqueIdentifier Identifier the device is reached by + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning the wallet + /// @param _salt CREATE2 salt the wallet was deployed with + function postCreateAccount( + address _deviceWallet, + string memory _deviceUniqueIdentifier, + bytes32[2] memory _deviceWalletOwnerKey, + uint256 _salt + ) external onlyAdminOrRegistry { + if(deviceWalletInfoAdded[_deviceWallet]) revert Errors.DeviceWalletInfoAlreadyAdded(_deviceWallet); + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); + _requireValidOwnerKey(_deviceWalletOwnerKey); + + // Same reason as the batch route: only a direct admin call is claiming a fresh identifier. + // This is also where the permissionless `createAccount` route gets checked, since that one + // runs inside ERC-4337 validation and may not read another contract's storage. + if(msg.sender != address(registry)) { + registry.requireDeviceIdentifierNotReserved(_deviceUniqueIdentifier); + } + + address derived = getCounterFactualAddress( + _deviceWalletOwnerKey, + _deviceUniqueIdentifier, + _salt + ); + if(derived != _deviceWallet) revert Errors.DeviceWalletMismatch(_deviceWallet, derived); + + // The derivation answers for an address whether or not anything stands there, so the code + // check is what separates a wallet from a slot someone could still deploy into. + if(_deviceWallet.code.length == 0) revert Errors.DeviceWalletNotDeployed(_deviceWallet); + + // Flag set before the call so a second pass through here cannot reach the registry at all. + // The registry already rejects a duplicate identifier or key, so this closes the window + // rather than being the only thing holding it shut. + deviceWalletInfoAdded[_deviceWallet] = true; + registry.updateDeviceWalletInfo(_deviceWallet, _deviceUniqueIdentifier, _deviceWalletOwnerKey); + } + + // --------------------------------------------------------------------------------------------- + // Deployment through the EntryPoint + // --------------------------------------------------------------------------------------------- + + /// @notice Deploys a device wallet, returning the existing one if that address already holds it + /// @dev Called by the EntryPoint during a user operation, so it must not read or write any + /// other contract's storage: that is barred by the ERC-4337 validation rules. The registry + /// is therefore not consulted here and not written, and `postCreateAccount` records the + /// wallet afterwards. Validation the registry would have done happens offchain, through + /// `preCreateAccountValidation`. + /// + /// Returning an existing address rather than reverting is what makes + /// `entryPoint.getSenderAddress()` keep working once the account has been created. + /// @param _deviceUniqueIdentifier Identifier the device is reached by + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning the wallet + /// @param _salt CREATE2 salt for the wallet + /// @return deviceWallet The wallet at the computed address, new or already deployed + function createAccount( + string memory _deviceUniqueIdentifier, + bytes32[2] memory _deviceWalletOwnerKey, + uint256 _salt + ) public payable returns (DeviceWallet deviceWallet) { + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); + _requireValidOwnerKey(_deviceWalletOwnerKey); + + address addr = getCounterFactualAddress( + _deviceWalletOwnerKey, + _deviceUniqueIdentifier, + _salt + ); + + uint256 codeSize = addr.code.length; + if (codeSize > 0) { + // The wallet is already deployed, so the ETH has to follow it. Keeping it here would + // strand it in the factory, which has no way to send it anywhere. + if (msg.value > 0) { + _fundDeviceWallet(addr, msg.value); + } + + return DeviceWallet(payable(addr)); + } + + deviceWallet = DeviceWallet( + payable( + new BeaconProxy{salt : bytes32(_salt)}( + address(beacon), + abi.encodeCall( + DeviceWallet.init, + (address(registry), _deviceWalletOwnerKey, _deviceUniqueIdentifier, address(eSIMWalletFactory)) + ) + ) + ) + ); + + // Funding has to come after deployment, since the wallet does not exist before this point + if (msg.value > 0) { + _fundDeviceWallet(address(deviceWallet), msg.value); + } + } + + // --------------------------------------------------------------------------------------------- + // Ownership and upgrades + // --------------------------------------------------------------------------------------------- + + /// @notice Ownership of this contract is never renounced + /// @dev The owner is the only caller _authorizeUpgrade accepts, and this contract owns the + /// beacon, so it is also the only route to updateDeviceWalletImplementation. Renouncing + /// would freeze every device wallet on its current logic permanently. + function renounceOwnership() public pure override { + revert Errors.OwnershipCannotBeRenounced(); + } + + /// @notice Restricts UUPS upgrades of this factory to the owner + /// @param newImplementation Address of the implementation being moved to + function _authorizeUpgrade(address newImplementation) + internal + override + onlyOwner + {} + + // --------------------------------------------------------------------------------------------- + // Deployment internals + // --------------------------------------------------------------------------------------------- + + /// @notice Deploys one device wallet, its first eSIM wallet and the binding between them /// @param _deviceUniqueIdentifier Unique device identifier for the device wallet /// @param _deviceWalletOwnerKey User's P256 public key (owner of the device wallet and respective eSIM wallets) + /// @param _salt CREATE2 salt for both wallets /// @param _depositAmount Amount of ETH to be deposited into the device wallet /// @return Deployed device wallet address + /// @return ETH actually forwarded to the wallet, zero if an existing wallet was returned function _deployDeviceWallet( string memory _deviceUniqueIdentifier, bytes32[2] memory _deviceWalletOwnerKey, uint256 _salt, uint256 _depositAmount - ) internal returns (Wallets memory) { - address deviceWalletAddress = address( - _createAccountForUser( - _deviceUniqueIdentifier, - _deviceWalletOwnerKey, - _salt, - _depositAmount - ) + ) internal returns (Wallets memory, uint256) { + (DeviceWallet deviceWallet, uint256 spentETH) = _createAccountForUser( + _deviceUniqueIdentifier, + _deviceWalletOwnerKey, + _salt, + _depositAmount ); + address deviceWalletAddress = address(deviceWallet); address eSIMWalletAddress = eSIMWalletFactory.deployESIMWallet(deviceWalletAddress, _salt); + // No ETH access: only the owner grants that, with a signed `toggleAccessToETH`. DeviceWallet(payable(deviceWalletAddress)).addESIMWallet( eSIMWalletAddress, - true + false ); emit DeviceWalletDeployed(deviceWalletAddress, eSIMWalletAddress, _deviceWalletOwnerKey); - return Wallets(deviceWalletAddress, eSIMWalletAddress); + return (Wallets(deviceWalletAddress, eSIMWalletAddress), spentETH); } + /// @notice Deploys a device wallet and writes its registry records, or adopts one that already exists + /// @dev Returns the ETH actually forwarded to the wallet, which is zero whenever an existing + /// wallet is returned instead of a new one being deployed. Callers holding a budget must + /// decrement by this value, not by the requested deposit. + /// @param _deviceUniqueIdentifier Identifier the device is reached by + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning the wallet + /// @param _salt CREATE2 salt for the wallet + /// @param _depositAmount ETH the caller asked to be deposited + /// @return deviceWallet The wallet at the computed address + /// @return spentETH ETH actually forwarded to it function _createAccountForUser( string memory _deviceUniqueIdentifier, bytes32[2] memory _deviceWalletOwnerKey, uint256 _salt, uint256 _depositAmount - ) internal returns (DeviceWallet deviceWallet) { - require( - bytes(_deviceUniqueIdentifier).length != 0, - "DeviceIdentifier cannot be empty" - ); + ) internal returns (DeviceWallet deviceWallet, uint256 spentETH) { + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); + _requireValidOwnerKey(_deviceWalletOwnerKey); address addr = getCounterFactualAddress( _deviceWalletOwnerKey, @@ -328,28 +440,35 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr // Check if the device identifier is actually unique address wallet = registry.uniqueIdentifierToDeviceWallet(_deviceUniqueIdentifier); if(wallet != address(0)) { - require(wallet == addr, "Wallet already exists with different owner"); - return DeviceWallet(payable(wallet)); + if(wallet != addr) revert Errors.DeviceWalletAlreadyExists(_deviceUniqueIdentifier, wallet); + return (DeviceWallet(payable(wallet)), 0); } // Check if P256 public key is actually unique bytes32 keyHash = keccak256(abi.encode(_deviceWalletOwnerKey[0], _deviceWalletOwnerKey[1])); wallet = registry.registeredP256Keys(keyHash); if(wallet != address(0)) { - require(wallet == addr, "Wallet already exists with different owner key"); - return DeviceWallet(payable(wallet)); + if(wallet != addr) revert Errors.OwnerKeyAlreadyRegistered(keyHash); + return (DeviceWallet(payable(wallet)), 0); } uint256 codeSize = addr.code.length; if (codeSize > 0) { - return DeviceWallet(payable(addr)); - } - - // Prefund the account with msg.value - if (msg.value > 0) { - // The ERC4337 wallet MUST have a stake in EntryPoint in order to interact using userops, - // regardless of it being deployed by an EOA or EntryPoint - entryPoint.depositTo{value: _depositAmount}(addr); + // The wallet exists but holds no registry record, which is the state createAccount + // leaves behind. Anyone can put a wallet into it, so adopt it here rather than + // returning an unregistered address that later registry writes would reject. + deviceWalletInfoAdded[addr] = true; + registry.updateDeviceWalletInfo(addr, _deviceUniqueIdentifier, _deviceWalletOwnerKey); + + // The deposit follows the wallet instead of staying behind to be refunded. Adoption is + // the one existing-wallet case where the deposit was still meant for the wallet the + // caller asked for, and leaving it behind hands anyone who deploys that address first + // a way to force the refund through a caller that cannot receive ETH. + if (_depositAmount > 0) { + _fundDeviceWallet(addr, _depositAmount); + } + + return (DeviceWallet(payable(addr)), _depositAmount); } deviceWallet = DeviceWallet( @@ -357,7 +476,7 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr new BeaconProxy{salt : bytes32(_salt)}( address(beacon), abi.encodeCall( - DeviceWallet.init, + DeviceWallet.init, (address(registry), _deviceWalletOwnerKey, _deviceUniqueIdentifier, address(eSIMWalletFactory)) ) ) @@ -366,6 +485,55 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr registry.updateDeviceWalletInfo(address(deviceWallet), _deviceUniqueIdentifier, _deviceWalletOwnerKey); deviceWalletInfoAdded[address(deviceWallet)] = true; + + // Funded last, after every storage write, because this hands control to the wallet + if (_depositAmount > 0) { + _fundDeviceWallet(address(deviceWallet), _depositAmount); + spentETH = _depositAmount; + } + } + + /// @notice Rejects a P256 public key that is not a point on the curve + /// @dev This is the same predicate FCL_ecdsa.ecdsa_verify applies before it does anything else, + /// so a key rejected here is one that could never have verified a signature. A wallet + /// deployed with such a key is unusable for its whole life and it consumes its device + /// identifier and key hash, neither of which the protocol can release. + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key to check + function _requireValidOwnerKey(bytes32[2] memory _deviceWalletOwnerKey) private pure { + if( + !FCL_Elliptic_ZZ.ecAff_isOnCurve( + uint256(_deviceWalletOwnerKey[0]), + uint256(_deviceWalletOwnerKey[1]) + ) + ) revert Errors.InvalidDeviceWalletOwnerKey(); + } + + /// @notice Sends ETH to a device wallet so that it lands in the wallet's own balance + /// @dev No EntryPoint deposit is created. A wallet holding no deposit still transacts: the + /// EntryPoint reports the whole prefund as missing during validation and the account pays + /// it out of this balance. Until an operation needs it, the ETH is spendable for anything + /// else the owner wants to do. Gas the EntryPoint does not consume is refunded into the + /// wallet's EntryPoint deposit rather than back here, so the balance drains slowly and + /// the owner reclaims it with withdrawDepositTo. + /// @param _deviceWallet Wallet to receive the ETH + /// @param _amount Amount of ETH to send + function _fundDeviceWallet(address _deviceWallet, uint256 _amount) private { + (bool success, ) = _deviceWallet.call{value: _amount}(""); + if(!success) revert Errors.FailedToTransfer(); + } + + // --------------------------------------------------------------------------------------------- + // Addresses and address prediction + // --------------------------------------------------------------------------------------------- + + /// @notice Admin address of the eSIM wallet project + /// @dev Held by the registry, which is where it is rotated, so this contract cannot fall + /// behind the rest of the protocol after a rotation. Answers address(0) before the + /// registry is wired up, which no caller can match, so admin functions stay closed until + /// then rather than reverting on a call into address(0). + function eSIMWalletAdmin() public view returns (address) { + if(address(registry) == address(0)) return address(0); + return registry.eSIMWalletAdmin(); } /// @notice Checks that all the input params needed for deploying a fresh device wallet are valid @@ -377,18 +545,9 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr string memory _deviceUniqueIdentifier, bytes32[2] memory _deviceWalletOwnerKey ) public view returns (address wallet) { - require( - bytes(_deviceUniqueIdentifier).length != 0, - "DeviceIdentifier cannot be empty" - ); - require( - _deviceWalletOwnerKey[0].length != 0, - "Key[0] cannot be empty" - ); - require( - _deviceWalletOwnerKey[1].length != 0, - "Key[1] cannot be empty" - ); + if(bytes(_deviceUniqueIdentifier).length == 0) revert Errors.EmptyDeviceIdentifier(); + _requireValidOwnerKey(_deviceWalletOwnerKey); + // Check if the device identifier is actually unique wallet = registry.uniqueIdentifierToDeviceWallet(_deviceUniqueIdentifier); if(wallet != address(0)) { @@ -403,75 +562,13 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr } } - /** - * create an account, and return its address. - * returns the address even if the account is already deployed. - * Note that during UserOperation execution, this method is called only if the account is not deployed. - * This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation - */ - /// @dev This createAccount needs to be called by the entry point, - /// hence it cannot read or write to any external contract storages - /// The validation should be done off-chain, and any storage update to external contracts should be done as a separate function - function createAccount( - string memory _deviceUniqueIdentifier, - bytes32[2] memory _deviceWalletOwnerKey, - uint256 _salt - ) public payable returns (DeviceWallet deviceWallet) { - require( - bytes(_deviceUniqueIdentifier).length != 0, - "DeviceIdentifier cannot be empty" - ); - - address addr = getCounterFactualAddress( - _deviceWalletOwnerKey, - _deviceUniqueIdentifier, - _salt - ); - - // Prefund the account with msg.value - if (msg.value > 0) { - entryPoint.depositTo{value: msg.value}(addr); - } - - uint256 codeSize = addr.code.length; - if (codeSize > 0) { - return DeviceWallet(payable(addr)); - } - - deviceWallet = DeviceWallet( - payable( - new BeaconProxy{salt : bytes32(_salt)}( - address(beacon), - abi.encodeCall( - DeviceWallet.init, - (address(registry), _deviceWalletOwnerKey, _deviceUniqueIdentifier, address(eSIMWalletFactory)) - ) - ) - ) - ); - } - - /// @notice Update the respective storage after createAccount was called via EntryPoint - /// @dev This is not needed if the admin deploys the wallet for users as an EOA - /// The function can be called by the admin directly, and can also be called by the registry - /// when deploying the wallet via lazy wallet registry - function postCreateAccount( - address _deviceWallet, - string memory _deviceUniqueIdentifier, - bytes32[2] memory _deviceWalletOwnerKey - ) external onlyAdminOrRegistry { - require(deviceWalletInfoAdded[_deviceWallet] == false, "Device info already added"); - require( - bytes(_deviceUniqueIdentifier).length != 0, - "DeviceIdentifier cannot be empty" - ); - registry.updateDeviceWalletInfo(address(_deviceWallet), _deviceUniqueIdentifier, _deviceWalletOwnerKey); - deviceWalletInfoAdded[_deviceWallet] = true; - } - - /** - * calculate the counterfactual address of this account as it would be returned by createAccount() - */ + /// @notice The address createAccount would deploy to for these inputs + /// @dev The owner key and the device identifier are part of the proxy's constructor arguments, + /// so they are folded into the address alongside the salt. Changing any of them moves it. + /// @param _deviceWalletOwnerKey X,Y co-ordinates of the P256 key owning the wallet + /// @param _deviceUniqueIdentifier Identifier the device is reached by + /// @param _salt CREATE2 salt + /// @return The predicted device wallet address function getCounterFactualAddress( bytes32[2] memory _deviceWalletOwnerKey, string memory _deviceUniqueIdentifier, @@ -494,7 +591,7 @@ contract DeviceWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgr ); } - /// @notice Public function to get the current device wallet implementation (logic) contract + /// @notice The device wallet logic contract every device wallet currently runs function getCurrentDeviceWalletImplementation() public view returns (address) { return beacon.implementation(); } diff --git a/contracts/esim-wallet/ESIMWallet.sol b/contracts/esim-wallet/ESIMWallet.sol index 30849679..8f07ca17 100644 --- a/contracts/esim-wallet/ESIMWallet.sol +++ b/contracts/esim-wallet/ESIMWallet.sol @@ -1,27 +1,57 @@ // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; -pragma solidity 0.8.25; +// Libraries +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {Errors} from "../Errors.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +// Types +import {DataBundleDetails} from "../CustomStructs.sol"; + +// Contracts import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {DeviceWallet} from "../device-wallet/DeviceWallet.sol"; import {Registry} from "../Registry.sol"; -import {Errors} from "../Errors.sol"; -import "../CustomStructs.sol"; +/// @notice One eSIM, its purchase history and the ETH that pays for its data bundles +/// @dev A beacon proxy deployed by `ESIMWalletFactory`, always owned by a device wallet. The owner +/// is a contract rather than a key, so every call that moves ETH or ownership arrives through +/// a device wallet `execute` and has already been signed for. The admin can charge this wallet +/// for a data bundle but cannot raise the ceiling that limits what it may charge. contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using Address for address; - /// Emitted when the eSIM wallet is deployed + /// @notice Address of the eSIM wallet factory contract + address public eSIMWalletFactory; + + /// @notice String identifier to uniquely identify eSIM wallet + string public eSIMUniqueIdentifier; + + /// @notice Device wallet contract instance associated with this eSIM wallet + DeviceWallet public deviceWallet; + + /// @notice Array of all the data bundle purchase + DataBundleDetails[] public transactionHistory; + + /// @notice Address of the owner (device wallet) that becomes the new owner + address public newRequestedOwner; + + /// @notice Most this wallet may be charged for one data bundle, or zero to follow the registry + /// @dev Appended, and this contract is a leaf, so the slot lands past everything a live proxy + /// already holds and reads zero there. Zero has to keep meaning "no limit of my own" for + /// that reason, which is why the fallback lives on the registry rather than here. + uint256 public dataBundlePriceCap; + + /// @notice Emitted when the eSIM wallet is deployed event ESIMWalletDeployed( address indexed _eSIMWalletAddress, address indexed _deviceWalletAddress, address indexed _owner ); - /// Emitted when the payment for a data bundle is made + /// @notice Emitted when the payment for a data bundle is made event DataBundleBought( string _dataBundleID, uint256 _dataBundlePrice, @@ -31,8 +61,10 @@ contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade /// @notice Emitted when the eSIM unique identifier is initialised event ESIMUniqueIdentifierInitialised(string _eSIMUniqueIdentifier); - /// @notice Emitted when the lazy wallet registry populates history after wallet deployment - event TransactionHistoryPopulated(DataBundleDetails[] _dataBundleDetails); + /// @notice Emitted for every batch of history the lazy wallet registry copies in after deployment. + /// `_totalEntries` is the transaction history length once the batch has landed, which is + /// what tells a partial copy apart from a finished one. + event TransactionHistoryPopulated(DataBundleDetails[] _dataBundleDetails, uint256 _totalEntries); /// @notice Emitted when ETH moves out of this contract event ETHSent(address indexed _recipient, uint256 _amount); @@ -43,35 +75,26 @@ contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade /// @notice Emitted when the current owner revoked the ownership transfer request event OwnershipTransferRevoked(address indexed _currentOwner, address indexed _revokedOwner); - /// @notice Address of the eSIM wallet factory contract - address public eSIMWalletFactory; - - /// @notice String identifier to uniquely identify eSIM wallet - string public eSIMUniqueIdentifier; - - /// @notice Device wallet contract instance associated with this eSIM wallet - DeviceWallet public deviceWallet; - - /// @notice Array of all the data bundle purchase - DataBundleDetails[] public transactionHistory; - - /// @notice Address of the owner (device wallet) that becomes the new owner - address public newRequestedOwner; - - /// @dev A map from owner and spender to transfer approval. Determines whether - /// the spender can transfer this wallet from the owner. - // mapping(address => mapping(address => bool)) internal _isTransferApproved; + /// @notice Emitted when the owner sets this wallet's own price ceiling + event DataBundlePriceCapUpdated(uint256 _cap); + /// @notice Restricts a call to the device wallet that owns this eSIM wallet + /// @dev Reaching this means the owner signed for it, since a device wallet only calls out + /// through `execute`. modifier onlyDeviceWallet() { if (msg.sender != address(deviceWallet)) revert Errors.OnlyDeviceWallet(); _; } + /// @notice Restricts a call to the registry modifier onlyRegistry() { if(msg.sender != address(deviceWallet.registry())) revert Errors.OnlyRegistry(); _; } + /// @notice Reverts unless the caller is the owning device wallet or the eSIM wallet admin + /// @dev A private function rather than the modifier body, so the check is emitted once instead + /// of at every use site. Keep it next to the modifier that calls it. function _onlyDeviceWalletOrESIMWalletAdmin() private view { if( msg.sender != address(deviceWallet) && @@ -81,25 +104,35 @@ contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade } } + /// @notice Restricts a call to the owning device wallet or the eSIM wallet admin modifier onlyDeviceWalletOrESIMWalletAdmin() { _onlyDeviceWalletOrESIMWalletAdmin(); _; } + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + + /// @dev `_disableInitializers` rather than an `initializer` modifier. The modifier leaves the + /// version at 1, which a later `reinitializer(2)` would still accept on the implementation + /// itself. This pins it at the maximum so no version can ever run there. /// @custom:oz-upgrades-unsafe-allow constructor - constructor() initializer {} + constructor() { + _disableInitializers(); + } - /// @notice ESIMWallet initialize function to initialise the contract - /// @dev If _eSIMUniqueIdentifier is empty, the eSIM wallet is being deployed before buying an eSIM - /// If _eSIMUniqueIdentifier is non-empty, the eSIM wallet is being deployed after the eSIM has been bought by the user + /// @notice Binds a freshly deployed eSIM wallet to its factory and its owning device wallet + /// @dev The eSIM identifier is not set here. It does not exist until the eSIM itself has been + /// bought, so it arrives later through `setESIMUniqueIdentifier`. /// @param _eSIMWalletFactoryAddress eSIM wallet factory contract address /// @param _deviceWalletAddress Device wallet contract address (the contract that deploys this eSIM wallet) function initialize( address _eSIMWalletFactoryAddress, address _deviceWalletAddress ) external initializer { - require(_eSIMWalletFactoryAddress != address(0), "_eSIMWalletFactoryAddress 0"); - require(_deviceWalletAddress != address(0), "_deviceWalletAddress 0"); + if(_eSIMWalletFactoryAddress == address(0)) revert Errors.ZeroAddress("_eSIMWalletFactoryAddress"); + if(_deviceWalletAddress == address(0)) revert Errors.ZeroAddress("_deviceWalletAddress"); eSIMWalletFactory = _eSIMWalletFactoryAddress; deviceWallet = DeviceWallet(payable(_deviceWalletAddress)); @@ -110,145 +143,227 @@ contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade emit ESIMWalletDeployed(address(this), _deviceWalletAddress, _deviceWalletAddress); } + // --------------------------------------------------------------------------------------------- + // Identifier, price ceiling and history + // --------------------------------------------------------------------------------------------- + /// @notice Since buying the eSIM (along with data bundle) happens before the identifier is generated, /// the identifier is to be set separately after the wallet is deployed and eSIM is created /// @dev This function can only be called once /// @param _eSIMUniqueIdentifier String that uniquely identifies eSIM wallet function setESIMUniqueIdentifier(string calldata _eSIMUniqueIdentifier) external onlyDeviceWallet { - require(bytes(eSIMUniqueIdentifier).length == 0, "Already initialised"); - require(bytes(_eSIMUniqueIdentifier).length != 0, "_eSIMUniqueIdentifier 0"); + // Read the identifier itself only on the failing branch, so setting one for the first time + // pays for the length slot alone + if(bytes(eSIMUniqueIdentifier).length != 0) revert Errors.ESIMIdentifierAlreadySet(eSIMUniqueIdentifier); + if(bytes(_eSIMUniqueIdentifier).length == 0) revert Errors.EmptyESIMIdentifier(); eSIMUniqueIdentifier = _eSIMUniqueIdentifier; emit ESIMUniqueIdentifierInitialised(_eSIMUniqueIdentifier); } - /// @notice Function to make payment for the data bundle - /// @param _dataBundleDetail Details of the data bundle being bought. (dataBundleID, dataBundlePrice) - /// @return True if the transaction is successful - function buyDataBundle( - DataBundleDetails memory _dataBundleDetail - ) public payable onlyDeviceWalletOrESIMWalletAdmin nonReentrant returns (bool) { - require(bytes(_dataBundleDetail.dataBundleID).length > 0, "Data bundle ID cannot be empty"); - require(_dataBundleDetail.dataBundlePrice > 0, "Price cannot be zero"); - - // 1. msg.value is received by contract - // 2. if wallet balance is less than dataBundlePrice, pull ETH from device wallet - // 3. send dataBundlePrice amount of ETH to vault - uint256 walletBalance = address(this).balance; - - if (walletBalance < _dataBundleDetail.dataBundlePrice) { - uint256 remainingETH = _dataBundleDetail.dataBundlePrice - walletBalance; - deviceWallet.pullETH(remainingETH); - } - - address vault = deviceWallet.getVaultAddress(); - _transferETH(vault, _dataBundleDetail.dataBundlePrice); - - transactionHistory.push(_dataBundleDetail); - - emit DataBundleBought(_dataBundleDetail.dataBundleID, _dataBundleDetail.dataBundlePrice, msg.value); - - return true; + /// @notice Sets the most this wallet may be charged for one data bundle + /// @dev Only the owning device wallet, which means the person holding its P256 key: reaching + /// this needs a device wallet `execute`, and that needs a signature. The admin names the + /// price on `buyDataBundle`, so it must not also be able to raise the ceiling on that + /// price. Setting zero hands the wallet back to the registry's ceiling. A handover clears + /// it, so an incoming owner starts on the registry ceiling. + /// @param _cap Maximum price in wei, or zero to follow the registry + function setDataBundlePriceCap(uint256 _cap) external onlyDeviceWallet { + dataBundlePriceCap = _cap; + emit DataBundlePriceCapUpdated(_cap); } - /// @notice Function to populate history for lazy wallets. Can only be called once, by lazy wallet registry - /// @param _dataBundleDetails Array of all the data bundle purchase details before the wallet was deployed - function populateHistory(DataBundleDetails[] memory _dataBundleDetails) external onlyRegistry returns (bool) { - require(transactionHistory.length == 0, "Wallet already in use"); - - // Using transactionHistory = _dataBundleDetails; would be gas efficient - // but it is not yet supported for struct types, hence using the loop - for (uint256 i = 0; i < _dataBundleDetails.length; i++) { - // Create a temporary variable in storage - transactionHistory.push(); // Increase the length of transactionHistory by 1 - DataBundleDetails storage newTransaction = transactionHistory[transactionHistory.length - 1]; - newTransaction.dataBundleID = _dataBundleDetails[i].dataBundleID; - newTransaction.dataBundlePrice = _dataBundleDetails[i].dataBundlePrice; + /// @notice Appends pre-deployment purchase history, one batch at a time, on behalf of the lazy + /// wallet registry + /// @dev The registry carries the cursor that says how much of an eSIM's history has already been + /// copied, so this function appends whatever it is handed and does not police repeats. + /// @param _dataBundleDetails One batch of data bundle purchase details from before the wallet + /// was deployed + /// @return True once the batch has been appended + function populateHistory(DataBundleDetails[] calldata _dataBundleDetails) external onlyRegistry returns (bool) { + // Assigning the whole calldata array at once is not supported for arrays of structs, so + // each entry is pushed on its own. The batch lands after whatever the array already held. + uint256 alreadyStored = transactionHistory.length; + uint256 entries = _dataBundleDetails.length; + for (uint256 i = 0; i < entries; ++i) { + transactionHistory.push(_dataBundleDetails[i]); } - emit TransactionHistoryPopulated(_dataBundleDetails); + emit TransactionHistoryPopulated(_dataBundleDetails, alreadyStored + entries); return true; } - /// @dev Returns the current owner of the wallet - function owner() public view override returns (address) { - return OwnableUpgradeable.owner(); - } + // --------------------------------------------------------------------------------------------- + // Ownership handover + // --------------------------------------------------------------------------------------------- - /// @notice Function to request transfer of ownership (a 2-step transfer) to a new device wallet - /// If the owner revokes the transfer, they have to manually add the eSIM wallet from their device wallet + /// @notice Nominates a new device wallet to take this eSIM wallet over, in two steps + /// @dev Any outstanding request is overwritten rather than refused, so an owner who nominated + /// the wrong address just calls this again. Nominating the current owner cancels the + /// request and re-binds the wallet to its device wallet in the same call, with ETH access + /// left off since the flag it had before the removal is not recorded anywhere. /// @param _newOwner Address of the new device wallet to transfer ownership of this wallet - /** - * @dev newRequestedOwner is deliberately not checked for address(0). - * This helps in scenario where the owner sends ownership request to a wrong address - * The owner (device wallet) can simply call this function to overwrite the request - */ - function requestTransferOwnership(address _newOwner) external onlyDeviceWallet { + function requestTransferOwnership(address _newOwner) external onlyDeviceWallet nonReentrant { Registry registry = deviceWallet.registry(); - require(registry.isDeviceWalletValid(_newOwner), "Invalid _newOwner"); + if(!registry.isDeviceWalletValid(_newOwner)) revert Errors.NotADeviceWallet(_newOwner); - // If the owner wants to retain the ownership of the contract, + // If the owner wants to retain the ownership of the contract, // they simply revoke the request by requesting a transfer to themselves if(_newOwner == owner()) { address revokedAddress = newRequestedOwner; newRequestedOwner = address(0); emit OwnershipTransferRevoked(owner(), revokedAddress); + + // The request being cancelled took this wallet off its device wallet, if it was ever + // sent. A request revoked before that removal landed leaves the wallet already bound. + if(!deviceWallet.isValidESIMWallet(address(this))) { + deviceWallet.addESIMWallet(address(this), false); + } return; } - // Remove this eSIMWallet from the device wallet and send all ETH to device wallet - deviceWallet.removeESIMWallet(address(this), true); + // Remove this eSIMWallet from the device wallet and send all ETH to device wallet. + // The transient window opens here rather than at acceptance, so a reader that sees the + // standby flag raised also sees the request that caused it. Guarded because + // removeESIMWallet refuses a wallet the device wallet no longer holds, which would + // otherwise make re-targeting an outstanding request revert instead of overwriting it. + if(deviceWallet.isValidESIMWallet(address(this))) { + deviceWallet.removeESIMWallet(address(this), true); + } newRequestedOwner = _newOwner; emit OwnershipTransferRequested(owner(), newRequestedOwner); } - /// @notice Function to be called by the new owner to accept the ownership + /// @notice Takes this eSIM wallet on, callable only by the nominated device wallet + /// @dev The check compares the caller to `newRequestedOwner`, which both sides satisfy when + /// they are zero. No transaction can arrive from the zero address, so this holds onchain, + /// but any reasoning about this function has to exclude that caller explicitly. function acceptOwnershipTransfer() external { - require(msg.sender == newRequestedOwner, "Not approved"); + address requestedOwner = newRequestedOwner; + if(msg.sender != requestedOwner) revert Errors.OnlyRequestedOwner(requestedOwner); _secureTransferOwnership(); } + // --------------------------------------------------------------------------------------------- + // ETH and data bundle payments + // --------------------------------------------------------------------------------------------- + /// @notice Allow the owner device wallet to callback all the ETH from this eSIM wallet /// @dev This function is generally called before the owner device wallet removes this eSIM wallet + /// @dev Deliberately not nonReentrant. removeESIMWallet calls this from inside a try/catch while + /// requestTransferOwnership already holds this contract's guard, so guarding here would + /// make the callback revert into that catch and strand the wallet's ETH with no error. + /// It writes no state of its own, and only the owner can call it to move ETH to itself, + /// so re-entering it gains nothing. /// @param _amount Amount of ETH to be sent function sendETHToDeviceWallet( uint256 _amount ) external onlyDeviceWallet returns (uint256) { - require(owner() != address(0), "owner 0"); + if(owner() == address(0)) revert Errors.ZeroAddress("owner"); _transferETH(owner(), _amount); return _amount; } - /// @notice Do not allow owner to directly call OwnableUpgradeable's transferOwnership function - /// The owner should first call requestTransferOwnership and specify the recipient (new owner) - /// The recipient (new owner) should accept the ownership using acceptOwnershipTransfer + /// @notice Pays the vault for one data bundle and records the purchase + /// @dev Callable by the owning device wallet or by the admin, since the admin is the party that + /// knows the price. Any shortfall is pulled from the device wallet, which is why the price + /// is checked against a ceiling the admin cannot raise. + /// @param _dataBundleDetail Details of the data bundle being bought. (dataBundleID, dataBundlePrice) + /// @return True if the transaction is successful + function buyDataBundle( + DataBundleDetails memory _dataBundleDetail + ) public payable onlyDeviceWalletOrESIMWalletAdmin nonReentrant returns (bool) { + Registry registry = deviceWallet.registry(); + registry.requireNotPaused(); + if(bytes(_dataBundleDetail.dataBundleID).length == 0) revert Errors.EmptyDataBundleID(); + if(_dataBundleDetail.dataBundlePrice == 0) revert Errors.ZeroDataBundlePrice(); + _requirePriceWithinCap(_dataBundleDetail.dataBundlePrice, registry); + + // 1. msg.value is received by contract + // 2. if wallet balance is less than dataBundlePrice, pull ETH from device wallet + // 3. send dataBundlePrice amount of ETH to vault + uint256 walletBalance = address(this).balance; + + if (walletBalance < _dataBundleDetail.dataBundlePrice) { + uint256 remainingETH = _dataBundleDetail.dataBundlePrice - walletBalance; + deviceWallet.pullETH(remainingETH); + } + + address vault = deviceWallet.getVaultAddress(); + + // Recorded before the transfer, so a vault that is a contract cannot observe a purchase + // that is not yet in the history it would be reading. + transactionHistory.push(_dataBundleDetail); + + _transferETH(vault, _dataBundleDetail.dataBundlePrice); + + emit DataBundleBought(_dataBundleDetail.dataBundleID, _dataBundleDetail.dataBundlePrice, msg.value); + + return true; + } + + // --------------------------------------------------------------------------------------------- + // Closed ownership routes + // --------------------------------------------------------------------------------------------- + + /// @notice The inherited one-step transfer is closed + /// @dev Ownership moves through `requestTransferOwnership` and `acceptOwnershipTransfer`, which + /// also keep `deviceWallet` in step with `owner()`. A one-step transfer would move only + /// the latter. function transferOwnership(address) public pure override { - require(false, "Use acceptOwnershipTransfer instead."); + revert Errors.UseAcceptOwnershipTransfer(); } - /// @notice Instead of using transferOwnership, the contract uses secureTransferOwnership + /// @notice An eSIM wallet always belongs to a device wallet, so ownership is never renounced + /// @dev Renouncing leaves owner() at zero while deviceWallet still points at the old device + /// wallet. sendETHToDeviceWallet then reverts on its own zero-owner check and + /// DeviceWallet._addESIMWallet can never accept this wallet again, so the ETH held here + /// is unreachable for the rest of the wallet's life. + function renounceOwnership() public pure override { + revert Errors.OwnershipCannotBeRenounced(); + } + + /// @notice Completes a handover, moving `deviceWallet`, `owner()` and the price ceiling together + /// @dev Clears the request before it writes anything, so a second acceptance finds nothing. + /// The ceiling is the owner's own limit and only the owner can set it, so it goes with the + /// owner rather than binding the incoming one to a figure it never chose. function _secureTransferOwnership() internal { address newOwner = newRequestedOwner; - address previousOwner = owner(); // Reset ownership transfer address newRequestedOwner = address(0); deviceWallet = DeviceWallet(payable(newOwner)); + + // Written only on a change, so a wallet that never set a ceiling emits nothing here + if(dataBundlePriceCap != 0) { + dataBundlePriceCap = 0; + emit DataBundlePriceCapUpdated(0); + } + // Transfer ownership to the request address + // _transferOwnership emits OwnershipTransferred, so this function must not emit it again _transferOwnership(newOwner); - emit OwnershipTransferred(previousOwner, owner()); } - /// @dev Internal function to send ETH from this contract + // --------------------------------------------------------------------------------------------- + // ETH transfers and cap checks + // --------------------------------------------------------------------------------------------- + + /// @notice Sends ETH out of this contract, reverting if the call fails + /// @dev A zero amount is a no-op rather than a revert, so callers that may have nothing to send + /// do not need their own guard. + /// @param _recipient Address receiving the ETH + /// @param _amount Amount in wei function _transferETH(address _recipient, uint256 _amount) internal virtual { - require(address(this).balance >= _amount, "Not enough ETH"); - require(_recipient != address(0), "_recipient 0"); + uint256 balance = address(this).balance; + if(balance < _amount) revert Errors.InsufficientBalance(balance, _amount); + if(_recipient == address(0)) revert Errors.ZeroAddress("_recipient"); if (_amount > 0) { (bool success,) = _recipient.call{value: _amount}(""); @@ -257,7 +372,30 @@ contract ESIMWallet is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade } } - receive() external payable { - // receive ETH + /// @notice Rejects a price above whichever ceiling applies to this wallet + /// @dev The wallet's own ceiling wins when it has one. Zero here means "follow the registry", + /// not "no ceiling": the registry default is guaranteed non-zero by `Registry.initialize` + /// and `setDefaultDataBundlePriceCap`, so `cap` always resolves to a real ceiling. + /// @param _price Price being charged + /// @param _registry Registry holding the fallback ceiling + function _requirePriceWithinCap(uint256 _price, Registry _registry) private view { + uint256 cap = dataBundlePriceCap; + if (cap == 0) { + cap = _registry.defaultDataBundlePriceCap(); + } + + if (cap != 0 && _price > cap) { + revert Errors.DataBundlePriceAboveCap(_price, cap); + } + } + + /// @notice The device wallet that owns this eSIM wallet + /// @dev Declared so subclasses and mocks have one place to override. + function owner() public view override returns (address) { + return OwnableUpgradeable.owner(); } + + /// @notice Accepts plain ETH transfers, which is how the device wallet tops this wallet up + // solhint-disable-next-line no-empty-blocks + receive() external payable {} } diff --git a/contracts/esim-wallet/ESIMWalletFactory.sol b/contracts/esim-wallet/ESIMWalletFactory.sol index 6d71b0e5..1f00f72d 100644 --- a/contracts/esim-wallet/ESIMWalletFactory.sol +++ b/contracts/esim-wallet/ESIMWalletFactory.sol @@ -1,20 +1,39 @@ -pragma solidity 0.8.25; - // SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Libraries +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Errors} from "../Errors.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +// Contracts import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; - import {ESIMWallet} from "./ESIMWallet.sol"; import {Registry} from "../Registry.sol"; -import {Errors} from "../Errors.sol"; -/// @notice Contract for deploying a new eSIM wallet +/// @notice Deploys eSIM wallets and owns the beacon they all point at +/// @dev A UUPS singleton. It owns an `UpgradeableBeacon`, so one call here moves every eSIM wallet +/// in the protocol onto new logic at once. There is no per-wallet opt-out. contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable { + + /// @notice Address of the registry contract + Registry public registry; + + /// @notice Upgradeable beacon that points to the correct eSIM wallet logic contract + /// @dev Every eSIM wallet is a beacon proxy reading its implementation from here, so the + /// implementation is replaced once rather than on each proxy: + /// + /// eSIM wallet beacon proxy ─┐ + /// eSIM wallet beacon proxy ─┼─> beacon ─> eSIM wallet implementation + /// eSIM wallet beacon proxy ─┘ + UpgradeableBeacon public beacon; + + /// @notice Set to true if eSIM wallet address is deployed using the factory, false otherwise + mapping(address eSIMWalletAddress => bool isDeployed) public isESIMWalletDeployed; + /// @notice Emitted when the eSIM wallet factory is deployed event ESIMWalletFactorydeployed( address indexed _upgradeManager, @@ -37,25 +56,9 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad /// @notice Emitted when the registry is added to the factory contract event AddedRegistry(address indexed registry); - /// @notice Address of the registry contract - Registry public registry; - - /// @notice Upgradeable beacon that points to the correct eSIM wallet logic contract - /// @dev Just updating the eSIM wallet implementation address in this contract resolves - /// the issue of manually updating each eSIM wallet proxy with a new implementation - /// eSIM Wallet proxies (Beacon Proxies) --> beacon (Upgradeable Beacon) --> eSIM wallet implementation (logic contract) - /** - eSIM wallet beacon proxy ------- - | - eSIM wallet beacon proxy ------- -------> beacon (Upgradeable beacon) -------> eSIM wallet implementation - | - eSIM wallet beacon proxy ------- - */ - UpgradeableBeacon public beacon; - - /// @notice Set to true if eSIM wallet address is deployed using the factory, false otherwise - mapping(address eSIMWalletAddress => bool isDeployed) public isESIMWalletDeployed; - + /// @notice Restricts a call to the registry, the device wallet factory or a known device wallet + /// @dev The first two deploy on behalf of a device wallet during setup. A device wallet reaching + /// this directly is constrained further inside `deployESIMWallet`. modifier onlyRegistryOrDeviceWalletFactoryOrDeviceWallet() { if( msg.sender != address(registry) && @@ -66,28 +69,32 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad } _; } - - // /// @custom:oz-upgrades-unsafe-allow constructor - // constructor() initializer {} - /// @dev Owner based upgrades for UUPS eSIM wallet factory - function _authorizeUpgrade(address newImplementation) - internal - override - onlyOwner - {} + // --------------------------------------------------------------------------------------------- + // Initialisation + // --------------------------------------------------------------------------------------------- + + /// @dev Locks the implementation contract itself. Without this, anyone can call initialize + /// directly on the implementation, own it, and make it deploy a beacon it controls. The + /// proxy is unaffected either way, but an owned implementation is a trap for any later + /// upgrade that adds an outward call. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /// @notice Deploys the beacon and hands ownership of this factory to the upgrade manager + /// @dev The factory owns the beacon rather than the upgrade manager owning it directly, so the + /// only way to move the implementation is `updateESIMWalletImplementation`, which is + /// owner gated and emits an event. + /// @param _eSIMWalletImplementation First eSIM wallet logic contract the beacon points at /// @param _upgradeManager Admin address responsible for upgrading contracts function initialize ( address _eSIMWalletImplementation, address _upgradeManager ) external initializer { - require(_upgradeManager != address(0), "Address cannot be zero"); + if(_upgradeManager == address(0)) revert Errors.ZeroAddress("_upgradeManager"); - // Upgradable beacon for eSIM wallet implementation contract - // Make the eSIM wallet factory the owner of the beacon - // Only the _upgradeManager can call the update function to update the beacon - // with the new implementation (logic) contract beacon = new UpgradeableBeacon(_eSIMWalletImplementation, (address(this))); emit ESIMWalletFactorydeployed( @@ -101,13 +108,20 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad __UUPSUpgradeable_init(); } - /// @notice Allow owner to add registry contract after it's been deployed + // --------------------------------------------------------------------------------------------- + // Registry wiring + // --------------------------------------------------------------------------------------------- + + /// @notice Points the factory at the registry, which is deployed after it + /// @dev Write-once. Every caller check in this contract reads the registry, so allowing it to + /// move would let a later owner redirect all of them at once. + /// @param _registryContractAddress Address of the registry + /// @return The registry address now in force function addRegistryAddress( address _registryContractAddress - ) external returns (address) { - require(msg.sender == owner(), "Only Owner"); - require(_registryContractAddress != address(0), "_registryContractAddress 0"); - require(address(registry) == address(0), "Already added"); + ) external onlyOwner returns (address) { + if(_registryContractAddress == address(0)) revert Errors.ZeroAddress("_registryContractAddress"); + if(address(registry) != address(0)) revert Errors.RegistryAlreadySet(address(registry)); registry = Registry(_registryContractAddress); emit AddedRegistry(address(registry)); @@ -115,28 +129,42 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad return address(registry); } - /// Function to deploy an eSIM wallet - /// @dev can only be called by the respective deviceWallet contract + // --------------------------------------------------------------------------------------------- + // eSIM wallet deployment + // --------------------------------------------------------------------------------------------- + + /// @notice Deploys an eSIM wallet at a deterministic address and binds it to a device wallet /// @param _deviceWalletAddress Address of the associated device wallet + /// @param _salt CREATE2 salt, chosen by the caller and unique per wallet /// @return Address of the newly deployed eSIM wallet function deployESIMWallet( address _deviceWalletAddress, uint256 _salt ) external onlyRegistryOrDeviceWalletFactoryOrDeviceWallet returns (address) { + // The registry and the device wallet factory deploy on behalf of a device wallet, so they + // name an arbitrary one. A device wallet calling directly may only name itself: otherwise + // it can create a wallet owned by another device wallet that never asked for it and that + // neither _addESIMWallet nor the registry records, and can take the CREATE2 address that + // owner would get for this salt, leaving its own deployment to fail without a reason. + if(registry.isDeviceWalletValid(msg.sender) && _deviceWalletAddress != msg.sender) { + revert Errors.OnlyDeployForSelf(); + } + + // CREATE2 reverts with no data when something already sits at the address, which leaves + // the caller nothing to go on. The salt is its own input, so name it back. + address predicted = getCounterFactualAddress(_deviceWalletAddress, _salt); + if(predicted.code.length > 0) revert Errors.SaltAlreadyUsed(_deviceWalletAddress, _salt); + + bytes memory initialisation = abi.encodeCall( + ESIMWallet.initialize, + (address(this), _deviceWalletAddress) + ); - // Beacon Proxy deploys all the proxies which interact with the - // beacon contract to get the implementation (logic) contract address - // of the eSIM wallet. This way, the eSIM wallet implementation contract update - // takes affect immediately without having to update each proxy separately - // msg.value will be sent along with the abi.encodeCall address eSIMWalletAddress = address( payable( new BeaconProxy{salt : bytes32(_salt)}( address(beacon), - abi.encodeCall( - ESIMWallet.initialize, - (address(this), _deviceWalletAddress) - ) + initialisation ) ) ); @@ -147,19 +175,40 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad return eSIMWalletAddress; } - /// @notice Public function to get the current eSIM wallet implementation (logic) contract - function getCurrentESIMWalletImplementation() public view returns (address) { - return beacon.implementation(); + /// @notice The address deployESIMWallet would land on for these inputs + /// @dev Lets a caller probe a salt for occupancy before spending a deployment on it. + /// @param _deviceWalletAddress Device wallet the eSIM wallet would be bound to + /// @param _salt CREATE2 salt + /// @return The predicted eSIM wallet address + function getCounterFactualAddress( + address _deviceWalletAddress, + uint256 _salt + ) public view returns (address) { + bytes memory initialisation = abi.encodeCall( + ESIMWallet.initialize, + (address(this), _deviceWalletAddress) + ); + + return Create2.computeAddress( + bytes32(_salt), + keccak256(abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(beacon), initialisation))) + ); } + // --------------------------------------------------------------------------------------------- + // Beacon and ownership + // --------------------------------------------------------------------------------------------- + /// @notice Update the eSIM wallet implementation address in the beacon contract - /// @dev Beacon Proxy uses the beacon contract to get the current implementation address + /// @dev Moves every eSIM wallet in the protocol at once. Treat any change here as a + /// protocol-wide upgrade, since no wallet can decline it. /// @param _eSIMWalletImpl Address of the new eSIM wallet implementation contract + /// @return The implementation now in force function updateESIMWalletImplementation( address _eSIMWalletImpl ) external onlyOwner returns (address) { - require(_eSIMWalletImpl != address(0), "_eSIMWalletImpl 0"); - require(_eSIMWalletImpl != getCurrentESIMWalletImplementation(), "Same implementation"); + if(_eSIMWalletImpl == address(0)) revert Errors.ZeroAddress("_eSIMWalletImpl"); + if(_eSIMWalletImpl == getCurrentESIMWalletImplementation()) revert Errors.ImplementationUnchanged(_eSIMWalletImpl); beacon.upgradeTo(_eSIMWalletImpl); @@ -167,4 +216,25 @@ contract ESIMWalletFactory is Initializable, UUPSUpgradeable, Ownable2StepUpgrad return getCurrentESIMWalletImplementation(); } + + /// @notice Ownership of this contract is never renounced + /// @dev The owner is the only caller _authorizeUpgrade accepts, and this contract owns the + /// beacon, so it is also the only route to updateESIMWalletImplementation. Renouncing + /// would freeze every eSIM wallet on its current logic permanently. + function renounceOwnership() public pure override { + revert Errors.OwnershipCannotBeRenounced(); + } + + /// @notice Restricts UUPS upgrades of this factory to the owner + /// @param newImplementation Address of the implementation being moved to + function _authorizeUpgrade(address newImplementation) + internal + override + onlyOwner + {} + + /// @notice The eSIM wallet logic contract every eSIM wallet currently runs + function getCurrentESIMWalletImplementation() public view returns (address) { + return beacon.implementation(); + } } diff --git a/contracts/interfaces/IOwnable2Step.sol b/contracts/interfaces/IOwnable2Step.sol new file mode 100644 index 00000000..ab4200a1 --- /dev/null +++ b/contracts/interfaces/IOwnable2Step.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.36; + +/// @notice Minimal view of the two-step ownership handover the protocol contracts use +/// @dev Matches the part of OpenZeppelin's `Ownable2Step` an incoming owner needs. The offer is made +/// by the current owner and completed by the nominee, so a contract taking ownership only ever +/// calls these two. +interface IOwnable2Step { + /// @notice Completes a handover the current owner already offered to the caller + function acceptOwnership() external; + + /// @notice Address the current owner has offered ownership to, or zero + /// @return The nominated address + function pendingOwner() external view returns (address); +} diff --git a/contracts/interfaces/IPausable.sol b/contracts/interfaces/IPausable.sol new file mode 100644 index 00000000..04eb2cd1 --- /dev/null +++ b/contracts/interfaces/IPausable.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.36; + +/// @notice Minimal view of the pause a guardian is allowed to release +/// @dev Only `unpause()` is here. Raising a pause is the hot admin key's lever and releasing one is +/// the timelock's, so the two are deliberately not offered through the same interface. +interface IPausable { + /// @notice Clears the pause + function unpause() external; +} diff --git a/contracts/interfaces/IRegistryAdmin.sol b/contracts/interfaces/IRegistryAdmin.sol new file mode 100644 index 00000000..d05e5b3e --- /dev/null +++ b/contracts/interfaces/IRegistryAdmin.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.36; + +/// @notice Minimal view of the admin role the protocol owner controls +/// @dev Holds only the two calls `ProtocolAdmin` makes. `enableAdmin` is deliberately absent: the +/// owner reaches it as an ordinary scheduled payload, and putting it here would invite a +/// named function beside the guardian's, which is the one place a fast path could be added by +/// accident. Suspending is instant and restoring waits, and the split is what stops a +/// compromised key from undoing its own suspension. +interface IRegistryAdmin { + /// @notice Suspends the admin's powers protocol-wide, leaving its address on the books + function disableAdmin() external; + + /// @notice Nominates a new admin, which strips the incumbent until the nominee accepts + /// @param _newAdmin Address of the recipient to receive the admin role + function requestAdminUpdate(address _newAdmin) external; +} diff --git a/deployments/address.json b/deployments/address.json index ea2374fb..8cf75650 100644 --- a/deployments/address.json +++ b/deployments/address.json @@ -1,42 +1,42 @@ { - "sepolia": { - "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", - "DeviceWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", - "ESIMWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", - "DeviceWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", - "ESIMWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", - "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", - "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" - }, - "optimism-sepolia": { - "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - "P256Verifier": "0x3c15a78046838481788613A9F111F972B562623C", - "DeviceWalletImpl": "0x50x22FCFa80868dc9F423873F9332817eDAe44839746000094e2E7a034cfd0333fa2856e1293886E29", - "ESIMWalletImpl": "0xf86FE9253b6ea9454abda657f47aE508B00c15C1", - "DeviceWalletFactoryProxy": "0x243cCdE6a56b0Ba740E067f39896772748E20fFD", - "ESIMWalletFactoryProxy": "0x8444bF9C39F01e4B092e42DC11695C61f8B93957", - "RegistryProxy": "0x96dA9cE92D2C09f7b3ADE01260608e9079f16d12", - "LazyWalletRegistryProxy": "0x3F14D060074B174B0784056bDe5e0f8970D25ff1" - }, - "base-sepolia": { - "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", - "DeviceWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", - "ESIMWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", - "DeviceWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", - "ESIMWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", - "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", - "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" - }, - "kokio-mainnet-fork": { - "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", - "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", - "DeviceWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", - "ESIMWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", - "DeviceWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", - "ESIMWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", - "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", - "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" - } + "sepolia-11155111": { + "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", + "DeviceWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", + "ESIMWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", + "DeviceWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", + "ESIMWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", + "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", + "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" + }, + "optimism-sepolia-11155420": { + "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "P256Verifier": "0x3c15a78046838481788613A9F111F972B562623C", + "DeviceWalletImpl": "0x22FCFa80868dc9F423873F9332817eDAe4483974", + "ESIMWalletImpl": "0xf86FE9253b6ea9454abda657f47aE508B00c15C1", + "DeviceWalletFactoryProxy": "0x243cCdE6a56b0Ba740E067f39896772748E20fFD", + "ESIMWalletFactoryProxy": "0x8444bF9C39F01e4B092e42DC11695C61f8B93957", + "RegistryProxy": "0x96dA9cE92D2C09f7b3ADE01260608e9079f16d12", + "LazyWalletRegistryProxy": "0x3F14D060074B174B0784056bDe5e0f8970D25ff1" + }, + "base-sepolia-84532": { + "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", + "DeviceWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", + "ESIMWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", + "DeviceWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", + "ESIMWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", + "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", + "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" + }, + "kokio-mainnet-fork-1122334455": { + "EntryPoint": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "P256Verifier": "0xF04f3b3935aD461D17d4a8a78E7ea21d4a61AEb1", + "DeviceWalletImpl": "0x59A78Cbb73e94a3fD6ada0136C89AE658BA16Dd9", + "ESIMWalletImpl": "0xde0dC03eF67317D4702e1d6Ef3f8cE246517e84e", + "DeviceWalletFactoryProxy": "0x63005d8214533fC7209678Aa39F7b9b0b51a7bcB", + "ESIMWalletFactoryProxy": "0xB4473979ff8cE4e09161B08f74EEb66BD7718076", + "RegistryProxy": "0xCa447f5C75C57f6C59027304A5Fb5A09F0E005c9", + "LazyWalletRegistryProxy": "0x8a1E53b903efcc6b252CE4bD3b255202318505Ef" + } } diff --git a/docs/CustomStructs.md b/docs/CustomStructs.md index 710eef91..38c69747 100644 --- a/docs/CustomStructs.md +++ b/docs/CustomStructs.md @@ -11,8 +11,24 @@ struct DataBundleDetails { } ``` +## Wallets + +Object returned when a new device and eSIM wallet is deployed + +```solidity +struct Wallets { + address deviceWallet; + address eSIMWallet; +} +``` + ## WebAuthnSignature +One WebAuthn assertion, as the authenticator produced it + +_Decoded from calldata by `WebAuthn.tryDecodeSignature`, which zeroes the whole struct on a + malformed body rather than reverting. A zeroed struct fails verification._ + ```solidity struct WebAuthnSignature { bytes authenticatorData; @@ -26,6 +42,8 @@ struct WebAuthnSignature { ## Call +One call an account makes on its owner's behalf + ```solidity struct Call { address dest; diff --git a/docs/Errors.md b/docs/Errors.md new file mode 100644 index 00000000..5b1c87aa --- /dev/null +++ b/docs/Errors.md @@ -0,0 +1,466 @@ +# Solidity API + +## Errors + +Every custom error the protocol reverts with, in one place + +_An interface rather than a library so each contract reaches them as `Errors.Name` without + inheriting anything. Grouped by the contract that raises them; several are shared, and the + comment above each group names who uses it._ + +### ZeroAddress + +```solidity +error ZeroAddress(string parameter) +``` + +### OwnershipCannotBeRenounced + +```solidity +error OwnershipCannotBeRenounced() +``` + +### OnlyDeviceWallet + +```solidity +error OnlyDeviceWallet() +``` + +### OnlyDeviceWalletFactory + +```solidity +error OnlyDeviceWalletFactory() +``` + +### OnlyRequestedAdmin + +```solidity +error OnlyRequestedAdmin(address requestedAdmin) +``` + +### NotTheESIMWalletOwnerOrItsDeviceWallet + +```solidity +error NotTheESIMWalletOwnerOrItsDeviceWallet(address eSIMWallet) +``` + +### ESIMWalletOwnershipTransferPending + +```solidity +error ESIMWalletOwnershipTransferPending(address eSIMWallet, address newRequestedOwner) +``` + +### NotTheAssociatedDeviceWallet + +```solidity +error NotTheAssociatedDeviceWallet(address eSIMWallet, address associatedDeviceWallet) +``` + +### AdminAlreadyDisabled + +```solidity +error AdminAlreadyDisabled() +``` + +### AdminNotDisabled + +```solidity +error AdminNotDisabled() +``` + +### ProtocolPaused + +```solidity +error ProtocolPaused() +``` + +### OnlyLazyWalletRegistry + +```solidity +error OnlyLazyWalletRegistry() +``` + +### DeviceIdentifierAlreadyRegistered + +```solidity +error DeviceIdentifierAlreadyRegistered(string deviceIdentifier) +``` + +### OwnerKeyAlreadyRegistered + +```solidity +error OwnerKeyAlreadyRegistered(bytes32 ownerKeyHash) +``` + +### SaltTooHigh + +```solidity +error SaltTooHigh(uint256 salt, uint256 count) +``` + +### DeviceWalletAlreadyExists + +```solidity +error DeviceWalletAlreadyExists(string deviceIdentifier, address deviceWallet) +``` + +### NotAProtocolESIMWallet + +```solidity +error NotAProtocolESIMWallet(address eSIMWallet) +``` + +### DeviceIdentifierReservedForLazyWallet + +```solidity +error DeviceIdentifierReservedForLazyWallet(string deviceIdentifier) +``` + +### ESIMIdentifierReservedForLazyWallet + +```solidity +error ESIMIdentifierReservedForLazyWallet(string eSIMIdentifier) +``` + +### ESIMIdentifierAlreadyClaimed + +```solidity +error ESIMIdentifierAlreadyClaimed(string eSIMIdentifier, address eSIMWallet) +``` + +### EmptyDeviceIdentifier + +```solidity +error EmptyDeviceIdentifier() +``` + +### EmptyESIMIdentifier + +```solidity +error EmptyESIMIdentifier() +``` + +### ArrayLengthMismatch + +```solidity +error ArrayLengthMismatch(uint256 expected, uint256 actual) +``` + +### LazyWalletAlreadyDeployed + +```solidity +error LazyWalletAlreadyDeployed(string deviceIdentifier) +``` + +### IdentifierTooLong + +```solidity +error IdentifierTooLong(string identifier, uint256 maxLength) +``` + +### DepositDoesNotMatchValue + +```solidity +error DepositDoesNotMatchValue(uint256 depositAmount, uint256 value) +``` + +### NoESIMIdentifiersForDevice + +```solidity +error NoESIMIdentifiersForDevice(string deviceIdentifier) +``` + +### UnknownESIMIdentifier + +```solidity +error UnknownESIMIdentifier(string eSIMIdentifier) +``` + +### ESIMBoundToADifferentDevice + +```solidity +error ESIMBoundToADifferentDevice(string eSIMIdentifier, string boundDeviceIdentifier) +``` + +### ESIMIdentifierNotFound + +```solidity +error ESIMIdentifierNotFound(string eSIMIdentifier, string deviceIdentifier) +``` + +### CannotSwitchToTheSameDevice + +```solidity +error CannotSwitchToTheSameDevice(string deviceIdentifier) +``` + +### ESIMWalletNotLazyDeployed + +```solidity +error ESIMWalletNotLazyDeployed(string eSIMIdentifier) +``` + +### HistoryAlreadyCopied + +```solidity +error HistoryAlreadyCopied(string eSIMIdentifier) +``` + +### TooManyHistoryEntries + +```solidity +error TooManyHistoryEntries(uint256 requested, uint256 maxPerCall) +``` + +### LazyWalletNotDeployed + +```solidity +error LazyWalletNotDeployed(string deviceIdentifier) +``` + +### AllESIMWalletsDeployed + +```solidity +error AllESIMWalletsDeployed(string deviceIdentifier) +``` + +### TooManyESIMWallets + +```solidity +error TooManyESIMWallets(uint256 requested, uint256 maxPerCall) +``` + +### OnlyRegistryOrDeviceWalletFactoryOrDeviceWallet + +```solidity +error OnlyRegistryOrDeviceWalletFactoryOrDeviceWallet() +``` + +### OnlyDeployForSelf + +```solidity +error OnlyDeployForSelf() +``` + +### SaltAlreadyUsed + +```solidity +error SaltAlreadyUsed(address deviceWallet, uint256 salt) +``` + +### RegistryAlreadySet + +```solidity +error RegistryAlreadySet(address registry) +``` + +### ImplementationUnchanged + +```solidity +error ImplementationUnchanged(address implementation) +``` + +### OnlyAdmin + +```solidity +error OnlyAdmin() +``` + +### OnlyAdminOrRegistry + +```solidity +error OnlyAdminOrRegistry() +``` + +### OnlyEntryPoint + +```solidity +error OnlyEntryPoint() +``` + +### InvalidDeviceWalletOwnerKey + +```solidity +error InvalidDeviceWalletOwnerKey() +``` + +### VaultUnchanged + +```solidity +error VaultUnchanged(address vault) +``` + +### EmptyBatch + +```solidity +error EmptyBatch() +``` + +### DeviceWalletInfoAlreadyAdded + +```solidity +error DeviceWalletInfoAlreadyAdded(address deviceWallet) +``` + +### DeviceWalletMismatch + +```solidity +error DeviceWalletMismatch(address deviceWallet, address derived) +``` + +### DeviceWalletNotDeployed + +```solidity +error DeviceWalletNotDeployed(address deviceWallet) +``` + +### OnlySelf + +```solidity +error OnlySelf() +``` + +### OnlyEntryPointOrSelf + +```solidity +error OnlyEntryPointOrSelf() +``` + +### FailedToTransfer + +```solidity +error FailedToTransfer() +``` + +### InsufficientBalance + +```solidity +error InsufficientBalance(uint256 balance, uint256 amount) +``` + +### OnlyRegistry + +```solidity +error OnlyRegistry() +``` + +### OnlyDeviceWalletOrESIMWalletAdmin + +```solidity +error OnlyDeviceWalletOrESIMWalletAdmin() +``` + +### DataBundlePriceAboveCap + +```solidity +error DataBundlePriceAboveCap(uint256 price, uint256 cap) +``` + +### ESIMIdentifierAlreadySet + +```solidity +error ESIMIdentifierAlreadySet(string eSIMUniqueIdentifier) +``` + +### EmptyDataBundleID + +```solidity +error EmptyDataBundleID() +``` + +### ZeroDataBundlePrice + +```solidity +error ZeroDataBundlePrice() +``` + +### ZeroDataBundlePriceCap + +```solidity +error ZeroDataBundlePriceCap() +``` + +### NotADeviceWallet + +```solidity +error NotADeviceWallet(address account) +``` + +### OnlyRequestedOwner + +```solidity +error OnlyRequestedOwner(address newRequestedOwner) +``` + +### UseAcceptOwnershipTransfer + +```solidity +error UseAcceptOwnershipTransfer() +``` + +### UnknownESIMWallet + +```solidity +error UnknownESIMWallet(address eSIMWallet) +``` + +### ZeroAmount + +```solidity +error ZeroAmount() +``` + +### ETHAccessRevoked + +```solidity +error ETHAccessRevoked(address eSIMWallet) +``` + +### ETHAccessNotGrantableAtBind + +```solidity +error ETHAccessNotGrantableAtBind(address eSIMWallet) +``` + +### ESIMWalletAlreadyAdded + +```solidity +error ESIMWalletAlreadyAdded(address eSIMWallet) +``` + +### ESIMWalletNotOwnedByThisDeviceWallet + +```solidity +error ESIMWalletNotOwnedByThisDeviceWallet(address eSIMWallet, address eSIMWalletOwner) +``` + +### OnlyRegistryOrDeviceWalletFactoryOrOwner + +```solidity +error OnlyRegistryOrDeviceWalletFactoryOrOwner() +``` + +### OnlySelfOrAssociatedESIMWallet + +```solidity +error OnlySelfOrAssociatedESIMWallet() +``` + +### OnlyESIMWalletAdminOrRegistry + +```solidity +error OnlyESIMWalletAdminOrRegistry() +``` + +### OnlyAssociatedESIMWallets + +```solidity +error OnlyAssociatedESIMWallets() +``` + +### OnlyESIMWalletAdmin + +```solidity +error OnlyESIMWalletAdmin() +``` + diff --git a/docs/LazyWalletRegistry.md b/docs/LazyWalletRegistry.md index a980a214..0a70cc58 100644 --- a/docs/LazyWalletRegistry.md +++ b/docs/LazyWalletRegistry.md @@ -2,29 +2,42 @@ ## LazyWalletRegistry -Contract for deploying the factory contracts and maintaining registry +Holds what a fiat user bought before they had a wallet, then deploys the wallets and + copies the record onto them -### DataUpdatedForDevice +_Everything here is keyed by string identifiers rather than by address, because a lazy user + has no address yet. Deployment and the history copy are both batched and both carry their + own cursor in storage, so a dropped transaction is retried by repeating the same call. Each + batch loop reverts on its terminal condition rather than returning quietly, which is what + lets a caller loop until it stops._ + +### MAX_HISTORY_ENTRIES_PER_CALL ```solidity -event DataUpdatedForDevice(string _deviceUniqueIdentifier, string[] _eSIMUniqueIdentifiers, struct DataBundleDetails[] _dataBundleDetails) +uint256 MAX_HISTORY_ENTRIES_PER_CALL ``` -Emitted when data related to a device is updated - -### LazyWalletDeployed +Most purchase history entries `setHistoryForLazyWallet` will copy in one call -```solidity -event LazyWalletDeployed(bytes32[2] _deviceOwnerPublicKey, address deviceWallet, string _deviceUniqueIdentifier, address[] eSIMWallets, string[] _eSIMUniqueIdentifiers) -``` +_Each entry costs roughly 50,000 gas to write into the wallet, so a full batch is around + 2,500,000. The limit is about keeping a failed batch cheap to retry rather than about + the block limit, which is 30,000,000 at its tightest across the deployment chains. + Refused rather than clamped, so a caller never believes it wrote more than it did._ -### upgradeManager +### MAX_ESIM_WALLETS_PER_CALL ```solidity -address upgradeManager +uint256 MAX_ESIM_WALLETS_PER_CALL ``` -Address (owned/controlled by eSIM wallet project) that can upgrade contracts +Most eSIM wallets a single call will deploy for one device + +_A deployment costs roughly 500,000 gas per eSIM wallet, so a full batch is around + 10,000,000. As with the history cap this is set for retry cost rather than the block + limit: a batch that runs out of gas is paid for and thrown away, and a device with forty + eSIMs should not lose a whole block's worth of gas to one bad estimate. It also leaves + room for `forge coverage --ir-minimum`, which inflates the same call by about a fifth. + Refused rather than clamped, so a caller never believes it deployed more than it did._ ### registry @@ -40,7 +53,7 @@ Registry contract instance mapping(string => mapping(string => struct DataBundleDetails[])) deviceIdentifierToESIMDetails ``` -Device identifier <> eSIM identifier <> DataBundleDetails[](list of purchase history) +eSIM identifiers and their details associated with the device identifiers ### eSIMIdentifierToDeviceIdentifier @@ -59,47 +72,194 @@ But an eSIM identifier can have only a single device identifier._ mapping(string => string[]) eSIMIdentifiersAssociatedWithDeviceIdentifier ``` -Device identifier <> List of associated eSIM identifiers +List of eSIM identifiers associated with the device identifiers -### onlyESIMWalletAdmin +### historyEntriesCopied ```solidity -modifier onlyESIMWalletAdmin() +mapping(string => uint256) historyEntriesCopied ``` -### constructor +How many of an eSIM's stored purchase entries have already reached its wallet + +_The wallet appends whatever batch it is handed, so this is the only thing stopping a + repeated call from writing the same entries twice. Reading it rather than taking start + and end indexes from the caller also makes two admin transactions in flight at once + safe: the second reads the position the first left._ + +### lazyDeployedESIMWallet ```solidity -constructor() public +mapping(string => address) lazyDeployedESIMWallet ``` -### _authorizeUpgrade +The eSIM wallet this contract deployed for an eSIM identifier + +_Nothing enforces that an eSIM identifier is unique across eSIM wallets, so without this + record a wallet deployed through the ordinary route could claim an identifier that + already belongs to a lazy user and receive their purchase history. Written from the + addresses the deployment returns, and unaffected by any later ownership transfer, so + the copy follows the wallet rather than whichever device is holding it._ + +### eSIMWalletsDeployed ```solidity -function _authorizeUpgrade(address newImplementation) internal +mapping(string => uint256) eSIMWalletsDeployed ``` -_Owner based upgrades_ +How many of a device's eSIM wallets this contract has already deployed -### initialize +_Also the marker for the lazy route itself. The first batch always deploys at least one + wallet, so a non-zero value means this contract set the device up. Reading the registry + for a device wallet instead would accept one deployed through the ordinary route under + an identifier a lazy user's eSIMs are already bound to, and hand that device their + wallets._ + +### lazyDeploymentSalt ```solidity -function initialize(address _registry, address _upgradeManager) external +mapping(string => uint256) lazyDeploymentSalt ``` -### isLazyWalletDeployed +Salt the device's first deployment batch started from + +_Every later batch derives its salts from this, so the sequence continues rather than + restarting on an address that already holds a wallet. Stored rather than taken from the + caller again, because a value that disagrees with the first batch is not something the + contract can detect: it just produces different addresses._ + +### DataUpdatedForDevice ```solidity -function isLazyWalletDeployed(string _deviceUniqueIdentifier) public view returns (bool) +event DataUpdatedForDevice(string _deviceUniqueIdentifier, string[] _eSIMUniqueIdentifiers, struct DataBundleDetails[] _dataBundleDetails) ``` -Function to check if a lazy wallet has been deployed or not +Emitted when data related to a device is updated -#### Return Values +### ESIMBindedWithDevice + +```solidity +event ESIMBindedWithDevice(string _eSIMUniqueIdentifier, string _deviceUniqueIdentifier) +``` + +Emitted when an eSIM identifier is associated with a device identifier + +### LazyWalletDeployed + +```solidity +event LazyWalletDeployed(bytes32[2] _deviceOwnerPublicKey, address deviceWallet, string _deviceUniqueIdentifier, address[] eSIMWallets, string[] _eSIMUniqueIdentifiers) +``` + +Emitted when the Lazy wallet is deployed + +_The device wallet is indexed so an indexer can follow one device without reading every + log. The two string arrays are left unindexed on purpose: indexing a dynamic type stores + its hash instead of its value, which no consumer of these can use._ + +### LazyESIMWalletsDeployed + +```solidity +event LazyESIMWalletsDeployed(string _deviceUniqueIdentifier, address _deviceWallet, address[] _eSIMWallets, string[] _eSIMUniqueIdentifiers, uint256 _remaining) +``` + +Emitted for every batch of eSIM wallets deployed for a device, including the first. + `_remaining` reaching zero is what says the device is fully deployed. + +_`LazyWalletDeployed` fires once, when the device wallet itself is created, and carries + only the first batch. Anything waiting for the whole set has to follow this instead._ + +### LazyHistoryCopied + +```solidity +event LazyHistoryCopied(string _eSIMIdentifier, address _eSIMWallet, uint256 _copied, uint256 _remaining) +``` + +Emitted for every batch of purchase history copied into a deployed eSIM wallet. + `_remaining` reaching zero is what says the copy is finished. + +### ESIMIdentifierSwitchedToNewDeviceIdentifier + +```solidity +event ESIMIdentifierSwitchedToNewDeviceIdentifier(string _eSIMIdentifier, string _oldDeviceIdentifier, string currentDeviceIdentifier) +``` + +Emitted when the user switches eSIM to a new device + +### NewDeviceIdentifierAssociatedWithESIMIdentifier + +```solidity +event NewDeviceIdentifierAssociatedWithESIMIdentifier(string _eSIMIdentifier, string _oldDeviceIdentifier, string _newDeviceIdentifier) +``` + +Emitted when the device identifier associated with an eSIM identifier is updated + +### DataBundleDetailsTransferredToNewDeviceIdentifier + +```solidity +event DataBundleDetailsTransferredToNewDeviceIdentifier(string _newDeviceIdentifier, struct DataBundleDetails[] _newDataBundleDetails) +``` + +Emitted when the Data bundle related details of an eSIM are transferred to a new device identifier + +### DataBundleDetailsDeletedFromOldDeviceIdentifier + +```solidity +event DataBundleDetailsDeletedFromOldDeviceIdentifier(string _oldDeviceIdentifier, string _eSIMIdentifier) +``` + +Emitted when the data bundle details are deleted from the old device identifier + +### ESIMIdentifierRemovedFromOldDeviceIdentifier + +```solidity +event ESIMIdentifierRemovedFromOldDeviceIdentifier(string _oldDeviceIdentifier, string _eSIMIdentifier, string[] _eSIMIdentifierOfOldDevice) +``` + +Emitted when an eSIM identifier is removed from a device identifier's list + +### ESIMIdentifierAddedToNewDeviceIdentifier + +```solidity +event ESIMIdentifierAddedToNewDeviceIdentifier(string _newDeviceIdentifier, string _eSIMIdentifier, string[] _eSIMIdentifierOfNewDevice) +``` + +Emitted when an eSIM identifier is added to a new device identifier's list + +### onlyESIMWalletAdmin + +```solidity +modifier onlyESIMWalletAdmin() +``` + +Restricts a call to the eSIM wallet admin + +_Read from the registry on every call, so a rotation there takes effect immediately. + Every state-changing function in this contract sits behind it._ + +### constructor + +```solidity +constructor() public +``` + +_Locks the implementation contract itself. Without this, anyone can call initialize + directly on the implementation and own it. The proxy is unaffected either way, but an + owned implementation is a trap for any later upgrade that adds an outward call._ + +### initialize + +```solidity +function initialize(address _registry, address _upgradeManager) external +``` + +Points this contract at the registry and hands ownership to the upgrade manager + +#### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | bool | Boolean. True if deployed, false otherwise | +| _registry | address | Registry this contract reads the admin from and deploys wallets through | +| _upgradeManager | address | Admin address responsible for upgrading contracts | ### batchPopulateHistory @@ -109,6 +269,9 @@ function batchPopulateHistory(string[] _deviceUniqueIdentifiers, string[][] _eSI Function to populate all the device and eSIM related data along with the data bundles +_Refused for any device that already has a wallet, which is what freezes a device's eSIM + list and its history for the whole time a deployment is walking them._ + #### Parameters | Name | Type | Description | @@ -120,12 +283,21 @@ Function to populate all the device and eSIM related data along with the data bu ### deployLazyWalletAndSetESIMIdentifier ```solidity -function deployLazyWalletAndSetESIMIdentifier(bytes32[2] _deviceOwnerPublicKey, string _deviceUniqueIdentifier, uint256 _salt) external returns (address, address[]) +function deployLazyWalletAndSetESIMIdentifier(bytes32[2] _deviceOwnerPublicKey, string _deviceUniqueIdentifier, uint256 _salt, uint256 _depositAmount, uint256 _maxWallets) external payable returns (address deviceWallet, address[] eSIMWallets, uint256 remaining) ``` -Function to deploy a device wallet and eSIM wallets on behalf of a user, also setting the eSIM identifiers +Deploys a device wallet and the first batch of its eSIM wallets, setting their identifiers + +_Only the first `_maxWallets` eSIM wallets are deployed here. Anything left goes through + `deployMoreESIMWalletsForLazyDevice`, because one transaction carrying every wallet grew + without bound with the eSIM count and stopped fitting in a block somewhere past forty. -__salt should never be near to max value of uint256, if it is, the function call fails_ + The device wallet is usable the moment this returns. Its eSIM wallets are complete and + independent of each other, so holding it back until the last one lands would mean one + dropped transaction leaves the user with nothing rather than with most of what they + bought. Switching an eSIM to another device is refused for the whole time the rest are + outstanding, which `switchESIMIdentifierToNewDeviceIdentifier` already does by refusing + any device that has a wallet._ #### Parameters @@ -133,14 +305,129 @@ __salt should never be near to max value of uint256, if it is, the function call | ---- | ---- | ----------- | | _deviceOwnerPublicKey | bytes32[2] | P256 public key of the device owner | | _deviceUniqueIdentifier | string | Unique device identifier associated with the device | -| _salt | uint256 | | +| _salt | uint256 | Salt the whole deployment derives its eSIM wallet addresses from | +| _depositAmount | uint256 | Amount of ETH to be deposited in the device wallet | +| _maxWallets | uint256 | Most eSIM wallets to deploy here, at most MAX_ESIM_WALLETS_PER_CALL | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| deviceWallet | address | Address of the deployed device wallet | +| eSIMWallets | address[] | eSIM wallets this call deployed, in the order of the device's identifiers | +| remaining | uint256 | eSIM wallets still waiting after this call | + +### deployMoreESIMWalletsForLazyDevice + +```solidity +function deployMoreESIMWalletsForLazyDevice(string _deviceUniqueIdentifier, uint256 _maxWallets) external returns (address[] eSIMWallets, uint256 remaining) +``` + +Deploys the next batch of eSIM wallets for a device already set up by the lazy route + +_Call it repeatedly until it reverts `AllESIMWalletsDeployed`, which is the terminal + condition rather than a failure. Reverting instead of returning quietly is what lets a + caller loop on it. The cursor is read here rather than taken as an argument, so a + dropped transaction is retried by repeating the same call. + + No pause check and no deposit. This moves no ETH, and the identifier list it walks was + frozen when the device wallet appeared: `_populateHistory` refuses a device that already + has one, so nothing can be appended to the list under a running deployment._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Device whose remaining eSIM wallets are being deployed | +| _maxWallets | uint256 | Most eSIM wallets to deploy here, at most MAX_ESIM_WALLETS_PER_CALL | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| eSIMWallets | address[] | eSIM wallets this call deployed, in the order of the device's identifiers | +| remaining | uint256 | eSIM wallets still waiting after this call | + +### setHistoryForLazyWallet + +```solidity +function setHistoryForLazyWallet(string _eSIMIdentifier, uint256 _maxEntries) external returns (uint256 copied, uint256 remaining) +``` + +Copies the next batch of an eSIM's stored purchase history into its deployed wallet + +_Split out of the deployment because carrying history there made one transaction grow + with the eSIM count and the history length at the same time. Call it repeatedly until + it reverts `HistoryAlreadyCopied`, which is the terminal condition rather than a + failure. Reverting instead of returning quietly is what lets a caller loop on it. + + No pause check. This moves no ETH, and the entries it writes were frozen when the + wallet was deployed: `_populateHistory` refuses a device that already has one._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMIdentifier | string | eSIM whose history is being copied | +| _maxEntries | uint256 | Most entries to copy in this call, at most MAX_HISTORY_ENTRIES_PER_CALL | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| copied | uint256 | Entries written by this call | +| remaining | uint256 | Entries still waiting after this call | + +### switchESIMIdentifierToNewDeviceIdentifier + +```solidity +function switchESIMIdentifierToNewDeviceIdentifier(string _eSIMIdentifier, string _oldDeviceIdentifier, string _newDeviceIdentifier) external returns (bool) +``` + +This function should be called when the fiat user wants to switch their eSIM to a new device + +_Only ever before deployment. Once a wallet exists onchain, the onchain graph is the + record and the eSIM moves through ESIMWallet's ownership transfer instead._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMIdentifier | string | unique eSIM identifier that needs to be switched to a new device | +| _oldDeviceIdentifier | string | device identifier that the eSIM is currently associated with | +| _newDeviceIdentifier | string | new device identifier that the eSIM needs to be switched to | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address | Return device wallet address and list of eSIM wallet addresses | -| [1] | address[] | | +| [0] | bool | bool Returns `true` if the switching of eSIM was successful | + +### renounceOwnership + +```solidity +function renounceOwnership() public pure +``` + +Ownership of this contract is never renounced + +_The owner is the only caller _authorizeUpgrade accepts, and there is no other route to + replace this implementation. Renouncing would freeze the contract on its current logic + permanently._ + +### _authorizeUpgrade + +```solidity +function _authorizeUpgrade(address newImplementation) internal +``` + +Restricts UUPS upgrades to the owner + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newImplementation | address | Address of the implementation being moved to | ### _populateHistory @@ -148,7 +435,121 @@ __salt should never be near to max value of uint256, if it is, the function call function _populateHistory(string _deviceUniqueIdentifier, string[] _eSIMUniqueIdentifiers, struct DataBundleDetails[] _dataBundleDetails) internal ``` -Internal function for populating information of all the eSIMs related to a device +Records one device's eSIM identifiers and the purchases made against them + +_`_eSIMUniqueIdentifiers` may repeat an identifier, since one eSIM can have several + purchases. An identifier already bound to a different device is refused._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Device the purchases belong to | +| _eSIMUniqueIdentifiers | string[] | One entry per purchase, naming the eSIM it was made for | +| _dataBundleDetails | struct DataBundleDetails[] | The purchases themselves, aligned with the identifiers | + +### _moveESIMPurchaseHistory -_The _eSIMUniqueIdentifiers array can have multiple repeating occurrences since there can be multiple purchases per eSIM_ +```solidity +function _moveESIMPurchaseHistory(string _eSIMIdentifier, string _oldDeviceIdentifier, string _newDeviceIdentifier) internal +``` + +Moves what an eSIM bought to the device taking it over + +_Carries the purchase entries themselves. Its counterpart + `_moveESIMIdentifierBetweenDeviceLists` carries the membership record saying the eSIM + exists at all, and a switch needs both._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMIdentifier | string | eSIM being switched | +| _oldDeviceIdentifier | string | Device it is leaving | +| _newDeviceIdentifier | string | Device it is joining | + +### _moveESIMIdentifierBetweenDeviceLists + +```solidity +function _moveESIMIdentifierBetweenDeviceLists(string _eSIMIdentifier, string _oldDeviceIdentifier, string _newDeviceIdentifier) internal +``` + +Moves an eSIM identifier between the two devices' lists + +_Carries the membership record, which is what a deployment walks to know an eSIM exists. + Its counterpart `_moveESIMPurchaseHistory` carries the purchases. The removal is a swap + with the last element and a pop, so the old device's list keeps its members but not + their order._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMIdentifier | string | eSIM being switched | +| _oldDeviceIdentifier | string | Device it is leaving | +| _newDeviceIdentifier | string | Device it is joining | + +### upgradeManager + +```solidity +function upgradeManager() public view returns (address) +``` + +Address (owned/controlled by eSIM wallet project) that can upgrade contracts + +_Reads through to the owner rather than holding its own copy. `_authorizeUpgrade` is + gated on `onlyOwner`, so the owner is the upgrade authority by definition and a second + copy could only ever disagree with it._ + +### isDeviceIdentifierReserved + +```solidity +function isDeviceIdentifierReserved(string _deviceUniqueIdentifier) public view returns (bool) +``` + +Whether a device identifier has purchases recorded against it here + +_The ordinary deployment route asks this before taking an identifier, since a wallet + created under a reserved one strands every eSIM bound to it: the deploy, the history + copy and the device switch all refuse an identifier that has a wallet. + + Stays true once the lazy deployment finishes. Harmless, since the registry's own + identifier check refuses the second claim by then, and clearing it would mean walking + the whole list._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Device identifier being checked | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True if a lazy user is waiting on this identifier | + +### isESIMIdentifierReserved + +```solidity +function isESIMIdentifierReserved(string _eSIMUniqueIdentifier) public view returns (bool) +``` + +Whether an eSIM identifier is bound to a device here + +_The registry refuses a claim on a reserved identifier from any device but the one that + reserved it, and reads `eSIMIdentifierToDeviceIdentifier` itself to make that + comparison. This is the plain question, for a caller that only wants the fact._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMUniqueIdentifier | string | eSIM identifier being checked | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True if a lazy user is waiting on this identifier | diff --git a/docs/P256Verifier.md b/docs/P256Verifier.md index 11a6b86e..985530eb 100644 --- a/docs/P256Verifier.md +++ b/docs/P256Verifier.md @@ -2,9 +2,34 @@ ## P256Verifier +Thin contract wrapper around the WebAuthn verification library + +_Adapted from Daimo's DaimoVerifier: + https://github.com/daimo-eth/daimo/blob/master/packages/contract/src/DaimoVerifier.sol + It exists as a contract so accounts hold one immutable address to verify through, rather + than linking the library into every implementation._ + ### verifySignature ```solidity function verifySignature(bytes message, bool requireUserVerification, struct WebAuthnSignature webAuthnSignature, uint256 x, uint256 y) public view returns (bool) ``` +Verifies a WebAuthn assertion against a P256 public key + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| message | bytes | Raw challenge bytes expected inside the assertion's clientDataJSON | +| requireUserVerification | bool | True to demand the authenticator's user verification flag | +| webAuthnSignature | struct WebAuthnSignature | The assertion to check | +| x | uint256 | X co-ordinate of the P256 public key | +| y | uint256 | Y co-ordinate of the P256 public key | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True when the assertion is valid for that key | + diff --git a/docs/Registry.md b/docs/Registry.md index 6f5113f1..ad145be7 100644 --- a/docs/Registry.md +++ b/docs/Registry.md @@ -1,20 +1,21 @@ # Solidity API -## OnlyDeviceWallet - -```solidity -error OnlyDeviceWallet() -``` - -## OnlyDeviceWalletFactory +## Registry -```solidity -error OnlyDeviceWalletFactory() -``` +Single source of truth for who is who in the protocol, and the switchboard the wallets + read on every guarded path -## Registry +_Holds the admin address, the vault, the pause flag and the price ceiling in one place. + Device wallets and eSIM wallets are beacon proxies tracked by mappings with no enumerable + list, so there is no way to write a value into each of them: one write here is how a change + reaches all of them in the same transaction. -Contract for deploying the factory contracts and maintaining registry + `IPausable` and `IRegistryAdmin` are declared so the compiler checks the signatures + `ProtocolAdmin` calls through them. A guardian acts with no delay, so a drift between the + two would only show as a revert during an incident. What each interface leaves out is + deliberate: `pause()` is the hot admin key's lever while releasing it is the timelock's, + and `enableAdmin()` is absent for the same reason in reverse, so nothing invites a fast + path for handing a suspended key its powers back._ ### entryPoint @@ -24,13 +25,22 @@ contract IEntryPoint entryPoint Entry point contract address (one entryPoint per chain) -### admin +### adminOfRecord ```solidity -address admin +address adminOfRecord ``` -eSIM wallet project admin address +Address holding the admin role, whether or not its powers are currently live + +_The only copy in the protocol. `DeviceWalletFactory`, `DeviceWallet`, `ESIMWallet` and + `LazyWalletRegistry` all read it from here, so rotating it below reaches every one of + them in the same transaction. Holding it in more than one place is what previously let + a rotation update some readers and leave the rest authorising the retired key. + + Read `eSIMWalletAdmin()` rather than this to find out who may act: this is the address + on the books, and it keeps naming a suspended admin so the suspension can be lifted + without anyone having to remember who it was._ ### vault @@ -40,13 +50,56 @@ address vault Address of the vault that receives payments for the eSIM data bundles -### upgradeManager +### newRequestedAdmin ```solidity -address upgradeManager +address newRequestedAdmin ``` -Address (owned/controlled by eSIM wallet project) that can upgrade contracts +Address of the admin to be appointed + +_Only the owner can request the transfer. The nominated address has to accept it, and + this resets once they do. While it is set the incumbent has no powers, so a handover + that is never accepted leaves the role dormant rather than shared._ + +### paused + +```solidity +bool paused +``` + +True while the ETH-moving paths are stopped protocol-wide + +_Held here for the same reason the admin address is: device wallets and eSIM wallets are + beacon proxies tracked by a mapping with no enumerable list, so there is no way to + write a flag into each of them. Both already read this contract on their guarded paths, + so one write here reaches every wallet in the same transaction._ + +### adminDisabled + +```solidity +bool adminDisabled +``` + +True while the admin's powers are suspended, leaving the address on the books + +_Packs into the spare bytes beside `paused`, so it costs no slot of its own. Suspension + is the lever against a compromised admin key: it is instant through a guardian, while + lifting it is an owner action and therefore waits. A key that could restore itself as + fast as it was suspended would leave the two sides trading transactions forever._ + +### defaultDataBundlePriceCap + +```solidity +uint256 defaultDataBundlePriceCap +``` + +Most an eSIM wallet may be charged for one data bundle unless it sets its own limit + +_Held here rather than only on each wallet because a wallet deployed before this existed + reads zero, and there is no enumerable list to write a value into. Never zero: `initialize` + and `setDefaultDataBundlePriceCap` both reject it, since a zero here or on a wallet's own + cap reads as "no ceiling" in `ESIMWallet._requirePriceWithinCap`._ ### onlyDeviceWallet @@ -54,32 +107,47 @@ Address (owned/controlled by eSIM wallet project) that can upgrade contracts modifier onlyDeviceWallet() ``` +Restricts a call to a device wallet this registry has recorded + ### onlyDeviceWalletFactory ```solidity modifier onlyDeviceWalletFactory() ``` -### constructor +Restricts a call to the device wallet factory + +### onlyESIMWalletAdmin ```solidity -constructor(contract IEntryPoint _entryPoint) public +modifier onlyESIMWalletAdmin() ``` -### _authorizeUpgrade +Restricts a call to the current eSIM wallet admin + +_The hot key the backend signs with, not the owner. It can trip the pause but not + release it, and cannot upgrade anything. Reads the accessor rather than the stored + address, so a suspended admin is refused here for the same reason it is refused + everywhere else._ + +### constructor ```solidity -function _authorizeUpgrade(address newImplementation) internal +constructor() public ``` -_Owner based upgrades_ +_Locks the implementation contract itself. Without this, anyone can call initialize + directly on the implementation and own it. The proxy is unaffected either way, but an + owned implementation is a trap for any later upgrade that adds an outward call._ ### initialize ```solidity -function initialize(address _eSIMWalletAdmin, address _vault, address _upgradeManager, contract P256Verifier _verifier) external +function initialize(address _eSIMWalletAdmin, address _vault, address _upgradeManager, address _deviceWalletFactory, address _eSIMWalletFactory, contract IEntryPoint _entryPoint, uint256 _defaultDataBundlePriceCap) external ``` +Wires the registry to the two factories and sets the protocol's addresses + #### Parameters | Name | Type | Description | @@ -87,53 +155,199 @@ function initialize(address _eSIMWalletAdmin, address _vault, address _upgradeMa | _eSIMWalletAdmin | address | Admin address of the eSIM wallet project | | _vault | address | Address of the vault that receives payments for the data bundles | | _upgradeManager | address | Admin address responsible for upgrading contracts | -| _verifier | contract P256Verifier | | +| _deviceWalletFactory | address | Factory that deploys device wallets | +| _eSIMWalletFactory | address | Factory that deploys eSIM wallets | +| _entryPoint | contract IEntryPoint | ERC-4337 EntryPoint singleton for this chain | +| _defaultDataBundlePriceCap | uint256 | Starting price ceiling. Must be non-zero: a zero cap, here or on a wallet's own, reads as "no ceiling" in `ESIMWallet._requirePriceWithinCap`. | -### addOrUpdateLazyWalletRegistryAddress +### requestAdminUpdate ```solidity -function addOrUpdateLazyWalletRegistryAddress(address _lazyWalletRegistry) public returns (address) +function requestAdminUpdate(address _newAdmin) external ``` -Function to add or update the lazy wallet registry address +Nominates the next eSIM wallet admin, who then has to accept -### deployWallet +_Owner and not the admin, deliberately. An admin that had to nominate its own + replacement could not be removed once its key was in someone else's hands, and the + pause is the admin's own lever, so a compromised key could hold the protocol stopped + for as long as it liked and no other key could end it. + + Nominating strips the incumbent at once, through the accessor rather than through a + write: a handover in flight leaves the role dormant until the nominee accepts, so the + two never hold it at the same time. A rotation therefore has a gap in it, and the + nomination and the acceptance belong close together. + + Deliberately does not check for an existing request, so an unintended nomination is + overridden by calling this again. Naming the incumbent withdraws the request and hands + the powers back, which also lifts a suspension, so one call undoes either mistake._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _newAdmin | address | Address of the recipient to receive the admin role | + +### acceptAdminUpdate ```solidity -function deployWallet(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt) external returns (address, address) +function acceptAdminUpdate() external returns (address) ``` -Allow anyone to deploy a device wallet and an eSIM wallet for themselves +Takes up the admin role, callable only by the nominated address + +_Clears the suspension as well as the request. The suspension names a key, not the + role, so a fresh key accepting is the end of the incident rather than something that + has to be lifted separately afterwards._ + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | Address of the new admin | + +### disableAdmin + +```solidity +function disableAdmin() external +``` + +Suspends the admin's powers protocol-wide, leaving its address on the books + +_Every gate in the protocol reads `eSIMWalletAdmin()`, which answers zero from here on, + and no transaction can arrive from the zero address, so one write closes all of them + in the same transaction. The address itself is kept so the suspension can be lifted + without anyone having to supply it again. + + Owner gated, which is what lets `ProtocolAdmin` offer a guardian an instant route to + it. Refuses a repeat rather than passing quietly: a guardian doing this during an + incident should not be left believing it acted when it did not._ + +### enableAdmin + +```solidity +function enableAdmin() external +``` + +Hands a suspended admin its powers back + +_Owner only, with no instant route for anyone. Suspending is instant and restoring + waits, so a compromised key cannot undo its own suspension as fast as it is applied. + Reversing that would recreate the deadlock the suspension exists to break. + + Does nothing for an outstanding handover, which keeps the incumbent powerless on its + own. Withdraw that with `requestAdminUpdate` naming the incumbent._ + +### eSIMWalletAdmin + +```solidity +function eSIMWalletAdmin() public view returns (address) +``` + +Admin address every gated call in the protocol is checked against + +_Zero while the admin is suspended or while a handover is outstanding, which is how + both states close every gate at once: `msg.sender` is never zero, so no caller matches. + `adminOfRecord` holds the address itself either way._ + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The address that may act as admin right now, or zero if nobody may | + +### updateVaultAddress + +```solidity +function updateVaultAddress(address _newVaultAddress) external returns (address) +``` + +Points every data bundle payment at a different vault + +_Owner and not admin, deliberately. This is the destination of every payment the protocol + collects, so moving it is a fund-flow change and belongs behind the same delay as an + upgrade rather than on the hot key that signs backend batches all day. + + Device wallets read `vault` here on every purchase instead of caching it, so one write + reaches all of them in the same transaction. This used to live on `DeviceWalletFactory`, + which nothing on the payment path ever read, so rotating the vault there changed nothing + and the real address could not be moved at all._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _deviceUniqueIdentifier | string | Unique device identifier associated with the device | -| _deviceWalletOwnerKey | bytes32[2] | | -| _salt | uint256 | | +| _newVaultAddress | address | Address that receives payments for the data bundles from now on | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address | Return device wallet address and eSIM wallet address | -| [1] | address | | +| [0] | address | The vault address now in force | + +### pause -### updateDeviceWalletAssociatedWithESIMWallet +```solidity +function pause() external +``` + +Stops the ETH-moving paths on every device wallet and eSIM wallet + +_The admin trips this and the owner clears it. The admin key signs backend batches all + day and is the one watching, so it needs to act without waiting; giving it the release + as well would let a single hot key hold user funds indefinitely. Neither key can reach + an owner's own `execute`, so a pause never stops someone spending their own ETH._ + +### unpause ```solidity -function updateDeviceWalletAssociatedWithESIMWallet(address _eSIMWalletAddress, address _deviceWalletAddress) external +function unpause() external ``` +Releases the pause + +_Owner only, see `pause`_ + +### requireNotPaused + +```solidity +function requireNotPaused() external view +``` + +Reverts while the protocol is paused + +_Device wallets and eSIM wallets call this rather than reading `paused` and reverting + themselves, so the revert reason is the same wherever it comes from._ + +### setDefaultDataBundlePriceCap + +```solidity +function setDefaultDataBundlePriceCap(uint256 _cap) external +``` + +Sets the price ceiling eSIM wallets fall back to when they hold none of their own + +_Owner and not admin, deliberately. The admin is the party this ceiling constrains, so + letting it raise its own limit would leave the ceiling meaningless. Zero is refused: + it would read as "no ceiling" in `ESIMWallet._requirePriceWithinCap` for every wallet + that has not set its own._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _cap | uint256 | Maximum price in wei, non-zero | + ### updateDeviceWalletInfo ```solidity function updateDeviceWalletInfo(address _deviceWallet, string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey) external ``` -_For all the device wallets deployed by the esim wallet admin using the device wallet factory, - update the mappings_ +Records a device wallet the factory has just deployed + +_Factory only. Writes the identifier, the address and the owner key together, so the + three stay consistent with each other._ #### Parameters @@ -141,5 +355,178 @@ _For all the device wallets deployed by the esim wallet admin using the device w | ---- | ---- | ----------- | | _deviceWallet | address | Address of the device wallet | | _deviceUniqueIdentifier | string | String unique identifier associated with the device wallet | -| _deviceWalletOwnerKey | bytes32[2] | | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | + +### updateDeviceWalletOwnerKey + +```solidity +function updateDeviceWalletOwnerKey(bytes32[2] _newOwnerKey) external +``` + +Called by a device wallet when the P256 key that owns it is replaced + +_Only the wallet itself can move its own bindings, so `msg.sender` is the subject + rather than a parameter. Without this the registry keeps naming the retired key after + a rotation, and the key taking over stays unregistered and can be claimed by a second + wallet, which breaks the one key to one wallet rule the deploy paths enforce._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _newOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key taking over | + +### requireDeviceIdentifierNotReserved + +```solidity +function requireDeviceIdentifierNotReserved(string _deviceUniqueIdentifier) external view +``` + +Refuses a device identifier a fiat user's eSIMs are already waiting on + +_The ordinary deployment route calls this. Taking such an identifier used to succeed and + strand the lazy user: the history copy, the wallet deployment and the device switch all + refuse an identifier that has a wallet. + + Passes while `lazyWalletRegistry` is unset, the window between deploying this contract + and wiring the two together. Nothing can be reserved before the contract holding + reservations exists, so the window is empty rather than unguarded._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Identifier the caller is about to take | + +### bindESIMWallet + +```solidity +function bindESIMWallet(address _eSIMWalletAddress, address _deviceWalletAddress) external +``` + +Binds an eSIM wallet to the calling device wallet and settles any outstanding transfer + +_The association is a registration: once the registry has named a device wallet for an + eSIM wallet it always names one, and this is the only place it moves. Zero is refused + for that reason, so releasing an eSIM wallet raises the standby flag through + `toggleESIMWalletStandbyStatus` and leaves the association naming the last device + wallet that held it. + + Authorization reads `ESIMWallet.owner()` rather than the association above, because the + association can still name a former device wallet after an ownership transfer has been + accepted and never bound back through `addESIMWallet`. + + Taking a wallet on is the one moment both facts change together, which is why the flag + is cleared here rather than in a second call. Nothing else in this function reads it._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWalletAddress | address | Address of the eSIM wallet | +| _deviceWalletAddress | address | The device wallet taking it on, which must be the caller | + +### claimESIMIdentifier + +```solidity +function claimESIMIdentifier(string _eSIMUniqueIdentifier, address _eSIMWalletAddress) external +``` + +Records that an eSIM wallet now holds an eSIM identifier, refusing a second holder + +_The guard lives here rather than in `DeviceWallet` because a device wallet can reach + this directly through `execute`, which would skip anything sitting on the wallet side. + For the same reason the caller's device identifier is read from it rather than taken as + an argument. + + A reservation is compared against the caller's own identifier rather than refused + outright, since the lazy route reaches this while deploying against its own._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMUniqueIdentifier | string | Identifier being claimed | +| _eSIMWalletAddress | address | Wallet claiming it, which must be one the caller owns | + +### toggleESIMWalletStandbyStatus + +```solidity +function toggleESIMWalletStandbyStatus(address _eSIMWalletAddress, bool _isOnStandby) public +``` + +Marks an eSIM wallet as being moved from one device wallet to another, or cancels that + +_Only the flag moves here. The association is a separate fact and keeps naming the device + wallet that last held the eSIM wallet, so raising standby on a wallet this caller still + holds is the ordinary case rather than a contradiction. + + Authorization reads `ESIMWallet.owner()` rather than the association, for the same reason + as `bindESIMWallet`: the association can still name a former device wallet after an + accepted transfer that was never bound back._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWalletAddress | address | Address of the eSIM wallet | +| _isOnStandby | bool | True while a transfer is outstanding, false once it is settled or revoked | + +### addOrUpdateLazyWalletRegistryAddress + +```solidity +function addOrUpdateLazyWalletRegistryAddress(address _lazyWalletRegistry) public returns (address) +``` + +Points the registry at the lazy wallet registry, which is deployed after it + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _lazyWalletRegistry | address | Address of the lazy wallet registry | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The address now in force | + +### renounceOwnership + +```solidity +function renounceOwnership() public pure +``` + +Ownership of this contract is never renounced + +_The owner is the only caller _authorizeUpgrade accepts, and there is no other route to + replace this implementation. Renouncing would freeze the contract on its current logic + permanently._ + +### _authorizeUpgrade + +```solidity +function _authorizeUpgrade(address newImplementation) internal +``` + +Restricts UUPS upgrades to the owner + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newImplementation | address | Address of the implementation being moved to | + +### upgradeManager + +```solidity +function upgradeManager() public view returns (address) +``` + +Address (owned/controlled by eSIM wallet project) that can upgrade contracts + +_Reads through to the owner rather than holding its own copy. `_authorizeUpgrade` is + gated on `onlyOwner`, so the owner is the upgrade authority by definition and a second + copy could only ever disagree with it._ diff --git a/docs/RegistryHelper.md b/docs/RegistryHelper.md index 16a837db..a3dd3890 100644 --- a/docs/RegistryHelper.md +++ b/docs/RegistryHelper.md @@ -1,98 +1,260 @@ # Solidity API -## OnlyLazyWalletRegistry +## RegistryHelper + +Storage and the lazy deployment paths that `Registry` inherits + +_Split out so `Registry` holds the admin and pause logic while the mappings and the calls + into the two factories live here. Only the lazy wallet registry reaches the functions in + this file; everything else goes through `Registry` itself._ + +### lazyWalletRegistry ```solidity -error OnlyLazyWalletRegistry() +address lazyWalletRegistry ``` -## RegistryHelper +Address of the Lazy wallet registry + +### deviceWalletFactory + +```solidity +contract DeviceWalletFactory deviceWalletFactory +``` + +Device wallet factory instance + +### eSIMWalletFactory + +```solidity +contract ESIMWalletFactory eSIMWalletFactory +``` + +eSIM wallet factory instance + +### uniqueIdentifierToDeviceWallet + +```solidity +mapping(string => address) uniqueIdentifierToDeviceWallet +``` + +Mapping for all the device wallets deployed by the registry + +_Use this to check if a device identifier has already been used or not_ + +### deviceWalletToOwner + +```solidity +mapping(address => bytes32[2]) deviceWalletToOwner +``` + +X,Y co-ordinates of the P256 keys associated with the device wallet + +### registeredP256Keys + +```solidity +mapping(bytes32 => address) registeredP256Keys +``` + +keccak256 hash to device wallet address + +_keccak256(abi.encode(X, Y)) <> device wallet address +Used to maintain one-to-one relationship between P256 keys and device wallet_ + +### isDeviceWalletValid + +```solidity +mapping(address => bool) isDeviceWalletValid +``` + +true if deployed by the registry or device wallet factory + Mapping of all the device wallets deployed by the registry (or the device wallet factory) are set to true + +### isESIMWalletValid + +```solidity +mapping(address => address) isESIMWalletValid +``` -### WalletDeployed +All the eSIM wallets deployed using this registry are valid and mapped to their owner device wallet + +_This is the registration record. A non-zero entry means the protocol deployed this eSIM + wallet, and it stays non-zero for the rest of the wallet's life. Mid-transfer it names + the device wallet that last held it, so it is never zero to mean "released". + `bindESIMWallet` is the only writer and it checks the deployment with the factory, which + is what makes the first sentence true rather than assumed._ + +### isESIMWalletOnStandby + +```solidity +mapping(address => bool) isESIMWalletOnStandby +``` + +If an existing eSIM wallet is in the process of being transferred from one device wallet to another + +_If bool is `true`, the eSIM wallet is in a transient state. `isESIMWalletValid` still + points at the old device wallet. Do not use this mapping to check whether an eSIM + wallet belongs to the protocol; that is what `isESIMWalletValid` is for. Its job is to + hold transactions on this eSIM wallet until it reads false again, meaning the new + device wallet has accepted it._ + +### claimedESIMIdentifiers + +```solidity +mapping(bytes32 => address) claimedESIMIdentifiers +``` + +The eSIM wallet holding each eSIM identifier, protocol-wide + +_An eSIM wallet's own identifier slot is set once, but nothing stopped two wallets from + being set to the same identifier, one per deployment route. This is what makes the + identifier answer with a single wallet. Keyed by hash for the same reason + `registeredP256Keys` is: `eSIMWalletForIdentifier` takes the string. + + Written once and never cleared, including through an ownership transfer, because the + eSIM belongs to the wallet rather than to whichever device is holding it._ + +### LazyWalletDeployed ```solidity -event WalletDeployed(string _deviceUniqueIdentifier, address _deviceWallet, address _eSIMWallet) +event LazyWalletDeployed(address _deviceWallet, string _deviceUniqueIdentifier, address _eSIMWallet, string _eSIMUniqueIdentifier) ``` +Emitted for each eSIM wallet deployed on behalf of the lazy wallet registry + ### DeviceWalletInfoUpdated ```solidity event DeviceWalletInfoUpdated(address _deviceWallet, string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey) ``` +Emitted when a device wallet is first recorded, with its identifier and owner key + +### DeviceWalletOwnerKeyUpdated + +```solidity +event DeviceWalletOwnerKeyUpdated(address _deviceWallet, bytes32[2] _oldOwnerKey, bytes32[2] _newOwnerKey) +``` + +Emitted when a device wallet rotates the P256 key that owns it + +### ESIMIdentifierClaimed + +```solidity +event ESIMIdentifierClaimed(bytes32 _hashOfESIMIdentifier, string _eSIMUniqueIdentifier, address _eSIMWallet) +``` + +Emitted the first and only time an eSIM identifier is bound to an eSIM wallet + +_The identifier is carried unindexed as well as hashed, because indexing a dynamic type + stores its hash and no consumer can read the value back out of that._ + ### UpdatedDeviceWalletassociatedWithESIMWallet ```solidity event UpdatedDeviceWalletassociatedWithESIMWallet(address _eSIMWalletAddress, address _deviceWalletAddress) ``` +Emitted when an eSIM wallet is bound to a device wallet + ### UpdatedLazyWalletRegistryAddress ```solidity event UpdatedLazyWalletRegistryAddress(address _lazyWalletRegistry) ``` -### lazyWalletRegistry +Emitted when the owner points the registry at the lazy wallet registry + +### RegistryInitialized ```solidity -address lazyWalletRegistry +event RegistryInitialized(address _eSIMWalletAdmin, address _vault, address _upgradeManager, address _deviceWalletFactory, address _eSIMWalletFactory) ``` -Address of the Lazy wallet registry +Emitted once, when the registry is initialised -### deviceWalletFactory +### AdminUpdateRequested ```solidity -contract DeviceWalletFactory deviceWalletFactory +event AdminUpdateRequested(address eSIMWalletAdmin, address _newAdmin) ``` -Device wallet factory instance +Emitted when the owner nominates a new address for the admin role -### eSIMWalletFactory +_The incumbent is powerless from here until the nominee accepts, so a reader following + the admin has to treat this as the moment the role went dormant._ + +### AdminUpdated ```solidity -contract ESIMWalletFactory eSIMWalletFactory +event AdminUpdated(address _newAdmin) ``` -eSIM wallet factory instance +Emitted when the newly requested admin accepts the role -### uniqueIdentifierToDeviceWallet +### AdminUpdateRevoked ```solidity -mapping(string => address) uniqueIdentifierToDeviceWallet +event AdminUpdateRevoked(address _caller, address _revokedAddress) ``` -device unique identifier <> device wallet address - Mapping for all the device wallets deployed by the registry +Emitted when the owner withdraws an outstanding nomination -_Use this to check if a device identifier has already been used or not_ +### AdminDisabled -### deviceWalletToOwner +```solidity +event AdminDisabled(address _adminOfRecord, address _caller) +``` + +Emitted when the admin's powers are suspended, naming the address left on the books + +### AdminEnabled ```solidity -mapping(address => bytes32[2]) deviceWalletToOwner +event AdminEnabled(address _adminOfRecord, address _caller) ``` -device wallet address <> owner P256 public key. +Emitted when a suspended admin is given its powers back -### isDeviceWalletValid +### VaultAddressUpdated ```solidity -mapping(address => bool) isDeviceWalletValid +event VaultAddressUpdated(address _updatedVaultAddress) ``` -device wallet address <> boolean (true if deployed by the registry or device wallet factory) - Mapping of all the device wallets deployed by the registry (or the device wallet factory) - to their respective owner. +Emitted when the owner points data bundle payments at a different vault -### isESIMWalletValid +### Paused ```solidity -mapping(address => address) isESIMWalletValid +event Paused(address _admin) ``` -eSIM wallet address <> device wallet address - All the eSIM wallets deployed using this registry are valid and set to true +Emitted when the admin stops the ETH-moving paths protocol-wide + +### Unpaused + +```solidity +event Unpaused(address _owner) +``` + +Emitted when the owner releases the pause + +### DefaultDataBundlePriceCapUpdated + +```solidity +event DefaultDataBundlePriceCapUpdated(uint256 _cap) +``` + +Emitted when the owner changes the price ceiling eSIM wallets fall back to + +### ESIMWalletSetOnStandby + +```solidity +event ESIMWalletSetOnStandby(address _eSIMWalletAddress, bool _isOnStandby, address _deviceWalletAddress) +``` + +Emitted when an eSIM wallet's outstanding transfer is raised or settled ### onlyLazyWalletRegistry @@ -100,13 +262,24 @@ eSIM wallet address <> device wallet address modifier onlyLazyWalletRegistry() ``` +Restricts a call to the lazy wallet registry + ### deployLazyWallet ```solidity -function deployLazyWallet(bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, uint256 _salt, string[] _eSIMUniqueIdentifiers, struct DataBundleDetails[][] _dataBundleDetails) external returns (address, address[]) +function deployLazyWallet(bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, uint256 _salt, string[] _eSIMUniqueIdentifiers, uint256 _depositAmount) external payable returns (address, address[]) ``` -Allow LazyWalletRegistry to deploy a device wallet and an eSIM wallet on behalf of a user +Allow LazyWalletRegistry to deploy a device wallet and its first eSIM wallets + +_Deploys the wallets and sets their identifiers only. Purchase history is copied in + afterwards through `populateLazyHistory`, because carrying it here made one transaction + grow with the eSIM count and each eSIM's history at the same time. + + `_eSIMUniqueIdentifiers` is the first batch rather than the device's whole list, and any + identifier past it reaches `deployMoreLazyESIMWallets`. The lazy wallet registry owns + the cursor deciding where one batch ends and the next begins, and it reserves the whole + salt range before this runs, so no bound on the salt is needed here._ #### Parameters @@ -114,26 +287,198 @@ Allow LazyWalletRegistry to deploy a device wallet and an eSIM wallet on behalf | ---- | ---- | ----------- | | _deviceWalletOwnerKey | bytes32[2] | P256 public key of user | | _deviceUniqueIdentifier | string | Unique device identifier associated with the device | -| _salt | uint256 | | -| _eSIMUniqueIdentifiers | string[] | | -| _dataBundleDetails | struct DataBundleDetails[][] | | +| _salt | uint256 | CREATE2 salt the device wallet and its first eSIM wallet are deployed at | +| _eSIMUniqueIdentifiers | string[] | First batch of eSIM identifiers, in the order the full list holds them | +| _depositAmount | uint256 | ETH forwarded to the new device wallet | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address | Return device wallet address and list of addresses of all the eSIM wallets | +| [0] | address | Return device wallet address and the eSIM wallet addresses this call deployed | | [1] | address[] | | +### deployMoreLazyESIMWallets + +```solidity +function deployMoreLazyESIMWallets(address _deviceWallet, string _deviceUniqueIdentifier, uint256 _baseSalt, uint256 _startIndex, string[] _eSIMUniqueIdentifiers) external returns (address[]) +``` + +Deploys the next batch of eSIM wallets for a device the lazy registry already set up + +_Separate from `deployLazyWallet` because that call deploys the device wallet itself, and + the owner key, salt and deposit it takes describe a one-time act. Reaching a device this + way needs none of them, and repeating them would either be ignored or checked against a + key the owner is free to rotate between batches. + + The salt continues from where the first batch stopped rather than starting over, because + the eSIM wallet factory salts CREATE2 with it and a repeat would land on an address that + already holds a wallet._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWallet | address | Device wallet the new eSIM wallets are bound to | +| _deviceUniqueIdentifier | string | Device identifier the wallets belong to | +| _baseSalt | uint256 | Salt the device's deployment started from | +| _startIndex | uint256 | Position of this batch's first identifier in the device's full list | +| _eSIMUniqueIdentifiers | string[] | This batch's identifiers, in the order the full list holds them | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address[] | Addresses of the eSIM wallets this call deployed | + +### populateLazyHistory + +```solidity +function populateLazyHistory(address _eSIMWallet, struct DataBundleDetails[] _dataBundleDetails) external +``` + +Forwards one batch of pre-deployment purchase history to an eSIM wallet on behalf of + the lazy wallet registry + +_eSIM wallets accept history from this contract and nothing else, so the copy is routed + through here rather than giving them a second address to trust. The lazy wallet + registry owns the cursor that decides which entries a batch carries._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWallet | address | Wallet receiving the batch | +| _dataBundleDetails | struct DataBundleDetails[] | One batch of data bundle purchase details | + +### _deployLazyESIMWallet + +```solidity +function _deployLazyESIMWallet(address _deviceWallet, string _deviceUniqueIdentifier, uint256 _salt, string _eSIMUniqueIdentifier) internal returns (address) +``` + +Deploys one eSIM wallet, binds it to the device wallet and sets its eSIM identifier + +_Shared by the first batch and every batch after it so the two cannot drift apart. The + identifier is known up front on this route, unlike the ordinary one, so setting it here + saves the admin a second transaction per wallet._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWallet | address | Device wallet the eSIM wallet is bound to | +| _deviceUniqueIdentifier | string | Device identifier the wallet belongs to | +| _salt | uint256 | CREATE2 salt for this eSIM wallet | +| _eSIMUniqueIdentifier | string | Identifier written onto the new eSIM wallet | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | Address of the eSIM wallet deployed | + ### _updateDeviceWalletInfo ```solidity function _updateDeviceWalletInfo(address _deviceWallet, string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey) internal ``` -### _updateESIMInfo +Records a device wallet against its identifier and its owner key + +_Writes all four mappings together, so a wallet is either fully recorded or not recorded._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWallet | address | Address of the device wallet | +| _deviceUniqueIdentifier | string | Identifier the wallet is reached by | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | + +### _updateDeviceWalletOwnerKey + +```solidity +function _updateDeviceWalletOwnerKey(address _deviceWallet, bytes32[2] _newOwnerKey) internal +``` + +Moves a device wallet's registry bindings from its current owner key to a new one + +_The retired key comes from `deviceWalletToOwner` rather than from the caller, so a + wallet cannot name a key it never held and free someone else's reservation. Clearing + the old hash before checking the new one is what lets a wallet rotate onto the key it + already holds: the clear removes its own reservation, so the check sees a free slot._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWallet | address | Wallet whose owner key is rotating | +| _newOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key taking over | + +### isDeviceIdentifierAlreadyUsed + +```solidity +function isDeviceIdentifierAlreadyUsed(string _deviceUniqueIdentifier) public view returns (bool) +``` + +Whether a device identifier already has a wallet recorded against it + +_True whichever route deployed it. Both routes have to refuse an identifier the other + already used, and this contract is the only place that knows about both._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Device identifier being checked | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True if the identifier is taken | + +### eSIMWalletForIdentifier ```solidity -function _updateESIMInfo(address _eSIMWalletAddress, address _deviceWalletAddress) internal +function eSIMWalletForIdentifier(string _eSIMUniqueIdentifier) public view returns (address) ``` +The eSIM wallet holding an eSIM identifier, or zero if nobody holds it + +_Takes the string so callers do not have to hash it themselves, which is the only + difference from reading `claimedESIMIdentifiers` directly._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMUniqueIdentifier | string | eSIM identifier being looked up | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The wallet that claimed it | + +### isESIMIdentifierClaimed + +```solidity +function isESIMIdentifierClaimed(string _eSIMUniqueIdentifier) public view returns (bool) +``` + +Whether an eSIM identifier is already held by a wallet + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMUniqueIdentifier | string | eSIM identifier being checked | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True if the identifier is taken | + diff --git a/docs/WebAuthn.md b/docs/WebAuthn.md index 64149099..f9c4c1c4 100644 --- a/docs/WebAuthn.md +++ b/docs/WebAuthn.md @@ -8,6 +8,44 @@ A library for verifying WebAuthn Authentication Assertions, built off the work _Attempts to use the RIP-7212 precompile for signature verification. If precompile verification fails, it falls back to FreshCryptoLib._ +### tryDecodeSignature + +```solidity +function tryDecodeSignature(bytes encodedSignature) internal pure returns (struct WebAuthnSignature decoded) +``` + +Decodes an encoded `WebAuthnSignature` without reverting on a malformed encoding. + +_The decoder solc generates reverts when the bytes are not a well formed encoding, and a + revert is not a rejection anywhere this is reached from. Inside ERC-4337 validation it + fails the whole bundle rather than the one operation, and behind `isValidSignature` it + reaches an integrating contract as an error rather than as an invalid signature. + Everything below this point in this library was already written to return false instead + of reverting; the decode one level above it was not, so anything too malformed to decode + never reached the hardening. + + An encoding failing any bound leaves `decoded` as solc allocated it, with both dynamic + members pointing at the zero slot. `verifySignature` then returns false, because an + empty `clientDataJSON` cannot contain the index it is handed. + + Assembly, and a copy of solady's `WebAuthn.tryDecodeAuth` rather than a fresh + implementation: `WebAuthnAuth` and `WebAuthnSignature` have identical layouts, and + rewriting an audited ABI bounds check by hand only adds somewhere for a mistake to + live. Memory-safe: every read is inside `encodedSignature`, and the only writes are to + the six words solc already reserved for the return value._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| encodedSignature | bytes | `abi.encode` of a `WebAuthnSignature`, as supplied by the caller. | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| decoded | struct WebAuthnSignature | The signature, or a zeroed struct when the encoding is malformed. | + ### verifySignature ```solidity diff --git a/docs/aa-helper/Account4337.md b/docs/aa-helper/Account4337.md index 0a80270b..3b51294a 100644 --- a/docs/aa-helper/Account4337.md +++ b/docs/aa-helper/Account4337.md @@ -2,11 +2,13 @@ ## Account4337 -### owner +ERC-4337 account owned by a P256 key rather than by an address -```solidity -bytes32[2] owner -``` +_The owner never sends a transaction itself. It signs a WebAuthn assertion, and either + the EntryPoint or this account calling into itself turns that into a call. Two entry + points read signatures, `validateUserOp` for user operations and `isValidSignature` for + ERC-1271, and each hashes a different precursor, so a signature made for one is not + accepted by the other._ ### entryPoint @@ -14,7 +16,10 @@ bytes32[2] owner contract IEntryPoint entryPoint ``` -The ERC-4337 entry point singleton +The ERC-4337 EntryPoint singleton this account answers to + +_Immutable to keep validation cheap, which means moving to a new EntryPoint version + is a new implementation rather than a setter call._ ### verifier @@ -22,7 +27,21 @@ The ERC-4337 entry point singleton contract P256Verifier verifier ``` -Signature verifier contract +Contract that verifies every WebAuthn assertion for this account + +### owner + +```solidity +bytes32[2] owner +``` + +X and Y co-ordinates of the P256 key that owns this account + +_DeviceWallet inherits this contract, and base storage comes first, so its own + variables begin immediately after this one. A state variable added here moves all of + them on wallets that are already deployed, which then read back as zero. Anything + this contract needs later belongs in its own ERC-7201 namespace, not in a slot + following `owner`._ ### Account4337Initialized @@ -30,29 +49,34 @@ Signature verifier contract event Account4337Initialized(contract IEntryPoint entryPoint, bytes32[2] owner) ``` +Emitted once, when the account's owner key is first set + ### AccountOwnershipTransferred ```solidity event AccountOwnershipTransferred(bytes32[2] newOwner) ``` +Emitted when the owner key is replaced + ### onlySelf ```solidity modifier onlySelf() ``` +Restricts a call to the account itself + +_The only way to satisfy this from outside is `execute` or `executeBatch` targeting this + address, which the owner key has to have signed for._ + ### onlyEntryPoint ```solidity modifier onlyEntryPoint() ``` -### onlyOwnerOrEntryPoint - -```solidity -modifier onlyOwnerOrEntryPoint() -``` +Restricts a call to the EntryPoint singleton ### constructor @@ -60,15 +84,31 @@ modifier onlyOwnerOrEntryPoint() constructor(contract IEntryPoint _entryPoint, contract P256Verifier _verifier) public ``` +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _entryPoint | contract IEntryPoint | EntryPoint singleton this account validates against | +| _verifier | contract P256Verifier | Contract used to verify WebAuthn assertions | + ### initialize ```solidity -function initialize(bytes32[2] anOwner) public virtual +function initialize(bytes32[2] anOwner) internal virtual ``` -_The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint, -a new implementation of SimpleAccount must be deployed with the new EntryPoint address, then upgrading -the implementation by calling `upgradeTo()`_ +Sets the owner key on a freshly deployed account + +_Internal on purpose. A public setup function guarded only by `initializer` names no + caller, so a proxy created without its init call in the same transaction could be + claimed by anyone with an owner key of their choosing and none of the protocol wiring. + Internal keeps the subclass path working and leaves no other way in._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| anOwner | bytes32[2] | X,Y co-ordinates of the P256 key taking ownership | ### _initialize @@ -76,11 +116,15 @@ the implementation by calling `upgradeTo()`_ function _initialize(bytes32[2] anOwner) internal virtual ``` -### transferOwnership +Writes the owner key without the initializer guard -```solidity -function transferOwnership(bytes32[2] newOwner) public returns (bytes32[2]) -``` +_Split out so a subclass can reuse the write from its own initializer._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| anOwner | bytes32[2] | X,Y co-ordinates of the P256 key taking ownership | ### execute @@ -88,7 +132,16 @@ function transferOwnership(bytes32[2] newOwner) public returns (bytes32[2]) function execute(struct Call call) external ``` -execute a transaction (called directly from owner, or by entryPoint) +Makes one call from this account + +_Callable by the EntryPoint or by this account. There is no path here for the P256 key + directly: it holds no address, so it reaches this only by signing a user operation._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| call | struct Call | Target, value and calldata of the call to make | ### executeBatch @@ -96,16 +149,43 @@ execute a transaction (called directly from owner, or by entryPoint) function executeBatch(struct Call[] calls) external ``` -execute a sequence of transactions +Makes a sequence of calls from this account, reverting all of them if one fails + +_Same callers as `execute`. Each entry carries its own value, so a batch that moves no + ETH simply leaves every value at zero._ + +#### Parameters -_to reduce gas consumption for trivial case (no value), use a zero-length array to mean zero value_ +| Name | Type | Description | +| ---- | ---- | ----------- | +| calls | struct Call[] | Targets, values and calldata, executed in order | ### isValidSignature ```solidity -function isValidSignature(bytes32 message, bytes signature) external view returns (bytes4 magicValue) +function isValidSignature(bytes32 _messageHash, bytes _signature) external view returns (bytes4 magicValue) ``` +Validates a signature over an arbitrary message, per ERC-1271 + +_The challenge inside `clientDataJSON` is not `_messageHash`. It is the EIP-191 digest + over version, validUntil, chain id, this address and `_messageHash`, so an offchain + signer needs all five. Signature layout is version (1 byte) then validUntil (6 bytes) + then the ABI-encoded WebAuthn assertion._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _messageHash | bytes32 | EIP-191 digest of the original message | +| _signature | bytes | Packed version, validUntil and WebAuthn assertion | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| magicValue | bytes4 | `0x1626ba7e` when the signature is valid and unexpired, `0xffffffff` otherwise | + ### validateUserOp ```solidity @@ -118,8 +198,9 @@ signature failure should be reported by returning SIG_VALIDATION_FAILED (1). This allows making a "simulation call" without a valid signature Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure. -_Must validate caller is the entryPoint. - Must validate the signature and nonce_ +_Must stay within the ERC-4337 validation rules: no banned opcodes, no external calls + to other contracts, no TIMESTAMP. Expiry is handed to the EntryPoint through the packed + return value instead of being checked here._ #### Parameters @@ -133,19 +214,32 @@ _Must validate caller is the entryPoint. | Name | Type | Description | | ---- | ---- | ----------- | -| validationData | uint256 | - Packaged ValidationData structure. use `_packValidationData` and `_unpackValidationData` to encode and decode. <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure, otherwise, an address of an "authorizer" contract. <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite" <6-byte> validAfter - First timestamp this operation is valid If an account doesn't use time-range, it is enough to return SIG_VALIDATION_FAILED value (1) for signature failure. Note that the validation code cannot use block.timestamp (or block.number) directly. | +| validationData | uint256 | - Packaged ValidationData structure. use `_packValidationData` and `_unpackValidationData` to encode and decode. <20-byte> aggregatorOrSigFail - 0 for valid signature, 1 to mark signature failure, otherwise, an address of an "aggregator" contract. <6-byte> validUntil - Last timestamp this operation is valid at, or 0 for "indefinitely" <6-byte> validAfter - First timestamp this operation is valid If an account doesn't use time-range, it is enough to return SIG_VALIDATION_FAILED value (1) for signature failure. Note that the validation code cannot use block.timestamp (or block.number) directly. | -### _requireFromEntryPointOrOwner +### transferOwnership ```solidity -function _requireFromEntryPointOrOwner() internal view +function transferOwnership(bytes32[2] newOwner) public virtual returns (bytes32[2]) ``` -### _call +Replaces the P256 key that owns this account -```solidity -function _call(address target, uint256 value, bytes data) internal -``` +_Reachable only through `execute` or `executeBatch` with this account as the target, so + the current owner has to sign for it. Nothing outside this contract is told: a + subclass holding its own record of the owner has to override this and keep that record + in step._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newOwner | bytes32[2] | X,Y co-ordinates of the P256 key taking over | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bytes32[2] | The owner key now in force | ### getDeposit @@ -153,7 +247,7 @@ function _call(address target, uint256 value, bytes data) internal function getDeposit() public view returns (uint256) ``` -check current account deposit in the entryPoint +This account's gas deposit held by the EntryPoint ### addDeposit @@ -161,7 +255,9 @@ check current account deposit in the entryPoint function addDeposit() public payable ``` -deposit more funds for this account in the entryPoint +Tops up this account's gas deposit at the EntryPoint + +_Open to anyone, since paying another account's gas costs the payer and nobody else._ ### withdrawDepositTo @@ -169,32 +265,47 @@ deposit more funds for this account in the entryPoint function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public ``` -withdraw value from the account's deposit +Withdraws part of this account's gas deposit from the EntryPoint #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| withdrawAddress | address payable | target to send to | -| amount | uint256 | to withdraw | +| withdrawAddress | address payable | Recipient of the withdrawn ETH | +| amount | uint256 | Amount to withdraw | -### _authorizeUpgrade +### _requireFromEntryPointOrOwner ```solidity -function _authorizeUpgrade(address newImplementation) internal view +function _requireFromEntryPointOrOwner() internal view ``` -UUPSUpsgradeable: only allow self-upgrade. +Reverts unless the caller is the EntryPoint or this account itself -### receive +_"Owner" in the name means `address(this)`, not the P256 key, which has no address to + call from._ + +### _call ```solidity -receive() external payable +function _call(address target, uint256 value, bytes data) internal ``` -### fallback +Calls a target and bubbles its revert data unchanged + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| target | address | Address to call | +| value | uint256 | ETH to send with the call | +| data | bytes | Calldata for the call | + +### receive ```solidity -fallback() external payable +receive() external payable ``` +Accepts plain ETH transfers + diff --git a/docs/admin/ProtocolAdmin.md b/docs/admin/ProtocolAdmin.md new file mode 100644 index 00000000..2d94b1ec --- /dev/null +++ b/docs/admin/ProtocolAdmin.md @@ -0,0 +1,352 @@ +# Solidity API + +## ProtocolAdmin + +Owner of the four upgradeable protocol contracts, with a delay on every change + +_Replaces the single externally owned account that owns `Registry`, `LazyWalletRegistry`, + `DeviceWalletFactory` and `ESIMWalletFactory` today. Both wallet beacons sit under the two + factories, so owning the factories reaches every device wallet and every eSIM wallet. + + Everything this contract can do to the protocol goes one way: a proposer schedules it, the + delay elapses, and anyone at all executes it. Execution is open on purpose. The + announcement is what the delay buys, and once the wait is served there is no reason to make + the protocol depend on one key still being available to press the button. + + A guardian does not get a general fast path. It can say exactly three things, and each is + written here as its own function rather than as a payload, so no fourth sentence is + expressible however the role is held: + + 1. Release a pause. An upgrade that waits is reviewable; an outage that waits is an outage. + 2. Take `CANCELLER_ROLE` away from an account. + 3. Suspend the protocol's admin key. + + All three take something away and none of them grants anything, which is what keeps the + role away from user funds. Releasing a pause cannot move ETH, stripping a canceller cannot, + and a suspended admin is an admin that has stopped being able to spend rather than one + chosen by the guardian. Restoring any of the three is an owner action and waits, so the + side taking power away always wins the race against the side handing it back. A guardian + able to reinstate an admin would lose that, and a guardian able to appoint one would reach + `ESIMWallet.buyDataBundle` and every wallet holding ETH access through it. + + The second one exists because without it a compromised canceller is permanent. Evicting any + role holder means scheduling `revokeRole`, a scheduled operation can be cancelled by any + canceller, and so a compromised canceller cancels its own eviction forever. Nothing else can + break that loop, because the delay is what every other route waits on. + + Two limits on that power carry the whole recovery argument, and both are enforced rather + than documented. A guardian may not hold `CANCELLER_ROLE` itself, or it could revoke every + other canceller, become the only one, and cancel its own eviction. And it may not touch + `PROPOSER_ROLE`, because reaching zero proposers is unrecoverable: re-granting any role + needs a scheduled operation, scheduling needs a proposer, and `Registry.unpause` is owner + only, so a bricked admin plus a pause is a pause nobody can ever release. Reaching zero + cancellers is fine by comparison. It costs the veto, and a proposer can schedule it back. + + Not upgradeable, deliberately. An upgradeable owner of upgradeable contracts moves the + trust to whoever can upgrade it, and there is no delay left to protect that step._ + +### GUARDIAN_ROLE + +```solidity +bytes32 GUARDIAN_ROLE +``` + +Releases a pause and strips a canceller, both without waiting for the delay + +_Always granted `EXECUTOR_ROLE` alongside it, which keeps the role useful if open + execution is ever closed off. Deliberately not granted `CANCELLER_ROLE`; see the note on + the contract for why that pairing is what makes a guardian un-evictable._ + +### minDelayFloor + +```solidity +uint256 minDelayFloor +``` + +Shortest delay this contract will ever accept, whatever `updateDelay` was given + +_`updateDelay` takes any value including zero, and it is reachable by scheduling a call + to this contract like any other. Without a floor, one scheduled operation turns the + timelock into a plain multisig and nothing after it ever waits again. Read through + `getMinDelay`, which is what `schedule` measures against._ + +### PauseReleased + +```solidity +event PauseReleased(address target, address guardian) +``` + +A guardian released a pause + +### CancellerRevoked + +```solidity +event CancellerRevoked(address account, address guardian) +``` + +A guardian took the cancel power away from an account + +### AdminDisabled + +```solidity +event AdminDisabled(address target, address guardian) +``` + +A guardian suspended a protocol contract's admin key + +### AdminDisabledAndNominated + +```solidity +event AdminDisabledAndNominated(address target, address newAdmin) +``` + +A scheduled operation suspended an admin key and nominated its replacement + +### OwnershipAccepted + +```solidity +event OwnershipAccepted(address target) +``` + +Ownership of a protocol contract was accepted + +### DelayBelowFloor + +```solidity +error DelayBelowFloor(uint256 delay, uint256 floor) +``` + +The initial delay was below the floor + +### NoGuardians + +```solidity +error NoGuardians() +``` + +No guardian was named at construction + +### NoProposers + +```solidity +error NoProposers() +``` + +No proposer was named at construction + +### ZeroAddress + +```solidity +error ZeroAddress(string parameter) +``` + +A zero address appeared in one of the constructor role lists + +### RolesMustNotOverlap + +```solidity +error RolesMustNotOverlap(address account) +``` + +An account was named in two role lists that have to stay separate + +### NotACanceller + +```solidity +error NotACanceller(address account) +``` + +The account named does not hold the cancel power + +### OwnershipNotOffered + +```solidity +error OwnershipNotOffered(address target) +``` + +The contract was not offered ownership of this target + +### constructor + +```solidity +constructor(uint256 _initialDelay, uint256 _minDelayFloor, address[] _proposers, address[] _cancellers, address[] _guardians) public +``` + +_No admin account. The zero passed to `TimelockController` leaves this contract holding + its own `DEFAULT_ADMIN_ROLE`, so granting or revoking any role is itself an operation + that has to be scheduled and waited out. Rotating a signer set is a role change here and + never touches the protocol contracts, whose owner stays this address. + + The base constructor gives every proposer `CANCELLER_ROLE` as well, which is kept. + `_cancellers` is for the accounts that cancel and do nothing else, typically the + individual keys behind a proposer multisig, so that one key can veto on its own without + being able to schedule anything on its own. + + `EXECUTOR_ROLE` goes to the zero address, which `onlyRoleOrOpenRole` reads as open to + everyone. See the note on the contract for why. + + An empty `_cancellers` is allowed, since the proposers already carry the role. An empty + `_guardians` is not: there would be no way to add one without the delay it exists to + skip, and no way at all to break a compromised canceller loose._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _initialDelay | uint256 | Delay new operations wait before they can be executed | +| _minDelayFloor | uint256 | Shortest delay `updateDelay` can ever bring the contract down to | +| _proposers | address[] | Accounts that may schedule operations, and that may also cancel them | +| _cancellers | address[] | Further accounts that may cancel, holding no other role | +| _guardians | address[] | Accounts that may release a pause and strip a canceller | + +### getMinDelay + +```solidity +function getMinDelay() public view virtual returns (uint256) +``` + +_Held at the floor whatever `updateDelay` last wrote. `schedule` reads this rather than + the stored value, so the floor binds every new operation without needing to intercept + the setter. + + Anything reporting the delay should call this rather than follow `MinDelayChange`, + which carries the value `updateDelay` stored and not the floor that overrides it._ + +### grantRole + +```solidity +function grantRole(bytes32 role, address account) public virtual +``` + +_The constructor refuses these overlaps and nothing else did. Granting is a scheduled + operation like any other, so without this a single proposer can schedule the pairing + that makes a guardian un-evictable and anyone can execute it once the delay is served. + + Authority is checked first, exactly where the base checks it, so a caller with no right + to grant the role still gets `AccessControlUnauthorizedAccount`. Answering that caller + with an overlap instead would name a problem it never reached. + + The constructor's other rule, that `_cancellers` and `_proposers` do not intersect, is + deliberately not repeated here. That one shapes the deployment, keeping a veto key off + the schedule path. It is not a safety property, and pairing the two roles later is a + legitimate decision for a scheduled operation to make. + + A guardian granted here picks up `EXECUTOR_ROLE` with it, as the constructor does, so + the two ways of installing one leave the same state behind. The reverse is not paired: + `revokeRole` cannot tell that grant from an independent one, so evicting a guardian + means scheduling both revocations in one batch._ + +### unpauseInstantly + +```solidity +function unpauseInstantly(address target) external +``` + +Releases a pause on a protocol contract immediately + +_The selector is fixed in the interface rather than passed in, so this cannot be pointed + at anything else on the target. Whatever a guardian does here, the worst outcome + reachable is that something is unpaused which someone wanted paused, and the key that + applies a pause is not this one and can simply apply it again._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| target | address | Contract to unpause | + +### revokeCancellersInstantly + +```solidity +function revokeCancellersInstantly(address[] accounts) external +``` + +Takes the cancel power away from accounts immediately + +_Only `CANCELLER_ROLE`, never anything else, and adding it back is an ordinary scheduled + operation. A batch because the account being evicted is usually one signer set holding + the role several times over, and doing that in one transaction rather than several is + the difference between the eviction landing and the operation it is racing landing + first. + + All or nothing, and an account that does not hold the role reverts rather than passing + quietly. A guardian doing this is acting on a named list during an incident, and a + silent no-op would leave it believing a veto is gone while the veto is still there._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| accounts | address[] | Accounts to strip | + +### disableAdminInstantly + +```solidity +function disableAdminInstantly(address target) external +``` + +Suspends a protocol contract's admin key immediately + +_The lever against a compromised hot key. That key holds `Registry.pause`, so leaving + its removal to the delay would mean an outage running for the whole wait while the key + re-applies the pause after every release. Suspending it is what makes + `unpauseInstantly` stick. + + Takes powers away and hands none out. The suspended address stays on the target's + books, and only the owner can reinstate it or name a replacement, both of which wait. + Whatever a guardian does here, the worst outcome reachable is a stopped backend, which + the owner ends._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| target | address | Contract whose admin is being suspended | + +### disableAndNominate + +```solidity +function disableAndNominate(address target, address newAdmin) external +``` + +Suspends the current admin and nominates its replacement in one operation + +_Scheduled like anything else, so this is not a fast path and holds no role of its own. + It exists because the two effects belong in one transaction: the target strips the + incumbent the moment a nomination is outstanding, so scheduling the nomination alone + already suspends the old key, and naming the compound effect is the difference between + a reviewer reading the intent off the operation and having to infer it from a payload. + + The nominee still has to accept, so this cannot hand the role to an address that + cannot act, and the role stays dormant until it does._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| target | address | Contract whose admin is being replaced | +| newAdmin | address | Address nominated to take the role | + +### acceptOwnershipBatch + +```solidity +function acceptOwnershipBatch(address[] targets) external +``` + +Completes the handover of every contract that has offered this one its ownership + +_Permissionless, and safe to be: it only takes ownership that the current owner already + offered, and the offer is the decision. Scheduling it instead would mean waiting out + the delay before this contract could own anything, including during the deployment it + is being installed by. + + Each event is emitted ahead of the call it describes, so a target that emits its own + events on handover cannot interleave them out of order. A failing handover takes the + whole batch down with it, so no event here can outlive the call it announced._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| targets | address[] | Contracts whose `pendingOwner` is this address | + diff --git a/docs/device-wallet/DeviceWallet.md b/docs/device-wallet/DeviceWallet.md index e5214650..bff9009e 100644 --- a/docs/device-wallet/DeviceWallet.md +++ b/docs/device-wallet/DeviceWallet.md @@ -1,56 +1,53 @@ # Solidity API -## OnlyRegistryOrDeviceWalletFactoryOrOwner +## DeviceWallet -```solidity -error OnlyRegistryOrDeviceWalletFactoryOrOwner() -``` +A user's device: an ERC-4337 account that owns the eSIM wallets bought for that device -## OnlyDeviceWalletOrOwner +_A beacon proxy deployed by `DeviceWalletFactory`, owned by a P256 key the user holds. It + funds its eSIM wallets, decides which of them may pull ETH, and is the only party that can + move one to another device. Its own owner key rotates through `transferOwnership`, which + also tells the registry so the two records cannot drift apart._ -```solidity -error OnlyDeviceWalletOrOwner() -``` - -## OnlyESIMWalletAdminOrLazyWallet +### registry ```solidity -error OnlyESIMWalletAdminOrLazyWallet() +contract Registry registry ``` -## OnlyESIMWalletAdminOrDeviceWalletOwner - -```solidity -error OnlyESIMWalletAdminOrDeviceWalletOwner() -``` +Registry contract instance -## OnlyESIMWalletAdminOrDeviceWalletFactory +### eSIMWalletFactory ```solidity -error OnlyESIMWalletAdminOrDeviceWalletFactory() +contract ESIMWalletFactory eSIMWalletFactory ``` -## OnlyAssociatedESIMWallets +eSIM wallet factory address + +### deviceUniqueIdentifier ```solidity -error OnlyAssociatedESIMWallets() +string deviceUniqueIdentifier ``` -## FailedToTransfer +String identifier to uniquely identify user's device + +### isValidESIMWallet ```solidity -error FailedToTransfer() +mapping(address => bool) isValidESIMWallet ``` -## DeviceWallet +Set to true if the eSIM wallet belongs to this device wallet -### ETHPaidForDataBundle +### canPullETH ```solidity -event ETHPaidForDataBundle(address _vault, address _eSIMWallet, uint256 _amount) +mapping(address => bool) canPullETH ``` -Emitted when the contract pays ETH for data bundle +Tracks if an associated eSIM wallet can pull ETH or not ### ETHAccessUpdated @@ -58,7 +55,7 @@ Emitted when the contract pays ETH for data bundle event ETHAccessUpdated(address _eSIMWalletAddress, bool _hasAccessToETH) ``` -Emitted when ower updates ETH access to a particular eSIM wallet +Emitted when owner updates ETH access to a particular eSIM wallet ### ETHSent @@ -70,77 +67,80 @@ Emitted when ETH is sent out from the contract _mostly when an eSIM wallet pulls ETH from this contract_ -### ESIMWalletDeployed +### ESIMWalletAdded ```solidity -event ESIMWalletDeployed(address _eSIMWalletAddress, bool _hasAccessToETH) +event ESIMWalletAdded(address _eSIMWalletAddress, bool _hasAccessToETH, address _caller) ``` -Emitted when eSIM wallet is deployed +Emitted when eSIM wallet is added to this Device Wallet -### registry +### ESIMWalletRemoved ```solidity -contract Registry registry +event ESIMWalletRemoved(address _eSIMWalletAddress, address _deviceWalletAddress, address _caller) ``` -Registry contract instance +Emitted when the eSIM wallet is removed from this Device Wallet -### deviceUniqueIdentifier +### NoETHToCallback ```solidity -string deviceUniqueIdentifier +event NoETHToCallback() ``` -String identifier to uniquely identify user's device +Emitted when the eSIM wallet being removed has no ETH to call back -### uniqueIdentifierToESIMWallet +### ETHCalledBack ```solidity -mapping(string => address) uniqueIdentifierToESIMWallet +event ETHCalledBack(uint256 _amount) ``` -Mapping from eSIMUniqueIdentifier to the respective eSIM wallet address +Emitted when the eSIM being removed sends back ETH to this device wallet -### isValidESIMWallet +### onlyRegistryOrDeviceWalletFactoryOrOwner ```solidity -mapping(address => bool) isValidESIMWallet +modifier onlyRegistryOrDeviceWalletFactoryOrOwner(address _eSIMWalletAddress) ``` -Set to true if the eSIM wallet belongs to this device wallet +Restricts a call to the registry, the device wallet factory, this wallet itself, or + the named eSIM wallet re-adding itself -### canPullETH +### onlySelfOrESIMWalletBeingRemoved ```solidity -mapping(address => bool) canPullETH +modifier onlySelfOrESIMWalletBeingRemoved(address _eSIMWalletAddress) ``` -Mapping that tracks if an associated eSIM wallet can pull ETH or not +Restricts a call to this wallet itself or to the eSIM wallet being removed -### onlyRegistryOrDeviceWalletFactoryOrOwner +### onlyESIMWalletAdminOrRegistry ```solidity -modifier onlyRegistryOrDeviceWalletFactoryOrOwner() +modifier onlyESIMWalletAdminOrRegistry() ``` -### onlyDeviceWalletFactoryOrOwner +Restricts a call to the registry or the eSIM wallet admin + +### onlyAssociatedESIMWallets ```solidity -modifier onlyDeviceWalletFactoryOrOwner() +modifier onlyAssociatedESIMWallets() ``` -### onlyESIMWalletAdminOrLazyWallet +Restricts a call to an eSIM wallet this device wallet holds + +### onlyESIMWalletAdmin ```solidity -modifier onlyESIMWalletAdminOrLazyWallet() +modifier onlyESIMWalletAdmin() ``` -### onlyAssociatedESIMWallets +Restricts a call to the eSIM wallet admin -```solidity -modifier onlyAssociatedESIMWallets() -``` +_Read from the registry on every call, so a rotation there takes effect immediately._ ### constructor @@ -148,13 +148,33 @@ modifier onlyAssociatedESIMWallets() constructor(contract IEntryPoint anEntryPoint, contract P256Verifier _verifier) public ``` +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| anEntryPoint | contract IEntryPoint | EntryPoint singleton this wallet validates against | +| _verifier | contract P256Verifier | Contract used to verify WebAuthn assertions | + ### init ```solidity -function init(address _registry, bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier) external +function init(address _registry, bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, address _eSIMWalletFactory) external ``` -Initialises the device wallet and deploys eSIM wallets for any already existing eSIMs +Wires the wallet to the registry and the factory, and sets its owner key + +_Called as the beacon proxy's constructor argument, so it always runs in the same + transaction as the deployment. `Account4337.initialize` is internal, and this is the + only path to it._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _registry | address | Registry contract this wallet reads the admin, vault and pause flag from | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning this wallet | +| _deviceUniqueIdentifier | string | Identifier the device is reached by | +| _eSIMWalletFactory | address | Factory this wallet deploys its eSIM wallets through | ### deployESIMWallet @@ -162,14 +182,19 @@ Initialises the device wallet and deploys eSIM wallets for any already existing function deployESIMWallet(bool _hasAccessToETH, uint256 _salt) external returns (address) ``` -Allow device wallet owner to deploy new eSIM wallet +Deploys an eSIM wallet for this device and binds it + +_The new wallet has no eSIM identifier yet. That arrives through + `setESIMUniqueIdentifierForAnESIMWallet` once the eSIM itself has been created. + + ETH access is granted only afterwards, by the owner, with `toggleAccessToETH`._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _hasAccessToETH | bool | Set to true if the eSIM wallet is allowed to pull ETH from this wallet. | -| _salt | uint256 | | +| _hasAccessToETH | bool | Must be false | +| _salt | uint256 | CREATE2 salt for the new eSIM wallet | #### Return Values @@ -177,6 +202,60 @@ Allow device wallet owner to deploy new eSIM wallet | ---- | ---- | ----------- | | [0] | address | eSIM wallet address | +### pullETH + +```solidity +function pullETH(uint256 _amount) external returns (uint256) +``` + +Allow the eSIM wallets associated with this device wallet to pull ETH (for data bundles) + +_Refused while the protocol is paused, and refused for a wallet whose ETH access the + owner has revoked._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _amount | uint256 | Amount of ETH to pull | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | uint256 | The amount pulled | + +### transferOwnership + +```solidity +function transferOwnership(bytes32[2] newOwner) public returns (bytes32[2]) +``` + +Replaces the P256 key that owns this account + +_The registry holds its own record of which key owns this wallet, and the deploy paths + keep one key to one wallet. Rotating without telling it leaves the retired key named + as the owner and leaves the key taking over unregistered, free for a second wallet to + claim. `super` runs after the key check because it carries the `onlySelf` guard and + because the registry call is an external one, so the local write has to land before it. + + A key that cannot verify a signature bricks the wallet for good: this function is + reachable only through `execute`, which needs a signature, so there is no rotating + back and no reaching the balance. The deploy paths reject such a key and this path + writes the same storage, so it has to reject it too._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newOwner | bytes32[2] | X,Y co-ordinates of the P256 key taking over | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bytes32[2] | The owner key now in force | + ### setESIMUniqueIdentifierForAnESIMWallet ```solidity @@ -185,7 +264,12 @@ function setESIMUniqueIdentifierForAnESIMWallet(address _eSIMWalletAddress, stri Allow wallet owner or admin to set unique identifier for their eSIM wallet -_Allow lazy wallet registry to call the function for fiat users who later decided to get a smart wallet_ +_The registry is also a caller, which is how a wallet deployed on the lazy path gets its + identifier in the same transaction as its deployment. + + The claim goes in before the wallet is written, and the order matters: the wallet's own + slot is set once and for good, so a claim that failed afterwards would leave a wallet + holding an identifier the registry does not record._ #### Parameters @@ -194,61 +278,81 @@ _Allow lazy wallet registry to call the function for fiat users who later decide | _eSIMWalletAddress | address | Address of the eSIM wallet smart contract | | _eSIMUniqueIdentifier | string | String unique identifier for the eSIM wallet | -### payETHForDataBundles +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | string | The identifier now written on the eSIM wallet | + +### toggleAccessToETH ```solidity -function payETHForDataBundles(uint256 _amount) external returns (uint256) +function toggleAccessToETH(address _eSIMWalletAddress, bool _hasAccessToETH) public ``` -Allow the eSIM wallets associated with this device wallet to pay ETH for data bundles +Allow owner to revoke or give access to any associated eSIM wallet for pulling ETH -_Instead of pulling the ETH into the eSIM wallet and then sending to the vault, - the eSIM wallet can directly request the device wallet to pay ETH for the data bundles_ +_The only way ETH access is ever granted. Binding a wallet never carries it, so a + revocation stands until the owner signs a grant._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _amount | uint256 | Amount of ETH to pull | +| _eSIMWalletAddress | address | Address of the eSIM wallet to toggle ETH access for | +| _hasAccessToETH | bool | Set to true to give access, false to revoke access | -### pullETH +### addESIMWallet ```solidity -function pullETH(uint256 _amount) external returns (uint256) +function addESIMWallet(address _eSIMWalletAddress, bool _hasAccessToETH) public ``` -Allow the eSIM wallets associated with this device wallet to pull ETH (for data bundles) +Allow the device wallet factory or the wallet owner to add new eSIM wallet to this device wallet #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _amount | uint256 | Amount of ETH to pull | +| _eSIMWalletAddress | address | Address of the eSIM wallet to be added | +| _hasAccessToETH | bool | Must be false. ETH access is granted only through `toggleAccessToETH` | -### getVaultAddress +### removeESIMWallet ```solidity -function getVaultAddress() public view returns (address) +function removeESIMWallet(address _eSIMWalletAddress, bool _callBackETH) public ``` -Fetches the vault address (that receives payment for data bundles) from the device wallet factory +Allow the device wallet owner or the eSIM wallet to remove any eSIM wallet bound with this device wallet -_Mostly used by the associated eSIM wallets for reference_ +#### Parameters -### toggleAccessToETH +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWalletAddress | address | Address of the eSIM wallet to be removed | +| _callBackETH | bool | `true` if any remaining ETH needs to be called back from the ESIM wallet to this device wallet, `false` otherwise | + +### _addESIMWallet ```solidity -function toggleAccessToETH(address _eSIMWalletAddress, bool _hasAccessToETH) external +function _addESIMWallet(address _eSIMWalletAddress, bool _hasAccessToETH) internal ``` -Allow owner to revoke or give access to any associated eSIM wallet for pulling ETH +Binds an eSIM wallet to this device wallet and records it with the registry + +_Refuses a wallet this device wallet does not already own, so binding cannot run ahead + of the ownership handover. + + A bind never carries ETH access. `toggleAccessToETH` is `onlySelf` and the only writer + of a `true`, so no bind can undo the owner's revocation. Asking for access here reverts + rather than being downgraded in silence._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _eSIMWalletAddress | address | Address of the eSIM wallet to toggle ETH access for | -| _hasAccessToETH | bool | Set to true to give access, false to revoke access | +| _eSIMWalletAddress | address | Address of the eSIM wallet to bind | +| _hasAccessToETH | bool | Must be false | ### _transferETH @@ -256,27 +360,31 @@ Allow owner to revoke or give access to any associated eSIM wallet for pulling E function _transferETH(address _recipient, uint256 _amount) internal virtual ``` -### updateESIMInfo +Sends ETH out of this wallet, reverting if the call fails -```solidity -function updateESIMInfo(address _eSIMWalletAddress, bool _isESIMWalletValid, bool _hasAccessToETH) external -``` +_A zero amount is a no-op rather than a revert._ -### _updateESIMInfo +#### Parameters -```solidity -function _updateESIMInfo(address _eSIMWalletAddress, bool _isESIMWalletValid, bool _hasAccessToETH) internal -``` +| Name | Type | Description | +| ---- | ---- | ----------- | +| _recipient | address | Address receiving the ETH | +| _amount | uint256 | Amount in wei | -### updateDeviceWalletAssociatedWithESIMWallet +### getVaultAddress ```solidity -function updateDeviceWalletAssociatedWithESIMWallet(address _eSIMWalletAddress, address _deviceWalletAddress) external +function getVaultAddress() public view returns (address) ``` -### _updateDeviceWalletAssociatedWithESIMWallet +Fetches the vault address that receives payment for data bundles -```solidity -function _updateDeviceWalletAssociatedWithESIMWallet(address _eSIMWalletAddress, address _deviceWalletAddress) internal -``` +_Read through to the registry rather than cached, so a vault change reaches every + wallet at once. The associated eSIM wallets call this before paying._ + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The vault address | diff --git a/docs/device-wallet/DeviceWalletFactory.md b/docs/device-wallet/DeviceWalletFactory.md index 1c441fe5..2cb07d4d 100644 --- a/docs/device-wallet/DeviceWalletFactory.md +++ b/docs/device-wallet/DeviceWalletFactory.md @@ -1,193 +1,299 @@ # Solidity API -## OnlyAdmin +## DeviceWalletFactory + +Deploys device wallets at deterministic addresses and owns the beacon they all point at + +_A UUPS singleton with two deployment routes. The admin batch route deploys a wallet, its + first eSIM wallet and the registry records together. The EntryPoint route, `createAccount`, + writes no external storage at all, so a wallet created that way is registered afterwards + through `postCreateAccount`. Both land on the same CREATE2 address for the same inputs._ + +### beacon ```solidity -error OnlyAdmin() +contract UpgradeableBeacon beacon ``` -## DeviceWalletFactory +Upgradeable beacon that points to correct Device wallet implementation -Contract for deploying a new eSIM wallet +_Every device wallet is a beacon proxy reading its implementation from here, so one + update moves all of them at once and none can decline it._ -### DeviceWalletFactoryDeployed +### entryPoint ```solidity -event DeviceWalletFactoryDeployed(address _admin, address _vault, address _upgradeManager, address _deviceWalletImplementation, address _beacon) +contract IEntryPoint entryPoint ``` -Emitted when factory is deployed and admin is set +ERC-4337 EntryPoint singleton passed into every device wallet implementation -### VaultAddressUpdated +### verifier ```solidity -event VaultAddressUpdated(address _updatedVaultAddress) +contract P256Verifier verifier ``` -Emitted when the Vault address is updated +Contract the device wallets verify WebAuthn assertions through -### DeviceWalletDeployed +### registry ```solidity -event DeviceWalletDeployed(address _deviceWalletAddress, address _eSIMWalletAddress, bytes32[2] _deviceWalletOwnerKey) +contract Registry registry ``` -Emitted when a new device wallet is deployed +Registry contract instance -### AdminUpdated +### eSIMWalletFactory ```solidity -event AdminUpdated(address _newAdmin) +contract ESIMWalletFactory eSIMWalletFactory ``` -Emitted when the admin address is updated +eSIM wallet factory contract instance -### entryPoint +### deviceWalletInfoAdded ```solidity -contract IEntryPoint entryPoint +mapping(address => bool) deviceWalletInfoAdded ``` -### verifier +Tracks all the device wallets that have their data added into the registry upon deployment + +### DeviceWalletFactoryDeployed ```solidity -contract P256Verifier verifier +event DeviceWalletFactoryDeployed(address _upgradeManager, address _deviceWalletImplementation, address _beacon) ``` -### eSIMWalletAdmin +Emitted when factory is deployed + +### DeviceWalletDeployed ```solidity -address eSIMWalletAdmin +event DeviceWalletDeployed(address _deviceWalletAddress, address _eSIMWalletAddress, bytes32[2] _deviceWalletOwnerKey) ``` -Admin address of the eSIM wallet project +Emitted when a new device wallet is deployed -### vault +### DeviceWalletImplementationUpdated ```solidity -address vault +event DeviceWalletImplementationUpdated(address _newDeviceImplementation) ``` -Vault address that receives payments for eSIM data bundles +Emitted when the device wallet implementation is updated -### deviceWalletImplementation +### AddedRegistry ```solidity -contract DeviceWallet deviceWalletImplementation +event AddedRegistry(address registry) ``` -Implementation (logic) contract address of the device wallet +Emitted when the registry is added to the factory contract -### beacon +### onlyAdminOrRegistry ```solidity -address beacon +modifier onlyAdminOrRegistry() ``` -Beacon contract address for this contract +Restricts a call to the eSIM wallet admin or the registry -### registry +### constructor ```solidity -contract Registry registry +constructor() public ``` -Registry contract instance +_Locks the implementation contract itself. Without this, anyone can call initialize + directly on the implementation, own it, and make it deploy a beacon it controls. The + proxy is unaffected either way, but an owned implementation is a trap for any later + upgrade that adds an outward call._ -### onlyAdmin +### initialize ```solidity -modifier onlyAdmin() +function initialize(address _deviceWalletImplementation, address _upgradeManager, address _eSIMWalletFactoryAddress, contract IEntryPoint _entryPoint, contract P256Verifier _verifier) external ``` -### constructor +Deploys the beacon and hands ownership of this factory to the upgrade manager -```solidity -constructor(contract IEntryPoint _entryPoint, contract P256Verifier _verifier) public -``` +_Neither the admin nor the vault is taken here. Both come from the registry, which is + added afterwards through addRegistryAddress, so admin functions stay closed until that + is done and every payment reads one address._ -### _authorizeUpgrade +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWalletImplementation | address | First device wallet logic contract the beacon points at | +| _upgradeManager | address | Admin address responsible for upgrading contracts | +| _eSIMWalletFactoryAddress | address | Factory the device wallets deploy their eSIM wallets through | +| _entryPoint | contract IEntryPoint | ERC-4337 EntryPoint singleton for this chain | +| _verifier | contract P256Verifier | Contract the device wallets verify WebAuthn assertions through | + +### addRegistryAddress ```solidity -function _authorizeUpgrade(address newImplementation) internal +function addRegistryAddress(address _registryContractAddress) external returns (address) ``` -_Owner based upgrades_ +Allow the owner to add the registry contract after it has been deployed -### initialize +_Write-once, and the owner rather than the admin, because the admin is read from the + registry and there is no admin to check against until this call lands. Matches + ESIMWalletFactory, which has always gated its own version on the owner._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _registryContractAddress | address | Address of the registry | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The registry address now in force | + +### updateDeviceWalletImplementation ```solidity -function initialize(address _registryContractAddress, address _eSIMWalletAdmin, address _vault, address _upgradeManager) external +function updateDeviceWalletImplementation(address _newDeviceImpl) external returns (address) ``` +Function to update the device wallet implementation + +_Moves every device wallet in the protocol at once. Treat any change here as a + protocol-wide upgrade, since no wallet can decline it._ + #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _registryContractAddress | address | | -| _eSIMWalletAdmin | address | Admin address of the eSIM wallet project | -| _vault | address | Address of the vault that receives payments for the data bundles | -| _upgradeManager | address | Admin address responsible for upgrading contracts | +| _newDeviceImpl | address | Address of the new device implementation contract | -### updateVaultAddress +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The implementation now in force | + +### deployDeviceWalletForUsers ```solidity -function updateVaultAddress(address _newVaultAddress) public returns (address) +function deployDeviceWalletForUsers(string[] _deviceUniqueIdentifiers, bytes32[2][] _deviceWalletOwnersKey, uint256[] _salts, uint256[] _depositAmounts) external payable returns (struct Wallets[]) ``` -Function to update vault address. +To deploy multiple device wallets at once -_Can only be called by the admin_ +_Each entry deploys a device wallet, its first eSIM wallet and the registry records in + one go. ETH left over once the batch has been funded is returned to the caller._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _newVaultAddress | address | New vault address | +| _deviceUniqueIdentifiers | string[] | Array of unique device identifiers for each device wallet | +| _deviceWalletOwnersKey | bytes32[2][] | Array of P256 public keys of owners of the respective device wallets | +| _salts | uint256[] | Array of CREATE2 salts, one per device wallet | +| _depositAmounts | uint256[] | Array of all the ETH to be deposited into each of the device wallets | -### updateAdmin +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | struct Wallets[] | Array of deployed device wallet address | + +### postCreateAccount ```solidity -function updateAdmin(address _newAdmin) public returns (address) +function postCreateAccount(address _deviceWallet, string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt) external ``` -Function to update admin address +Records a wallet the EntryPoint deployed through createAccount + +_Not needed on the admin batch route, which writes the registry itself. Callable by the + admin directly and by the registry on the lazy deployment path. + + The wallet was not deployed in this call, so nothing binds the arguments to it. The + re-derivation does: the key and the identifier are proxy constructor arguments, so an + address matching the derivation and holding code was deployed here with exactly those._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _newAdmin | address | New admin address | +| _deviceWallet | address | Wallet that was deployed | +| _deviceUniqueIdentifier | string | Identifier the device is reached by | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | +| _salt | uint256 | CREATE2 salt the wallet was deployed with | -### deployDeviceWalletForUsers +### createAccount ```solidity -function deployDeviceWalletForUsers(string[] _deviceUniqueIdentifiers, bytes32[2][] _deviceWalletOwnersKey, uint256[] _salts) public returns (address[]) +function createAccount(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt) public payable returns (contract DeviceWallet deviceWallet) ``` -To deploy multiple device wallets at once +Deploys a device wallet, returning the existing one if that address already holds it + +_Called by the EntryPoint during a user operation, so it must not read or write any + other contract's storage: that is barred by the ERC-4337 validation rules. The registry + is therefore not consulted here and not written, and `postCreateAccount` records the + wallet afterwards. Validation the registry would have done happens offchain, through + `preCreateAccountValidation`. + + Returning an existing address rather than reverting is what makes + `entryPoint.getSenderAddress()` keep working once the account has been created._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _deviceUniqueIdentifiers | string[] | Array of unique device identifiers for each device wallet | -| _deviceWalletOwnersKey | bytes32[2][] | Array of P256 public keys of owners of the respective device wallets | -| _salts | uint256[] | | +| _deviceUniqueIdentifier | string | Identifier the device is reached by | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | +| _salt | uint256 | CREATE2 salt for the wallet | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address[] | Array of deployed device wallet address | +| deviceWallet | contract DeviceWallet | The wallet at the computed address, new or already deployed | -### deployDeviceWalletAsAdmin +### renounceOwnership ```solidity -function deployDeviceWalletAsAdmin(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt) public returns (address) +function renounceOwnership() public pure ``` -_Allow admin to deploy a device wallet (and an eSIM wallet) for given unique device identifiers_ +Ownership of this contract is never renounced + +_The owner is the only caller _authorizeUpgrade accepts, and this contract owns the + beacon, so it is also the only route to updateDeviceWalletImplementation. Renouncing + would freeze every device wallet on its current logic permanently._ + +### _authorizeUpgrade + +```solidity +function _authorizeUpgrade(address newImplementation) internal +``` + +Restricts UUPS upgrades of this factory to the owner + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newImplementation | address | Address of the implementation being moved to | + +### _deployDeviceWallet + +```solidity +function _deployDeviceWallet(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt, uint256 _depositAmount) internal returns (struct Wallets, uint256) +``` + +Deploys one device wallet, its first eSIM wallet and the binding between them #### Parameters @@ -195,21 +301,66 @@ _Allow admin to deploy a device wallet (and an eSIM wallet) for given unique dev | ---- | ---- | ----------- | | _deviceUniqueIdentifier | string | Unique device identifier for the device wallet | | _deviceWalletOwnerKey | bytes32[2] | User's P256 public key (owner of the device wallet and respective eSIM wallets) | -| _salt | uint256 | | +| _salt | uint256 | CREATE2 salt for both wallets | +| _depositAmount | uint256 | Amount of ETH to be deposited into the device wallet | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | struct Wallets | Deployed device wallet address | +| [1] | uint256 | ETH actually forwarded to the wallet, zero if an existing wallet was returned | + +### _createAccountForUser + +```solidity +function _createAccountForUser(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt, uint256 _depositAmount) internal returns (contract DeviceWallet deviceWallet, uint256 spentETH) +``` + +Deploys a device wallet and writes its registry records, or adopts one that already exists + +_Returns the ETH actually forwarded to the wallet, which is zero whenever an existing + wallet is returned instead of a new one being deployed. Callers holding a budget must + decrement by this value, not by the requested deposit._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceUniqueIdentifier | string | Identifier the device is reached by | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | +| _salt | uint256 | CREATE2 salt for the wallet | +| _depositAmount | uint256 | ETH the caller asked to be deposited | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address | Deployed device wallet address | +| deviceWallet | contract DeviceWallet | The wallet at the computed address | +| spentETH | uint256 | ETH actually forwarded to it | -### deployDeviceWallet +### eSIMWalletAdmin ```solidity -function deployDeviceWallet(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey, uint256 _salt) public returns (address) +function eSIMWalletAdmin() public view returns (address) ``` -_Allow admin to deploy a device wallet (and an eSIM wallet) for given unique device identifiers_ +Admin address of the eSIM wallet project + +_Held by the registry, which is where it is rotated, so this contract cannot fall + behind the rest of the protocol after a rotation. Answers address(0) before the + registry is wired up, which no caller can match, so admin functions stay closed until + then rather than reverting on a call into address(0)._ + +### preCreateAccountValidation + +```solidity +function preCreateAccountValidation(string _deviceUniqueIdentifier, bytes32[2] _deviceWalletOwnerKey) public view returns (address wallet) +``` + +Checks that all the input params needed for deploying a fresh device wallet are valid + +_This is needed when deploying the device wallet via the EntryPoint using userops_ #### Parameters @@ -217,30 +368,43 @@ _Allow admin to deploy a device wallet (and an eSIM wallet) for given unique dev | ---- | ---- | ----------- | | _deviceUniqueIdentifier | string | Unique device identifier for the device wallet | | _deviceWalletOwnerKey | bytes32[2] | User's P256 public key (owner of the device wallet and respective eSIM wallets) | -| _salt | uint256 | | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | address | Deployed device wallet address | +| wallet | address | address(0) if valid. device wallet address for any existing wallet | -### createAccount +### getCounterFactualAddress ```solidity -function createAccount(address _registry, bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, uint256 _salt) public payable returns (contract DeviceWallet ret) +function getCounterFactualAddress(bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, uint256 _salt) public view returns (address) ``` -create an account, and return its address. -returns the address even if the account is already deployed. -Note that during UserOperation execution, this method is called only if the account is not deployed. -This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation +The address createAccount would deploy to for these inputs -### getCounterFactualAddress +_The owner key and the device identifier are part of the proxy's constructor arguments, + so they are folded into the address alongside the salt. Changing any of them moves it._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWalletOwnerKey | bytes32[2] | X,Y co-ordinates of the P256 key owning the wallet | +| _deviceUniqueIdentifier | string | Identifier the device is reached by | +| _salt | uint256 | CREATE2 salt | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The predicted device wallet address | + +### getCurrentDeviceWalletImplementation ```solidity -function getCounterFactualAddress(address _registry, bytes32[2] _deviceWalletOwnerKey, string _deviceUniqueIdentifier, uint256 _salt) public view returns (address) +function getCurrentDeviceWalletImplementation() public view returns (address) ``` -calculate the counterfactual address of this account as it would be returned by createAccount() +The device wallet logic contract every device wallet currently runs diff --git a/docs/esim-wallet/ESIMWallet.md b/docs/esim-wallet/ESIMWallet.md index abeb4567..f8319f2a 100644 --- a/docs/esim-wallet/ESIMWallet.md +++ b/docs/esim-wallet/ESIMWallet.md @@ -1,24 +1,65 @@ # Solidity API -## OnlyDeviceWallet +## ESIMWallet + +One eSIM, its purchase history and the ETH that pays for its data bundles + +_A beacon proxy deployed by `ESIMWalletFactory`, always owned by a device wallet. The owner + is a contract rather than a key, so every call that moves ETH or ownership arrives through + a device wallet `execute` and has already been signed for. The admin can charge this wallet + for a data bundle but cannot raise the ceiling that limits what it may charge._ + +### eSIMWalletFactory ```solidity -error OnlyDeviceWallet() +address eSIMWalletFactory ``` -## OnlyRegistry +Address of the eSIM wallet factory contract + +### eSIMUniqueIdentifier ```solidity -error OnlyRegistry() +string eSIMUniqueIdentifier ``` -## FailedToTransfer +String identifier to uniquely identify eSIM wallet + +### deviceWallet ```solidity -error FailedToTransfer() +contract DeviceWallet deviceWallet ``` -## ESIMWallet +Device wallet contract instance associated with this eSIM wallet + +### transactionHistory + +```solidity +struct DataBundleDetails[] transactionHistory +``` + +Array of all the data bundle purchase + +### newRequestedOwner + +```solidity +address newRequestedOwner +``` + +Address of the owner (device wallet) that becomes the new owner + +### dataBundlePriceCap + +```solidity +uint256 dataBundlePriceCap +``` + +Most this wallet may be charged for one data bundle, or zero to follow the registry + +_Appended, and this contract is a leaf, so the slot lands past everything a live proxy + already holds and reads zero there. Zero has to keep meaning "no limit of my own" for + that reason, which is why the fallback lives on the registry rather than here._ ### ESIMWalletDeployed @@ -47,10 +88,12 @@ Emitted when the eSIM unique identifier is initialised ### TransactionHistoryPopulated ```solidity -event TransactionHistoryPopulated(struct DataBundleDetails[] _dataBundleDetails) +event TransactionHistoryPopulated(struct DataBundleDetails[] _dataBundleDetails, uint256 _totalEntries) ``` -Emitted when the lazy wallet registry populates history after wallet deployment +Emitted for every batch of history the lazy wallet registry copies in after deployment. + `_totalEntries` is the transaction history length once the batch has landed, which is + what tells a partial copy apart from a finished one. ### ETHSent @@ -60,58 +103,56 @@ event ETHSent(address _recipient, uint256 _amount) Emitted when ETH moves out of this contract -### eSIMWalletFactory +### OwnershipTransferRequested ```solidity -address eSIMWalletFactory +event OwnershipTransferRequested(address _currentOwner, address _newOwner) ``` -Address of the eSIM wallet factory contract +Emitted when the current owner wants to transfer the ownership to a new device wallet -### eSIMUniqueIdentifier +### OwnershipTransferRevoked ```solidity -string eSIMUniqueIdentifier +event OwnershipTransferRevoked(address _currentOwner, address _revokedOwner) ``` -String identifier to uniquely identify eSIM wallet +Emitted when the current owner revoked the ownership transfer request -### deviceWallet +### DataBundlePriceCapUpdated ```solidity -contract DeviceWallet deviceWallet +event DataBundlePriceCapUpdated(uint256 _cap) ``` -Device wallet contract instance associated with this eSIM wallet +Emitted when the owner sets this wallet's own price ceiling -### transactionHistory +### onlyDeviceWallet ```solidity -struct DataBundleDetails[] transactionHistory +modifier onlyDeviceWallet() ``` -Array of all the data bundle purchase +Restricts a call to the device wallet that owns this eSIM wallet -### _isTransferApproved +_Reaching this means the owner signed for it, since a device wallet only calls out + through `execute`._ + +### onlyRegistry ```solidity -mapping(address => mapping(address => bool)) _isTransferApproved +modifier onlyRegistry() ``` -_A map from owner and spender to transfer approval. Determines whether - the spender can transfer this wallet from the owner._ +Restricts a call to the registry -### onlyDeviceWallet +### onlyDeviceWalletOrESIMWalletAdmin ```solidity -modifier onlyDeviceWallet() +modifier onlyDeviceWalletOrESIMWalletAdmin() ``` -### onlyRegistry - -```solidity -modifier onlyRegistry() -``` +Restricts a call to the owning device wallet or the eSIM wallet admin ### constructor @@ -119,16 +160,20 @@ modifier onlyRegistry() constructor() public ``` +_`_disableInitializers` rather than an `initializer` modifier. The modifier leaves the + version at 1, which a later `reinitializer(2)` would still accept on the implementation + itself. This pins it at the maximum so no version can ever run there._ + ### initialize ```solidity function initialize(address _eSIMWalletFactoryAddress, address _deviceWalletAddress) external ``` -ESIMWallet initialize function to initialise the contract +Binds a freshly deployed eSIM wallet to its factory and its owning device wallet -_If _eSIMUniqueIdentifier is empty, the eSIM wallet is being deployed before buying an eSIM - If _eSIMUniqueIdentifier is non-empty, the eSIM wallet is being deployed after the eSIM has been bought by the user_ +_The eSIM identifier is not set here. It does not exist until the eSIM itself has been + bought, so it arrives later through `setESIMUniqueIdentifier`._ #### Parameters @@ -154,116 +199,190 @@ _This function can only be called once_ | ---- | ---- | ----------- | | _eSIMUniqueIdentifier | string | String that uniquely identifies eSIM wallet | -### buyDataBundle +### setDataBundlePriceCap ```solidity -function buyDataBundle(struct DataBundleDetails _dataBundleDetail) public payable returns (bool) +function setDataBundlePriceCap(uint256 _cap) external ``` -Function to make payment for the data bundle +Sets the most this wallet may be charged for one data bundle + +_Only the owning device wallet, which means the person holding its P256 key: reaching + this needs a device wallet `execute`, and that needs a signature. The admin names the + price on `buyDataBundle`, so it must not also be able to raise the ceiling on that + price. Setting zero hands the wallet back to the registry's ceiling. A handover clears + it, so an incoming owner starts on the registry ceiling._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _dataBundleDetail | struct DataBundleDetails | Details of the data bundle being bought. (dataBundleID, dataBundlePrice) | +| _cap | uint256 | Maximum price in wei, or zero to follow the registry | + +### populateHistory + +```solidity +function populateHistory(struct DataBundleDetails[] _dataBundleDetails) external returns (bool) +``` + +Appends pre-deployment purchase history, one batch at a time, on behalf of the lazy + wallet registry + +_The registry carries the cursor that says how much of an eSIM's history has already been + copied, so this function appends whatever it is handed and does not police repeats._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _dataBundleDetails | struct DataBundleDetails[] | One batch of data bundle purchase details from before the wallet was deployed | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | bool | True if the transaction is successful | +| [0] | bool | True once the batch has been appended | -### populateHistory +### requestTransferOwnership ```solidity -function populateHistory(struct DataBundleDetails[] _dataBundleDetails) external returns (bool) +function requestTransferOwnership(address _newOwner) external ``` -Function to populate history for lazy wallets. Can only be called once, by lazy wallet registry +Nominates a new device wallet to take this eSIM wallet over, in two steps + +_Any outstanding request is overwritten rather than refused, so an owner who nominated + the wrong address just calls this again. Nominating the current owner cancels the + request and re-binds the wallet to its device wallet in the same call, with ETH access + left off since the flag it had before the removal is not recorded anywhere._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _dataBundleDetails | struct DataBundleDetails[] | Array of all the data bundle purchase details before the wallet was deployed | +| _newOwner | address | Address of the new device wallet to transfer ownership of this wallet | -### owner +### acceptOwnershipTransfer ```solidity -function owner() public view returns (address) +function acceptOwnershipTransfer() external ``` -_Returns the current owner of the wallet_ +Takes this eSIM wallet on, callable only by the nominated device wallet -### transferOwnership +_The check compares the caller to `newRequestedOwner`, which both sides satisfy when + they are zero. No transaction can arrive from the zero address, so this holds onchain, + but any reasoning about this function has to exclude that caller explicitly._ + +### sendETHToDeviceWallet ```solidity -function transferOwnership(address newOwner) public +function sendETHToDeviceWallet(uint256 _amount) external returns (uint256) ``` -_Transfers ownership from the current owner to another address_ +Allow the owner device wallet to callback all the ETH from this eSIM wallet + +_This function is generally called before the owner device wallet removes this eSIM wallet +Deliberately not nonReentrant. removeESIMWallet calls this from inside a try/catch while + requestTransferOwnership already holds this contract's guard, so guarding here would + make the callback revert into that catch and strand the wallet's ETH with no error. + It writes no state of its own, and only the owner can call it to move ETH to itself, + so re-entering it gains nothing._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| newOwner | address | The address that will be the new owner | +| _amount | uint256 | Amount of ETH to be sent | -### isTransferApproved +### buyDataBundle ```solidity -function isTransferApproved(address from, address to) public view returns (bool) +function buyDataBundle(struct DataBundleDetails _dataBundleDetail) public payable returns (bool) ``` -The owner can always transfer the wallet to someone, i.e., - approval from an address to itself is always 'true' +Pays the vault for one data bundle and records the purchase -_Returns whether the address 'to' can transfer a wallet from address 'from'_ +_Callable by the owning device wallet or by the admin, since the admin is the party that + knows the price. Any shortfall is pulled from the device wallet, which is why the price + is checked against a ceiling the admin cannot raise._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| from | address | The owner address | -| to | address | The spender address | +| _dataBundleDetail | struct DataBundleDetails | Details of the data bundle being bought. (dataBundleID, dataBundlePrice) | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bool | True if the transaction is successful | -### setApproval +### transferOwnership ```solidity -function setApproval(address to, bool status) external +function transferOwnership(address) public pure ``` -_Changes authorization status for transfer approval from msg.sender to an address_ +The inherited one-step transfer is closed -#### Parameters +_Ownership moves through `requestTransferOwnership` and `acceptOwnershipTransfer`, which + also keep `deviceWallet` in step with `owner()`. A one-step transfer would move only + the latter._ -| Name | Type | Description | -| ---- | ---- | ----------- | -| to | address | Address to change allowance status for | -| status | bool | The new approval status | +### renounceOwnership + +```solidity +function renounceOwnership() public pure +``` + +An eSIM wallet always belongs to a device wallet, so ownership is never renounced + +_Renouncing leaves owner() at zero while deviceWallet still points at the old device + wallet. sendETHToDeviceWallet then reverts on its own zero-owner check and + DeviceWallet._addESIMWallet can never accept this wallet again, so the ETH held here + is unreachable for the rest of the wallet's life._ + +### _secureTransferOwnership -### _setApproval +```solidity +function _secureTransferOwnership() internal +``` + +Completes a handover, moving `deviceWallet`, `owner()` and the price ceiling together + +_Clears the request before it writes anything, so a second acceptance finds nothing. + The ceiling is the owner's own limit and only the owner can set it, so it goes with the + owner rather than binding the incoming one to a figure it never chose._ + +### _transferETH ```solidity -function _setApproval(address from, address to, bool status) internal +function _transferETH(address _recipient, uint256 _amount) internal virtual ``` +Sends ETH out of this contract, reverting if the call fails + +_A zero amount is a no-op rather than a revert, so callers that may have nothing to send + do not need their own guard._ + #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| from | address | The owner address | -| to | address | The spender address | -| status | bool | Status of approval | +| _recipient | address | Address receiving the ETH | +| _amount | uint256 | Amount in wei | -### _transferETH +### owner ```solidity -function _transferETH(address _recipient, uint256 _amount) internal virtual +function owner() public view returns (address) ``` -_Internal function to send ETH from this contract_ +The device wallet that owns this eSIM wallet + +_Declared so subclasses and mocks have one place to override._ ### receive @@ -271,3 +390,5 @@ _Internal function to send ETH from this contract_ receive() external payable ``` +Accepts plain ETH transfers, which is how the device wallet tops this wallet up + diff --git a/docs/esim-wallet/ESIMWalletFactory.md b/docs/esim-wallet/ESIMWalletFactory.md index af1fc1cb..381c79fa 100644 --- a/docs/esim-wallet/ESIMWalletFactory.md +++ b/docs/esim-wallet/ESIMWalletFactory.md @@ -1,62 +1,74 @@ # Solidity API -## OnlyRegistryOrDeviceWalletFactoryOrDeviceWallet +## ESIMWalletFactory + +Deploys eSIM wallets and owns the beacon they all point at + +_A UUPS singleton. It owns an `UpgradeableBeacon`, so one call here moves every eSIM wallet + in the protocol onto new logic at once. There is no per-wallet opt-out._ + +### registry ```solidity -error OnlyRegistryOrDeviceWalletFactoryOrDeviceWallet() +contract Registry registry ``` -## ESIMWalletFactory - -Contract for deploying a new eSIM wallet +Address of the registry contract -### ESIMWalletFactorydeployed +### beacon ```solidity -event ESIMWalletFactorydeployed(address _upgradeManager, address _eSIMWalletImplementation, address beacon) +contract UpgradeableBeacon beacon ``` -Emitted when the eSIM wallet factory is deployed +Upgradeable beacon that points to the correct eSIM wallet logic contract -### ESIMWalletDeployed +_Every eSIM wallet is a beacon proxy reading its implementation from here, so the + implementation is replaced once rather than on each proxy: + + eSIM wallet beacon proxy ─┐ + eSIM wallet beacon proxy ─┼─> beacon ─> eSIM wallet implementation + eSIM wallet beacon proxy ─┘_ + +### isESIMWalletDeployed ```solidity -event ESIMWalletDeployed(address _eSIMWalletAddress, address _deviceWalletAddress, address _caller) +mapping(address => bool) isESIMWalletDeployed ``` -Emitted when a new eSIM wallet is deployed +Set to true if eSIM wallet address is deployed using the factory, false otherwise -### registry +### ESIMWalletFactorydeployed ```solidity -contract Registry registry +event ESIMWalletFactorydeployed(address _upgradeManager, address _eSIMWalletImplementation, address beacon) ``` -Address of the registry contract +Emitted when the eSIM wallet factory is deployed -### eSIMWalletImplementation +### ESIMWalletDeployed ```solidity -address eSIMWalletImplementation +event ESIMWalletDeployed(address _eSIMWalletAddress, address _deviceWalletAddress, address _caller) ``` -Implementation at the time of deployment +Emitted when a new eSIM wallet is deployed -### beacon +### ESIMWalletImplementationUpdated ```solidity -address beacon +event ESIMWalletImplementationUpdated(address _newImplementation) ``` -Beacon referenced by each deployment of a savETH vault +Emitted when the eSIM wallet implementation is updated -### isESIMWalletDeployed +### AddedRegistry ```solidity -mapping(address => bool) isESIMWalletDeployed +event AddedRegistry(address registry) ``` -Set to true if eSIM wallet address is deployed using the factory, false otherwise +Emitted when the registry is added to the factory contract ### onlyRegistryOrDeviceWalletFactoryOrDeviceWallet @@ -64,32 +76,63 @@ Set to true if eSIM wallet address is deployed using the factory, false otherwis modifier onlyRegistryOrDeviceWalletFactoryOrDeviceWallet() ``` +Restricts a call to the registry, the device wallet factory or a known device wallet + +_The first two deploy on behalf of a device wallet during setup. A device wallet reaching + this directly is constrained further inside `deployESIMWallet`._ + ### constructor ```solidity constructor() public ``` -### _authorizeUpgrade +_Locks the implementation contract itself. Without this, anyone can call initialize + directly on the implementation, own it, and make it deploy a beacon it controls. The + proxy is unaffected either way, but an owned implementation is a trap for any later + upgrade that adds an outward call._ + +### initialize ```solidity -function _authorizeUpgrade(address newImplementation) internal +function initialize(address _eSIMWalletImplementation, address _upgradeManager) external ``` -_Owner based upgrades_ +Deploys the beacon and hands ownership of this factory to the upgrade manager -### initialize +_The factory owns the beacon rather than the upgrade manager owning it directly, so the + only way to move the implementation is `updateESIMWalletImplementation`, which is + owner gated and emits an event._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWalletImplementation | address | First eSIM wallet logic contract the beacon points at | +| _upgradeManager | address | Admin address responsible for upgrading contracts | + +### addRegistryAddress ```solidity -function initialize(address _registryContractAddress, address _upgradeManager) external +function addRegistryAddress(address _registryContractAddress) external returns (address) ``` +Points the factory at the registry, which is deployed after it + +_Write-once. Every caller check in this contract reads the registry, so allowing it to + move would let a later owner redirect all of them at once._ + #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | -| _registryContractAddress | address | Address of the registry contract | -| _upgradeManager | address | Admin address responsible for upgrading contracts | +| _registryContractAddress | address | Address of the registry | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The registry address now in force | ### deployESIMWallet @@ -97,16 +140,14 @@ function initialize(address _registryContractAddress, address _upgradeManager) e function deployESIMWallet(address _deviceWalletAddress, uint256 _salt) external returns (address) ``` -Function to deploy an eSIM wallet - -_can only be called by the respective deviceWallet contract_ +Deploys an eSIM wallet at a deterministic address and binds it to a device wallet #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _deviceWalletAddress | address | Address of the associated device wallet | -| _salt | uint256 | | +| _salt | uint256 | CREATE2 salt, chosen by the caller and unique per wallet | #### Return Values @@ -114,3 +155,83 @@ _can only be called by the respective deviceWallet contract_ | ---- | ---- | ----------- | | [0] | address | Address of the newly deployed eSIM wallet | +### getCounterFactualAddress + +```solidity +function getCounterFactualAddress(address _deviceWalletAddress, uint256 _salt) public view returns (address) +``` + +The address deployESIMWallet would land on for these inputs + +_Lets a caller probe a salt for occupancy before spending a deployment on it._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _deviceWalletAddress | address | Device wallet the eSIM wallet would be bound to | +| _salt | uint256 | CREATE2 salt | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The predicted eSIM wallet address | + +### updateESIMWalletImplementation + +```solidity +function updateESIMWalletImplementation(address _eSIMWalletImpl) external returns (address) +``` + +Update the eSIM wallet implementation address in the beacon contract + +_Moves every eSIM wallet in the protocol at once. Treat any change here as a + protocol-wide upgrade, since no wallet can decline it._ + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _eSIMWalletImpl | address | Address of the new eSIM wallet implementation contract | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The implementation now in force | + +### renounceOwnership + +```solidity +function renounceOwnership() public pure +``` + +Ownership of this contract is never renounced + +_The owner is the only caller _authorizeUpgrade accepts, and this contract owns the + beacon, so it is also the only route to updateESIMWalletImplementation. Renouncing + would freeze every eSIM wallet on its current logic permanently._ + +### _authorizeUpgrade + +```solidity +function _authorizeUpgrade(address newImplementation) internal +``` + +Restricts UUPS upgrades of this factory to the owner + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| newImplementation | address | Address of the implementation being moved to | + +### getCurrentESIMWalletImplementation + +```solidity +function getCurrentESIMWalletImplementation() public view returns (address) +``` + +The eSIM wallet logic contract every eSIM wallet currently runs + diff --git a/docs/interfaces/IOwnable2Step.md b/docs/interfaces/IOwnable2Step.md new file mode 100644 index 00000000..b06150e2 --- /dev/null +++ b/docs/interfaces/IOwnable2Step.md @@ -0,0 +1,32 @@ +# Solidity API + +## IOwnable2Step + +Minimal view of the two-step ownership handover the protocol contracts use + +_Matches the part of OpenZeppelin's `Ownable2Step` an incoming owner needs. The offer is made + by the current owner and completed by the nominee, so a contract taking ownership only ever + calls these two._ + +### acceptOwnership + +```solidity +function acceptOwnership() external +``` + +Completes a handover the current owner already offered to the caller + +### pendingOwner + +```solidity +function pendingOwner() external view returns (address) +``` + +Address the current owner has offered ownership to, or zero + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | address | The nominated address | + diff --git a/docs/interfaces/IPausable.md b/docs/interfaces/IPausable.md new file mode 100644 index 00000000..941409e0 --- /dev/null +++ b/docs/interfaces/IPausable.md @@ -0,0 +1,17 @@ +# Solidity API + +## IPausable + +Minimal view of the pause a guardian is allowed to release + +_Only `unpause()` is here. Raising a pause is the hot admin key's lever and releasing one is + the timelock's, so the two are deliberately not offered through the same interface._ + +### unpause + +```solidity +function unpause() external +``` + +Clears the pause + diff --git a/docs/interfaces/IRegistryAdmin.md b/docs/interfaces/IRegistryAdmin.md new file mode 100644 index 00000000..6c67031f --- /dev/null +++ b/docs/interfaces/IRegistryAdmin.md @@ -0,0 +1,34 @@ +# Solidity API + +## IRegistryAdmin + +Minimal view of the admin role the protocol owner controls + +_Holds only the two calls `ProtocolAdmin` makes. `enableAdmin` is deliberately absent: the + owner reaches it as an ordinary scheduled payload, and putting it here would invite a + named function beside the guardian's, which is the one place a fast path could be added by + accident. Suspending is instant and restoring waits, and the split is what stops a + compromised key from undoing its own suspension._ + +### disableAdmin + +```solidity +function disableAdmin() external +``` + +Suspends the admin's powers protocol-wide, leaving its address on the books + +### requestAdminUpdate + +```solidity +function requestAdminUpdate(address _newAdmin) external +``` + +Nominates a new admin, which strips the incumbent until the nominee accepts + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| _newAdmin | address | Address of the recipient to receive the admin role | + diff --git a/env.sample b/env.sample index 6be864dc..353f42e6 100644 --- a/env.sample +++ b/env.sample @@ -1,18 +1,75 @@ -UPGRADE_MANAGER= -PRIVATE_KEY_1= -VAULT= -ESIM_WALLET_ADMIN= -PRIVATE_KEY_3= -ENTRY_POINT_ZERO_POINT_SEVEN_ADDRESS="0x0000000071727De22E5E9d8BAf0edAc6f37da032" -TENDERLY_ACCESS_TOKEN= -TENDERLY_VIRTUAL_TESTNET_RPC_URL= -TENDERLY_VERIFIER_URL=$TENDERLY_VIRTUAL_TESTNET_RPC_URL/verify/etherscan -ALCHEMY_OP_SEPOLIA_HTTPS= -ALCHEMY_SEPOLIA_HTTPS= -TENDERLY_VIRTUAL_MAINNET= -ETHERSCAN_API_TENDERLY_VIRTUAL_MAINNET= -ALCHEMY_API_KEY= -ALCHEMY_TENDERLY_OP_SEPOLIA_HTTPS= -DEFENDER_API_KEY= -DEFENDER_TEAM_SECRET_KEY= -TENDERLY_KOKIO_MAINNET_FORK= +# Copy to .env and fill in. Every variable below is read by something in this repo; nothing here +# is decorative. Grouped by what needs it, so a run that only builds and tests needs almost none. + +# --------------------------------------------------------------------------------------------- +# Deployment: scripts/deploy/Deploy.s.sol, Configure.s.sol, TransferOwnership.s.sol +# --------------------------------------------------------------------------------------------- + +# Deploys every contract and owns all four singletons until TransferOwnership.s.sol runs. +DEPLOYER_PRIVATE_KEY= + +# Hot key that registers device wallets and can pause the registry. Not an owner. +ESIM_WALLET_ADMIN= + +# Receives data bundle payments. +VAULT= + +# Comma separated, no spaces. These schedule timelock operations and the base TimelockController +# constructor also gives each of them the cancel power. +TIMELOCK_PROPOSERS= + +# Comma separated, no spaces. Separate 2-of-3 Safe. Can unpause the registry without waiting and +# can strip the cancel power from a compromised canceller. Nothing else. +# Must share no address with TIMELOCK_PROPOSERS or TIMELOCK_CANCELLERS. The deploy checks this +# before broadcasting and ProtocolAdmin's constructor rejects it again. +TIMELOCK_GUARDIANS= + +# --------------------------------------------------------------------------------------------- +# Deployment, optional +# --------------------------------------------------------------------------------------------- + +# Comma separated, no spaces. Accounts that cancel and do nothing else. May be left empty, since +# every proposer can already cancel. +TIMELOCK_CANCELLERS= + +# Ceiling on a single data bundle purchase, in wei. Unset or 0 means no ceiling, which is what the +# protocol does when the registry default is unset. Set it before wallets carry real balances. +DATA_BUNDLE_PRICE_CAP= + +# --------------------------------------------------------------------------------------------- +# Upgrades: scripts/upgrade/UpgradeSingleton.s.sol, UpgradeBeacon.s.sol +# --------------------------------------------------------------------------------------------- + +# Holds PROPOSER_ROLE on ProtocolAdmin. Deploys the new implementation and schedules the upgrade. +PROPOSER_PRIVATE_KEY= + +# Executes once the delay has elapsed. EXECUTOR_ROLE is granted to address(0), so this can be any +# funded key, including the proposer's. +EXECUTOR_PRIVATE_KEY= + +# UPGRADE_TARGET and UPGRADE_ACTION are set on the command line rather than here, because they +# change every run: +# UPGRADE_TARGET=RegistryProxy UPGRADE_ACTION=schedule forge script ... + +# --------------------------------------------------------------------------------------------- +# Fork tests, optional +# --------------------------------------------------------------------------------------------- + +# test/foundry/fork/EntryPointValidation.t.sol forks these to run against the deployed EntryPoint. +# Unset means those tests skip rather than fail, so the suite stays runnable without them. +ALCHEMY_OP_SEPOLIA_HTTPS= +ALCHEMY_BASE_SEPOLIA_HTTPS= + +# --------------------------------------------------------------------------------------------- +# Contract verification, optional +# --------------------------------------------------------------------------------------------- + +# For forge verify-contract. Etherscan v2 uses one key across chains. +ETHERSCAN_API_KEY= + +# --------------------------------------------------------------------------------------------- +# Formal verification, optional +# --------------------------------------------------------------------------------------------- + +# Read by certoraRun. Only needed to submit a prover job. +CERTORAKEY= diff --git a/foundry.toml b/foundry.toml index 53eb4fe2..90998618 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,12 +1,20 @@ [profile.default] src = 'contracts' out = 'out' -solc = "0.8.25" -evm_version = "paris" -fs_permissions = [{ access = "read", path = "out" }] +solc = "0.8.36" +evm_version = "osaka" +fs_permissions = [ + { access = "read", path = "out" }, + # The deployment scripts read the record to find what they are acting on, and write it back. + # Scoped to the one directory: a script that can write anywhere can rewrite the tests that + # would have caught it. + { access = "read-write", path = "deployments" }, +] libs = ['node_modules', 'lib', 'test/utils'] test = 'test/foundry' +script = 'scripts' cache_path = 'forge-cache' +via_ir = true optimizer = true optimizer-runs = 10_000_000 ffi = true @@ -14,4 +22,30 @@ ast = true build_info = true extra_output = ["storageLayout"] +# The metadata hash covers source unit names, which differ between forge's remappings and +# hardhat's import paths. Dropping it makes both toolchains emit byte-identical bytecode. +# The CBOR trailer is kept, so the compiler version is still recorded onchain. +bytecode_hash = "none" + +# Every campaign runs at these settings. fail_on_revert is false because the protocol is covered in +# access control modifiers, so a handler call from the wrong sender reverting is the expected case +# rather than a failure. HandlerDistribution.t.sol is what stops that hiding a starved entry point. +# +# These have to be the only place the settings are written. An inline +# `/// forge-config: default.invariant.runs = N` comment wins over the active profile whatever that +# profile is named, so one left on a test would pin it here and the campaign profile below would +# silently not reach it. Measured: with the comments present, a profile asking for 3 runs still ran +# 256. +[profile.default.invariant] +runs = 256 +depth = 500 +fail_on_revert = false + +# The long campaign, run before an audit or a release rather than on every change. +# FOUNDRY_PROFILE=campaign forge test --via-ir --match-path "test/foundry/invariant-testing/*" +[profile.campaign.invariant] +runs = 5000 +depth = 100 +fail_on_revert = false + # See more config options https://book.getfoundry.sh/reference/config.html diff --git a/hardhat.config.js b/hardhat.config.js index 3141cb49..ebb259ea 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -93,13 +93,17 @@ module.exports = { }, }, solidity: { - version: "0.8.25", + version: "0.8.36", settings: { optimizer: { enabled: true, - runs: 200 + runs: 10_000_000 }, viaIR: true, + evmVersion: "osaka", + metadata: { + bytecodeHash: "none" + }, } }, paths: { diff --git a/lib/account-abstraction b/lib/account-abstraction index 7af70c89..4cbc0607 160000 --- a/lib/account-abstraction +++ b/lib/account-abstraction @@ -1 +1 @@ -Subproject commit 7af70c8993a6f42973f520ae0752386a5032abe7 +Subproject commit 4cbc06072cdc19fd60f285c5997f4f7f57a588de diff --git a/lib/forge-std b/lib/forge-std index 978ac6fa..bf647bd6 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 +Subproject commit bf647bd6046f2f7da30d0c2bf435e5c76a780c1b diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 723f8cab..e725abdd 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 +Subproject commit e725abddf1e01cf05ace496e950fc8e243cc7cab diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades index 16e0ae21..258e12e7 160000 --- a/lib/openzeppelin-foundry-upgrades +++ b/lib/openzeppelin-foundry-upgrades @@ -1 +1 @@ -Subproject commit 16e0ae21e0e39049f619f2396fa28c57fad07368 +Subproject commit 258e12e727bfe7f0ec30c51995d01ec88b82efc1 diff --git a/lib/solady b/lib/solady index e7024bee..acd959aa 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit e7024bee47b1623f436ee491ca9458a6dc8abce9 +Subproject commit acd959aa4bd04720d640bf4e6a5c71037510cc4b diff --git a/package-lock.json b/package-lock.json index d7d645da..b185afa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,996 +6,417 @@ "": { "name": "smart-contract-suite", "dependencies": { - "dotenv": "^16.4.5", - "lcov-filter": "^0.1.1", - "userop": "^0.4.0-beta.5" + "dotenv": "17.4.2", + "ethers": "6.17.0" }, "devDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.8", - "@nomicfoundation/hardhat-foundry": "^1.1.3", - "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "@openzeppelin/hardhat-upgrades": "^3.9.0", - "ethers": "^6.13.5", - "hardhat": "^2.22.19", - "solidity-docgen": "^0.6.0-beta.36" + "@nomicfoundation/hardhat-ethers": "3.1.3", + "@nomicfoundation/hardhat-foundry": "1.2.1", + "@openzeppelin/hardhat-upgrades": "3.9.1", + "hardhat": "2.29.0", + "solidity-docgen": "0.6.0-beta.36" } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", - "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "license": "MIT" }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "node_modules/@aws-crypto/sha256-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz", + "integrity": "sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "@aws-crypto/util": "^1.2.2", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, "license": "0BSD" }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "node_modules/@aws-crypto/util": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz", + "integrity": "sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } + "license": "0BSD" }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@aws-sdk/client-lambda": { + "version": "3.1107.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1107.0.tgz", + "integrity": "sha512-k3xBmiBJYtpY1RKJms3BnTVg6nvFGEhbzINLkSfxAGkfS+Ehz3bOl6BcPWjKQP9Jsyi3IHMNY3Opjp1sf6J3EA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/credential-provider-node": "^3.972.78", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@aws-sdk/core": { + "version": "3.977.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.6.tgz", + "integrity": "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@aws-sdk/types": "^3.974.2", + "@aws-sdk/xml-builder": "^3.972.37", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.31.1", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.67.tgz", + "integrity": "sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.69", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.69.tgz", + "integrity": "sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.973.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.12.tgz", + "integrity": "sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/credential-provider-env": "^3.972.67", + "@aws-sdk/credential-provider-http": "^3.972.69", + "@aws-sdk/credential-provider-login": "^3.972.74", + "@aws-sdk/credential-provider-process": "^3.972.67", + "@aws-sdk/credential-provider-sso": "^3.973.11", + "@aws-sdk/credential-provider-web-identity": "^3.972.73", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.74.tgz", + "integrity": "sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.78.tgz", + "integrity": "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", + "@aws-sdk/credential-provider-env": "^3.972.67", + "@aws-sdk/credential-provider-http": "^3.972.69", + "@aws-sdk/credential-provider-ini": "^3.973.12", + "@aws-sdk/credential-provider-process": "^3.972.67", + "@aws-sdk/credential-provider-sso": "^3.973.11", + "@aws-sdk/credential-provider-web-identity": "^3.972.73", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/credential-provider-imds": "^4.4.16", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-lambda": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.758.0.tgz", - "integrity": "sha512-k7L9fe0NN1v2Vhg4ofA1pb26gTdGVFdkA6XUQyElLEdcKzJzoYiQ60faNLuMPfH0zsKNvy/xKfNOD6DFZWjgEg==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.67.tgz", + "integrity": "sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/credential-provider-node": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/eventstream-serde-browser": "^4.0.1", - "@smithy/eventstream-serde-config-resolver": "^4.0.1", - "@smithy/eventstream-serde-node": "^4.0.1", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", - "@smithy/util-waiter": "^4.0.2", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz", - "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.973.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.11.tgz", + "integrity": "sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/token-providers": "3.1103.0", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/core": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", - "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.73", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.73.tgz", + "integrity": "sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz", - "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", + "node_modules/@aws-sdk/nested-clients": { + "version": "3.997.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.41.tgz", + "integrity": "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/signature-v4-multi-region": "^3.996.43", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/fetch-http-handler": "^5.6.13", + "@smithy/node-http-handler": "^4.9.13", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz", - "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.43", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.43.tgz", + "integrity": "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@aws-sdk/types": "^3.974.2", + "@smithy/signature-v4": "^5.6.12", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.758.0.tgz", - "integrity": "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==", + "node_modules/@aws-sdk/token-providers": { + "version": "3.1103.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1103.0.tgz", + "integrity": "sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/credential-provider-env": "3.758.0", - "@aws-sdk/credential-provider-http": "3.758.0", - "@aws-sdk/credential-provider-process": "3.758.0", - "@aws-sdk/credential-provider-sso": "3.758.0", - "@aws-sdk/credential-provider-web-identity": "3.758.0", - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "^3.977.6", + "@aws-sdk/nested-clients": "^3.997.41", + "@aws-sdk/types": "^3.974.2", + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz", - "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", + "node_modules/@aws-sdk/types": { + "version": "3.974.2", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz", + "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.758.0", - "@aws-sdk/credential-provider-http": "3.758.0", - "@aws-sdk/credential-provider-ini": "3.758.0", - "@aws-sdk/credential-provider-process": "3.758.0", - "@aws-sdk/credential-provider-sso": "3.758.0", - "@aws-sdk/credential-provider-web-identity": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz", - "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "tslib": "^2.3.1" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz", - "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.37.tgz", + "integrity": "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.758.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/token-providers": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.758.0.tgz", - "integrity": "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==", + "node_modules/@aws/lambda-invoke-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.0.tgz", + "integrity": "sha512-JorcEwe4ud0x5BS/Ar2aQWOQoFzjq/7jcnxYXCvSMh0oRm0dQXzOA+hqLDBnOMks1LLBA7dmiLLsEBl09Yd6iQ==", "dev": true, - "license": "0BSD" + "license": "(Apache-2.0 WITH LLVM-exception)" }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", - "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", + "node_modules/@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp.cjs" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.734.0.tgz", - "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", + "node_modules/@ethereumjs/util": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-9.1.0.tgz", + "integrity": "sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==", "dev": true, - "license": "Apache-2.0", + "license": "MPL-2.0", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@ethereumjs/rlp": "^5.0.2", + "ethereum-cryptography": "^2.2.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.734.0.tgz", - "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", + "node_modules/@ethereumjs/util/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@noble/hashes": "1.4.0" }, - "engines": { - "node": ">=18.0.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.758.0.tgz", - "integrity": "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==", + "node_modules/@ethereumjs/util/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.5", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", - "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", - "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", - "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/token-providers/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/types": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.734.0.tgz", - "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.743.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", - "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "@smithy/util-endpoints": "^3.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", - "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", - "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.758.0.tgz", - "integrity": "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@bytecodealliance/preview2-shim": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.17.0.tgz", - "integrity": "sha512-JorcEwe4ud0x5BS/Ar2aQWOQoFzjq/7jcnxYXCvSMh0oRm0dQXzOA+hqLDBnOMks1LLBA7dmiLLsEBl09Yd6iQ==", - "dev": true, - "license": "(Apache-2.0 WITH LLVM-exception)" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@ethereumjs/rlp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", - "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", - "dev": true, - "license": "MPL-2.0", - "peer": true, - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", - "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", - "dev": true, - "license": "MPL-2.0", - "peer": true, - "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util/node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@ethereumjs/util/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@ethereumjs/util/node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@ethereumjs/util/node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { @@ -1004,7 +425,6 @@ "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", @@ -1134,28 +554,6 @@ "@ethersproject/bytes": "^5.8.0" } }, - "node_modules/@ethersproject/basex": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz", - "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/properties": "^5.8.0" - } - }, "node_modules/@ethersproject/bignumber": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", @@ -1218,37 +616,7 @@ "@ethersproject/bignumber": "^5.8.0" } }, - "node_modules/@ethersproject/contracts": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz", - "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.8.0", - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/transactions": "^5.8.0" - } - }, - "node_modules/@ethersproject/hash": { + "node_modules/@ethersproject/hash": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", @@ -1276,79 +644,6 @@ "@ethersproject/strings": "^5.8.0" } }, - "node_modules/@ethersproject/hdnode": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz", - "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/basex": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/pbkdf2": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/wordlists": "^5.8.0" - } - }, - "node_modules/@ethersproject/json-wallets": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz", - "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hdnode": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/pbkdf2": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "node_modules/@ethersproject/json-wallets/node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@ethersproject/keccak256": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", @@ -1407,28 +702,6 @@ "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/pbkdf2": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz", - "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/sha2": "^5.8.0" - } - }, "node_modules/@ethersproject/properties": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", @@ -1449,91 +722,6 @@ "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/providers": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz", - "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/base64": "^5.8.0", - "@ethersproject/basex": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/networks": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/rlp": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/strings": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/web": "^5.8.0", - "bech32": "1.1.4", - "ws": "8.18.0" - } - }, - "node_modules/@ethersproject/providers/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@ethersproject/random": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz", - "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, "node_modules/@ethersproject/rlp": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", @@ -1555,29 +743,6 @@ "@ethersproject/logger": "^5.8.0" } }, - "node_modules/@ethersproject/sha2": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", - "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "hash.js": "1.1.7" - } - }, "node_modules/@ethersproject/signing-key": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", @@ -1603,32 +768,6 @@ "hash.js": "1.1.7" } }, - "node_modules/@ethersproject/solidity": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz", - "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/sha2": "^5.8.0", - "@ethersproject/strings": "^5.8.0" - } - }, "node_modules/@ethersproject/strings": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", @@ -1679,64 +818,6 @@ "@ethersproject/signing-key": "^5.8.0" } }, - "node_modules/@ethersproject/units": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz", - "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/constants": "^5.8.0", - "@ethersproject/logger": "^5.8.0" - } - }, - "node_modules/@ethersproject/wallet": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz", - "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abstract-provider": "^5.8.0", - "@ethersproject/abstract-signer": "^5.8.0", - "@ethersproject/address": "^5.8.0", - "@ethersproject/bignumber": "^5.8.0", - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/hdnode": "^5.8.0", - "@ethersproject/json-wallets": "^5.8.0", - "@ethersproject/keccak256": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/random": "^5.8.0", - "@ethersproject/signing-key": "^5.8.0", - "@ethersproject/transactions": "^5.8.0", - "@ethersproject/wordlists": "^5.8.0" - } - }, "node_modules/@ethersproject/web": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", @@ -1761,125 +842,142 @@ "@ethersproject/strings": "^5.8.0" } }, - "node_modules/@ethersproject/wordlists": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz", - "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==", + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.8.0", - "@ethersproject/hash": "^5.8.0", - "@ethersproject/logger": "^5.8.0", - "@ethersproject/properties": "^5.8.0", - "@ethersproject/strings": "^5.8.0" + "engines": { + "node": ">=14" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@noble/curves": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "1.7.2" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "^14.21.3 || >=16" + "node": ">=12" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" + "dependencies": { + "@noble/hashes": "1.3.2" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@noble/secp256k1": { "version": "1.7.1", @@ -1891,6125 +989,2233 @@ "type": "individual", "url": "https://paulmillr.com/funding/" } - ] - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + ], + "license": "MIT" }, "node_modules/@nomicfoundation/edr": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.8.0.tgz", - "integrity": "sha512-dwWRrghSVBQDpt0wP+6RXD8BMz2i/9TI34TcmZqeEAZuCLei3U9KZRgGTKVAM1rMRvrpf5ROfPqrWNetKVUTag==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0-next.23.tgz", + "integrity": "sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==", "dev": true, "license": "MIT", "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.8.0", - "@nomicfoundation/edr-darwin-x64": "0.8.0", - "@nomicfoundation/edr-linux-arm64-gnu": "0.8.0", - "@nomicfoundation/edr-linux-arm64-musl": "0.8.0", - "@nomicfoundation/edr-linux-x64-gnu": "0.8.0", - "@nomicfoundation/edr-linux-x64-musl": "0.8.0", - "@nomicfoundation/edr-win32-x64-msvc": "0.8.0" + "@nomicfoundation/edr-darwin-arm64": "0.12.0-next.23", + "@nomicfoundation/edr-darwin-x64": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0-next.23", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0-next.23", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0-next.23" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.8.0.tgz", - "integrity": "sha512-sKTmOu/P5YYhxT0ThN2Pe3hmCE/5Ag6K/eYoiavjLWbR7HEb5ZwPu2rC3DpuUk1H+UKJqt7o4/xIgJxqw9wu6A==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0-next.23.tgz", + "integrity": "sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.8.0.tgz", - "integrity": "sha512-8ymEtWw1xf1Id1cc42XIeE+9wyo3Dpn9OD/X8GiaMz9R70Ebmj2g+FrbETu8o6UM+aL28sBZQCiCzjlft2yWAg==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0-next.23.tgz", + "integrity": "sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.8.0.tgz", - "integrity": "sha512-h/wWzS2EyQuycz+x/SjMRbyA+QMCCVmotRsgM1WycPARvVZWIVfwRRsKoXKdCftsb3S8NTprqBdJlOmsFyETFA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.8.0.tgz", - "integrity": "sha512-gnWxDgdkka0O9GpPX/gZT3REeKYV28Guyg13+Vj/bbLpmK1HmGh6Kx+fMhWv+Ht/wEmGDBGMCW1wdyT/CftJaQ==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.8.0.tgz", - "integrity": "sha512-DTMiAkgAx+nyxcxKyxFZk1HPakXXUCgrmei7r5G7kngiggiGp/AUuBBWFHi8xvl2y04GYhro5Wp+KprnLVoAPA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0-next.23.tgz", + "integrity": "sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.8.0.tgz", - "integrity": "sha512-iTITWe0Zj8cNqS0xTblmxPbHVWwEtMiDC+Yxwr64d7QBn/1W0ilFQ16J8gB6RVVFU3GpfNyoeg3tUoMpSnrm6Q==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0-next.23.tgz", + "integrity": "sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.8.0.tgz", - "integrity": "sha512-mNRDyd/C3j7RMcwapifzv2K57sfA5xOw8g2U84ZDvgSrXVXLC99ZPxn9kmolb+dz8VMm9FONTZz9ESS6v8DTnA==", + "version": "0.12.0-next.23", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0-next.23.tgz", + "integrity": "sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", - "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", + "node_modules/@nomicfoundation/hardhat-ethers": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.1.3.tgz", + "integrity": "sha512-208JcDeVIl+7Wu3MhFUUtiA8TJ7r2Rn3Wr+lSx9PfsDTKkbsAsWPY6N6wQ4mtzDv0/pB9nIbJhkjoHe1EsgNsA==", "dev": true, + "license": "MIT", "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", - "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", - "dev": true, - "bin": { - "rlp": "bin/rlp.cjs" + "debug": "^4.1.1", + "lodash.isequal": "^4.5.0" }, - "engines": { - "node": ">=18" + "peerDependencies": { + "ethers": "^6.14.0", + "hardhat": "^2.28.0" } }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", - "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", + "node_modules/@nomicfoundation/hardhat-foundry": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-foundry/-/hardhat-foundry-1.2.1.tgz", + "integrity": "sha512-pH1KeyI0sysgi7I7uQKPLXWl895EkuS6V41rSi820Ipqp/FScIwDh27RbevgC9zJ4ufSsSz34njm9cvRMGMNVA==", "dev": true, + "license": "MIT", "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" + "picocolors": "^1.1.0" }, "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } + "hardhat": "^2.26.0" } }, - "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/slang": { + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.18.3.tgz", + "integrity": "sha512-YqAWgckqbHM0/CZxi9Nlf4hjk9wUNLC9ngWCWBiqMxPIZmzsVKYuChdlrfeBPQyvQQBoOhbx+7C1005kLVQDZQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "@bytecodealliance/preview2-shim": "0.17.0" } }, - "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", - "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", + "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "ethereum-cryptography": "0.1.3" - }, + "license": "MIT", "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" + "node": ">= 12" }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.2", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.2" } }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", + "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.8.tgz", - "integrity": "sha512-Z5PiCXH4xhNLASROlSUOADfhfpfhYO6D7Hn9xp8PddmHey0jq704cr6kfU8TRrQ4PUZbpfsZadPj+pCfZdjPIg==", + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", + "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/chai-as-promised": "^7.1.3", - "chai-as-promised": "^7.1.1", - "deep-eql": "^4.0.1", - "ordinal": "^1.0.3" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "chai": "^4.2.0", - "ethers": "^6.1.0", - "hardhat": "^2.9.4" + "optional": true, + "engines": { + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.8.tgz", - "integrity": "sha512-zhOZ4hdRORls31DTOqg+GmEZM0ujly8GGIuRY7t7szEk2zW/arY1qDug/py8AEktT00v5K+b6RvbVog+va51IA==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", + "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "lodash.isequal": "^4.5.0" - }, - "peerDependencies": { - "ethers": "^6.1.0", - "hardhat": "^2.0.0" + "optional": true, + "engines": { + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-foundry": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-foundry/-/hardhat-foundry-1.1.3.tgz", - "integrity": "sha512-30Ezc3hlZ4pC5Z/9W9euW5uoPKKQQKaecLETHJH8BPpd30zYOooy6HfjmcTY1/taOQjlwirOdNO7tHlje8Qcgw==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", + "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", "dev": true, "license": "MIT", - "dependencies": { - "picocolors": "^1.1.0" - }, - "peerDependencies": { - "hardhat": "^2.17.2" + "optional": true, + "engines": { + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-ignition": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-0.15.10.tgz", - "integrity": "sha512-UScXyLLG5rEm+ANchQYCDOsskdXl6ux3oCPgC24PKE/QMJEib5crGZIo8spAyzdK6vOnRW6i4FG+1qvoO0AGWA==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", + "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@nomicfoundation/ignition-core": "^0.15.10", - "@nomicfoundation/ignition-ui": "^0.15.10", - "chalk": "^4.0.0", - "debug": "^4.3.2", - "fs-extra": "^10.0.0", - "json5": "^2.2.3", - "prompts": "^2.4.2" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-verify": "^2.0.1", - "hardhat": "^2.18.0" - } - }, - "node_modules/@nomicfoundation/hardhat-ignition-ethers": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-ethers/-/hardhat-ignition-ethers-0.15.10.tgz", - "integrity": "sha512-P90glRiBbR4mnMKP/LePovfUJjYT2YWJjx7118i7yxssUwcaW9wFohb4bFh+236N1tqM4q7aGx9cBvHNgve3zA==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-ignition": "^0.15.10", - "@nomicfoundation/ignition-core": "^0.15.10", - "ethers": "^6.7.0", - "hardhat": "^2.18.0" + "optional": true, + "engines": { + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", + "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", + "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 12" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@openzeppelin/defender-sdk-base-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-2.7.1.tgz", + "integrity": "sha512-7gFCteA+V3396A3McgqzmirwmbPXuHJYN896O3AbsHX9XcxInN74C5Zv3tFHld0GmIX/VlaIvILNMhOpdISZjA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@aws-sdk/client-lambda": "^3.563.0", + "amazon-cognito-identity-js": "^6.3.6", + "async-retry": "^1.3.3", + "axios": "^1.7.4" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/@openzeppelin/defender-sdk-deploy-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-2.7.1.tgz", + "integrity": "sha512-vFkDupn8ATW83KjZlY5U7UdsvSo9YZwOMQoVaHJO3S+Z6h0wa6cTzuQV9C0AKYq524quQkFsQ4AQq5CgsgdEkQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" + "@openzeppelin/defender-sdk-base-client": "^2.7.1", + "axios": "^1.7.4", + "lodash": "^4.17.21" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@openzeppelin/defender-sdk-network-client": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-2.7.1.tgz", + "integrity": "sha512-AWJKT9YKv9wH3/1AJZCztF3VIsg1sX+v8fjtyFLROqtVAzmhB8WKBRVt9GHAZ+PmsixAKDMOEbH6R1cipTIVHQ==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^2.7.1", + "axios": "^1.7.4", + "lodash": "^4.17.21" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@openzeppelin/hardhat-upgrades": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.9.1.tgz", + "integrity": "sha512-pSDjlOnIpP+PqaJVe144dK6VVKZw2v6YQusyt0OOLiCsl+WUzfo4D0kylax7zjrOxqy41EK2ipQeIF4T+cCn2A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "universalify": "^2.0.0" + "@openzeppelin/defender-sdk-base-client": "^2.1.0", + "@openzeppelin/defender-sdk-deploy-client": "^2.1.0", + "@openzeppelin/defender-sdk-network-client": "^2.1.0", + "@openzeppelin/upgrades-core": "^1.41.0", + "chalk": "^4.1.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.1.5", + "proper-lockfile": "^4.1.1", + "undici": "^6.11.1" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "bin": { + "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js" + }, + "peerDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.6", + "@nomicfoundation/hardhat-verify": "^2.0.14", + "ethers": "^6.6.0", + "hardhat": "^2.24.1" + }, + "peerDependenciesMeta": { + "@nomicfoundation/hardhat-verify": { + "optional": true + } } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@openzeppelin/upgrades-core": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.46.0.tgz", + "integrity": "sha512-UFSeO/4r8eeXj0C/HAwV+J4b72sE1HX0aALQFs5S2RBOsfXvKweyjQf35vrK32LQiyHdP6IPShAsEBVvpSEgGQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "@nomicfoundation/slang": "^0.18.3", + "bignumber.js": "^9.1.2", + "cbor": "^10.0.0", + "chalk": "^4.1.0", + "compare-versions": "^6.0.0", + "debug": "^4.1.1", + "ethereumjs-util": "^7.0.3", + "minimatch": "^10.2.5", + "minimist": "^1.2.7", + "proper-lockfile": "^4.1.1", + "solidity-ast": "^0.4.60" }, - "engines": { - "node": ">=8" + "bin": { + "openzeppelin-upgrades-core": "dist/cli/cli.js" } }, - "node_modules/@nomicfoundation/hardhat-ignition/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "peer": true, + "optional": true, "engines": { - "node": ">= 10.0.0" + "node": ">=14" } }, - "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.12.tgz", - "integrity": "sha512-xTNQNI/9xkHvjmCJnJOTyqDSl8uq1rKb2WOVmixQxFtRd7Oa3ecO8zM0cyC2YmOK+jHB9WPZ+F/ijkHg1CoORA==", + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ethereumjs-util": "^7.1.4" - }, - "peerDependencies": { - "hardhat": "^2.9.5" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "license": "MPL-2.0", - "peer": true, "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" }, - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/hardhat-toolbox": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-5.0.0.tgz", - "integrity": "sha512-FnUtUC5PsakCbwiVNsqlXVIWG5JIb5CEZoSXbJUsEBun22Bivx2jhF1/q9iQbzuaGpJKFQyOhemPB2+XlEE6pQ==", + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dev": true, "license": "MIT", - "peerDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-ignition-ethers": "^0.15.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-verify": "^2.0.0", - "@typechain/ethers-v6": "^0.5.0", - "@typechain/hardhat": "^9.0.0", - "@types/chai": "^4.2.0", - "@types/mocha": ">=9.1.0", - "@types/node": ">=18.0.0", - "chai": "^4.2.0", - "ethers": "^6.4.0", - "hardhat": "^2.11.0", - "hardhat-gas-reporter": "^1.0.8", - "solidity-coverage": "^0.8.1", - "ts-node": ">=8.0.0", - "typechain": "^8.3.0", - "typescript": ">=4.5.0" - } - }, - "node_modules/@nomicfoundation/hardhat-verify": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.13.tgz", - "integrity": "sha512-i57GX1sC0kYGyRVnbQrjjyBTpWTKgrvKC+jH8CMKV6gHp959Upb8lKaZ58WRHIU0espkulTxLnacYeUDirwJ2g==", - "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "debug": "^4.1.1", - "lodash.clonedeep": "^4.5.0", - "picocolors": "^1.1.0", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" + "@noble/hashes": "1.4.0" }, - "peerDependencies": { - "hardhat": "^2.0.4" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-0.15.10.tgz", - "integrity": "sha512-AWvCviNlBkPT8EKcg34N+yUdQTYFiC/HdpfFZdw8oMFuAs9SMZE0zQA9gJQSCay41GbuyXt2Kietp5/1/nlBIA==", + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/address": "5.6.1", - "@nomicfoundation/solidity-analyzer": "^0.1.1", - "cbor": "^9.0.0", - "debug": "^4.3.2", - "ethers": "^6.7.0", - "fs-extra": "^10.0.0", - "immer": "10.0.2", - "lodash": "4.17.21", - "ndjson": "2.0.0" + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core/node_modules/@ethersproject/address": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", - "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", + "node_modules/@scure/bip32/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.6.2", - "@ethersproject/bytes": "^5.6.1", - "@ethersproject/keccak256": "^5.6.1", - "@ethersproject/logger": "^5.6.0", - "@ethersproject/rlp": "^5.6.1" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core/node_modules/cbor": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", - "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "nofilter": "^3.1.0" + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" }, - "engines": { - "node": ">=16" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/@scure/bip39/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nomicfoundation/ignition-core/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, "engines": { - "node": ">= 10.0.0" + "node": ">=6" } }, - "node_modules/@nomicfoundation/ignition-ui": { - "version": "0.15.10", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.10.tgz", - "integrity": "sha512-82XQPF+1fvxTimDUPgDVwpTjHjfjFgFs84rERbBiMLQbz6sPtgTlV8HHrlbMx8tT/JKCI/SCU4gxV8xA4CPfcg==", + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "peer": true + "license": "0BSD" }, - "node_modules/@nomicfoundation/slang": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.18.3.tgz", - "integrity": "sha512-YqAWgckqbHM0/CZxi9Nlf4hjk9wUNLC9ngWCWBiqMxPIZmzsVKYuChdlrfeBPQyvQQBoOhbx+7C1005kLVQDZQ==", + "node_modules/@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@bytecodealliance/preview2-shim": "0.17.0" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", - "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", - "dev": true, - "engines": { - "node": ">= 12" - }, - "optionalDependencies": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.2", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.2", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.2", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.2", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.2", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.2", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.2" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", - "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", - "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", - "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", - "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", - "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", - "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", - "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", - "dev": true, - "optional": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@openzeppelin/defender-sdk-base-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-2.4.0.tgz", - "integrity": "sha512-oB18M3DNR/nREQhHLEXdcCsxv04DPbezBHzFz6APIez+MEpQQl2SwHYxA16vOyZ6A/zV0b8sFDqAjq0oogUMyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@aws-sdk/client-lambda": "^3.563.0", - "amazon-cognito-identity-js": "^6.3.6", - "async-retry": "^1.3.3" - } - }, - "node_modules/@openzeppelin/defender-sdk-deploy-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-2.4.0.tgz", - "integrity": "sha512-7brIWq7uW6ejYuxdBE6kQnZJQSF4v8g9Jed4/leXtPFjCowhDtIldhcYUfI+UPcZnDKdbvxnxXVJM2AWyVTNtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@openzeppelin/defender-sdk-base-client": "^2.4.0", - "axios": "^1.7.4", - "lodash": "^4.17.21" - } - }, - "node_modules/@openzeppelin/defender-sdk-network-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-2.4.0.tgz", - "integrity": "sha512-+19/J7NiW7EC8yN3DbqA290JBzbdu1/iHpz9lE5RJ5YTPv50eReou0v7vZSBhKRN10P7AKM3V263W1zoVGpVdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@openzeppelin/defender-sdk-base-client": "^2.4.0", - "axios": "^1.7.4", - "lodash": "^4.17.21" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.9.0.tgz", - "integrity": "sha512-7YYBSxRnO/X+tsQkVgtz3/YbwZuQPjbjQ3m0A/8+vgQzdPfulR93NaFKgZfMonnrriXb5O/ULjIDPI+8nuqtyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@openzeppelin/defender-sdk-base-client": "^2.1.0", - "@openzeppelin/defender-sdk-deploy-client": "^2.1.0", - "@openzeppelin/defender-sdk-network-client": "^2.1.0", - "@openzeppelin/upgrades-core": "^1.41.0", - "chalk": "^4.1.0", - "debug": "^4.1.1", - "ethereumjs-util": "^7.1.5", - "proper-lockfile": "^4.1.1", - "undici": "^6.11.1" - }, - "bin": { - "migrate-oz-cli-project": "dist/scripts/migrate-oz-cli-project.js" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-verify": "^2.0.0", - "ethers": "^6.6.0", - "hardhat": "^2.0.2" - }, - "peerDependenciesMeta": { - "@nomicfoundation/hardhat-verify": { - "optional": true - } - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/hardhat-upgrades/node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/@openzeppelin/upgrades-core": { - "version": "1.42.1", - "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.42.1.tgz", - "integrity": "sha512-8qnz2XfQrco8R8u9NjV+KiSLrVn7DnWFd+3BuhTUjhVy0bzCSu2SMKCVpZLtXbxf4f2dpz8jYPQYRa6s23PhLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nomicfoundation/slang": "^0.18.3", - "cbor": "^10.0.0", - "chalk": "^4.1.0", - "compare-versions": "^6.0.0", - "debug": "^4.1.1", - "ethereumjs-util": "^7.0.3", - "minimatch": "^9.0.5", - "minimist": "^1.2.7", - "proper-lockfile": "^4.1.1", - "solidity-ast": "^0.4.51" - }, - "bin": { - "openzeppelin-upgrades-core": "dist/cli/cli.js" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/cbor": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.3.tgz", - "integrity": "sha512-72Jnj81xMsqepqdcSdf2+fflz/UDsThOHy5hj2MW5F5xzHL8Oa0KQ6I6V9CwVUPxg5pf+W9xp6W2KilaRXWWtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nofilter": "^3.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@openzeppelin/upgrades-core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@scure/base": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", - "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", - "dev": true, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "dependencies": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", - "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/abort-controller/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", - "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/config-resolver/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/core": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.5.tgz", - "integrity": "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", - "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/eventstream-codec": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.1.tgz", - "integrity": "sha512-Q2bCAAR6zXNVtJgifsU16ZjKGqdw/DyecKNgIgi7dlqw04fqDu0mnq+JmGphqheypVc64CYq3azSuCpAdFk2+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-codec/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.1.tgz", - "integrity": "sha512-HbIybmz5rhNg+zxKiyVAnvdM3vkzjE6ccrJ620iPL8IXcJEntd3hnBl+ktMwIy12Te/kyrSbUb8UCdnUT4QEdA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.0.1.tgz", - "integrity": "sha512-lSipaiq3rmHguHa3QFF4YcCM3VJOrY9oq2sow3qlhFY+nBSTF/nrO82MUQRPrxHQXA58J5G1UnU2WuJfi465BA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.1.tgz", - "integrity": "sha512-o4CoOI6oYGYJ4zXo34U8X9szDe3oGjmHgsMGiZM0j4vtNoT+h80TLnkUcrLZR3+E6HIxqW+G+9WHAVfl0GXK0Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.1.tgz", - "integrity": "sha512-Z94uZp0tGJuxds3iEAZBqGU2QiaBHP4YytLUjwZWx+oUeohCsLyUm33yp4MMBmhkuPqSbQCXq5hDet6JGUgHWA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/eventstream-codec": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", - "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/hash-node": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", - "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/hash-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", - "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/invalid-dependency/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/is-array-buffer/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", - "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-content-length/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.6.tgz", - "integrity": "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.7.tgz", - "integrity": "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-retry/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-retry/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", - "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-serde/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", - "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-stack/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", - "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-config-provider/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.3.tgz", - "integrity": "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/property-provider": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", - "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/property-provider/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", - "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/protocol-http/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/querystring-builder": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", - "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-builder/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/querystring-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", - "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/querystring-parser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/service-error-classification": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", - "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", - "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", - "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.6.tgz", - "integrity": "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/smithy-client/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/url-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", - "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/url-parser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-base64/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-body-length-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-buffer-from/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-config-provider/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.7.tgz", - "integrity": "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.7.tgz", - "integrity": "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-endpoints": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", - "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-endpoints/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-middleware/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-retry": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", - "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-retry/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-stream": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", - "integrity": "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-stream/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-uri-escape/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-utf8/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/util-waiter": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.0.2.tgz", - "integrity": "sha512-piUTHyp2Axx3p/kc2CIJkYSv0BAaheBQmbACZgQSSfWUumWNW+R1lL+H9PDBxKJkvOeEX+hKYEFiwO8xagL8AQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/util-waiter/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@solidity-parser/parser": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", - "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@typechain/ethers-v6": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz", - "integrity": "sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" - }, - "peerDependencies": { - "ethers": "6.x", - "typechain": "^8.3.2", - "typescript": ">=4.7.0" - } - }, - "node_modules/@typechain/hardhat": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-9.1.0.tgz", - "integrity": "sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fs-extra": "^9.1.0" - }, - "peerDependencies": { - "@typechain/ethers-v6": "^0.5.1", - "ethers": "^6.1.0", - "hardhat": "^2.9.9", - "typechain": "^8.3.2" - } - }, - "node_modules/@typechain/hardhat/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typechain/hardhat/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@typechain/hardhat/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.20", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", - "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/chai-as-promised": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", - "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/secp256k1": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", - "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amazon-cognito-identity-js": { - "version": "6.3.12", - "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz", - "integrity": "sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "1.2.2", - "buffer": "4.9.2", - "fast-base64-decode": "^1.0.0", - "isomorphic-unfetch": "^3.0.0", - "js-cookie": "^2.2.1" - } - }, - "node_modules/amazon-cognito-identity-js/node_modules/@aws-crypto/sha256-js": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-1.2.2.tgz", - "integrity": "sha512-Nr1QJIbW/afYYGzYvrF70LtaHrIRtd4TNAglX8BvlfxJLZ45SAmueIKYl5tWoNBPzp65ymXGFK0Bb1vZUpuc9g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^1.2.2", - "@aws-sdk/types": "^3.1.0", - "tslib": "^1.11.1" - } - }, - "node_modules/amazon-cognito-identity-js/node_modules/@aws-crypto/util": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-1.2.2.tgz", - "integrity": "sha512-H8PjG5WJ4wz0UXAFXeJjWCW1vkvIJ3qUUD+rGRwJ2/hj+xT58Qle2MTql/2MGzkU+1JLAFuR6aJpLAjHwhmwwg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.1.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "license": "BSD-3-Clause OR MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "license": "ISC", - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz", - "integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "license": "Apache-2.0", - "peer": true - }, - "node_modules/cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", - "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", - "dev": true, - "license": "WTFPL", - "peer": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 6" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/cli-table3/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dev": true, - "peer": true, - "dependencies": { - "heap": ">= 0.2.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/elliptic": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", - "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.12.0" - }, - "optionalDependencies": { - "source-map": "~0.2.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eth-gas-reporter": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz", - "integrity": "sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@solidity-parser/parser": "^0.14.0", - "axios": "^1.5.1", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^5.7.2", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^10.2.0", - "req-cwd": "^2.0.0", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "peerDependencies": { - "@codechecks/client": "^0.1.0" - }, - "peerDependenciesMeta": { - "@codechecks/client": { - "optional": true - } - } - }, - "node_modules/eth-gas-reporter/node_modules/ethers": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz", - "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "@ethersproject/abi": "5.8.0", - "@ethersproject/abstract-provider": "5.8.0", - "@ethersproject/abstract-signer": "5.8.0", - "@ethersproject/address": "5.8.0", - "@ethersproject/base64": "5.8.0", - "@ethersproject/basex": "5.8.0", - "@ethersproject/bignumber": "5.8.0", - "@ethersproject/bytes": "5.8.0", - "@ethersproject/constants": "5.8.0", - "@ethersproject/contracts": "5.8.0", - "@ethersproject/hash": "5.8.0", - "@ethersproject/hdnode": "5.8.0", - "@ethersproject/json-wallets": "5.8.0", - "@ethersproject/keccak256": "5.8.0", - "@ethersproject/logger": "5.8.0", - "@ethersproject/networks": "5.8.0", - "@ethersproject/pbkdf2": "5.8.0", - "@ethersproject/properties": "5.8.0", - "@ethersproject/providers": "5.8.0", - "@ethersproject/random": "5.8.0", - "@ethersproject/rlp": "5.8.0", - "@ethersproject/sha2": "5.8.0", - "@ethersproject/signing-key": "5.8.0", - "@ethersproject/solidity": "5.8.0", - "@ethersproject/strings": "5.8.0", - "@ethersproject/transactions": "5.8.0", - "@ethersproject/units": "5.8.0", - "@ethersproject/wallet": "5.8.0", - "@ethersproject/web": "5.8.0", - "@ethersproject/wordlists": "5.8.0" - } - }, - "node_modules/ethereum-bloom-filters": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.2.0.tgz", - "integrity": "sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/hashes": "^1.4.0" - } - }, - "node_modules/ethereum-bloom-filters/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "node_modules/ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "node_modules/ethereumjs-abi/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ethereumjs-util/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/ethers": { - "version": "6.13.5", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", - "integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/ethers/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=6" } }, - "node_modules/ethers/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "node_modules/@sentry/hub/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, "license": "0BSD" }, - "node_modules/ethers/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", + "node_modules/@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "BSD-3-Clause", "dependencies": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" + "node": ">=6" } }, - "node_modules/ethjs-unit/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", + "node_modules/@sentry/minimal/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "0BSD" }, - "node_modules/ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "node_modules/@sentry/node": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" + "@sentry/core": "5.30.0", + "@sentry/hub": "5.30.0", + "@sentry/tracing": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "node": ">=6" } }, - "node_modules/fast-base64-decode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", - "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "0BSD" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "node_modules/@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" }, "engines": { - "node": ">=8.6.0" + "node": ">=6" } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/@sentry/tracing/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "0BSD" }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "node_modules/@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], "license": "BSD-3-Clause", - "peer": true + "engines": { + "node": ">=6" + } }, - "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "node_modules/@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "strnum": "^1.0.5" + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=6" } }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "reusify": "^1.0.4" - } + "license": "0BSD" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/@smithy/core": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.31.1.tgz", + "integrity": "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "to-regex-range": "^5.0.1" + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "node_modules/@smithy/credential-provider-imds": { + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.16.tgz", + "integrity": "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "array-back": "^3.0.1" + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=4.0.0" + "node": ">=18.0.0" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@smithy/fetch-http-handler": { + "version": "5.6.13", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.13.tgz", + "integrity": "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", - "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "node_modules/@smithy/node-http-handler": { + "version": "4.9.13", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.13.tgz", + "integrity": "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 6" + "node": ">=18.0.0" } }, - "node_modules/fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "node_modules/@smithy/signature-v4": { + "version": "5.6.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.12.tgz", + "integrity": "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "@smithy/core": "^3.31.1", + "@smithy/types": "^4.16.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=18.0.0" } }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@smithy/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=18.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/@types/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@types/node": "*" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/@types/node": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", + "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/@types/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": "*" + "dependencies": { + "@types/node": "*" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/@types/secp256k1": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.7.tgz", + "integrity": "sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/node": "*" } }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=4" + "node": ">=0.3.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "debug": "4" }, "engines": { - "node": ">= 0.4" + "node": ">= 6.0.0" } }, - "node_modules/ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, - "bin": { - "testrpc-sc": "index.js" + "engines": { + "node": ">=8" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/amazon-cognito-identity-js": { + "version": "6.3.20", + "resolved": "https://registry.npmjs.org/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.20.tgz", + "integrity": "sha512-akaaLpDqz4i0m2XG4+x+XcHAlboRt3rydVrSiEFCKvaGZSmZdvnYW4SXqm2OGeVdZK0R1nIXjp8yEpID3rHC5Q==", "dev": true, - "license": "ISC", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "@aws-crypto/sha256-js": "1.2.2", + "buffer": "4.9.2", + "fast-base64-decode": "^1.0.0", + "isomorphic-unfetch": "^3.0.0", + "js-cookie": "^3.0.7" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, + "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "string-width": "^4.1.0" } }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, "engines": { "node": ">=6" } }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" + "type-fest": "^0.21.3" }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } + "license": "Python-2.0" }, - "node_modules/hardhat": { - "version": "2.22.19", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.19.tgz", - "integrity": "sha512-jptJR5o6MCgNbhd7eKa3mrteR+Ggq1exmE5RUL5ydQEVKcZm0sss5laa86yZ0ixIavIvF4zzS7TdGDuyopj0sQ==", + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "dev": true, "license": "MIT", "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.8.0", - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-tx": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "boxen": "^5.1.2", - "chokidar": "^4.0.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^5.0.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "json-stream-stringify": "^3.1.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "picocolors": "^1.1.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.8.26", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tinyglobby": "^0.2.6", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - }, - "bin": { - "hardhat": "internal/cli/bootstrap.js" - }, - "peerDependencies": { - "ts-node": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - }, - "typescript": { - "optional": true - } + "retry": "0.13.1" } }, - "node_modules/hardhat-gas-reporter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz", - "integrity": "sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - }, - "peerDependencies": { - "hardhat": "^2.0.2" - } + "license": "MIT" }, - "node_modules/hardhat/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 0.4" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hardhat/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": "18 || 20 || >=22" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "dev": true, "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "node_modules/boxen/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">= 0.4" + "node": "20 || >=22" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "dev": true, - "bin": { - "he": "bin/he" - } + "license": "MIT" }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC" }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, + "license": "MIT", "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" + "base-x": "^3.0.2" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", "dev": true, + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" } }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@types/node": "^10.0.3" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.8" + } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/immer": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", - "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "peer": true, + "engines": { + "node": ">=10" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/cbor": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.12.tgz", + "integrity": "sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==", "dev": true, + "license": "MIT", + "dependencies": { + "nofilter": "^3.0.2" + }, "engines": { - "node": ">=8" + "node": ">=20" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, "engines": { - "node": ">= 0.10" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/io-ts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", - "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true, - "dependencies": { - "fp-ts": "^1.0.0" - } + "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", "dev": true, + "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" }, "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { - "is-extglob": "^2.1.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" + "node": ">=7.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "engines": { - "node": ">=0.12.0" - } + "license": "MIT" }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", "dev": true, "license": "MIT" }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/isomorphic-unfetch": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", - "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true, "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.1", - "unfetch": "^4.2.0" - } - }, - "node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" + "engines": { + "node": ">= 0.6" } }, - "node_modules/js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, "license": "MIT" }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } }, - "node_modules/json-stream-stringify": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.6.tgz", - "integrity": "sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=7.10.1" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC", - "peer": true + "node": ">= 8" + } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "ms": "^2.1.3" }, "engines": { - "node": ">=6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/jsonfile": { + "node_modules/decamelize": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonschema": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", - "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "*" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/keccak": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", - "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "hasInstallScript": true, + "license": "MIT", "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.4.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6" + "node": ">= 0.8" } }, - "node_modules/lcov-filter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/lcov-filter/-/lcov-filter-0.1.1.tgz", - "integrity": "sha512-oDbNqq0g8CMV4QoF1eV6bTHFzQIXPe2CG83bM4r3xly6psKd/BRfp/ZZZJXyqCyZahhXklVySqHUmiqNGnGJxA==", - "dependencies": { - "simple-bin-help": "^1.2.0" + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" }, - "bin": { - "lcov-filter": "bin/lcov-filter.js" + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "dev": true, + "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=6" + } }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">= 0.4" + } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" } }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, "engines": { - "node": ">=8" + "node": ">=10.0.0" } }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/ethers": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.17.0.tgz", + "integrity": "sha512-BpyrpIPJ3ydEVow8zGaz1DuPS7YU8DcWxuBnY9a0UA/lvAPwrMr+EPXsfrul628SRaekPNeIM4UFh/91GWZang==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@adraffy/ens-normalize": "1.11.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.21.0" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", - "peer": true, "dependencies": { - "get-func-name": "^2.0.1" + "undici-types": "~6.19.2" } }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true + "node_modules/ethers/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC", - "peer": true + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, - "node_modules/markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/fast-base64-decode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", + "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, - "engines": { - "node": ">= 0.10.0" + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", - "peer": true, "engines": { - "node": ">= 8" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/micro-ftch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", - "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "is-callable": "^1.2.7" }, "engines": { - "node": ">=8.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { - "node": ">= 0.6" + "node": ">= 6" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true + "node_modules/fp-ts": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true, + "license": "MIT" }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": "*" + "node": ">=6 <7 || >=8" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { - "obliterator": "^2.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 14.0.0" + "node": ">= 0.4" } }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" + "node": ">= 0.4" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=10" + "node": ">=0.4.7" }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/ndjson": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", - "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==", + "node_modules/hardhat": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.29.0.tgz", + "integrity": "sha512-tsj5mCSjDCFOhGfBl4vwqDEcwdlES9VUzRWfdrwvEVhus6D8W6u+WfUKRLLwFhKGS/8lKPoXGsjYWPXl3CCpOg==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "MIT", "dependencies": { - "json-stringify-safe": "^5.0.1", - "minimist": "^1.2.5", - "readable-stream": "^3.6.0", - "split2": "^3.0.0", - "through2": "^4.0.0" + "@ethereumjs/util": "^9.1.0", + "@ethersproject/abi": "^5.1.2", + "@nomicfoundation/edr": "0.12.0-next.23", + "@nomicfoundation/solidity-analyzer": "^0.1.0", + "@sentry/node": "^5.18.1", + "adm-zip": "^0.4.16", + "aggregate-error": "^3.0.0", + "ansi-escapes": "^4.3.0", + "boxen": "^5.1.2", + "chokidar": "^4.0.0", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "enquirer": "^2.3.0", + "env-paths": "^2.2.0", + "ethereum-cryptography": "^1.0.3", + "find-up": "^5.0.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "immutable": "^4.0.0-rc.12", + "io-ts": "1.10.4", + "json-stream-stringify": "^3.1.4", + "keccak": "^3.0.2", + "lodash": "^4.17.11", + "micro-eth-signer": "^0.14.0", + "mnemonist": "^0.38.0", + "mocha": "^11.1.0", + "p-map": "^4.0.0", + "picocolors": "^1.1.0", + "raw-body": "^2.4.1", + "resolve": "1.17.0", + "semver": "^6.3.0", + "solc": "0.8.26", + "source-map-support": "^0.5.13", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.6", + "tsort": "0.0.1", + "undici": "^5.14.0", + "uuid": "^8.3.2", + "ws": "^7.4.6" }, "bin": { - "ndjson": "cli.js" + "hardhat": "internal/cli/bootstrap.js" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "ts-node": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "node_modules/hardhat/node_modules/@noble/hashes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", + "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true + "node_modules/hardhat/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "node_modules/hardhat/node_modules/@scure/bip32": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", + "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "license": "MIT", - "peer": true, "dependencies": { - "lodash": "^4.17.21" + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/hardhat/node_modules/@scure/bip39": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", + "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" } }, - "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "node_modules/hardhat/node_modules/ethereum-cryptography": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", + "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" } }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "node_modules/hardhat/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", "dev": true, "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, "engines": { - "node": ">=12.19" + "node": ">=14.0" } }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "node_modules/hardhat/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "abbrev": "1" + "license": "MIT", + "engines": { + "node": ">=8.3.0" }, - "bin": { - "nopt": "bin/nopt.js" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -8017,1677 +3223,1463 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "node_modules/hash-base/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/ox": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, "license": "MIT", "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "safe-buffer": "~5.1.0" } }, - "node_modules/ox/node_modules/@noble/curves": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.0.tgz", - "integrity": "sha512-7YDlXiNMdO1YZeH6t/kvopHHbIZzlxrCV9WLqCY6QhcXOoXiNCMDqJIglZ9Yjx5+w7Dz30TITFrlTjnRg7sKEg==", + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "node_modules/ox/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" + "dependencies": { + "function-bind": "^1.1.2" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ox/node_modules/@scure/base": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", - "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" + "engines": { + "node": ">= 0.4" } }, - "node_modules/ox/node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "bin": { + "he": "bin/he" } }, - "node_modules/ox/node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/immutable": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.9.tgz", + "integrity": "sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==", "dev": true, - "peer": true + "license": "MIT" }, - "node_modules/path-exists": { + "node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "license": "ISC" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/io-ts": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", "dev": true, "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "dependencies": { + "fp-ts": "^1.0.0" } }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "*" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "node": ">= 0.4" }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=8" } }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin-prettier.js" + "dependencies": { + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", - "peer": true + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } + "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } + "license": "ISC" }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "node_modules/isomorphic-unfetch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", + "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" } }, - "node_modules/proper-lockfile/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", "dev": true, "license": "MIT" }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/puzrin" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/nodeca" } ], "license": "MIT", - "peer": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { - "safe-buffer": "^5.1.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/json-stream-stringify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.7.tgz", + "integrity": "sha512-F4MWetLtY42YMaAKw5cV4e47zMD5aOT+tjjQWjX18ACtdkQ5Y/vrcfbcQ107Rh+MXjOCIx4KhW0wPmOvG8iQ5w==", "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=7.10.1" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "dev": true, + "hasInstallScript": true, + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=10.0.0" } }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "resolve": "^1.1.6" + "p-locate": "^5.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "minimatch": "^3.0.5" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "req-from": "^2.0.0" - }, + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, "engines": { - "node": ">=4" + "node": ">= 0.10.0" } }, - "node_modules/req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", + "node_modules/micro-eth-signer": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", + "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/micro-eth-signer/node_modules/@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.2" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/micro-eth-signer/node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=0.10.0" + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "node_modules/micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", "dev": true, + "license": "MIT", "dependencies": { - "path-parse": "^1.0.6" + "@scure/base": "~1.2.5" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, "engines": { - "node": ">= 4" + "node": ">= 0.6" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } + "license": "ISC" }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } + "license": "MIT" }, - "node_modules/rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "bn.js": "^5.2.0" + "brace-expansion": "^5.0.8" }, - "bin": { - "rlp": "bin/rlp" + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", - "peer": true, - "dependencies": { - "queue-microtask": "^1.2.2" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "node_modules/sc-istanbul/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/mnemonist": { + "version": "0.38.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", + "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "sprintf-js": "~1.0.2" + "obliterator": "^2.0.0" } }, - "node_modules/sc-istanbul/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/mocha": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.8.0.tgz", + "integrity": "sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": "*" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/sc-istanbul/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "node_modules/mocha/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/sc-istanbul/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "balanced-match": "^1.0.0" } }, - "node_modules/sc-istanbul/node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sc-istanbul/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/sc-istanbul/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "has-flag": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, - "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } + "license": "MIT" }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, + "license": "MIT", "bin": { - "sha.js": "bin.js" + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, - "node_modules/sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">=12.19" } }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" + "p-limit": "^3.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "aggregate-error": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/simple-bin-help": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/simple-bin-help/-/simple-bin-help-1.8.0.tgz", - "integrity": "sha512-0LxHn+P1lF5r2WwVB/za3hLRIsYoLaNq1CXqjbrs3ZvLuvlWnRKrUjEWzV7umZL7hpQ7xULiQMV+0iXdRa5iFg==", - "engines": { - "node": ">=14.16" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, - "license": "MIT", - "peer": true + "license": "BlueOak-1.0.0" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "color-convert": "^2.0.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/pbkdf2": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.6.tgz", + "integrity": "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-name": "~1.1.4" + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 0.10" } }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC" }, - "node_modules/solc": { - "version": "0.8.26", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", - "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, - "dependencies": { - "command-exists": "^1.2.8", - "commander": "^8.1.0", - "follow-redirects": "^1.12.1", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "bin": { - "solcjs": "solc.js" - }, + "license": "MIT", "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/solc/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/solidity-ast": { - "version": "0.4.58", - "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.58.tgz", - "integrity": "sha512-fiAEDlMEc+xziMn0IpZf2vUbqxyXYZK4BqBiTaz2ZUqOP0p1fdJzUc9xpv74Jdxb5BLAiCUFv5UenkXIpHn3cA==", - "dev": true - }, - "node_modules/solidity-coverage": { - "version": "0.8.14", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.14.tgz", - "integrity": "sha512-ItAAObe5GaEOp20kXC2BZRnph+9P7Rtoqg2mQc2SXGEHgSDF2wWd1Wxz3ntzQWXkbCtIIGdJT918HG00cObwbA==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.19.0", - "chalk": "^2.4.2", - "death": "^1.1.0", - "difflib": "^0.2.4", - "fs-extra": "^8.1.0", - "ghost-testrpc": "^0.0.2", - "global-modules": "^2.0.0", - "globby": "^10.0.1", - "jsonschema": "^1.2.4", - "lodash": "^4.17.21", - "mocha": "^10.2.0", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.6" - }, - "bin": { - "solidity-coverage": "plugins/bin.js" - }, - "peerDependencies": { - "hardhat": "^2.11.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.19.0.tgz", - "integrity": "sha512-RV16k/qIxW/wWc+mLzV3ARyKUaMUTBy9tOLMzFhtNSKYeTAanQ3a5MudJKf/8arIFnA2L27SNjarQKmFg0w/jA==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, - "node_modules/solidity-coverage/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" } }, - "node_modules/solidity-coverage/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/solidity-docgen": { - "version": "0.6.0-beta.36", - "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.36.tgz", - "integrity": "sha512-f/I5G2iJgU1h0XrrjRD0hHMr7C10u276vYvm//rw1TzFcYQ4xTOyAoi9oNAHRU0JU4mY9eTuxdVc2zahdMuhaQ==", + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "dependencies": { - "handlebars": "^4.7.7", - "solidity-ast": "^0.4.38" - }, - "peerDependencies": { - "hardhat": "^2.8.0" - } + "license": "ISC" }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "safe-buffer": "^5.1.0" } }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "readable-stream": "^3.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.7.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" + "node": ">=0.10.0" } }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true, - "license": "WTFPL OR MIT", - "peer": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "path-parse": "^1.0.6" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", "dev": true, + "license": "MIT", "dependencies": { - "is-hex-prefixed": "1.0.0" + "hash-base": "^3.1.2", + "inherits": "^2.0.4" }, "engines": { - "node": ">=6.5.0", - "npm": ">=3" + "node": ">= 0.8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", "dev": true, - "engines": { - "node": ">=8" + "license": "MPL-2.0", + "dependencies": { + "bn.js": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "rlp": "bin/rlp" } }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], "license": "MIT" }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } + "license": "MIT" + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true, + "license": "MIT" }, - "node_modules/sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "node_modules/secp256k1": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.5.tgz", + "integrity": "sha512-SQZi5+/uiJIFPYbeRrVuu77Sr3bFOTq0oCQs67CqYwdmg0lhnqi/8djSWhzNO3GKGOqxBYCdx8zJJv0zUwDDvw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=18.0.0" } }, - "node_modules/sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "node_modules/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "get-port": "^3.1.0" + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" + "randombytes": "^2.1.0" } }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=8.0.0" + "node": ">= 0.4" } }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "dev": true, - "license": "MIT", - "peer": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, "engines": { - "node": ">=8" + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/then-request/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/then-request/node_modules/form-data": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", - "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "node_modules/solc": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", + "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solc.js" }, "engines": { - "node": ">= 0.12" + "node": ">=10.0.0" } }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "node_modules/solc/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "readable-stream": "3" + "license": "ISC", + "bin": { + "semver": "bin/semver" } }, - "node_modules/tinyglobby": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", - "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "node_modules/solidity-ast": { + "version": "0.4.62", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.62.tgz", + "integrity": "sha512-jSC7msQCkJXIzM8LlDjRZ5cif5w40g6THlXHFk3zchbL5dm3YLoBETvqPGo5KndYkftjhcs5kz1fnTu4d34lVQ==", "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.3", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } + "license": "MIT" }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "node_modules/solidity-docgen": { + "version": "0.6.0-beta.36", + "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.6.0-beta.36.tgz", + "integrity": "sha512-f/I5G2iJgU1h0XrrjRD0hHMr7C10u276vYvm//rw1TzFcYQ4xTOyAoi9oNAHRU0JU4mY9eTuxdVc2zahdMuhaQ==", "dev": true, "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "handlebars": "^4.7.7", + "solidity-ast": "^0.4.38" + }, + "peerDependencies": { + "hardhat": "^2.8.0" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=0.10.0" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "type-fest": "^0.7.1" }, "engines": { - "node": ">=8.0" + "node": ">=6" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=0.6" + "node": ">=8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/ts-command-line-args": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", - "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "license": "ISC", - "peer": true, + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" - }, - "bin": { - "write-markdown": "dist/write-markdown.js" + "safe-buffer": "~5.2.0" } }, - "node_modules/ts-command-line-args/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ts-command-line-args/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/ts-command-line-args/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/ts-command-line-args/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/ts-command-line-args/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ts-command-line-args/node_modules/supports-color": { + "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9695,127 +4687,95 @@ "node": ">=8" } }, - "node_modules/ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "typescript": ">=3.7.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "engines": { + "node": ">=12.0.0" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, "engines": { - "node": ">=0.3.1" + "node": ">=0.6.0" } }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "prelude-ls": "~1.1.2" + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=4" + "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", + "dev": true, + "license": "MIT" + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -9823,101 +4783,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typechain": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", - "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", - "peer": true, - "dependencies": { - "@types/prettier": "^2.1.1", - "debug": "^4.3.1", - "fs-extra": "^7.0.0", - "glob": "7.1.7", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.3.1", - "ts-command-line-args": "^2.2.0", - "ts-essentials": "^7.0.1" - }, - "bin": { - "typechain": "dist/cli/cli.js" - }, - "peerDependencies": { - "typescript": ">=4.3.0" - } - }, - "node_modules/typechain/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "peer": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typechain/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "devOptional": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=14.17" - } - }, - "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" + "node": ">= 0.4" } }, "node_modules/uglify-js": { @@ -9925,6 +4803,7 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -9934,21 +4813,21 @@ } }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=14.0" + "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" }, "node_modules/unfetch": { "version": "4.2.0", @@ -9962,6 +4841,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -9971,247 +4851,29 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/userop": { - "version": "0.4.0-beta.5", - "resolved": "https://registry.npmjs.org/userop/-/userop-0.4.0-beta.5.tgz", - "integrity": "sha512-+ZSw1OpaFOuC/7hSP3e+AaEr8Jc8zesWd+RH8hljK902hp4wBzapGfVlrAfSm8yPq5fY4iIj2iZ0FC8BnL2XXQ==", - "dependencies": { - "abitype": "^1.0.0", - "ethers": "^6.11.1", - "viem": "^2.9.12" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/viem": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.28.0.tgz", - "integrity": "sha512-Z4W5O1pe+6pirYTFm451FcZmfGAUxUWt2L/eWC+YfTF28j/8rd7q6MBAi05lMN4KhLJjhN0s5YGIPB+kf1L20g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.2", - "@noble/hashes": "1.7.2", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.9", - "ws": "8.18.1" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/base": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.5.tgz", - "integrity": "sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/web3-utils": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", - "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", - "dev": true, - "license": "LGPL-3.0", - "peer": true, - "dependencies": { - "@ethereumjs/util": "^8.1.0", - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereum-cryptography": "^2.1.2", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils/node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/web3-utils/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/web3-utils/node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/web3-utils/node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", - "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@noble/curves": "1.4.2", - "@noble/hashes": "1.4.0", - "@scure/bip32": "1.4.0", - "@scure/bip39": "1.3.0" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -10231,17 +4893,41 @@ } }, "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, "bin": { - "which": "bin/which" + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/widest-line": { @@ -10249,6 +4935,7 @@ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.0.0" }, @@ -10256,60 +4943,26 @@ "node": ">=8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -10322,55 +4975,36 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -10386,35 +5020,38 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-unparser": { @@ -10422,6 +5059,7 @@ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, + "license": "MIT", "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -10432,17 +5070,6 @@ "node": ">=10" } }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 747a976f..f63f164c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "smart-contract-suite", + "private": true, "scripts": { "forge-build": "forge build --via-ir --sizes", "test": "forge test --via-ir -v", @@ -7,20 +8,18 @@ "coverage": "forge coverage", "coverage-with-report": "forge coverage --report lcov", "html-coverage-report": "forge coverage --report lcov && genhtml lcov.info -o report", - "generate-doc": "hardhat docgen" + "generate-doc": "hardhat docgen", + "bytecode-parity": "hardhat compile && forge build --via-ir" }, "dependencies": { - "dotenv": "^16.4.5", - "lcov-filter": "^0.1.1", - "userop": "^0.4.0-beta.5" + "dotenv": "17.4.2", + "ethers": "6.17.0" }, "devDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.8", - "@nomicfoundation/hardhat-foundry": "^1.1.3", - "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "@openzeppelin/hardhat-upgrades": "^3.9.0", - "ethers": "^6.13.5", - "hardhat": "^2.22.19", - "solidity-docgen": "^0.6.0-beta.36" + "@nomicfoundation/hardhat-ethers": "3.1.3", + "@nomicfoundation/hardhat-foundry": "1.2.1", + "@openzeppelin/hardhat-upgrades": "3.9.1", + "hardhat": "2.29.0", + "solidity-docgen": "0.6.0-beta.36" } } diff --git a/scripts/checks/branch-coverage.py b/scripts/checks/branch-coverage.py new file mode 100644 index 00000000..ee66a473 --- /dev/null +++ b/scripts/checks/branch-coverage.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Report branch coverage over the branches forge can actually see. + +`forge coverage --ir-minimum` does not count `require(cond, "string")` as a branch. Those sites +report zero hits on both arms even where the line itself runs hundreds of times, and they are the +majority of the branch denominator in this repo, so the percentage forge prints tracks how much of +a contract is written with custom errors rather than how much of it is tested. + +`if (cond) revert CustomError()` is counted correctly, reverting arm included. This script drops +the require sites and reports what is left, plus the arms that are genuinely uncovered. + +Usage: + forge coverage --ir-minimum --report lcov --report-file lcov.info \ + --no-match-path "test/foundry/{fork,invariant-testing}/*" + python3 script/branch-coverage.py lcov.info + +Fork tests are excluded because they skip when the RPC variables are unset, so including them +makes the number depend on the environment. Invariant runs are excluded for runtime: a campaign +under `--ir-minimum` costs more than the rest of the suite put together. They do reach branches, +so this is a deliberate choice about what the number means. A branch that only a random walk +reaches counts as uncovered here, which is the intent: it should be pinned by a named test. + +Fuzz tests stay in. They run in seconds and reach revert arms that fixed inputs do not. +""" + +import collections +import pathlib +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent.parent + + +def parse(lcov_path): + """Read an lcov file into per-file branch arms and per-line hit counts.""" + branches = collections.defaultdict(lambda: collections.defaultdict(list)) + line_hits = collections.defaultdict(dict) + current = None + + for raw in pathlib.Path(lcov_path).read_text().splitlines(): + record = raw.strip() + if record.startswith("SF:"): + current = record[3:] + elif record.startswith("BRDA:"): + line, _block, _arm, hits = record[5:].split(",") + branches[current][int(line)].append(0 if hits == "-" else int(hits)) + elif record.startswith("DA:"): + line, hits = record[3:].split(",") + line_hits[current][int(line)] = int(hits) + + return branches, line_hits + + +def source_line(sources, path, number): + if path not in sources: + sources[path] = (ROOT / path).read_text().splitlines() + return sources[path][number - 1].strip() + + +def main(): + lcov = sys.argv[1] if len(sys.argv) > 1 else "lcov.info" + branches, line_hits = parse(lcov) + sources = {} + + total_arms = measurable_arms = covered_arms = 0 + gaps = [] + + print(f"{'contract':<26}{'raw':>12}{'measurable':>16}") + for path in sorted(branches): + if "contracts/" not in path: + continue + + arms_here = skipped_here = covered_here = 0 + for number, arms in sorted(branches[path].items()): + arms_here += len(arms) + if source_line(sources, path, number).startswith("require"): + skipped_here += len(arms) + continue + for hits in arms: + if hits: + covered_here += 1 + else: + gaps.append((path, number, line_hits[path].get(number, 0))) + + measurable_here = arms_here - skipped_here + total_arms += arms_here + measurable_arms += measurable_here + covered_arms += covered_here + + raw_covered = sum(1 for arms in branches[path].values() for h in arms if h) + share = 100 * covered_here / measurable_here if measurable_here else 0 + print( + f"{path.split('/')[-1]:<26}{raw_covered:>4}/{arms_here:<7}" + f"{covered_here:>7}/{measurable_here:<5} = {share:5.1f}%" + ) + + raw_total = sum( + 1 for p in branches if "contracts/" in p for arms in branches[p].values() for h in arms if h + ) + print(f"\nraw {raw_total}/{total_arms} = {100 * raw_total / total_arms:.2f}% (do not quote this)") + print( + f"measurable {covered_arms}/{measurable_arms} = " + f"{100 * covered_arms / measurable_arms:.2f}% " + f"({total_arms - measurable_arms} require arms excluded)" + ) + + print(f"\nuncovered measurable arms: {len(gaps)}") + for path, number, executions in gaps: + # A line that never ran at all is a function no test calls, rather than an untaken branch + note = "function never called" if executions == 0 else f"line ran {executions}x" + print(f" {path}:{number} ({note})") + print(f" {source_line(sources, path, number)[:96]}") + + return 1 if gaps else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/checks/build-provenance.py b/scripts/checks/build-provenance.py new file mode 100644 index 00000000..1aa4efa1 --- /dev/null +++ b/scripts/checks/build-provenance.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Emit a JSON object describing the tree a deployment was built from. + +The deploy script calls this through `vm.ffi` and nests the result under `build` in +`deployments/address.json`. Everything here answers the same question: given only that file, +can somebody rebuild byte-identical bytecode and check it against what is onchain? + +That needs three things, and a commit hash alone is none of them. It needs the commit, the +seven submodule commits (a `lib/` bump changes the output without changing this repo's +history), and the compiler settings, because `bytecode_hash`, the optimizer run count and the +evm version all move the bytes. + +The storage layout hashes come last. They are what an upgrade is checked against later, and +this is the only moment the layout and the deployed address are known together. +""" + +import hashlib +import json +import pathlib +import re +import subprocess +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent.parent + + +def git(*args): + return subprocess.run( + ["git", *args], cwd=ROOT, capture_output=True, text=True, check=True + ).stdout.strip() + + +def submodule_pins(): + """Map each `lib/` path to the commit it is checked out at. + + `git submodule status` puts a flag character in column one: a space when the checkout sits + at the commit this repo records, `+` when it has moved, `-` when it is not initialised. + Read with a regex rather than by slicing, because the flag is a space on the common case + and stripping whitespace anywhere would eat the first digit of the hash instead. + + `atPin` false is a reason to stop and look. It means the deployed bytecode was built from a + dependency this repo does not record, so nobody can reproduce it from the commit alone. + """ + raw = subprocess.run( + ["git", "submodule", "status"], cwd=ROOT, capture_output=True, text=True, check=True + ).stdout + + pins = {} + for line in raw.splitlines(): + match = re.match(r"^([ +\-U])([0-9a-f]{40})\s+(\S+)", line) + if not match: + continue + flag, commit, path = match.groups() + pins[path] = {"commit": commit, "atPin": flag == " "} + return pins + + +def compiler_settings(): + """Read the settings that move bytecode straight out of foundry.toml. + + Parsed rather than hardcoded so this file cannot drift away from the build. Only the + `[profile.default]` block is read, since that is what a deploy runs under. + """ + text = (ROOT / "foundry.toml").read_text() + default = text.split("[profile.default]", 1)[1].split("\n[", 1)[0] + + def value(key): + match = re.search(rf"^{key}\s*=\s*(.+)$", default, re.MULTILINE) + if not match: + return None + return match.group(1).strip().strip("'\"").replace("_", "") + + return { + "solc": value("solc"), + "evmVersion": value("evm_version"), + "optimizer": value("optimizer") == "true", + "optimizerRuns": int(value("optimizer-runs")), + "viaIR": value("via_ir") == "true", + "bytecodeHash": value("bytecode_hash"), + } + + +def storage_layout_hashes(): + """Hash each committed storage layout so an upgrade can be diffed against the deployment. + + Absent rather than empty when the directory has not been generated, so a missing entry + reads as "not recorded" instead of "no storage". + """ + layouts = {} + directory = ROOT / "storage-layouts" + if not directory.is_dir(): + return layouts + for path in sorted(directory.glob("*.json")): + digest = hashlib.sha256(path.read_bytes()).hexdigest() + layouts[path.stem] = f"0x{digest}" + return layouts + + +def main(): + # Scoped to `contracts/` on purpose. Third party submodules accumulate stray lockfiles, so + # `lib/` reads as modified almost always without a single source line having changed, and + # folding that in here would make every deployment record read dirty and mean nothing. + # Whether a dependency actually moved is the `atPin` flag, recorded per submodule. + dirty = git("status", "--porcelain", "--", "contracts") != "" + + provenance = { + "commit": git("rev-parse", "HEAD"), + "branch": git("rev-parse", "--abbrev-ref", "HEAD"), + "dirty": dirty, + "submodules": submodule_pins(), + "compiler": compiler_settings(), + "storageLayoutHashes": storage_layout_hashes(), + } + + # Compact, because forge reads this back through ffi as one value. + json.dump(provenance, sys.stdout, separators=(",", ":"), sort_keys=True) + + +if __name__ == "__main__": + main() diff --git a/scripts/checks/storage-layouts.py b/scripts/checks/storage-layouts.py new file mode 100644 index 00000000..034072a5 --- /dev/null +++ b/scripts/checks/storage-layouts.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Write a storage layout baseline for every contract that can hold storage. + +Two proxy layers sit under this protocol, four UUPS singletons and beacon-proxied wallets behind +two factories, so a slot that moves silently is the failure mode with the widest blast radius. The +baseline exists to make that visible in a diff rather than after a deploy. + +Usage: + python3 script/storage-layouts.py + +Writes storage-layouts/.json. CI regenerates and fails on `git diff --exit-code`, the +same shape the gas snapshots under snapshots/ already use. + +`forge inspect` output is normalised before it is written. It carries an `astId` on every entry and +inside every contract, struct and enum type name, and those renumber whenever an unrelated source +line moves. Left in, the check would fail on edits that touch no storage at all. Array lengths look +similar and are kept: the 2 in `t_array(t_bytes32)2_storage` is the length, not an id. +""" + +import json +import pathlib +import re +import subprocess +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent.parent +OUT = ROOT / "storage-layouts" + +# Every contract under contracts/ that declares storage, plus P256Verifier, which declares none and +# is baselined anyway so that gaining a slot shows up as a new file rather than as nothing. +CONTRACTS = ( + "Registry", + "RegistryHelper", + "LazyWalletRegistry", + "DeviceWalletFactory", + "ESIMWalletFactory", + "DeviceWallet", + "ESIMWallet", + "Account4337", + "ProtocolAdmin", + "P256Verifier", +) + +# t_contract(Name)1234 and t_struct(Name)1234_storage carry an ast id. t_array(...)2_storage does +# not, so array is deliberately absent from this list. +AST_ID_IN_TYPE = re.compile(r"(t_(?:contract|struct|enum|userDefinedValueType)\([^()]*\))\d+") + + +def declared_contracts(): + """Every `contract X` declared under contracts/, mapped to its path. + + Scanning rather than trusting the list below is what stops a newly added contract from being + baselined by nobody. It also supplies the source path for a contract with no storage, where + forge omits it. + """ + found = {} + for path in sorted((ROOT / "contracts").rglob("*.sol")): + for match in re.finditer(r"^contract\s+(\w+)", path.read_text(), re.MULTILINE): + found[match.group(1)] = path.relative_to(ROOT).as_posix() + return found + + +def normalise(type_name): + return AST_ID_IN_TYPE.sub(r"\1", type_name) + + +def layout(contract, source): + result = subprocess.run( + ["forge", "inspect", contract, "storage-layout", "--json"], + capture_output=True, + text=True, + cwd=ROOT, + ) + if result.returncode != 0: + raise SystemExit(f"forge inspect failed for {contract}:\n{result.stderr}") + + raw = json.loads(result.stdout) + types = raw.get("types") or {} + + entries = [] + for entry in raw.get("storage", []): + type_name = entry["type"] + entries.append( + { + "slot": entry["slot"], + "offset": entry["offset"], + "bytes": types.get(type_name, {}).get("numberOfBytes"), + "label": entry["label"], + "type": normalise(type_name), + } + ) + + return {"contract": f"{source}:{contract}", "storage": entries} + + +def main(): + declared = declared_contracts() + missing = set(declared) - set(CONTRACTS) + if missing: + raise SystemExit( + "contracts/ declares a contract with no baseline: " + + ", ".join(sorted(missing)) + + "\nAdd it to CONTRACTS in this file and regenerate." + ) + + OUT.mkdir(exist_ok=True) + for contract in CONTRACTS: + body = layout(contract, declared[contract]) + (OUT / f"{contract}.json").write_text(json.dumps(body, indent=2) + "\n") + print(f"{contract:<22}{len(body['storage']):>3} entries") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/deploy.js b/scripts/deploy.js deleted file mode 100644 index b7f6926b..00000000 --- a/scripts/deploy.js +++ /dev/null @@ -1,199 +0,0 @@ -const { ethers, upgrades } = require("hardhat"); -const dotenv = require("dotenv"); - -dotenv.config(); - -async function main() { - console.log("Starting deployment script..."); - - // Get deployer account - const [deployer] = await ethers.getSigners(); - console.log(`Deploying contracts with account: ${deployer.address}`); - console.log(`Account balance: ${ethers.formatEther(await deployer.provider.getBalance(deployer.address))} ETH`); - - const provider = new ethers.JsonRpcProvider(network.config.url); - // const provider = new ethers.AlchemyProvider(network.config.name, process.env.ALCHEMY_API_KEY); - - // Check for required environment variables - const upgradeManagerAddress = process.env.UPGRADE_MANAGER; - const upgradeManagerSigner = new ethers.Wallet(process.env.PRIVATE_KEY_1, provider); - - const eSIMWalletAdminAddress = process.env.ESIM_WALLET_ADMIN; - const eSIMWalletAdminSigner = new ethers.Wallet(process.env.PRIVATE_KEY_3, provider); - - const vaultAddress = process.env.VAULT; - - // Set balance in case of hardhat localhost or tenderly virtual-mainnet deployment or localhost - // await network.provider.send("hardhat_setBalance", [ - // eSIMWalletAdminAddress, - // "0x1000000000000000000000000", // we are giving ourselves a LOT eth - // ]); - // await network.provider.send("hardhat_setBalance", [ - // upgradeManagerAddress, - // "0x1000000000000000000000000", // we are giving ourselves a LOT eth - // ]); - // console.log(`Admin address: ${eSIMWalletAdminAddress}, balance: ${await provider.getBalance(eSIMWalletAdminAddress)}`); - // console.log(`Vault address: ${vaultAddress}, balance: ${await provider.getBalance(eSIMWalletAdminAddress)}`); - // console.log(`Upgrade manager address: ${upgradeManagerAddress}, balance: ${await provider.getBalance(eSIMWalletAdminAddress)}`); - - // 1. Deploy or use existing EntryPoint - let entryPointAddress; - if (process.env.ENTRY_POINT_ZERO_POINT_SEVEN_ADDRESS) { - entryPointAddress = process.env.ENTRY_POINT_ZERO_POINT_SEVEN_ADDRESS; - console.log(`Using existing EntryPoint at ${entryPointAddress}`); - } else { - console.log("Deploying EntryPoint..."); - const EntryPoint = await ethers.getContractFactory("EntryPoint"); - const entryPoint = await EntryPoint.deploy(); - await entryPoint.waitForDeployment(); - entryPointAddress = await entryPoint.getAddress(); - console.log(`EntryPoint deployed to: ${entryPointAddress}`); - } - - // 2. Deploy P256Verifier - console.log("Deploying P256Verifier..."); - const P256Verifier = await ethers.getContractFactory("P256Verifier"); - const p256Verifier = await P256Verifier.deploy(); - await p256Verifier.waitForDeployment(); - const p256VerifierAddress = await p256Verifier.getAddress(); - console.log(`P256Verifier deployed to: ${p256VerifierAddress}`); - - // 3. Deploy ESIMWallet implementation - console.log("Deploying ESIMWallet implementation..."); - const ESIMWallet = await ethers.getContractFactory("ESIMWallet"); - const esimWalletImpl = await ESIMWallet.deploy(); - await esimWalletImpl.waitForDeployment(); - const esimWalletImplAddress = await esimWalletImpl.getAddress(); - console.log(`ESIMWallet implementation deployed to: ${esimWalletImplAddress}`); - - // 4. Deploy ESIMWalletFactory with proxy - console.log("Deploying ESIMWalletFactory with proxy..."); - const ESIMWalletFactory = await ethers.getContractFactory("ESIMWalletFactory"); - const esimWalletFactory = await upgrades.deployProxy( - ESIMWalletFactory, - [ - esimWalletImplAddress, - upgradeManagerAddress - ], - { - initializer: "initialize", - kind: "uups", - } - ); - await esimWalletFactory.waitForDeployment(); - const esimWalletFactoryAddress = await esimWalletFactory.getAddress(); - console.log(`ESIMWalletFactory proxy deployed to: ${esimWalletFactoryAddress}`); - - // 5. Deploy DeviceWallet implementation - console.log("Deploying DeviceWallet implementation..."); - const DeviceWallet = await ethers.getContractFactory("DeviceWallet"); - const deviceWalletImpl = await DeviceWallet.deploy(entryPointAddress, p256VerifierAddress); - const deviceWalletImplAddress = await deviceWalletImpl.getAddress(); - console.log(`DeviceWallet implementation deployed to: ${deviceWalletImplAddress}`); - - // 6. Deploy DeviceWalletFactory with proxy - console.log("Deploying DeviceWalletFactory with proxy..."); - const DeviceWalletFactory = await ethers.getContractFactory("DeviceWalletFactory"); - const deviceWalletFactory = await upgrades.deployProxy( - DeviceWalletFactory, - [ - deviceWalletImplAddress, - eSIMWalletAdminAddress, - vaultAddress, - upgradeManagerAddress, - esimWalletFactoryAddress, - entryPointAddress, - p256VerifierAddress - ], - { - initializer: "initialize", - kind: "uups", - } - ); - await deviceWalletFactory.waitForDeployment(); - const deviceWalletFactoryAddress = await deviceWalletFactory.getAddress(); - console.log(`DeviceWalletFactory proxy deployed to: ${deviceWalletFactoryAddress}`); - - // 7. Deploy Registry with proxy - console.log("Deploying Registry with proxy..."); - const Registry = await ethers.getContractFactory("Registry"); - const registry = await upgrades.deployProxy( - Registry, - [ - eSIMWalletAdminAddress, - vaultAddress, - upgradeManagerAddress, - deviceWalletFactoryAddress, - esimWalletFactoryAddress, - entryPointAddress, - p256VerifierAddress - ], - { - initializer: "initialize", - kind: "uups" - } - ); - await registry.waitForDeployment(); - const registryAddress = await registry.getAddress(); - console.log(`Registry proxy deployed to: ${registryAddress}`); - - // 8. Deploy LazyWalletRegistry with proxy - console.log("Deploying LazyWalletRegistry with proxy..."); - const LazyWalletRegistry = await ethers.getContractFactory("LazyWalletRegistry"); - const lazyWalletRegistry = await upgrades.deployProxy( - LazyWalletRegistry, - [ - registryAddress, - upgradeManagerAddress - ], - { - initializer: "initialize", - kind: "uups", - } - ); - await lazyWalletRegistry.waitForDeployment(); - const lazyWalletRegistryAddress = await lazyWalletRegistry.getAddress(); - console.log(`LazyWalletRegistry proxy deployed to: ${lazyWalletRegistryAddress}`); - - // Post-deployment configuration - console.log("Performing post-deployment configuration..."); - - // 1. Set LazyWalletRegistry address in Registry (as upgradeManager) - console.log("Setting LazyWalletRegistry address in Registry..."); - const tx1 = await registry.connect(upgradeManagerSigner).addOrUpdateLazyWalletRegistryAddress(lazyWalletRegistryAddress); - await tx1.wait(); - console.log("LazyWalletRegistry address set in Registry"); - - // 2. Set Registry address in DeviceWalletFactory (as eSIMWalletAdmin) - console.log("Setting Registry address in DeviceWalletFactory..."); - const tx2 = await deviceWalletFactory.connect(eSIMWalletAdminSigner).addRegistryAddress(registryAddress); - await tx2.wait(); - console.log("Registry address set in DeviceWalletFactory"); - - // 3. Set Registry address in ESIMWalletFactory (as upgradeManager) - console.log("Setting Registry address in ESIMWalletFactory..."); - const tx3 = await esimWalletFactory.connect(upgradeManagerSigner).addRegistryAddress(registryAddress); - await tx3.wait(); - console.log("Registry address set in ESIMWalletFactory"); - - // Deployment summary - console.log("\n--- DEPLOYMENT SUMMARY ---"); - console.log(`"EntryPoint": "${entryPointAddress}",`); - console.log(`"P256Verifier": "${p256VerifierAddress}",`); - console.log(`"DeviceWalletImpl": "${deviceWalletImplAddress}",`); - console.log(`"ESIMWalletImpl": "${esimWalletImplAddress}",`); - console.log(`"DeviceWalletFactoryProxy": "${deviceWalletFactoryAddress}",`); - console.log(`"ESIMWalletFactoryProxy": "${esimWalletFactoryAddress}",`); - console.log(`"RegistryProxy": "${registryAddress}",`); - console.log(`"LazyWalletRegistryProxy": "${lazyWalletRegistryAddress}"`); - console.log("--- END SUMMARY ---\n"); - - console.log("Deployment completed successfully!"); -} - -main() -.then(() => process.exit(0)) -.catch((error) => { - console.error("Deployment failed:", error); - process.exit(1); -}); diff --git a/scripts/deploy/Configure.s.sol b/scripts/deploy/Configure.s.sol new file mode 100644 index 00000000..760a5de4 --- /dev/null +++ b/scripts/deploy/Configure.s.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Contracts +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Registry} from "../../contracts/Registry.sol"; +import {DeviceWalletFactory} from "../../contracts/device-wallet/DeviceWalletFactory.sol"; +import {ESIMWalletFactory} from "../../contracts/esim-wallet/ESIMWalletFactory.sol"; + +// Config +import {DeployConfig} from "./config/DeployConfig.sol"; +import {DeploymentRecord} from "./config/DeploymentRecord.sol"; + +/// @notice Wires the deployed contracts to each other and sets the price ceiling +/// @dev Run after `Deploy.s.sol` and before `TransferOwnership.s.sol`. Every call here is owner +/// gated, and the deployer is still the owner at this point, so none of them waits on the +/// timelock. After the handover each of them would be a scheduled operation. +/// +/// The three wiring calls exist because the dependency between the registry and the two +/// factories is circular: both factories are constructor arguments to the registry, and the +/// registry cannot be an argument to either of them. There is no ordering that removes this +/// step. +/// +/// Every step checks whether it has already been done and skips it if so. Both +/// `addRegistryAddress` functions revert once set, so without that a single failed +/// transaction would leave this script unable to finish the run it started. +contract Configure is Script { + + /// @notice A wiring call did not take effect + error NotWired(string what, address expected, address actual); + + /// @notice The registry is already bound to a different address than the one recorded + error BoundElsewhere(string what, address recorded, address bound); + + /// @notice Wires the protocol together and records that it is configured + /// @dev Broadcasts as the deployer, which is still the owner of all four singletons. + function run() external { + DeployConfig.Config memory config = DeployConfig.load(); + + address registryAddress = DeploymentRecord.readAddress("RegistryProxy"); + address lazyWalletRegistry = DeploymentRecord.readAddress("LazyWalletRegistryProxy"); + address deviceWalletFactory = DeploymentRecord.readAddress("DeviceWalletFactoryProxy"); + address eSIMWalletFactory = DeploymentRecord.readAddress("ESIMWalletFactoryProxy"); + + vm.startBroadcast(config.deployerPrivateKey); + + _bindRegistryToFactory("DeviceWalletFactory", deviceWalletFactory, registryAddress); + _bindRegistryToFactory("ESIMWalletFactory", eSIMWalletFactory, registryAddress); + _bindLazyWalletRegistry(registryAddress, lazyWalletRegistry); + _setPriceCap(registryAddress, config.dataBundlePriceCap); + + vm.stopBroadcast(); + + _verify(registryAddress, lazyWalletRegistry, deviceWalletFactory, eSIMWalletFactory); + DeploymentRecord.writeStatus("configured", true); + + console.log(""); + console.log("Configured. Next: TransferOwnership.s.sol."); + } + + /// @notice Points a factory at the registry, unless it already is + /// @dev Both factories store the registry as `Registry public registry` and both refuse a + /// second write, so a factory already bound to the right address is a finished step and a + /// factory bound to a different one is a mismatch nothing here can fix. + function _bindRegistryToFactory(string memory name, address factory, address registryAddress) + private + { + // Both factories declare the same getter, so either type reads the other correctly. + address bound = address(DeviceWalletFactory(factory).registry()); + + if(bound == registryAddress) { + console.log(string.concat(name, ": already bound, skipping")); + return; + } + if(bound != address(0)) revert BoundElsewhere(name, registryAddress, bound); + + DeviceWalletFactory(factory).addRegistryAddress(registryAddress); + console.log(string.concat(name, ": bound to registry")); + } + + /// @notice Tells the registry where the lazy wallet registry lives + /// @dev This one is an update rather than a one-time set, so re-running it is harmless. It is + /// still skipped when already correct, to keep a resumed run from spending gas on nothing. + function _bindLazyWalletRegistry(address registryAddress, address lazyWalletRegistry) private { + if(Registry(registryAddress).lazyWalletRegistry() == lazyWalletRegistry) { + console.log("Registry: lazy wallet registry already set, skipping"); + return; + } + + Registry(registryAddress).addOrUpdateLazyWalletRegistryAddress(lazyWalletRegistry); + console.log("Registry: lazy wallet registry set"); + } + + /// @notice Rotates the fallback ceiling on what an eSIM wallet may be charged for a data bundle + /// @dev `Registry.initialize` already required a non-zero cap, so this only ever handles a + /// later change to it. Zero is not a legal configuration at any point after deployment + /// either: `setDefaultDataBundlePriceCap` refuses it the same way `initialize` does. + function _setPriceCap(address registryAddress, uint256 cap) private { + if(Registry(registryAddress).defaultDataBundlePriceCap() == cap) { + console.log("Registry: price cap already set, skipping"); + return; + } + + Registry(registryAddress).setDefaultDataBundlePriceCap(cap); + console.log("Registry: price cap set to", cap); + } + + /// @notice Reads every wiring back out of the contracts that hold it + function _verify( + address registryAddress, + address lazyWalletRegistry, + address deviceWalletFactory, + address eSIMWalletFactory + ) private view { + address boundOnDevice = address(DeviceWalletFactory(deviceWalletFactory).registry()); + if(boundOnDevice != registryAddress) { + revert NotWired("DeviceWalletFactory.registry", registryAddress, boundOnDevice); + } + + address boundOnESIM = address(ESIMWalletFactory(eSIMWalletFactory).registry()); + if(boundOnESIM != registryAddress) { + revert NotWired("ESIMWalletFactory.registry", registryAddress, boundOnESIM); + } + + address boundLazy = Registry(registryAddress).lazyWalletRegistry(); + if(boundLazy != lazyWalletRegistry) { + revert NotWired("Registry.lazyWalletRegistry", lazyWalletRegistry, boundLazy); + } + } +} diff --git a/scripts/deploy/Deploy.s.sol b/scripts/deploy/Deploy.s.sol new file mode 100644 index 00000000..2a41b41e --- /dev/null +++ b/scripts/deploy/Deploy.s.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Contracts +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ProtocolAdmin} from "../../contracts/admin/ProtocolAdmin.sol"; +import {P256Verifier} from "../../contracts/P256Verifier.sol"; +import {Registry} from "../../contracts/Registry.sol"; +import {LazyWalletRegistry} from "../../contracts/LazyWalletRegistry.sol"; +import {DeviceWallet} from "../../contracts/device-wallet/DeviceWallet.sol"; +import {DeviceWalletFactory} from "../../contracts/device-wallet/DeviceWalletFactory.sol"; +import {ESIMWallet} from "../../contracts/esim-wallet/ESIMWallet.sol"; +import {ESIMWalletFactory} from "../../contracts/esim-wallet/ESIMWalletFactory.sol"; + +// Config +import {DeployConfig} from "./config/DeployConfig.sol"; +import {DeploymentRecord} from "./config/DeploymentRecord.sol"; + +/// @notice Deploys the protocol from nothing and records what it deployed +/// @dev Run first, then `Configure.s.sol`, then `TransferOwnership.s.sol`. The three are separate +/// because only the first is expensive to repeat: a configuration call that runs out of gas +/// should not mean redeploying eight contracts to try again. +/// +/// The deployer is passed as `_upgradeManager` to all four singletons rather than +/// `ProtocolAdmin`, and that is deliberate. Three configuration calls are owner gated and +/// structurally cannot be folded into any `initialize`, because both factories have to exist +/// before the registry and the registry has to exist before either factory can be told about +/// it. Handing ownership to the timelock first would put a two day wait between deployment and +/// a working protocol, and would spend that window with the timelock deliberately weakened to +/// shorten it. Instead the deployer holds ownership across three scripts in one sitting and +/// hands it over at the end, which is what `ProtocolAdmin.acceptOwnershipBatch` exists for. +/// +/// `ProtocolAdmin` is still deployed first, so its address is recorded before anything can +/// depend on it and the handover script has nothing left to decide. +/// +/// Ordinary `ERC1967Proxy`, not the upgrades plugin. The whole test suite stands the protocol +/// up this way, so the deployed shape is the shape 573 tests run against, and the safety the +/// plugin checks at deploy time is already pinned by `ImplementationLocks.t.sol` and +/// `StorageLayout.t.sol`. +contract Deploy is Script { + + /// @notice Addresses produced by one run, carried between the deploy and the record + /// @dev A struct rather than locals because the recording step reads all of them at once, and a + /// long run of consecutive calls in one body is what pushes this repo into stack-too-deep. + struct Deployed { + address protocolAdmin; + address p256Verifier; + address eSIMWalletImplementation; + address eSIMWalletFactory; + address deviceWalletImplementation; + address deviceWalletFactory; + address registry; + address lazyWalletRegistry; + } + + /// @notice ERC-1967 implementation slot, read back rather than assumed + bytes32 private constant IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice A proxy did not come up pointing at the implementation it was given + error ImplementationMismatch(address proxy, address expected, address actual); + + /// @notice The record already holds a deployment for this chain + error AlreadyDeployed(string network); + + /// @notice A deployed contract does not point at what its constructor argument named + error WiringMismatch(string what, address expected, address actual); + + /// @notice Deploys the protocol and writes the record for this chain + /// @dev Broadcasts. Reverts before sending anything if the environment is incomplete or if the + /// record already holds a deployment for this chain. + function run() external { + DeployConfig.Config memory config = DeployConfig.load(); + + // Refuse to write over a chain that already has a deployment. Overwriting the record is + // how a live proxy stops being reachable by any script, since nothing else remembers it. + if(DeploymentRecord.has("contracts.RegistryProxy")) { + revert AlreadyDeployed(DeployConfig.recordKey()); + } + + _logPlan(config); + + vm.startBroadcast(config.deployerPrivateKey); + Deployed memory deployed = _deploy(config); + vm.stopBroadcast(); + + _verify(deployed); + _record(config, deployed); + + console.log(""); + console.log("Deployed. Next: Configure.s.sol, then TransferOwnership.s.sol."); + console.log("The protocol does not work until both have run."); + } + + /// @notice Deploys every contract in dependency order + /// @dev The deployer is the upgrade manager for now. Ownership moves in the third script. + function _deploy(DeployConfig.Config memory config) private returns (Deployed memory deployed) { + deployed.protocolAdmin = address( + new ProtocolAdmin( + DeployConfig.TIMELOCK_DELAY, + DeployConfig.TIMELOCK_DELAY_FLOOR, + config.proposers, + config.cancellers, + config.guardians + ) + ); + + deployed.p256Verifier = address(new P256Verifier()); + + deployed.eSIMWalletImplementation = address(new ESIMWallet()); + deployed.eSIMWalletFactory = _deployProxy( + address(new ESIMWalletFactory()), + abi.encodeCall( + ESIMWalletFactory.initialize, + (deployed.eSIMWalletImplementation, config.deployer) + ) + ); + + deployed.deviceWalletImplementation = address( + new DeviceWallet(config.entryPoint, P256Verifier(deployed.p256Verifier)) + ); + deployed.deviceWalletFactory = _deployProxy( + address(new DeviceWalletFactory()), + abi.encodeCall( + DeviceWalletFactory.initialize, + ( + deployed.deviceWalletImplementation, + config.deployer, + deployed.eSIMWalletFactory, + config.entryPoint, + P256Verifier(deployed.p256Verifier) + ) + ) + ); + + deployed.registry = _deployProxy( + address(new Registry()), + abi.encodeCall( + Registry.initialize, + ( + config.eSIMWalletAdmin, + config.vault, + config.deployer, + deployed.deviceWalletFactory, + deployed.eSIMWalletFactory, + config.entryPoint, + config.dataBundlePriceCap + ) + ) + ); + + deployed.lazyWalletRegistry = _deployProxy( + address(new LazyWalletRegistry()), + abi.encodeCall(LazyWalletRegistry.initialize, (deployed.registry, config.deployer)) + ); + } + + /// @notice Stands a UUPS implementation up behind a proxy and initializes it in one transaction + /// @dev Initializing in the proxy constructor closes the window where a deployed proxy sits + /// uninitialized and anybody can call `initialize` on it. + /// @param implementation Logic contract the proxy delegates to + /// @param initData Encoded `initialize` call run during construction + /// @return proxy Address of the deployed proxy + function _deployProxy(address implementation, bytes memory initData) + private + returns (address proxy) + { + proxy = address(new ERC1967Proxy(implementation, initData)); + + address stored = address(uint160(uint256(vm.load(proxy, IMPLEMENTATION_SLOT)))); + if(stored != implementation) { + revert ImplementationMismatch(proxy, implementation, stored); + } + } + + /// @notice Reads the deployment back through its own getters + /// @dev Checks the wiring the constructor arguments were supposed to produce rather than + /// trusting the arguments, which is the difference between a deployment log and a check. + /// Runs outside the broadcast, so nothing here costs gas. + function _verify(Deployed memory deployed) private view { + Registry registry = Registry(deployed.registry); + + address boundDeviceFactory = address(registry.deviceWalletFactory()); + if(boundDeviceFactory != deployed.deviceWalletFactory) { + revert WiringMismatch( + "Registry.deviceWalletFactory", + deployed.deviceWalletFactory, + boundDeviceFactory + ); + } + + address boundESIMFactory = address(registry.eSIMWalletFactory()); + if(boundESIMFactory != deployed.eSIMWalletFactory) { + revert WiringMismatch( + "Registry.eSIMWalletFactory", + deployed.eSIMWalletFactory, + boundESIMFactory + ); + } + + address boundRegistry = address(LazyWalletRegistry(deployed.lazyWalletRegistry).registry()); + if(boundRegistry != deployed.registry) { + revert WiringMismatch("LazyWalletRegistry.registry", deployed.registry, boundRegistry); + } + } + + /// @notice Writes the deployment record for this chain + function _record(DeployConfig.Config memory config, Deployed memory deployed) private { + string memory network = DeployConfig.recordKey(); + + string memory record = vm.serializeString("record", "build", _buildProvenance()); + record = vm.serializeString("record", "chain", _chainSection(config)); + record = vm.serializeString("record", "external", _externalSection(config)); + record = vm.serializeString("record", "params", _paramsSection(config)); + record = vm.serializeString("record", "admin", _adminSection(config, deployed)); + record = vm.serializeString("record", "contracts", _contractsSection(deployed)); + record = vm.serializeString("record", "status", _statusSection()); + + vm.writeJson(record, DeploymentRecord.PATH, string.concat(".", network)); + console.log("Record written to", DeploymentRecord.PATH, "under", network); + } + + /// @notice Everything needed to rebuild this bytecode later, collected offchain + /// @dev Through `ffi`, which this repo already enables for the WebAuthn test signer. A commit + /// hash on its own does not reproduce a build here: seven submodules and six compiler + /// settings move the output without touching this repo's history. + function _buildProvenance() private returns (string memory provenance) { + string[] memory command = new string[](2); + command[0] = "python3"; + command[1] = "scripts/checks/build-provenance.py"; + + provenance = vm.serializeJson("build", string(vm.ffi(command))); + } + + function _chainSection(DeployConfig.Config memory config) + private + returns (string memory section) + { + vm.serializeUint("chain", "chainId", config.chainId); + vm.serializeString("chain", "network", DeployConfig.chainLabel()); + vm.serializeString("chain", "recordKey", DeployConfig.recordKey()); + vm.serializeUint("chain", "deployedAtBlock", block.number); + vm.serializeUint("chain", "deployedAtTimestamp", block.timestamp); + section = vm.serializeAddress("chain", "deployer", config.deployer); + } + + function _externalSection(DeployConfig.Config memory config) + private + returns (string memory section) + { + vm.serializeString("external", "entryPointVersion", "0.8.0"); + section = vm.serializeAddress("external", "entryPoint", address(config.entryPoint)); + } + + function _paramsSection(DeployConfig.Config memory config) + private + returns (string memory section) + { + vm.serializeAddress("params", "eSIMWalletAdmin", config.eSIMWalletAdmin); + vm.serializeAddress("params", "vault", config.vault); + section = vm.serializeUint("params", "dataBundlePriceCap", config.dataBundlePriceCap); + } + + function _adminSection(DeployConfig.Config memory config, Deployed memory deployed) + private + returns (string memory section) + { + vm.serializeAddress("admin", "protocolAdmin", deployed.protocolAdmin); + vm.serializeUint("admin", "initialDelay", DeployConfig.TIMELOCK_DELAY); + vm.serializeUint("admin", "minDelayFloor", DeployConfig.TIMELOCK_DELAY_FLOOR); + vm.serializeAddress("admin", "proposers", config.proposers); + vm.serializeAddress("admin", "cancellers", config.cancellers); + section = vm.serializeAddress("admin", "guardians", config.guardians); + } + + /// @notice One entry per deployed contract, each carrying enough to check it onchain + /// @dev `codehash` is what makes the record checkable without a block explorer: rebuild at the + /// recorded commit and submodule pins, hash the runtime bytecode, compare. The + /// implementation address is read out of the ERC-1967 slot rather than repeated from the + /// constructor argument, so a proxy that came up wrong shows here. + function _contractsSection(Deployed memory deployed) private returns (string memory section) { + vm.serializeString("contracts", "ProtocolAdmin", _plain(deployed.protocolAdmin)); + vm.serializeString("contracts", "P256Verifier", _plain(deployed.p256Verifier)); + vm.serializeString( + "contracts", + "ESIMWalletImplementation", + _plain(deployed.eSIMWalletImplementation) + ); + vm.serializeString( + "contracts", + "DeviceWalletImplementation", + _plain(deployed.deviceWalletImplementation) + ); + vm.serializeString( + "contracts", + "ESIMWalletFactoryProxy", + _factory( + "esimFactory", + deployed.eSIMWalletFactory, + address(ESIMWalletFactory(deployed.eSIMWalletFactory).beacon()) + ) + ); + vm.serializeString( + "contracts", + "DeviceWalletFactoryProxy", + _factory( + "deviceFactory", + deployed.deviceWalletFactory, + address(DeviceWalletFactory(deployed.deviceWalletFactory).beacon()) + ) + ); + vm.serializeString("contracts", "RegistryProxy", _proxy("registry", deployed.registry)); + section = vm.serializeString( + "contracts", + "LazyWalletRegistryProxy", + _proxy("lazyRegistry", deployed.lazyWalletRegistry) + ); + } + + function _statusSection() private returns (string memory section) { + vm.serializeBool("status", "configured", false); + section = vm.serializeBool("status", "ownershipTransferred", false); + } + + /// @notice Record entry for a contract deployed directly + function _plain(address target) private returns (string memory entry) { + string memory key = string.concat("plain", vm.toString(target)); + vm.serializeAddress(key, "address", target); + entry = vm.serializeBytes32(key, "codehash", target.codehash); + } + + /// @notice Record entry for a UUPS proxy, carrying the implementation it points at + function _proxy(string memory key, address proxy) private returns (string memory entry) { + vm.serializeAddress(key, "address", proxy); + vm.serializeBytes32(key, "codehash", proxy.codehash); + entry = vm.serializeAddress( + key, + "implementation", + address(uint160(uint256(vm.load(proxy, IMPLEMENTATION_SLOT)))) + ); + } + + /// @notice Record entry for a factory, which is a UUPS proxy that also owns a beacon + /// @dev The beacon is recorded because a beacon upgrade reaches every wallet at once and + /// nothing else in the file names it. + function _factory(string memory key, address proxy, address beacon) + private + returns (string memory entry) + { + vm.serializeAddress(key, "address", proxy); + vm.serializeBytes32(key, "codehash", proxy.codehash); + vm.serializeAddress( + key, + "implementation", + address(uint160(uint256(vm.load(proxy, IMPLEMENTATION_SLOT)))) + ); + entry = vm.serializeAddress(key, "beacon", beacon); + } + + function _logPlan(DeployConfig.Config memory config) private view { + console.log("Network ", DeployConfig.chainLabel()); + console.log("Chain id ", config.chainId); + console.log("Record key ", DeployConfig.recordKey()); + console.log("Deployer ", config.deployer); + console.log("EntryPoint ", address(config.entryPoint)); + console.log("eSIM admin ", config.eSIMWalletAdmin); + console.log("Vault ", config.vault); + console.log("Proposers ", config.proposers.length); + console.log("Cancellers ", config.cancellers.length); + console.log("Guardians ", config.guardians.length); + console.log(""); + } +} diff --git a/scripts/deploy/TransferOwnership.s.sol b/scripts/deploy/TransferOwnership.s.sol new file mode 100644 index 00000000..989a5b82 --- /dev/null +++ b/scripts/deploy/TransferOwnership.s.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Contracts +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Ownable2StepUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {ProtocolAdmin} from "../../contracts/admin/ProtocolAdmin.sol"; + +// Config +import {DeployConfig} from "./config/DeployConfig.sol"; +import {DeploymentRecord} from "./config/DeploymentRecord.sol"; + +/// @notice Hands the four upgradeable singletons to the timelock and closes the deployer's window +/// @dev The last of the three deployment scripts. Until this has run, one externally owned account +/// owns `Registry`, `LazyWalletRegistry` and both factories, and owning the factories reaches +/// every device wallet and every eSIM wallet through the two beacons. That is the state the +/// deployment is being moved off, so the gap between `Deploy.s.sol` and this script should be +/// minutes rather than days. +/// +/// Two steps, because these contracts are `Ownable2Step`. The deployer offers ownership, then +/// the new owner accepts it. `ProtocolAdmin.acceptOwnershipBatch` is permissionless and takes +/// all four in one transaction, so the second step needs no key at all and cannot be the thing +/// that strands a handover halfway. +/// +/// Nothing here is timelocked. Accepting an offer of ownership is not an operation the +/// timelock schedules; it is a function on the timelock contract itself. After this runs, +/// every owner gated call on all four contracts is. +contract TransferOwnership is Script { + + /// @notice Number of contracts handed over in one run + uint256 private constant TARGET_COUNT = 4; + + /// @notice A contract did not end the run owned by the timelock + error OwnershipNotMoved(address target, address owner); + + /// @notice The deployer does not own a contract it is supposed to hand over + error NotOwner(address target, address owner, address deployer); + + /// @notice Offers all four singletons to the timelock and has it accept them + /// @dev Broadcasts as the deployer for the offers. The acceptance is permissionless but is + /// broadcast from the same key, since somebody has to pay for it. + function run() external { + DeployConfig.Config memory config = DeployConfig.load(); + + address protocolAdmin = DeploymentRecord.readRaw("admin.protocolAdmin"); + address[] memory targets = _targets(); + + _requireDeployerOwnsAll(targets, config.deployer); + + vm.startBroadcast(config.deployerPrivateKey); + + for(uint256 i = 0; i < targets.length; ++i) { + Ownable2StepUpgradeable(targets[i]).transferOwnership(protocolAdmin); + console.log("Offered ownership of", targets[i]); + } + + ProtocolAdmin(payable(protocolAdmin)).acceptOwnershipBatch(targets); + + vm.stopBroadcast(); + + _verify(targets, protocolAdmin); + DeploymentRecord.writeStatus("ownershipTransferred", true); + + console.log(""); + console.log("All four singletons are owned by", protocolAdmin); + console.log("Every owner gated call now waits for the timelock delay."); + } + + /// @notice The four contracts whose owner is the upgrade authority + /// @dev Read from the record rather than taken as arguments. An address typed on a command line + /// is the one way to hand the wrong contract to the wrong owner permanently. + function _targets() private view returns (address[] memory targets) { + targets = new address[](TARGET_COUNT); + targets[0] = DeploymentRecord.readAddress("RegistryProxy"); + targets[1] = DeploymentRecord.readAddress("LazyWalletRegistryProxy"); + targets[2] = DeploymentRecord.readAddress("DeviceWalletFactoryProxy"); + targets[3] = DeploymentRecord.readAddress("ESIMWalletFactoryProxy"); + } + + /// @notice Refuses to start unless the deployer still owns every target + /// @dev Checked before broadcasting rather than per call, so a run that would hand over three + /// of four and revert on the fourth never sends the first transaction. + function _requireDeployerOwnsAll(address[] memory targets, address deployer) private view { + for(uint256 i = 0; i < targets.length; ++i) { + address owner = Ownable2StepUpgradeable(targets[i]).owner(); + if(owner != deployer) revert NotOwner(targets[i], owner, deployer); + } + } + + /// @notice Reads the owner back off every target + function _verify(address[] memory targets, address protocolAdmin) private view { + for(uint256 i = 0; i < targets.length; ++i) { + address owner = Ownable2StepUpgradeable(targets[i]).owner(); + if(owner != protocolAdmin) revert OwnershipNotMoved(targets[i], owner); + } + } +} diff --git a/scripts/deploy/config/DeployConfig.sol b/scripts/deploy/config/DeployConfig.sol new file mode 100644 index 00000000..4bac2882 --- /dev/null +++ b/scripts/deploy/config/DeployConfig.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Interfaces +import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; + +// Contracts +import {Vm} from "forge-std/Vm.sol"; + +/// @notice Everything a deployment needs that is not derived from the code itself +/// @dev Split out of the scripts so the same values are read the same way by the deploy, the +/// configuration and the ownership handover, and so a missing variable fails before any +/// transaction is broadcast rather than halfway through one. +/// +/// Addresses come from the environment rather than from constants here. The one exception is +/// the EntryPoint, which is a fixed address the protocol does not choose, and even that is +/// checked for code on the target chain before it is used. A wrong address written into an +/// immutable is not recoverable by any admin path in this protocol. +library DeployConfig { + + /// @notice ERC-4337 EntryPoint v0.8, the version `lib/account-abstraction` is pinned at + /// @dev Deployed at the same address on every chain, so this is not a per-chain table. + /// Verified to carry code on OP Sepolia and Base Sepolia on 2026-08-11, and `load` + /// re-checks it at run time so a chain without it cannot be deployed to by accident. + /// + /// This is not the v0.7 singleton the existing testnet deployment binds to. Every + /// signature the offchain SDK produces is scoped to one EntryPoint, because v0.8 moved + /// `userOpHash` to an EIP-712 domain separated form. + address internal constant ENTRY_POINT_V08 = 0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108; + + /// @notice Delay every scheduled admin operation waits before it can be executed + uint256 internal constant TIMELOCK_DELAY = 2 days; + + /// @notice Shortest delay `updateDelay` can ever bring the timelock down to + /// @dev Immutable in the deployed contract. Without it one scheduled operation turns the + /// timelock into a plain multisig and nothing after it ever waits again. + uint256 internal constant TIMELOCK_DELAY_FLOOR = 1 hours; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + /// @notice Resolved deployment parameters for the chain the script is pointed at + struct Config { + uint256 chainId; + address deployer; + uint256 deployerPrivateKey; + IEntryPoint entryPoint; + address eSIMWalletAdmin; + address vault; + uint256 dataBundlePriceCap; + address[] proposers; + address[] cancellers; + address[] guardians; + } + + /// @notice A required environment variable resolved to the zero address + error MissingAddress(string variable); + + /// @notice A required numeric environment variable resolved to zero + error MissingValue(string variable); + + /// @notice A required address list was empty + error EmptyList(string variable); + + /// @notice The EntryPoint address carries no code on this chain + error EntryPointNotDeployed(address entryPoint, uint256 chainId); + + /// @notice An account appears in two role lists the timelock requires to stay separate + error RolesMustNotOverlap(address account); + + /// @notice Reads and checks every deployment parameter from the environment + /// @dev Every failure here is one a deployment can recover from, so they all happen before + /// anything is broadcast. The role overlap checks duplicate what `ProtocolAdmin`'s + /// constructor enforces, deliberately: reverting inside a broadcast leaves the earlier + /// contracts of that run deployed and orphaned. + /// @return config Resolved parameters for this run + function load() internal view returns (Config memory config) { + config.chainId = block.chainid; + + config.deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + config.deployer = vm.addr(config.deployerPrivateKey); + + config.eSIMWalletAdmin = vm.envAddress("ESIM_WALLET_ADMIN"); + if(config.eSIMWalletAdmin == address(0)) revert MissingAddress("ESIM_WALLET_ADMIN"); + + config.vault = vm.envAddress("VAULT"); + if(config.vault == address(0)) revert MissingAddress("VAULT"); + + // Required, and never zero: `Registry.initialize` refuses a zero cap, since zero would + // read as "no ceiling" for every wallet that never sets its own. + config.dataBundlePriceCap = vm.envUint("DATA_BUNDLE_PRICE_CAP"); + if(config.dataBundlePriceCap == 0) revert MissingValue("DATA_BUNDLE_PRICE_CAP"); + + config.entryPoint = IEntryPoint(ENTRY_POINT_V08); + if(ENTRY_POINT_V08.code.length == 0) { + revert EntryPointNotDeployed(ENTRY_POINT_V08, block.chainid); + } + + config.proposers = vm.envAddress("TIMELOCK_PROPOSERS", ","); + if(config.proposers.length == 0) revert EmptyList("TIMELOCK_PROPOSERS"); + + // Empty is allowed. The base constructor already gives every proposer the cancel power, + // so this list is for accounts that cancel and do nothing else. + config.cancellers = vm.envOr("TIMELOCK_CANCELLERS", ",", new address[](0)); + + config.guardians = vm.envAddress("TIMELOCK_GUARDIANS", ","); + if(config.guardians.length == 0) revert EmptyList("TIMELOCK_GUARDIANS"); + + _requireDisjoint(config.proposers, config.cancellers); + _requireDisjoint(config.proposers, config.guardians); + _requireDisjoint(config.cancellers, config.guardians); + } + + /// @notice Reverts if any account appears in both lists + function _requireDisjoint(address[] memory left, address[] memory right) private pure { + for(uint256 i = 0; i < left.length; ++i) { + for(uint256 j = 0; j < right.length; ++j) { + if(left[i] == right[j]) revert RolesMustNotOverlap(left[i]); + } + } + } + + /// @notice Key this chain's entry lives under in `deployments/address.json` + /// @dev The chain id is part of the key, not just a field inside the entry. A name on its own + /// is a label somebody chose, and two chains sharing one is how a mainnet deployment + /// overwrites a testnet one that is still being used. The id is what the chain answers + /// with, so the key cannot be wrong about which chain it describes. + /// + /// Forge writes its own broadcast logs under `broadcast/