diff --git a/.openzeppelin/unknown-2008.json b/.openzeppelin/unknown-2008.json new file mode 100644 index 0000000..21ad7c8 --- /dev/null +++ b/.openzeppelin/unknown-2008.json @@ -0,0 +1,2022 @@ +{ + "manifestVersion": "3.2", + "proxies": [ + { + "address": "0xeA86da4A617B32B081a4af12E6c13Ae7edf8DFc9", + "txHash": "0x1a04fadc3948be628159fd3e850163789b0abd763e90cd36272ce339349549d5", + "kind": "uups" + }, + { + "address": "0x78Fd8664336b58889A19D4C40b046361048941F5", + "txHash": "0xb21abee62715a4d78f06116a4dacc1a195d213af384b2028129f4273443aea0f", + "kind": "uups" + } + ], + "impls": { + "d1a5733d80870d236f633ab7a6fcd1b12231e7501f78f3dc82acc8d0aa1dc6cb": { + "address": "0x71Aca2fB40cb6803Dd7FC89BDF33955e3EADeF5D", + "txHash": "0xba084107bfa9b99d0e7013281962ed64c2720fdb43d15cbfb480de1ab878a080", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7137_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7151_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6989_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1200_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)93_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)321_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7110": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6989_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7137_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6989_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7137_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7110", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7151_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "0a971fb72767a6f722c73c817e3f7df23720d90004c5ea82412bc943f4190c0d": { + "address": "0x4cE55721597eF26F6F8b7F974bf1291cbC9f3daC", + "txHash": "0xca4d5e725e5078cec27209ec51749739c6f7e692892baed9230e2ccaf3f2c6e6", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7075_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7089_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)35_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1224_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)95_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)223_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7048": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7075_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6927_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7075_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7048", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7089_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "fb76da266d4e9c21ec576d1267181a5b050f9b271c9419d75cf91927377f8a08": { + "address": "0x8aB054508869820F4510BfbeBe50a48320E4E083", + "txHash": "0x40fe60efa7651b7547461cfb131606bb1d9a0947bc702cc546b251b5019ffb01", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7075_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7089_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)35_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1224_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)95_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)223_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7048": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7075_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6927_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7075_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7048", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7089_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "93efc5259c94f48674ae8652b8aa034d799bfcb08c452aa915bdd09567634210": { + "address": "0x5b3DB948a712D0448f5546F412e72c2D39D876E1", + "txHash": "0x43e4adc01a059dbacebe391176ff5de29e3f44163a45f9759cc78c1410b2bed2", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7329_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7343_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)7181_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)24_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1946_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)145_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)291_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)24_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7302": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)7181_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7329_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)7181_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7329_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7302", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7343_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "ce3b1cf87030ad2d2f7b237eeb743ed3fe9c2c4eda4c444b6b60a60c9197361a": { + "address": "0x917Ea2d04dB43Dc7940C59631C8b38ac21809df4", + "txHash": "0xd98a8b1124713cce7ede779f1f5efef71dff83a8c4c5eee50df22f5600976c90", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7400_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7414_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)7252_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)24_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1951_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)145_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)291_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)24_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7373": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)7252_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7400_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)7252_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7400_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7373", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7414_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + } + } +} diff --git a/.openzeppelin/unknown-2009.json b/.openzeppelin/unknown-2009.json new file mode 100644 index 0000000..25543f2 --- /dev/null +++ b/.openzeppelin/unknown-2009.json @@ -0,0 +1,2022 @@ +{ + "manifestVersion": "3.2", + "proxies": [ + { + "address": "0x0a0fD69B8F29a0Cacc400798F8f19747BE0aF2fd", + "txHash": "0x0347cda1eb0727e945b2e18c0076c7db19e8fca915e8bc8f6751f04ae2f10983", + "kind": "uups" + }, + { + "address": "0x10D71aCE2B4B1Ba26f27F4FDd1105CB1b90a98cF", + "txHash": "0xf33aa6ac12b39c003a1a9b4725384cd6d9acc2a265c91c09fbb7e78b19a5f33d", + "kind": "uups" + } + ], + "impls": { + "d1a5733d80870d236f633ab7a6fcd1b12231e7501f78f3dc82acc8d0aa1dc6cb": { + "address": "0x984EA695389a53cDDf4972c68609Cc6dB6a2b24A", + "txHash": "0x153f824d1fa6577a4444fc01dafb346ac2ccc0c5e8ad813f3ceb52e63ffdb686", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7137_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7151_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6989_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1200_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)93_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)321_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7110": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6989_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7137_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6989_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7137_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7110", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7151_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "0a971fb72767a6f722c73c817e3f7df23720d90004c5ea82412bc943f4190c0d": { + "address": "0x2566d6468254662196eaa1fA858628c1b7E28C7C", + "txHash": "0xe58ca0f2da1902f502023b085ee58d9e09fb4b6ce7669e64cb59ed109ec1a6cb", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7075_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7089_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)35_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1224_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)95_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)223_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7048": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7075_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6927_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7075_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7048", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7089_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "fb76da266d4e9c21ec576d1267181a5b050f9b271c9419d75cf91927377f8a08": { + "address": "0x6882761D8167F00DfFbE81D5DC8C55fCA9960308", + "txHash": "0x455b7a670d334e5c278ff740c242bd175a41654b2e2ba85933639a28f2eb3a2c", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7075_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7089_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)25_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)35_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1224_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)95_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)223_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)25_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7048": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)6927_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7075_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)6927_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7075_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7048", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7089_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)25_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "93efc5259c94f48674ae8652b8aa034d799bfcb08c452aa915bdd09567634210": { + "address": "0x4c4E0749FA4040941439E0F24F83952A06937c6A", + "txHash": "0x5b042607d1f28cfb084ece1720e30925558bb786c26505d62bf0225bc5671d6e", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7329_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7343_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)7181_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)24_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1946_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)145_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)291_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)24_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7302": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)7181_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7329_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)7181_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7329_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7302", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7343_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "ce3b1cf87030ad2d2f7b237eeb743ed3fe9c2c4eda4c444b6b60a60c9197361a": { + "address": "0xCaBbA3c0A0Fd5DaC1c0f08b1BE2c925b7D74f148", + "txHash": "0x77635934e1536535ac9d9662792c65fc66b0be7ff544924c691b6f54087fa556", + "layout": { + "solcVersion": "0.8.24", + "storage": [ + { + "label": "_token", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:11" + }, + { + "label": "_cashOutAccount", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:14" + }, + { + "label": "_payments", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(Payment)7400_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:17" + }, + { + "label": "_paymentStatistics", + "offset": 0, + "slot": "3", + "type": "t_struct(PaymentStatistics)7414_storage", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:20" + }, + { + "label": "_cashbackTreasury", + "offset": 0, + "slot": "5", + "type": "t_address", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:23" + }, + { + "label": "_cashbackEnabled", + "offset": 20, + "slot": "5", + "type": "t_bool", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:26" + }, + { + "label": "_cashbackRate", + "offset": 21, + "slot": "5", + "type": "t_uint16", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:29" + }, + { + "label": "_accountCashbackStates", + "offset": 0, + "slot": "6", + "type": "t_mapping(t_address,t_struct(AccountCashbackState)7252_storage)", + "contract": "CardPaymentProcessorStorageV1", + "src": "contracts\\CardPaymentProcessorStorage.sol:32" + }, + { + "label": "__gap", + "offset": 0, + "slot": "7", + "type": "t_array(t_uint256)43_storage", + "contract": "CardPaymentProcessorStorage", + "src": "contracts\\CardPaymentProcessorStorage.sol:50" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)24_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_struct(AccessControlStorage)34_storage": { + "label": "struct AccessControlUpgradeable.AccessControlStorage", + "members": [ + { + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(BlocklistableStorage)1951_storage": { + "label": "struct BlocklistableUpgradeable.BlocklistableStorage", + "members": [ + { + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(InitializableStorage)145_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(PausableStorage)291_storage": { + "label": "struct PausableUpgradeable.PausableStorage", + "members": [ + { + "label": "_paused", + "type": "t_bool", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)24_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "hasRole", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_array(t_uint256)43_storage": { + "label": "uint256[43]", + "numberOfBytes": "1376" + }, + "t_enum(PaymentStatus)7373": { + "label": "enum ICardPaymentProcessorTypes.PaymentStatus", + "members": [ + "Nonexistent", + "Active", + "Revoked", + "Reversed" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_struct(AccountCashbackState)7252_storage)": { + "label": "mapping(address => struct ICardPaymentCashbackTypes.AccountCashbackState)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(Payment)7400_storage)": { + "label": "mapping(bytes32 => struct ICardPaymentProcessorTypes.Payment)", + "numberOfBytes": "32" + }, + "t_struct(AccountCashbackState)7252_storage": { + "label": "struct ICardPaymentCashbackTypes.AccountCashbackState", + "members": [ + { + "label": "totalAmount", + "type": "t_uint72", + "offset": 0, + "slot": "0" + }, + { + "label": "capPeriodStartAmount", + "type": "t_uint72", + "offset": 9, + "slot": "0" + }, + { + "label": "capPeriodStartTime", + "type": "t_uint32", + "offset": 18, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(Payment)7400_storage": { + "label": "struct ICardPaymentProcessorTypes.Payment", + "members": [ + { + "label": "status", + "type": "t_enum(PaymentStatus)7373", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint8", + "offset": 1, + "slot": "0" + }, + { + "label": "payer", + "type": "t_address", + "offset": 2, + "slot": "0" + }, + { + "label": "cashbackRate", + "type": "t_uint16", + "offset": 22, + "slot": "0" + }, + { + "label": "confirmedAmount", + "type": "t_uint64", + "offset": 24, + "slot": "0" + }, + { + "label": "sponsor", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "subsidyLimit", + "type": "t_uint64", + "offset": 20, + "slot": "1" + }, + { + "label": "reserve2", + "type": "t_uint32", + "offset": 28, + "slot": "1" + }, + { + "label": "baseAmount", + "type": "t_uint64", + "offset": 0, + "slot": "2" + }, + { + "label": "extraAmount", + "type": "t_uint64", + "offset": 8, + "slot": "2" + }, + { + "label": "cashbackAmount", + "type": "t_uint64", + "offset": 16, + "slot": "2" + }, + { + "label": "refundAmount", + "type": "t_uint64", + "offset": 24, + "slot": "2" + } + ], + "numberOfBytes": "96" + }, + "t_struct(PaymentStatistics)7414_storage": { + "label": "struct ICardPaymentProcessorTypes.PaymentStatistics", + "members": [ + { + "label": "totalUnconfirmedRemainder", + "type": "t_uint128", + "offset": 0, + "slot": "0" + }, + { + "label": "reserve1", + "type": "t_uint128", + "offset": 16, + "slot": "0" + }, + { + "label": "reserve2", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint128": { + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint72": { + "label": "uint72", + "numberOfBytes": "9" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Pausable": [ + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\utils\\PausableUpgradeable.sol:21", + "offset": 0, + "slot": "0" + } + ], + "erc7201:cloudwalk.storage.Blocklistable": [ + { + "contract": "BlocklistableUpgradeable", + "label": "blocklisted", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts\\base\\BlocklistableUpgradeable.sol:33", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.AccessControl": [ + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)24_storage)", + "src": "@openzeppelin\\contracts-upgradeable\\access\\AccessControlUpgradeable.sol:61", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin\\contracts-upgradeable\\proxy\\utils\\Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + } + } +} diff --git a/contracts/CardPaymentProcessor.sol b/contracts/CardPaymentProcessor.sol new file mode 100644 index 0000000..fee0581 --- /dev/null +++ b/contracts/CardPaymentProcessor.sol @@ -0,0 +1,1384 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { UUPSExtUpgradeable } from "./base/UUPSExtUpgradeable.sol"; + +import { BlocklistableUpgradeable } from "./base/BlocklistableUpgradeable.sol"; +import { PausableExtUpgradeable } from "./base/PausableExtUpgradeable.sol"; +import { RescuableUpgradeable } from "./base/RescuableUpgradeable.sol"; +import { AccessControlExtUpgradeable } from "./base/AccessControlExtUpgradeable.sol"; +import { Versionable } from "./base/Versionable.sol"; + +import { CardPaymentProcessorStorage } from "./CardPaymentProcessorStorage.sol"; +import { ICardPaymentProcessor } from "./interfaces/ICardPaymentProcessor.sol"; +import { ICardPaymentCashback } from "./interfaces/ICardPaymentCashback.sol"; + +/** + * @title CardPaymentProcessor contract + * @dev A wrapper contract for the card payment operations. + */ +contract CardPaymentProcessor is + CardPaymentProcessorStorage, + AccessControlExtUpgradeable, + BlocklistableUpgradeable, + PausableExtUpgradeable, + RescuableUpgradeable, + UUPSExtUpgradeable, + ICardPaymentProcessor, + ICardPaymentCashback, + Versionable +{ + using SafeERC20 for IERC20; + + // ------------------ Constants ------------------------------- // + + /// @dev The role of this contract owner. + bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); + + /// @dev The role of executor that is allowed to execute the card payment operations. + bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + + /// @dev The number of decimals that is used in the underlying token contract. + uint256 public constant TOKE_DECIMALS = 6; + + /** + * @dev The factor to represent the cashback rates in the contract, e.g. number 15 means 1.5% cashback rate. + * + * The formula to calculate cashback by an amount: `cashbackAmount = cashbackRate * amount / CASHBACK_FACTOR`. + */ + uint256 public constant CASHBACK_FACTOR = 1000; + + /// @dev The maximum allowable cashback rate in units of `CASHBACK_FACTOR`. + uint256 public constant MAX_CASHBACK_RATE = 500; + + /** + * @dev The coefficient used to round the cashback according to the formula: + * `roundedCashback = ((cashback + coef / 2) / coef) * coef`. + * + * Currently, it can only be changed by deploying a new implementation of the contract. + */ + uint256 public constant CASHBACK_ROUNDING_COEF = 10 ** (TOKE_DECIMALS - 2); + + /// @dev The cashback cap reset period. + uint256 public constant CASHBACK_CAP_RESET_PERIOD = 30 days; + + /// @dev The maximum cashback for a cap period. + uint256 public constant MAX_CASHBACK_FOR_CAP_PERIOD = 300 * 10 ** TOKE_DECIMALS; + + /// @dev Event addendum flag mask defining whether the payment is sponsored. + uint256 internal constant EVENT_ADDENDUM_FLAG_MASK_SPONSORED = 1; + + /// @dev Default version of the event addendum. + uint8 internal constant EVENT_ADDENDUM_DEFAULT_VERSION = 1; + + // ------------------ Events ---------------------------------- // + + /// @dev Emitted when the cash-out account is changed. + event CashOutAccountChanged( + address oldCashOutAccount, + address newCashOutAccount + ); + + // ------------------ Errors ---------------------------------- // + + /// @dev The zero payer address has been passed as a function argument. + error AccountZeroAddress(); + + /// @dev The cashback operations are already enabled. + error CashbackAlreadyEnabled(); + + /// @dev The cashback operations are already disabled. + error CashbackAlreadyDisabled(); + + /// @dev The cashback treasury address is the same as previously set one. + error CashbackTreasuryUnchanged(); + + /// @dev The cashback treasury address is not configured. + error CashbackTreasuryNotConfigured(); + + /// @dev The zero cashback treasury address has been passed as a function argument. + error CashbackTreasuryZeroAddress(); + + /// @dev The provided cashback rate exceeds the allowed maximum. + error CashbackRateExcess(); + + /// @dev A new cashback rate is the same as previously set one. + error CashbackRateUnchanged(); + + /// @dev The cash-out account is not configured. + error CashOutAccountNotConfigured(); + + /// @dev A new cash-out account is the same as the previously set one. + error CashOutAccountUnchanged(); + + /// @dev Thrown if the provided new implementation address is not of a card payment processor contract. + error ImplementationAddressInvalid(); + + /// @dev The requested confirmation amount does not meet the requirements. + error InappropriateConfirmationAmount(); + + /** + * @dev The payment with the provided ID has an inappropriate status. + * @param paymentId The ID of the payment that does not exist. + * @param currentStatus The current status of the payment. + */ + error InappropriatePaymentStatus(bytes32 paymentId, PaymentStatus currentStatus); + + /// @dev The requested refunding amount does not meet the requirements. + error InappropriateRefundingAmount(); + + /// @dev The requested or result or updated sum amount (base + extra) does not meet the requirements. + error InappropriateSumAmount(); + + /// @dev The requested subsidy limit is greater than the allowed maximum to store. + error OverflowOfSubsidyLimit(); + + /// @dev The requested or result or updated sum amount (base + extra) is greater than the allowed maximum to store. + error OverflowOfSumAmount(); + + /// @dev The zero payer address has been passed as a function argument. + error PayerZeroAddress(); + + /// @dev The payment with the provided ID already exists and is not revoked. + error PaymentAlreadyExistent(); + + /// @dev The array of payment confirmations is empty. + error PaymentConfirmationArrayEmpty(); + + /** + * @dev The payment with the provided ID does not exist. + * @param paymentId The ID of the payment that does not exist. + */ + error PaymentNonExistent(bytes32 paymentId); + + /// @dev Zero payment ID has been passed as a function argument. + error PaymentZeroId(); + + /// @dev The sponsor address is zero while the subsidy limit is non-zero. + error SponsorZeroAddress(); + + /// @dev The zero token address has been passed as a function argument. + error TokenZeroAddress(); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The initializer of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable . + * + * Requirements: + * + * - The passed token address must not be zero. + * + * @param token_ The address of a token to set as the underlying one. + */ + function initialize(address token_) external initializer { + __CardPaymentProcessor_init(token_); + } + + /** + * @dev The internal initializer of the upgradable contract. + * + * See {CardPaymentProcessor-initialize}. + */ + function __CardPaymentProcessor_init(address token_) internal onlyInitializing { + __Context_init_unchained(); + __ERC165_init_unchained(); + __AccessControl_init_unchained(); + __AccessControlExt_init_unchained(); + __Blocklistable_init_unchained(OWNER_ROLE); + __Pausable_init_unchained(); + __PausableExt_init_unchained(OWNER_ROLE); + __Rescuable_init_unchained(OWNER_ROLE); + __UUPSUpgradeable_init_unchained(); + + __CardPaymentProcessor_init_unchained(token_); + } + + /** + * @dev The internal unchained initializer of the upgradable contract. + * + * See {CardPaymentProcessor-initialize}. + */ + function __CardPaymentProcessor_init_unchained(address token_) internal onlyInitializing { + if (token_ == address(0)) { + revert TokenZeroAddress(); + } + + _token = token_; + + _setRoleAdmin(OWNER_ROLE, OWNER_ROLE); + _setRoleAdmin(EXECUTOR_ROLE, OWNER_ROLE); + + _grantRole(OWNER_ROLE, _msgSender()); + } + + // ------------------ Functions ------------------------------- // + + /// @dev Contains parameters of a payment making operation. + struct MakingOperation { + bytes32 paymentId; + address payer; + address sponsor; + uint256 cashbackRate; + uint256 baseAmount; + uint256 extraAmount; + uint256 subsidyLimit; + uint256 cashbackAmount; + uint256 payerSumAmount; + uint256 sponsorSumAmount; + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The payment account address must not be zero. + * - The payment ID must not be zero. + * - The payment linked with the provided ID must be revoked or not exist. + * - The requested cashback rate must not exceed the maximum allowable cashback rate defined in the contract. + * - The sum of the provided base and extra amounts must not exceed the max 64-bit unsigned integer. + * - The provided subsidy limit must not exceed the max 64-bit unsigned integer. + * - The provided confirmation amount must not exceed the sum amount of the payment. + */ + function makePaymentFor( + bytes32 paymentId, + address payer, + uint256 baseAmount, + uint256 extraAmount, + address sponsor, + uint256 subsidyLimit, + int256 cashbackRate_, + uint256 confirmationAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + if (payer == address(0)) { + revert PayerZeroAddress(); + } + uint256 cashbackRateActual; + if (cashbackRate_ < 0) { + cashbackRateActual = _cashbackRate; + } else { + cashbackRateActual = uint256(cashbackRate_); + if (cashbackRateActual > MAX_CASHBACK_RATE) { + revert CashbackRateExcess(); + } + } + MakingOperation memory operation = MakingOperation({ + paymentId: paymentId, + payer: payer, + sponsor: sponsor, + cashbackRate: cashbackRateActual, + baseAmount: baseAmount, + extraAmount: extraAmount, + subsidyLimit: subsidyLimit, + cashbackAmount: 0, + payerSumAmount: 0, + sponsorSumAmount: 0 + }); + + _makePayment(operation); + if (confirmationAmount > 0) { + _confirmPaymentWithTransfer(paymentId, confirmationAmount); + } + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The payment account address must not be zero. + * - The payment ID must not be zero. + * - The payment linked with the provided ID must be revoked or not exist. + */ + function makeCommonPaymentFor( + bytes32 paymentId, + address payer, + uint256 baseAmount, + uint256 extraAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + if (payer == address(0)) { + revert PayerZeroAddress(); + } + + MakingOperation memory operation = MakingOperation({ + paymentId: paymentId, + payer: payer, + sponsor: address(0), + cashbackRate: _cashbackRate, + baseAmount: baseAmount, + extraAmount: extraAmount, + subsidyLimit: 0, + cashbackAmount: 0, + payerSumAmount: 0, + sponsorSumAmount: 0 + }); + + _makePayment(operation); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + * - The the new base amount plus the new extra amount must not be less than the the existing refund amount. + */ + function updatePayment( + bytes32 paymentId, + uint256 newBaseAmount, + uint256 newExtraAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + _updatePayment( + paymentId, + newBaseAmount, + newExtraAmount, + UpdatingOperationKind.Full + ); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + */ + function reversePayment(bytes32 paymentId) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + _cancelPayment(paymentId, PaymentStatus.Reversed); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + */ + function revokePayment(bytes32 paymentId) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + _cancelPayment(paymentId, PaymentStatus.Revoked); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + */ + function confirmPayment( + bytes32 paymentId, + uint256 confirmationAmount + ) public whenNotPaused onlyRole(EXECUTOR_ROLE) { + _confirmPaymentWithTransfer(paymentId, confirmationAmount); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input array must not be empty. + * - All payment IDs in the input array must not be zero. + */ + function confirmPayments( + PaymentConfirmation[] calldata paymentConfirmations + ) public whenNotPaused onlyRole(EXECUTOR_ROLE) { + if (paymentConfirmations.length == 0) { + revert PaymentConfirmationArrayEmpty(); + } + + uint256 totalConfirmedAmount = 0; + for (uint256 i = 0; i < paymentConfirmations.length; i++) { + totalConfirmedAmount += _confirmPayment( + paymentConfirmations[i].paymentId, + paymentConfirmations[i].amount + ); + } + + _paymentStatistics.totalUnconfirmedRemainder = uint128( + uint256(_paymentStatistics.totalUnconfirmedRemainder) - totalConfirmedAmount + ); + IERC20(_token).safeTransfer(_requireCashOutAccount(), totalConfirmedAmount); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + * - The the new base amount plus the new extra amount must not be less than the the existing refund amount. + */ + function updateLazyAndConfirmPayment( + bytes32 paymentId, + uint256 newBaseAmount, + uint256 newExtraAmount, + uint256 confirmationAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + _updatePayment( + paymentId, + newBaseAmount, + newExtraAmount, + UpdatingOperationKind.Lazy + ); + _confirmPaymentWithTransfer(paymentId, confirmationAmount); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The input payment ID must not be zero. + * - The result refund amount of the payment must not be higher than the new extra amount plus the base amount. + */ + function refundPayment( + bytes32 paymentId, + uint256 refundingAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + _refundPayment(paymentId, refundingAmount); + } + + /** + * @inheritdoc ICardPaymentProcessor + * + * @dev Requirements: + * + * - The contract must not be paused. + * - The caller must have the {EXECUTOR_ROLE} role. + * - The account address must not be zero. + */ + function refundAccount( + address account, + uint256 refundingAmount + ) external whenNotPaused onlyRole(EXECUTOR_ROLE) { + if (account == address(0)) { + revert AccountZeroAddress(); + } + + emit AccountRefunded( + account, + refundingAmount, + bytes("") + ); + + IERC20(_token).safeTransferFrom(_requireCashOutAccount(), account, refundingAmount); + } + + /** + * @dev Sets the cash-out account address. + * + * Requirements: + * + * - The caller must have the {OWNER_ROLE} role. + * - The new cash-out account must differ from the previously set one. + */ + function setCashOutAccount(address newCashOutAccount) external onlyRole(OWNER_ROLE) { + address oldCashOutAccount = _cashOutAccount; + + if (newCashOutAccount == oldCashOutAccount) { + revert CashOutAccountUnchanged(); + } + + _cashOutAccount = newCashOutAccount; + + emit CashOutAccountChanged(oldCashOutAccount, newCashOutAccount); + } + + /** + * @inheritdoc ICardPaymentCashback + * + * @dev Requirements: + * + * - The caller must have the {OWNER_ROLE} role. + * - The new cashback treasury address must not be zero. + * - The new cashback treasury address must not be equal to the current set one. + */ + function setCashbackTreasury(address newCashbackTreasury) external onlyRole(OWNER_ROLE) { + address oldCashbackTreasury = _cashbackTreasury; + + // This is needed to allow cashback changes for any existing active payments. + if (newCashbackTreasury == address(0)) { + revert CashbackTreasuryZeroAddress(); + } + if (oldCashbackTreasury == newCashbackTreasury) { + revert CashbackTreasuryUnchanged(); + } + + _cashbackTreasury = newCashbackTreasury; + + emit CashbackTreasuryChanged(oldCashbackTreasury, newCashbackTreasury); + } + + /** + * @inheritdoc ICardPaymentCashback + * + * @dev Requirements: + * + * - The caller must have the {OWNER_ROLE} role. + * - The new rate must differ from the previously set one. + * - The new rate must not exceed the allowable maximum specified in the {MAX_CASHBACK_RATE} constant. + */ + function setCashbackRate(uint256 newCashbackRate) external onlyRole(OWNER_ROLE) { + uint256 oldCashbackRate = _cashbackRate; + if (newCashbackRate == oldCashbackRate) { + revert CashbackRateUnchanged(); + } + if (newCashbackRate > MAX_CASHBACK_RATE) { + revert CashbackRateExcess(); + } + + _cashbackRate = uint16(newCashbackRate); + + emit CashbackRateChanged(oldCashbackRate, newCashbackRate); + } + + /** + * @inheritdoc ICardPaymentCashback + * + * @dev Requirements: + * + * - The caller must have the {OWNER_ROLE} role. + * - The cashback operations must not be already enabled. + * - The address of the current cashback treasury must not be zero. + */ + function enableCashback() external onlyRole(OWNER_ROLE) { + if (_cashbackEnabled) { + revert CashbackAlreadyEnabled(); + } + if (_cashbackTreasury == address(0)) { + revert CashbackTreasuryNotConfigured(); + } + + _cashbackEnabled = true; + + emit CashbackEnabled(); + } + + /** + * @inheritdoc ICardPaymentCashback + * + * @dev Requirements: + * + * - The caller must have the {OWNER_ROLE} role. + * - The cashback operations must not be already disabled. + */ + function disableCashback() external onlyRole(OWNER_ROLE) { + if (!_cashbackEnabled) { + revert CashbackAlreadyDisabled(); + } + + _cashbackEnabled = false; + + emit CashbackDisabled(); + } + + // ------------------ View functions -------------------------- // + + /// @inheritdoc ICardPaymentProcessor + function cashOutAccount() external view returns (address) { + return _cashOutAccount; + } + + /// @inheritdoc ICardPaymentProcessor + function token() external view returns (address) { + return _token; + } + + /// @inheritdoc ICardPaymentProcessor + function getPayment(bytes32 paymentId) external view returns (Payment memory) { + return _payments[paymentId]; + } + + /// @inheritdoc ICardPaymentProcessor + function getPaymentStatistics() external view returns (PaymentStatistics memory) { + return _paymentStatistics; + } + + /// @inheritdoc ICardPaymentCashback + function cashbackTreasury() external view returns (address) { + return _cashbackTreasury; + } + + /// @inheritdoc ICardPaymentCashback + function cashbackEnabled() external view returns (bool) { + return _cashbackEnabled; + } + + /// @inheritdoc ICardPaymentCashback + function cashbackRate() external view returns (uint256) { + return _cashbackRate; + } + + /// @inheritdoc ICardPaymentCashback + function getAccountCashbackState(address account) external view returns (AccountCashbackState memory) { + return _accountCashbackStates[account]; + } + // ------------------ Pure functions -------------------------- // + + /// @inheritdoc ICardPaymentProcessor + function proveCardPaymentProcessor() external pure {} + + // ------------------ Internal functions ---------------------- // + + /// @dev Making a payment internally. + function _makePayment(MakingOperation memory operation) internal { + if (operation.paymentId == 0) { + revert PaymentZeroId(); + } + + Payment storage storedPayment = _payments[operation.paymentId]; + + PaymentStatus status = storedPayment.status; + if (status != PaymentStatus.Nonexistent && status != PaymentStatus.Revoked) { + revert PaymentAlreadyExistent(); + } + + _processPaymentMaking(operation); + _sendCashback(operation); + _storeNewPayment(storedPayment, operation); + + address sponsor = operation.sponsor; + uint256 eventFlags = _defineEventFlags(sponsor); + bytes memory addendum = abi.encodePacked( + EVENT_ADDENDUM_DEFAULT_VERSION, + uint8(eventFlags), + uint64(operation.baseAmount), + uint64(operation.extraAmount), + uint64(operation.payerSumAmount) + ); + if (eventFlags & EVENT_ADDENDUM_FLAG_MASK_SPONSORED != 0) { + addendum = abi.encodePacked( + addendum, + sponsor, + uint64(operation.sponsorSumAmount) + ); + } + emit PaymentMade( + operation.paymentId, + operation.payer, + addendum + ); + } + + /// @dev Kind of a payment updating operation. + enum UpdatingOperationKind { + Full, // 0 The operation is executed fully regardless of the new values of the base amount and extra amount. + Lazy // 1 The operation is executed only if the new amounts differ from the current ones of the payment. + } + + /// @dev Updates the base amount and extra amount of a payment internally. + function _updatePayment( + bytes32 paymentId, + uint256 newBaseAmount, + uint256 newExtraAmount, + UpdatingOperationKind kind + ) internal { + if (paymentId == 0) { + revert PaymentZeroId(); + } + + Payment storage storedPayment = _payments[paymentId]; + Payment memory payment = storedPayment; + + if ( + kind == UpdatingOperationKind.Lazy && + payment.baseAmount == newBaseAmount && + payment.extraAmount == newExtraAmount + ) { + return; + } + + _checkActivePaymentStatus(paymentId, payment.status); + _checkPaymentSumAmount(newBaseAmount + newExtraAmount, payment.refundAmount); + + PaymentDetails memory oldPaymentDetails = _definePaymentDetails(payment, PaymentRecalculationKind.None); + uint256 oldBaseAmount = payment.baseAmount; + uint256 oldExtraAmount = payment.extraAmount; + payment.baseAmount = uint64(newBaseAmount); + payment.extraAmount = uint64(newExtraAmount); + PaymentDetails memory newPaymentDetails = _definePaymentDetails(payment, PaymentRecalculationKind.Full); + + _processPaymentChange(paymentId, payment, oldPaymentDetails, newPaymentDetails); + _storeChangedPayment(storedPayment, payment, newPaymentDetails); + _updatePaymentStatistics(oldPaymentDetails, newPaymentDetails); + + address sponsor = payment.sponsor; + uint256 eventFlags = _defineEventFlags(sponsor); + bytes memory addendum = abi.encodePacked( + EVENT_ADDENDUM_DEFAULT_VERSION, + uint8(eventFlags), + uint64(oldBaseAmount), + uint64(newBaseAmount), + uint64(oldExtraAmount), + uint64(newExtraAmount), + uint64(oldPaymentDetails.payerSumAmount), + uint64(newPaymentDetails.payerSumAmount) + ); + if (eventFlags & EVENT_ADDENDUM_FLAG_MASK_SPONSORED != 0) { + addendum = abi.encodePacked( + addendum, + sponsor, + uint64(oldPaymentDetails.sponsorSumAmount), + uint64(newPaymentDetails.sponsorSumAmount) + ); + } + emit PaymentUpdated( + paymentId, + payment.payer, + addendum + ); + } + + /// @dev Cancels a payment internally. + function _cancelPayment( + bytes32 paymentId, + PaymentStatus targetStatus + ) internal { + if (paymentId == 0) { + revert PaymentZeroId(); + } + + Payment storage storedPayment = _payments[paymentId]; + Payment memory payment = storedPayment; + + _checkActivePaymentStatus(paymentId, payment.status); + + PaymentDetails memory oldPaymentDetails = _definePaymentDetails(payment, PaymentRecalculationKind.None); + PaymentDetails memory newPaymentDetails; // All fields are zero + + _processPaymentChange(paymentId, payment, oldPaymentDetails, newPaymentDetails); + _updatePaymentStatistics(oldPaymentDetails, newPaymentDetails); + + storedPayment.status = targetStatus; + + address sponsor = payment.sponsor; + uint256 eventFlags = _defineEventFlags(sponsor); + bytes memory addendum = abi.encodePacked( + EVENT_ADDENDUM_DEFAULT_VERSION, + uint8(eventFlags), + uint64(payment.baseAmount), + uint64(payment.extraAmount), + uint64(oldPaymentDetails.payerRemainder) + ); + if (eventFlags & EVENT_ADDENDUM_FLAG_MASK_SPONSORED != 0) { + addendum = abi.encodePacked( + addendum, + sponsor, + uint64(oldPaymentDetails.sponsorRemainder) + ); + } + + if (targetStatus == PaymentStatus.Revoked) { + emit PaymentRevoked( + paymentId, + payment.payer, + addendum + ); + } else { + emit PaymentReversed( + paymentId, + payment.payer, + addendum + ); + } + } + + /// @dev Confirms a payment internally. + function _confirmPayment( + bytes32 paymentId, + uint256 confirmationAmount + ) internal returns (uint256) { + if (paymentId == 0) { + revert PaymentZeroId(); + } + Payment storage payment = _payments[paymentId]; + _checkActivePaymentStatus(paymentId, payment.status); + + if (confirmationAmount == 0) { + return confirmationAmount; + } + + uint256 remainder = uint256(payment.baseAmount) + uint256(payment.extraAmount) - uint256(payment.refundAmount); + uint256 oldConfirmedAmount = payment.confirmedAmount; + uint256 newConfirmedAmount = oldConfirmedAmount + confirmationAmount; + if (newConfirmedAmount > remainder) { + revert InappropriateConfirmationAmount(); + } + + payment.confirmedAmount = uint64(newConfirmedAmount); + _emitPaymentConfirmedAmountChanged( + paymentId, + payment.payer, + payment.sponsor, + oldConfirmedAmount, + newConfirmedAmount + ); + + return confirmationAmount; + } + + /// @dev Confirms a payment internally with the token transfer to the cash-out account. + function _confirmPaymentWithTransfer( + bytes32 paymentId, + uint256 confirmationAmount + ) internal { + confirmationAmount = _confirmPayment(paymentId, confirmationAmount); + _paymentStatistics.totalUnconfirmedRemainder = uint128( + uint256(_paymentStatistics.totalUnconfirmedRemainder) - confirmationAmount + ); + IERC20(_token).safeTransfer(_requireCashOutAccount(), confirmationAmount); + } + + /// @dev Makes a refund for a payment internally. + function _refundPayment( + bytes32 paymentId, + uint256 refundingAmount + ) internal { + if (paymentId == 0) { + revert PaymentZeroId(); + } + + Payment storage storedPayment = _payments[paymentId]; + Payment memory payment = storedPayment; + _checkActivePaymentStatus(paymentId, payment.status); + + uint256 newRefundAmount = uint256(payment.refundAmount) + refundingAmount; + if (newRefundAmount > uint256(payment.baseAmount) + uint256(payment.extraAmount)) { + revert InappropriateRefundingAmount(); + } + + PaymentDetails memory oldPaymentDetails = _definePaymentDetails(payment, PaymentRecalculationKind.None); + payment.refundAmount = uint64(newRefundAmount); + PaymentDetails memory newPaymentDetails = _definePaymentDetails(payment, PaymentRecalculationKind.Full); + + _processPaymentChange(paymentId, payment, oldPaymentDetails, newPaymentDetails); + _storeChangedPayment(storedPayment, payment, newPaymentDetails); + _updatePaymentStatistics(oldPaymentDetails, newPaymentDetails); + + address sponsor = payment.sponsor; + uint256 eventFlags = _defineEventFlags(sponsor); + bytes memory addendum = abi.encodePacked( + EVENT_ADDENDUM_DEFAULT_VERSION, + uint8(eventFlags), + uint64(oldPaymentDetails.payerSumAmount - oldPaymentDetails.payerRemainder), // oldPayerRefundAmount + uint64(newPaymentDetails.payerSumAmount - newPaymentDetails.payerRemainder) // newPayerRefundAmount + ); + if (eventFlags & EVENT_ADDENDUM_FLAG_MASK_SPONSORED != 0) { + addendum = abi.encodePacked( + addendum, + sponsor, + uint64(oldPaymentDetails.sponsorSumAmount - oldPaymentDetails.sponsorRemainder),//oldSponsorRefundAmount + uint64(newPaymentDetails.sponsorSumAmount - newPaymentDetails.sponsorRemainder) //newSponsorRefundAmount + ); + } + + emit PaymentRefunded( + paymentId, + payment.payer, + addendum + ); + } + + /// @dev Executes token transfers related to a new payment. + function _processPaymentMaking(MakingOperation memory operation) internal { + uint256 sumAmount = operation.baseAmount + operation.extraAmount; + if (sumAmount > type(uint64).max) { + revert OverflowOfSumAmount(); + } + if (operation.sponsor == address(0) && operation.subsidyLimit != 0) { + revert SponsorZeroAddress(); + } + if (operation.subsidyLimit > type(uint64).max) { + revert OverflowOfSubsidyLimit(); + } + (uint256 payerSumAmount, uint256 sponsorSumAmount) = _defineSumAmountParts(sumAmount, operation.subsidyLimit); + IERC20 erc20Token = IERC20(_token); + operation.payerSumAmount = payerSumAmount; + operation.sponsorSumAmount = sponsorSumAmount; + + erc20Token.safeTransferFrom(operation.payer, address(this), payerSumAmount); + if (operation.sponsor != address(0)) { + erc20Token.safeTransferFrom(operation.sponsor, address(this), sponsorSumAmount); + } + } + + /// @dev Checks if the status of a payment is active. Otherwise reverts with an appropriate error. + function _checkActivePaymentStatus(bytes32 paymentId, PaymentStatus status) internal pure { + if (status == PaymentStatus.Nonexistent) { + revert PaymentNonExistent(paymentId); + } + if (status != PaymentStatus.Active) { + revert InappropriatePaymentStatus(paymentId, status); + } + } + + /// @dev Checks if the payment sum amount and the refund amount meet the requirements. + function _checkPaymentSumAmount(uint256 sumAmount, uint256 refundAmount) internal pure { + if (refundAmount > sumAmount) { + revert InappropriateSumAmount(); + } + if (sumAmount > type(uint64).max) { + revert OverflowOfSumAmount(); + } + } + + /// @dev Executes token transfers related to changes of a payment and emits additional events. + function _processPaymentChange( + bytes32 paymentId, + Payment memory payment, + PaymentDetails memory oldPaymentDetails, + PaymentDetails memory newPaymentDetails + ) internal { + IERC20 erc20Token = IERC20(_token); + + // Cash-out account token transferring + if (newPaymentDetails.confirmedAmount < oldPaymentDetails.confirmedAmount) { + uint256 amount = oldPaymentDetails.confirmedAmount - newPaymentDetails.confirmedAmount; + erc20Token.safeTransferFrom(_requireCashOutAccount(), address(this), amount); + _emitPaymentConfirmedAmountChanged( + paymentId, + payment.payer, + payment.sponsor, + oldPaymentDetails.confirmedAmount, + newPaymentDetails.confirmedAmount + ); + } + + // Increase cashback ahead of payer token transfers to avoid conner cases with lack of payer balance + if (newPaymentDetails.cashbackAmount > oldPaymentDetails.cashbackAmount) { + uint256 amount = newPaymentDetails.cashbackAmount - oldPaymentDetails.cashbackAmount; + CashbackOperationStatus status; + (status, amount) = _increaseCashback(payment.payer, amount); + newPaymentDetails.cashbackAmount = oldPaymentDetails.cashbackAmount + amount; + emit CashbackIncreased( + paymentId, + payment.payer, + status, + oldPaymentDetails.cashbackAmount, + newPaymentDetails.cashbackAmount + ); + } + + // Payer token transferring + { + int256 amount = -(int256(newPaymentDetails.payerRemainder) - int256(oldPaymentDetails.payerRemainder)); + int256 cashbackChange = int256(newPaymentDetails.cashbackAmount) - int256(oldPaymentDetails.cashbackAmount); + if (cashbackChange < 0) { + amount += cashbackChange; + } + if (amount < 0) { + erc20Token.safeTransferFrom(payment.payer, address(this), uint256(-amount)); + } else if (amount > 0) { + erc20Token.safeTransfer(payment.payer, uint256(amount)); + } + } + + // Cashback processing if the cashback amount decreases + if (newPaymentDetails.cashbackAmount < oldPaymentDetails.cashbackAmount) { + uint256 amount = oldPaymentDetails.cashbackAmount - newPaymentDetails.cashbackAmount; + CashbackOperationStatus status; + (status, amount) = _revokeCashback(payment.payer, amount); + newPaymentDetails.cashbackAmount = oldPaymentDetails.cashbackAmount - amount; + emit CashbackRevoked( + paymentId, + payment.payer, + status, + oldPaymentDetails.cashbackAmount, + newPaymentDetails.cashbackAmount + ); + } + + // Sponsor token transferring + address sponsor = payment.sponsor; + if (payment.sponsor != address(0)) { + if (newPaymentDetails.sponsorRemainder > oldPaymentDetails.sponsorRemainder) { + uint256 amount = newPaymentDetails.sponsorRemainder - oldPaymentDetails.sponsorRemainder; + erc20Token.safeTransferFrom(sponsor, address(this), amount); + } else if (newPaymentDetails.sponsorRemainder < oldPaymentDetails.sponsorRemainder) { + uint256 amount = oldPaymentDetails.sponsorRemainder - newPaymentDetails.sponsorRemainder; + erc20Token.safeTransfer(sponsor, amount); + } + } + } + + /// @dev Emits an appropriate event when the confirmed amount is changed for a payment. + function _emitPaymentConfirmedAmountChanged( + bytes32 paymentId, + address payer, + address sponsor, + uint256 oldConfirmedAmount, + uint256 newConfirmedAmount + ) internal { + uint256 eventFlags = _defineEventFlags(sponsor); + bytes memory addendum = abi.encodePacked( + EVENT_ADDENDUM_DEFAULT_VERSION, + uint8(eventFlags), + uint64(oldConfirmedAmount), + uint64(newConfirmedAmount) + ); + if (eventFlags & EVENT_ADDENDUM_FLAG_MASK_SPONSORED != 0) { + addendum = abi.encodePacked( + addendum, + sponsor + ); + } + + emit PaymentConfirmedAmountChanged( + paymentId, + payer, + addendum + ); + } + + /// @dev Sends cashback related to a payment. + function _sendCashback(MakingOperation memory operation) internal { + if (operation.cashbackRate == 0) { + return; + } + // Condition (treasury != address(0)) is guaranteed by the current contract logic. So it is not checked here + if (_cashbackEnabled) { + uint256 basePaymentAmount = _definePayerBaseAmount(operation.baseAmount, operation.subsidyLimit); + uint256 amount = _calculateCashback(basePaymentAmount, operation.cashbackRate); + CashbackOperationStatus status; + (status, amount) = _increaseCashback(operation.payer, amount); + emit CashbackSent( + operation.paymentId, + operation.payer, + status, + amount + ); + operation.cashbackAmount = amount; + } else { + operation.cashbackRate = 0; + } + } + + /// @dev Revokes partially or fully cashback related to a payment. + function _revokeCashback( + address payer, + uint256 amount + ) internal returns (CashbackOperationStatus status, uint256 revokedAmount) { + address treasury = _cashbackTreasury; + // Condition (treasury != address(0)) is guaranteed by the current contract logic. So it is not checked here + status = CashbackOperationStatus.Success; + (bool success, bytes memory returnData) = _token.call( + abi.encodeWithSelector(IERC20.transfer.selector, treasury, amount) + ); + bool transferred = success && (returnData.length == 0 || abi.decode(returnData, (bool))); // Test coverage tip + if (transferred) { + _reduceTotalCashback(payer, amount); + revokedAmount = amount; + } else { + status = CashbackOperationStatus.Failed; + revokedAmount = 0; + } + } + + /// @dev Increases cashback related to a payment. + function _increaseCashback( + address payer, + uint256 amount + ) internal returns (CashbackOperationStatus status, uint256 increasedAmount) { + address treasury = _cashbackTreasury; + // Condition (treasury != address(0)) is guaranteed by the current contract logic. So it is not checked here + (status, increasedAmount) = _updateAccountCashbackState(payer, amount); + if (status == CashbackOperationStatus.Success || status == CashbackOperationStatus.Partial) { + (bool success, bytes memory returnData) = _token.call( + abi.encodeWithSelector(IERC20.transferFrom.selector, treasury, payer, increasedAmount) + ); + bool transferred = success && (returnData.length == 0 || abi.decode(returnData, (bool))); + if (!transferred) { + _reduceTotalCashback(payer, increasedAmount); + status = CashbackOperationStatus.Failed; + increasedAmount = 0; + } + } + } + + /// @dev Updates the account cashback state and checks the cashback cap. + function _updateAccountCashbackState( + address account, + uint256 amount + ) internal returns (CashbackOperationStatus cashbackStatus, uint256 acceptedAmount) { + AccountCashbackState storage state = _accountCashbackStates[account]; + + uint256 totalAmount = state.totalAmount; + uint256 capPeriodStartTime = state.capPeriodStartTime; + uint256 capPeriodStartAmount = state.capPeriodStartAmount; + uint256 capPeriodCollectedCashback = 0; + + unchecked { + uint256 blockTimeStamp = uint32(block.timestamp); // take only last 32 bits of the block timestamp + if (uint32(blockTimeStamp - capPeriodStartTime) > CASHBACK_CAP_RESET_PERIOD) { + capPeriodStartTime = blockTimeStamp; + } else { + capPeriodCollectedCashback = totalAmount - capPeriodStartAmount; + } + + if (capPeriodCollectedCashback < MAX_CASHBACK_FOR_CAP_PERIOD) { + uint256 leftAmount = MAX_CASHBACK_FOR_CAP_PERIOD - capPeriodCollectedCashback; + if (leftAmount >= amount) { + acceptedAmount = amount; + cashbackStatus = CashbackOperationStatus.Success; + } else { + acceptedAmount = leftAmount; + cashbackStatus = CashbackOperationStatus.Partial; + } + } else { + cashbackStatus = CashbackOperationStatus.Capped; + } + } + + if (capPeriodCollectedCashback == 0) { + capPeriodStartAmount = totalAmount; + } + + state.totalAmount = uint72(totalAmount) + uint72(acceptedAmount); + state.capPeriodStartAmount = uint72(capPeriodStartAmount); + state.capPeriodStartTime = uint32(capPeriodStartTime); + } + + /// @dev Reduces the total cashback amount for an account. + function _reduceTotalCashback(address account, uint256 amount) internal { + AccountCashbackState storage state = _accountCashbackStates[account]; + state.totalAmount = uint72(uint256(state.totalAmount) - amount); + } + + /// @dev Stores the data of a newly created payment. + function _storeNewPayment( + Payment storage storedPayment, + MakingOperation memory operation + ) internal { + PaymentStatus oldStatus = storedPayment.status; + storedPayment.status = PaymentStatus.Active; + storedPayment.payer = operation.payer; + storedPayment.cashbackRate = uint16(operation.cashbackRate); + storedPayment.confirmedAmount = 0; + if (oldStatus != PaymentStatus.Nonexistent || operation.sponsor != address(0)) { + storedPayment.sponsor = operation.sponsor; + storedPayment.subsidyLimit = uint64(operation.subsidyLimit); + } + storedPayment.baseAmount = uint64(operation.baseAmount); + storedPayment.extraAmount = uint64(operation.extraAmount); + storedPayment.cashbackAmount = uint64(operation.cashbackAmount); + storedPayment.refundAmount = 0; + + _paymentStatistics.totalUnconfirmedRemainder += uint128(operation.baseAmount + operation.extraAmount); + } + + /// @dev Stores the data of a changed payment. + function _storeChangedPayment( + Payment storage storedPayment, + Payment memory changedPayment, + PaymentDetails memory newPaymentDetails + ) internal { + storedPayment.baseAmount = changedPayment.baseAmount; + storedPayment.extraAmount = changedPayment.extraAmount; + storedPayment.cashbackAmount = uint64(newPaymentDetails.cashbackAmount); + storedPayment.refundAmount = changedPayment.refundAmount; + + if (newPaymentDetails.confirmedAmount != changedPayment.confirmedAmount) { + storedPayment.confirmedAmount = uint64(newPaymentDetails.confirmedAmount); + } + } + + /// @dev Updates statistics of all payments. + function _updatePaymentStatistics( + PaymentDetails memory oldPaymentDetails, + PaymentDetails memory newPaymentDetails + ) internal { + int256 paymentReminderChange = + (int256(newPaymentDetails.payerRemainder) + int256(newPaymentDetails.sponsorRemainder)) - + (int256(oldPaymentDetails.payerRemainder) + int256(oldPaymentDetails.sponsorRemainder)); + int256 paymentConfirmedAmountChange = + int256(newPaymentDetails.confirmedAmount) - int256(oldPaymentDetails.confirmedAmount); + + int256 unconfirmedReminderChange = paymentReminderChange - paymentConfirmedAmountChange; + + // This is done to protect against possible overflow/underflow of the `totalUnconfirmedRemainder` variable + if (unconfirmedReminderChange >= 0) { + _paymentStatistics.totalUnconfirmedRemainder += uint128(uint256(unconfirmedReminderChange)); + } else { + _paymentStatistics.totalUnconfirmedRemainder = uint128( + uint256(_paymentStatistics.totalUnconfirmedRemainder) - uint256(-unconfirmedReminderChange) + ); + } + } + + /// @dev Calculates cashback according to the amount and the rate. + function _calculateCashback(uint256 amount, uint256 cashbackRate_) internal pure returns (uint256) { + uint256 cashback = (amount * cashbackRate_) / CASHBACK_FACTOR; + return ((cashback + CASHBACK_ROUNDING_COEF / 2) / CASHBACK_ROUNDING_COEF) * CASHBACK_ROUNDING_COEF; + } + + /// @dev Contains details of a payment. + struct PaymentDetails { + uint256 confirmedAmount; + uint256 cashbackAmount; + uint256 payerSumAmount; + uint256 sponsorSumAmount; + uint256 payerRemainder; + uint256 sponsorRemainder; + } + + /// @dev Kind of a payment recalculation operation. + enum PaymentRecalculationKind { + None, + Full + } + + /// @dev Defines details of a payment. + function _definePaymentDetails( + Payment memory payment, + PaymentRecalculationKind kind + ) internal pure returns (PaymentDetails memory) { + uint256 sumAmount; + unchecked { + sumAmount = uint256(payment.baseAmount) + uint256(payment.extraAmount); + } + uint256 payerBaseAmount = _definePayerBaseAmount(payment.baseAmount, payment.subsidyLimit); + (uint256 payerSumAmount, uint256 sponsorSumAmount) = _defineSumAmountParts(sumAmount, payment.subsidyLimit); + uint256 sponsorRefund = _defineSponsorRefund(payment.refundAmount, payment.baseAmount, payment.subsidyLimit); + uint256 payerRefund = uint256(payment.refundAmount) - sponsorRefund; + uint256 cashbackAmount = payment.cashbackAmount; + uint256 confirmedAmount = payment.confirmedAmount; + if (kind != PaymentRecalculationKind.None) { + confirmedAmount = _defineNewConfirmedAmount(confirmedAmount, sumAmount - payment.refundAmount); + cashbackAmount = _defineNewCashback(payerBaseAmount, payerRefund, payment.cashbackRate); + } + PaymentDetails memory details = PaymentDetails({ + confirmedAmount: confirmedAmount, + cashbackAmount: cashbackAmount, + payerSumAmount: payerSumAmount, + sponsorSumAmount: sponsorSumAmount, + payerRemainder: payerSumAmount - payerRefund, + sponsorRemainder: sponsorSumAmount - sponsorRefund + }); + return details; + } + + /// @dev Defines the payer part of a payment base amount according to a subsidy limit. + function _definePayerBaseAmount(uint256 paymentBaseAmount, uint256 subsidyLimit) internal pure returns (uint256) { + if (paymentBaseAmount > subsidyLimit) { + return paymentBaseAmount - subsidyLimit; + } else { + return 0; + } + } + + /// @dev Defines the payer and sponsor parts of a payment sum amount according to a subsidy limit. + function _defineSumAmountParts( + uint256 paymentSumAmount, + uint256 subsidyLimit + ) internal pure returns (uint256 payerSumAmount, uint256 sponsorSumAmount) { + if (subsidyLimit >= paymentSumAmount) { + sponsorSumAmount = paymentSumAmount; + payerSumAmount = 0; + } else { + sponsorSumAmount = subsidyLimit; + payerSumAmount = paymentSumAmount - subsidyLimit; + } + } + + /// @dev Defines the sponsor refund amount according to a subsidy limit. + function _defineSponsorRefund( + uint256 refundAmount, + uint256 baseAmount, + uint256 subsidyLimit + ) internal pure returns (uint256) { + if (baseAmount > subsidyLimit) { + refundAmount = (refundAmount * subsidyLimit) / baseAmount; + } + if (refundAmount > subsidyLimit) { + refundAmount = subsidyLimit; + } + return refundAmount; + } + + /// @dev Defines the new confirmed amount of a payment according to the new old confirmed amount and the remainder. + function _defineNewConfirmedAmount( + uint256 oldConfirmedAmount, + uint256 commonRemainder + ) internal pure returns (uint256) { + if (oldConfirmedAmount > commonRemainder) { + return commonRemainder; + } else { + return oldConfirmedAmount; + } + } + + /// @dev Defines the new cashback amount of a payment according to the payer base amount and refund amount. + function _defineNewCashback( + uint256 payerBaseAmount, + uint256 payerRefund, + uint256 cashbackRate_ + ) internal pure returns (uint256) { + if (cashbackRate_ == 0 || payerBaseAmount <= payerRefund) { + return 0; + } + return _calculateCashback(payerBaseAmount - payerRefund, cashbackRate_); + } + + /// @dev Checks if the cash-out account exists and returns if it does. Otherwise reverts the execution. + function _requireCashOutAccount() internal view returns (address account) { + account = _cashOutAccount; + if (account == address(0)) { + revert CashOutAccountNotConfigured(); + } + } + + /// @dev Defines event flags according to the input parameters. + function _defineEventFlags(address sponsor) internal pure returns (uint256) { + uint256 eventFlags = 0; + if (sponsor != address(0)) { + eventFlags |= EVENT_ADDENDUM_FLAG_MASK_SPONSORED; + } + return eventFlags; + } + + /** + * @dev The upgrade validation function for the UUPSExtUpgradeable contract. + * @param newImplementation The address of the new implementation. + */ + function _validateUpgrade(address newImplementation) internal view override onlyRole(OWNER_ROLE) { + try ICardPaymentProcessor(newImplementation).proveCardPaymentProcessor() {} catch { + revert ImplementationAddressInvalid(); + } + } + + // ------------------ Service functions ----------------------- // + + /** + * @dev The version of the standard upgrade function without the second parameter for backward compatibility. + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function upgradeTo(address newImplementation) external { + upgradeToAndCall(newImplementation, ""); + } +} diff --git a/contracts/CardPaymentProcessorStorage.sol b/contracts/CardPaymentProcessorStorage.sol new file mode 100644 index 0000000..2723a32 --- /dev/null +++ b/contracts/CardPaymentProcessorStorage.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ICardPaymentProcessorTypes } from "./interfaces/ICardPaymentProcessor.sol"; +import { ICardPaymentCashbackTypes } from "./interfaces/ICardPaymentCashback.sol"; + +/// @title CardPaymentProcessor storage version 1 +abstract contract CardPaymentProcessorStorageV1 is ICardPaymentProcessorTypes, ICardPaymentCashbackTypes { + /// @dev The address of the underlying token. + address internal _token; + + /// @dev The account to transfer confirmed tokens to. + address internal _cashOutAccount; + + /// @dev The mapping of a payment for a given payment ID. + mapping(bytes32 => Payment) internal _payments; + + /// @dev The payment statistics. + PaymentStatistics internal _paymentStatistics; + + /// @dev The address of the cashback treasury. + address internal _cashbackTreasury; + + /// @dev The enable flag of the cashback operations for new payments. Does not affect the existing payments. + bool internal _cashbackEnabled; + + /// @dev The default cashback rate for new payments in units of `CASHBACK_FACTOR`. + uint16 internal _cashbackRate; + + /// @dev The mapping of an account cashback structure for a given account address. + mapping(address => AccountCashbackState) internal _accountCashbackStates; +} + +/** + * @title CardPaymentProcessor storage + * @dev Contains storage variables of the {CardPaymentProcessor} contract. + * + * We are following Compound's approach of upgrading new contract implementations. + * See https://github.com/compound-finance/compound-protocol. + * When we need to add new storage variables, we create a new version of CardPaymentProcessorStorage + * e.g. CardPaymentProcessorStorage, so finally it would look like + * "contract CardPaymentProcessorStorage is CardPaymentProcessorStorageV1, CardPaymentProcessorStorageV2". + */ +abstract contract CardPaymentProcessorStorage is CardPaymentProcessorStorageV1 { + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + */ + uint256[43] private __gap; +} diff --git a/contracts/base/AccessControlExtUpgradeable.sol b/contracts/base/AccessControlExtUpgradeable.sol new file mode 100644 index 0000000..449beb4 --- /dev/null +++ b/contracts/base/AccessControlExtUpgradeable.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; + +/** + * @title AccessControlExtUpgradeable base contract + * @author CloudWalk Inc. + * @dev Extends the OpenZeppelin's {AccessControlUpgradeable} contract by adding the `grantRoleBatch` and + * `revokeRoleBatch` function. + * + * This contract is used through inheritance. It introduces the `grantRoleBatch` and `revokeRoleBatch` functions + * that is allowed to grant and revoke roles in batch. + */ +abstract contract AccessControlExtUpgradeable is AccessControlUpgradeable { + // ------------------ Initializers ---------------------------- // + + /** + * @dev The internal initializer of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function __AccessControlExt_init() internal onlyInitializing { + __Context_init_unchained(); + __ERC165_init_unchained(); + __AccessControl_init_unchained(); + + __AccessControlExt_init_unchained(); + } + + /** + * @dev The unchained internal initializer of the upgradable contract. + * + * See {AccessControlExtUpgradeable-__AccessControlExt_init}. + */ + function __AccessControlExt_init_unchained() internal onlyInitializing {} + + // ------------------ Functions ------------------------------- // + + /** + * @dev Grants `role` to `account` in batch. + * + * If `accounts` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event for each account. + */ + function grantRoleBatch(bytes32 role, address[] memory accounts) public virtual onlyRole(getRoleAdmin(role)) { + for (uint i = 0; i < accounts.length; i++) { + _grantRole(role, accounts[i]); + } + } + + /** + * @dev Revokes `role` from `account` in batch. + * + * If `accounts` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event for each account. + */ + function revokeRoleBatch(bytes32 role, address[] memory accounts) public virtual onlyRole(getRoleAdmin(role)) { + for (uint i = 0; i < accounts.length; i++) { + _revokeRole(role, accounts[i]); + } + } +} diff --git a/contracts/base/BlocklistableUpgradeable.sol b/contracts/base/BlocklistableUpgradeable.sol new file mode 100644 index 0000000..599f505 --- /dev/null +++ b/contracts/base/BlocklistableUpgradeable.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { AccessControlExtUpgradeable } from "./AccessControlExtUpgradeable.sol"; + +/** + * @title BlocklistableUpgradeable base contract + * @author CloudWalk Inc. + * @dev Allows to blocklist and unblocklist accounts using the {BLOCKLISTER_ROLE} role. + * + * This contract is used through inheritance. It makes available the modifier `notBlocklisted`, + * which can be applied to functions to restrict their usage to not blocklisted accounts only. + */ +abstract contract BlocklistableUpgradeable is AccessControlExtUpgradeable { + /// @dev The role of the blocklister that is allowed to blocklist and unblocklist accounts. + bytes32 public constant BLOCKLISTER_ROLE = keccak256("BLOCKLISTER_ROLE"); + + /** + * @dev The first storage slot of the contract data. + * + * Calculated as: + * keccak256(abi.encode(uint256(keccak256("cloudwalk.storage.Blocklistable")) - 1)) & ~bytes32(uint256(0xff)) + */ + bytes32 private constant BlocklistableStorageLocation = + 0x9a5d41467ec00b9c4ff3b10f2ab1b7fef3c7f16bd8fba9cd308a28e3cd7ef400; + + /** + * @dev The structure that contains all the data of the Blocklistable contract. + * @custom:storage-location erc7201:cloudwalk.storage.Blocklistable + */ + struct BlocklistableStorage { + mapping(address => bool) blocklisted; // Mapping of presence in the blocklist for a given address. + } + + // ------------------ Events ---------------------------------- // + + /// @dev Emitted when an account is blocklisted. + event Blocklisted(address indexed account); + + /// @dev Emitted when an account is unblocklisted. + event UnBlocklisted(address indexed account); + + /// @dev Emitted when an account is self blocklisted. + event SelfBlocklisted(address indexed account); + + // ------------------ Errors ---------------------------------- // + + /// @dev The account is blocklisted. + error BlocklistedAccount(address account); + + // ------------------ Modifiers ------------------------------- // + + /** + * @dev Throws if called by a blocklisted account. + * @param account The address to check for presence in the blocklist. + */ + modifier notBlocklisted(address account) { + if (_getBlocklistableStorage().blocklisted[account]) { + revert BlocklistedAccount(account); + } + _; + } + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The internal initializer of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function __Blocklistable_init(bytes32 blocklisterRoleAdmin) internal onlyInitializing { + __Context_init_unchained(); + __ERC165_init_unchained(); + __AccessControl_init_unchained(); + __AccessControlExt_init_unchained(); + + __Blocklistable_init_unchained(blocklisterRoleAdmin); + } + + /** + * @dev The unchained internal initializer of the upgradable contract. + * + * See {BlocklistableUpgradeable-__Blocklistable_init}. + */ + function __Blocklistable_init_unchained(bytes32 blocklisterRoleAdmin) internal onlyInitializing { + _setRoleAdmin(BLOCKLISTER_ROLE, blocklisterRoleAdmin); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Adds an account to the blocklist. + * + * Requirements: + * + * - The caller must have the {BLOCKLISTER_ROLE} role. + * + * Emits a {Blocklisted} event. + * + * @param account The address to blocklist. + */ + function blocklist(address account) public onlyRole(BLOCKLISTER_ROLE) { + BlocklistableStorage storage s = _getBlocklistableStorage(); + if (s.blocklisted[account]) { + return; + } + + s.blocklisted[account] = true; + + emit Blocklisted(account); + } + + /** + * @dev Removes an account from the blocklist. + * + * Requirements: + * + * - The caller must have the {BLOCKLISTER_ROLE} role. + * + * Emits an {UnBlocklisted} event. + * + * @param account The address to remove from the blocklist. + */ + function unBlocklist(address account) public onlyRole(BLOCKLISTER_ROLE) { + BlocklistableStorage storage s = _getBlocklistableStorage(); + if (!s.blocklisted[account]) { + return; + } + + s.blocklisted[account] = false; + + emit UnBlocklisted(account); + } + + /** + * @dev Adds the message sender to the blocklist. + * + * Emits a {SelfBlocklisted} event. + * Emits a {Blocklisted} event. + */ + function selfBlocklist() public { + address sender = _msgSender(); + BlocklistableStorage storage s = _getBlocklistableStorage(); + + if (s.blocklisted[sender]) { + return; + } + + s.blocklisted[sender] = true; + + emit SelfBlocklisted(sender); + emit Blocklisted(sender); + } + + // ------------------ View functions -------------------------- // + + /** + * @dev Checks if an account is blocklisted. + * @param account The address to check for presence in the blocklist. + * @return True if the account is present in the blocklist. + */ + function isBlocklisted(address account) public view returns (bool) { + return _getBlocklistableStorage().blocklisted[account]; + } + + // ------------------ Private functions ----------------------- // + + /** + * @dev Returns the contract storage structure. + */ + function _getBlocklistableStorage() private pure returns (BlocklistableStorage storage $) { + assembly { + $.slot := BlocklistableStorageLocation + } + } +} diff --git a/contracts/base/PausableExtUpgradeable.sol b/contracts/base/PausableExtUpgradeable.sol new file mode 100644 index 0000000..358295d --- /dev/null +++ b/contracts/base/PausableExtUpgradeable.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; + +import { AccessControlExtUpgradeable } from "./AccessControlExtUpgradeable.sol"; + +/** + * @title PausableExtUpgradeable base contract + * @author CloudWalk Inc. + * @dev Extends the OpenZeppelin's {PausableUpgradeable} contract by adding the {PAUSER_ROLE} role. + * + * This contract is used through inheritance. It introduces the {PAUSER_ROLE} role that is allowed to + * trigger the paused or unpaused state of the contract that is inherited from this one. + */ +abstract contract PausableExtUpgradeable is AccessControlExtUpgradeable, PausableUpgradeable { + /// @dev The role of pauser that is allowed to trigger the paused or unpaused state of the contract. + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The internal initializer of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function __PausableExt_init(bytes32 pauserRoleAdmin) internal onlyInitializing { + __Context_init_unchained(); + __ERC165_init_unchained(); + __AccessControl_init_unchained(); + __AccessControlExt_init_unchained(); + __Pausable_init_unchained(); + + __PausableExt_init_unchained(pauserRoleAdmin); + } + + /** + * @dev The unchained internal initializer of the upgradable contract. + * + * See {PausableExtUpgradeable-__PausableExt_init}. + */ + function __PausableExt_init_unchained(bytes32 pauserRoleAdmin) internal onlyInitializing { + _setRoleAdmin(PAUSER_ROLE, pauserRoleAdmin); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Triggers the paused state of the contract. + * + * Requirements: + * + * - The caller must have the {PAUSER_ROLE} role. + */ + function pause() public onlyRole(PAUSER_ROLE) { + _pause(); + } + + /** + * @dev Triggers the unpaused state of the contract. + * + * Requirements: + * + * - The caller must have the {PAUSER_ROLE} role. + */ + function unpause() public onlyRole(PAUSER_ROLE) { + _unpause(); + } +} diff --git a/contracts/base/RescuableUpgradeable.sol b/contracts/base/RescuableUpgradeable.sol new file mode 100644 index 0000000..5ec9cb0 --- /dev/null +++ b/contracts/base/RescuableUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import { AccessControlExtUpgradeable } from "./AccessControlExtUpgradeable.sol"; + +/** + * @title RescuableUpgradeable base contract + * @author CloudWalk Inc. + * @dev Allows to rescue ERC20 tokens locked up in the contract using the {RESCUER_ROLE} role. + * + * This contract is used through inheritance. It introduces the {RESCUER_ROLE} role that is allowed to + * rescue tokens locked up in the contract that is inherited from this one. + */ +abstract contract RescuableUpgradeable is AccessControlExtUpgradeable { + using SafeERC20 for IERC20; + + /// @dev The role of rescuer that is allowed to rescue tokens locked up in the contract. + bytes32 public constant RESCUER_ROLE = keccak256("RESCUER_ROLE"); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The internal initializer of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function __Rescuable_init(bytes32 rescuerRoleAdmin) internal onlyInitializing { + __Context_init_unchained(); + __ERC165_init_unchained(); + __AccessControl_init_unchained(); + __AccessControlExt_init_unchained(); + + __Rescuable_init_unchained(rescuerRoleAdmin); + } + + /** + * @dev The unchained internal initializer of the upgradable contract. + * + * See {RescuableUpgradeable-__Rescuable_init}. + */ + function __Rescuable_init_unchained(bytes32 rescuerRoleAdmin) internal onlyInitializing { + _setRoleAdmin(RESCUER_ROLE, rescuerRoleAdmin); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Withdraws ERC20 tokens locked up in the contract. + * + * Requirements: + * + * - The caller must have the {RESCUER_ROLE} role. + * + * @param token The address of the ERC20 token contract. + * @param to The address of the recipient of tokens. + * @param amount The amount of tokens to withdraw. + */ + function rescueERC20( + address token, + address to, + uint256 amount + ) public onlyRole(RESCUER_ROLE) { + IERC20(token).safeTransfer(to, amount); + } +} diff --git a/contracts/base/UUPSExtUpgradeable.sol b/contracts/base/UUPSExtUpgradeable.sol new file mode 100644 index 0000000..0d7e7e2 --- /dev/null +++ b/contracts/base/UUPSExtUpgradeable.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +/** + * @title UUPSExtUpgradeable base contract + * @author CloudWalk Inc. (See https://www.cloudwalk.io) + * @dev Extends the OpenZeppelin's {UUPSUpgradeable} contract by adding additional checks for + * the new implementation address. + * + * This contract is used through inheritance. It introduces the virtual `_validateUpgrade()` function that must be + * implemented in the parent contract. + */ +abstract contract UUPSExtUpgradeable is UUPSUpgradeable { + // ------------------ Errors ---------------------------------- // + + /// @dev Thrown if the provided new implementation address is not a contract. + error UUPSExtUpgradeable_ImplementationAddressNotContract(); + + /// @dev Thrown if the provided new implementation contract address is zero. + error UUPSExtUpgradeable_ImplementationAddressZero(); + + // ------------------ Internal functions ---------------------- // + + /** + * @dev The upgrade authorization function for UUPSProxy. + * @param newImplementation The address of the new implementation. + */ + function _authorizeUpgrade(address newImplementation) internal override { + if (newImplementation == address(0)) { + revert UUPSExtUpgradeable_ImplementationAddressZero(); + } + + if (newImplementation.code.length == 0) { + revert UUPSExtUpgradeable_ImplementationAddressNotContract(); + } + + _validateUpgrade(newImplementation); + } + + /** + * @dev Executes further validation steps of the upgrade including authorization and implementation address checks. + * @param newImplementation The address of the new implementation. + */ + function _validateUpgrade(address newImplementation) internal virtual; +} diff --git a/contracts/base/Versionable.sol b/contracts/base/Versionable.sol new file mode 100644 index 0000000..3d98f03 --- /dev/null +++ b/contracts/base/Versionable.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../interfaces/IVersionable.sol"; + +/** + * @title Versionable contract + * @author CloudWalk Inc. (See https://cloudwalk.io) + * @dev Defines the contract version. + */ +abstract contract Versionable is IVersionable { + // ------------------ Pure functions -------------------------- // + + /** + * @inheritdoc IVersionable + */ + function $__VERSION() external pure returns (Version memory) { + return Version(2, 1, 0); + } +} diff --git a/contracts/interfaces/ICardPaymentCashback.sol b/contracts/interfaces/ICardPaymentCashback.sol new file mode 100644 index 0000000..773d225 --- /dev/null +++ b/contracts/interfaces/ICardPaymentCashback.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title CardPaymentCashback types interface +interface ICardPaymentCashbackTypes { + /** + * @dev Statuses of a cashback operation as an enum. + * + * The possible values: + * + * - Undefined - The operation does not exist (the default value). + * - Success --- The operation has been successfully executed with a full amount transfer. + * - Partial --- The operation has been successfully executed but with a partial amount transfer. + * - Capped ---- The operation has been refused because the cap for the period has been reached. + * - Failed ---- The operation has been refused because the token transfer has failed. + */ + enum CashbackOperationStatus { + Undefined, // 0 + Success, // 1 + Partial, // 2 + Capped, // 3 + Failed // 4 + } + + /// @dev Structure with cashback-related data for a single account. + struct AccountCashbackState { + uint72 totalAmount; + uint72 capPeriodStartAmount; + uint32 capPeriodStartTime; + } +} + +/** + * @title CardPaymentCashback interface + * @dev The interface of the wrapper contract for the card payment cashback operations. + */ +interface ICardPaymentCashback is ICardPaymentCashbackTypes { + // ------------------ Events ---------------------------------- // + + /** + * @dev Emitted when the cashback rate is changed. + * @param oldRate The value of the old cashback rate. + * @param newRate The value of the new cashback rate. + */ + event CashbackRateChanged(uint256 oldRate, uint256 newRate); + + /** + * @dev Emitted when the cashback treasury address is changed. + * @param oldTreasury The address of the old cashback treasury. + * @param newTreasure The address of the new cashback treasury. + */ + event CashbackTreasuryChanged(address oldTreasury, address newTreasure); + + /** + * @dev Emitted when a cashback sending request executed, successfully or not. + * @param paymentId The associated card transaction payment ID from the off-chain card processing backend. + * @param recipient The address of the cashback recipient. + * @param status The status of the cashback operation. + * @param amount The actual amount of the sent cashback. + */ + event CashbackSent( + bytes32 indexed paymentId, + address indexed recipient, + CashbackOperationStatus indexed status, + uint256 amount + ); + + /** + * @dev Emitted when a cashback revocation request executed, successfully or not. + * @param paymentId The associated card transaction payment ID from the off-chain card processing backend. + * @param recipient The address of the cashback recipient. + * @param status The status of the cashback operation. + * @param oldCashbackAmount The cashback amount before the operation. + * @param newCashbackAmount The cashback amount after the operation. + * + */ + event CashbackRevoked( + bytes32 indexed paymentId, + address indexed recipient, + CashbackOperationStatus indexed status, + uint256 oldCashbackAmount, + uint256 newCashbackAmount + ); + + /** + * @dev Emitted when a cashback increase request executed, successfully or not. + * @param paymentId The associated card transaction payment ID from the off-chain card processing backend. + * @param recipient The address of the cashback recipient. + * @param status The status of the cashback operation. + * @param oldCashbackAmount The cashback amount before the operation. + * @param newCashbackAmount The cashback amount after the operation. + */ + event CashbackIncreased( + bytes32 indexed paymentId, + address indexed recipient, + CashbackOperationStatus indexed status, + uint256 oldCashbackAmount, + uint256 newCashbackAmount + ); + + /// @dev Emitted when cashback operations for new payments are enabled. Does not affect the existing payments. + event CashbackEnabled(); + + /// @dev Emitted when cashback operations for new payments are disabled. Does not affect the existing payments. + event CashbackDisabled(); + + // ------------------ Functions ------------------------------- // + + /** + * @dev Sets a new address of the cashback treasury. + * + * Emits a {CashbackTreasuryChanged} event. + * + * @param newCashbackTreasury The address of the new cashback treasury. + */ + function setCashbackTreasury(address newCashbackTreasury) external; + + /** + * @dev Sets a new default cashback rate for new payments. + * + * Emits a {CashbackRateChanged} event. + * + * @param newCashbackRate The value of the new cashback rate. + */ + function setCashbackRate(uint256 newCashbackRate) external; + + /** + * @dev Enables the cashback operations. + * + * Emits a {CashbackEnabled} event. + */ + function enableCashback() external; + + /** + * @dev Disables the cashback operations. + * + * Emits a {CashbackDisabled} event. + */ + function disableCashback() external; + + // ------------------ View functions -------------------------- // + + /// @dev Returns the current cashback treasury address. + function cashbackTreasury() external view returns (address); + + /// @dev Checks if the cashback operations are enabled. + function cashbackEnabled() external view returns (bool); + + /// @dev Returns the current cashback rate. + function cashbackRate() external view returns (uint256); + + /** + * @dev Returns a structure with cashback-related data for a single account. + * @param account The account address to get the cashback state for. + */ + function getAccountCashbackState(address account) external view returns (AccountCashbackState memory); +} diff --git a/contracts/interfaces/ICardPaymentProcessor.sol b/contracts/interfaces/ICardPaymentProcessor.sol new file mode 100644 index 0000000..b341202 --- /dev/null +++ b/contracts/interfaces/ICardPaymentProcessor.sol @@ -0,0 +1,461 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title CardPaymentProcessor types interface +interface ICardPaymentProcessorTypes { + /** + * @dev Possible statuses of a payment as an enum. + * + * The possible values: + * + * - Nonexistent - The payment does not exist (the default value). + * - Active ------ The status immediately after the payment making. + * - Revoked ----- The payment was cancelled due to some technical reason. + * The related tokens have been transferred back to the payer and (optionally) sponsor. + * The payment can be made again with the same ID. + * All further operations with this payment except making again are prohibited. + * - Reversed ---- The payment was cancelled due to the decision of the off-chain card processing service. + * The related tokens have been transferred back to the payer and (optionally) sponsor. + * The payment cannot be made again with the same ID. + * All further operations with this payment are prohibited. + */ + enum PaymentStatus { + Nonexistent, // 0 + Active, // 1 + Revoked, // 2 + Reversed // 3 + } + + /** @dev Structure with data of a single payment. + * + * The following additional payment parameters can be derived from the structure fields: + * + * - sumAmount = baseAmount + extraAmount = payerSumAmount + sponsorSumAmount. + * - commonRemainder = sumAmount - refundAmount = payerRemainder + sponsorRemainder. + * - unconfirmedAmount = commonRemainder - confirmedAmount. + * - payerBaseAmount = (baseAmount > subsidyLimit) ? (baseAmount - subsidyLimit) : 0. + * - payerSumAmount = (sumAmount > subsidyLimit) ? (sumAmount - subsidyLimit) : 0. + * - sponsorSumAmount = sumAmount - payerSumAmount. + * - assumedSponsorRefundAmount = (baseAmount > subsidyLimit) + * ? (refundAmount * subsidyLimit / baseAmount) + * : refundAmount. + * - sponsorRefundAmount = (assumedSponsorRefundAmount < subsidyLimit) ? assumedSponsorRefundAmount : subsidyLimit. + * - payerRefundAmount = refundAmount - sponsorRefundAmount. + * - payerRemainder = payerSumAmount - payerRefundAmount. + * - sponsorRemainder = sponsorSumAmount - sponsorRefundAmount. + * - cashbackAmount = (payerBaseAmount > payerRefundAmount) + * ? (payerBaseAmount - payerRefundAmount) * cashbackRate + * : 0. + * + * The following restrictions are applied to a payment: + * - `refundAmount <= sumAmount`. + * - `commonReminder >= confirmedAmount`. + */ + struct Payment { + // Slot1 + PaymentStatus status; // The current status of the payment. + uint8 reserve1; // The reserved filed for future changes. + address payer; // The account who made the payment. + uint16 cashbackRate; // The cashback rate in units of `CASHBACK_FACTOR`. + uint64 confirmedAmount; // The confirmed amount that was transferred to the cash-out account. + // Slot2 + address sponsor; // The sponsor of the payment if it is subsidized. Otherwise the zero address. + uint64 subsidyLimit; // The subsidy limit of the payment if it is subsidized. Otherwise zero. + uint32 reserve2; // The reserved filed for future changes. + // Slot3 + uint64 baseAmount; // The base amount of tokens in the payment. + uint64 extraAmount; // The extra amount of tokens in the payment, without a cashback. + uint64 cashbackAmount; // The cumulative cashback amount that was granted to payer related to the payment. + uint64 refundAmount; // The total amount of all refunds related to the payment. + } + + /// @dev Structure with data of a single confirmation operation. + struct PaymentConfirmation { + bytes32 paymentId; // The card transaction payment ID from the off-chain card processing backend. + uint256 amount; // The amount to confirm for the payment. + } + + /// @dev Structure with statistics of all payments. + struct PaymentStatistics { + uint128 totalUnconfirmedRemainder; // The total remainder of all payments that are not confirmed yet. + uint128 reserve1; // The reserved filed for future changes. + uint256 reserve2; // The reserved filed for future changes. + } +} + +/** + * @title CardPaymentProcessor interface + * @dev The interface of the wrapper contract for the card payment operations. + */ +interface ICardPaymentProcessor is ICardPaymentProcessorTypes { + // ------------------ Events ---------------------------------- // + + /** + * @dev Emitted when a payment is made. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(baseAmount) -- the base amount of the payment. + * - uint64(extraAmount) -- the extra amount of the payment. + * - uint64(payerSumAmount) -- the payer sum amount part. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * - uint64(sponsorSumAmount) -- the sponsor sum amount part or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentMade( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when a payment is updated inside a function whose name started with the `update` word. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(oldBaseAmount) -- the old base amount of the payment. + * - uint64(newBaseAmount) -- the new base amount of the payment. + * - uint64(oldExtraAmount) -- the old extra amount of the payment. + * - uint64(newExtraAmount) -- the new extra amount of the payment. + * - uint64(oldPayerSumAmount) -- the old payer sum amount part. + * - uint64(newPayerSumAmount) -- the new payer sum amount part. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * - uint64(oldSponsorSumAmount) -- the old sponsor sum amount part or skipped if the payment is not subsidized. + * - uint64(newSponsorSumAmount) -- the new sponsor sum amount part or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentUpdated( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when a payment is revoked. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(baseAmount) -- the base amount of the payment. + * - uint64(extraAmount) -- the extra amount of the payment. + * - uint64(payerRemainder) -- the payer remainder part of the payment. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * - uint64(sponsorRemainder) -- the sponsor remainder part or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentRevoked( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when a payment is reversed. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(baseAmount) -- the base amount of the payment. + * - uint64(extraAmount) -- the extra amount of the payment. + * - uint64(payerRemainder) -- the payer remainder part of the payment. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * - uint64(sponsorRemainder) -- the sponsor remainder part or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentReversed( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when the confirmed amount of a payment is changed. It can be emitted during any operation. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(oldConfirmedAmount) -- the old confirmed amount of the payment. + * - uint64(newConfirmedAmount) -- the new confirmed amount of the payment. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentConfirmedAmountChanged( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when a payment is refunded inside a function whose name started with the `refund` word. + * + * Some data is encoded in the `addendum` parameter as the result of calling of the `abi.encodePacked()` + * function as described in https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode + * with the following arguments (addendum fields): + * + * - uint8(version) -- the version of the event addendum, for now it equals `0x01`. + * - uint8(flags) -- the flags that for now define whether the payment is subsidized (`0x01`) or not (`0x00`). + * - uint64(oldPayerRefundAmount) -- the old payer refund amount of the payment. + * - uint64(newPayerRefundAmount) -- the new payer refund amount of the payment. + * - address(sponsor) -- the address of the sponsor or skipped if the payment is not subsidized. + * - uint64(oldSponsorRefundAmount) -- the old sponsor refund amount or skipped if the payment is not subsidized. + * - uint64(newSponsorRefundAmount) -- the new sponsor refund amount or skipped if the payment is not subsidized. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param addendum The data of the event as described above. + */ + event PaymentRefunded( + bytes32 indexed paymentId, + address indexed payer, + bytes addendum + ); + + /** + * @dev Emitted when an account is refunded inside the `refundAccount()` function. + * @param account The account that is refunded. + * @param refundingAmount The amount of tokens to refund. + * @param addendum Empty. Reserved for future possible additional information. + */ + event AccountRefunded( + address indexed account, + uint256 refundingAmount, + bytes addendum + ); + + // ------------------ Functions ------------------------------- // + + /** + * @dev Makes a card payment for a given account initiated by a service account. + * + * The payment can be subsidized with full or partial reimbursement from a specified sponsor account. + * If cashback is disabled in the contract it will not be sent in any case. + * + * Transfers the underlying tokens from the payer and/or sponsor to this contract. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentMade} event. + * Emits a {PaymentConfirmedAmountChanged} event if the payment is confirmed immediately after making. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param baseAmount The base amount of tokens to transfer because of the payment. + * @param extraAmount The extra amount of tokens to transfer because of the payment. No cashback is applied. + * @param sponsor The address of a sponsor if the payment is subsidized, otherwise zero. + * @param subsidyLimit The amount of tokens that the sponsor is compensating for the payment. + * @param cashbackRate If positive then it is a special cashback rate for the payment in units of `CASHBACK_FACTOR`. + * If negative then the contract settings are used to determine cashback. + * If zero then cashback is not sent. + * @param confirmationAmount The amount to confirm for the payment immediately after making. + * Zero if confirmation is not needed. + */ + function makePaymentFor( + bytes32 paymentId, + address payer, + uint256 baseAmount, + uint256 extraAmount, + address sponsor, + uint256 subsidyLimit, + int256 cashbackRate, + uint256 confirmationAmount + ) external; + + /** + * @dev Makes a common card payment for a given account initiated by a service account. + * + * It is the same as the `makePaymentFor()` function but with less parameters. + * The payment is not subsidized, with the cashback defined by the contract settings, and without a confirmation. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param payer The account on that behalf the payment is made. + * @param baseAmount The base amount of tokens to transfer because of the payment. + * @param extraAmount The extra amount of tokens to transfer because of the payment. No cashback is applied. + */ + function makeCommonPaymentFor( + bytes32 paymentId, + address payer, + uint256 baseAmount, + uint256 extraAmount + ) external; + + /** + * @dev Updates a previously made payment. + * + * Transfers the underlying tokens from the payer and/or sponsor to this contract or vise versa. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentUpdated} event. + * Emits a {PaymentConfirmedAmountChanged} event if the confirmed amount of the payment is changed. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param newBaseAmount The new base amount of the payment. + * @param newExtraAmount The new extra amount of the payment. + */ + function updatePayment( + bytes32 paymentId, + uint256 newBaseAmount, + uint256 newExtraAmount + ) external; + + /** + * @dev Performs the revocation of a previously made card payment. + * + * Does not finalize the payment: it can be made again with the same paymentId. + * Transfers tokens back from this contract or cash-out account to the payer and/or sponsor. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentRevoked} event. + * Emits a {PaymentConfirmedAmountChanged} event if the confirmed amount of the payment is changed. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + */ + function revokePayment(bytes32 paymentId) external; + + /** + * @dev Performs the reverse of a previously made card payment. + * + * Finalizes the payment: no other operations can be done for the payment after this one. + * Transfers tokens back from this contract or cash-out account to the payer and/or sponsor. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentReversed} event. + * Emits a {PaymentConfirmedAmountChanged} event if the confirmed amount of the payment is changed. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + */ + function reversePayment(bytes32 paymentId) external; + + /** + * @dev Confirms a single previously made card payment. + * + * Does mot finalizes the payment: any other operations can be done for the payment after this one. + * Transfers tokens gotten from a payer and a sponsor to a dedicated cash-out account for further operations. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentConfirmedAmountChanged} event. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param confirmationAmount The amount to confirm for the payment. + */ + function confirmPayment( + bytes32 paymentId, + uint256 confirmationAmount + ) external; + + /** + * @dev Confirms multiple previously made card payment. + * + * Does mot finalizes the payments: any other operations can be done for the payments after this one. + * Transfers tokens gotten from payers and sponsors to a dedicated cash-out account for further operations. + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentConfirmedAmountChanged} event for each payment. + * + * @param paymentConfirmations The array of structures with payment confirmation parameters. + */ + function confirmPayments(PaymentConfirmation[] calldata paymentConfirmations) external; + + /** + * @dev Executes updating and confirmation operations for a single previously made card payment. + * + * Updating of the base amount and extra amount executes lazy, i.e. only if any of the provided new amounts differ + * from the current once of the payment. Otherwise the update operation is skipped. + * + * This function can be called by a limited number of accounts that are allowed to execute processing operations. + * + * Emits a {PaymentUpdated} event if the update operation is executed. + * Emits a {PaymentConfirmedAmountChanged} event. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param newBaseAmount The new base amount of the payment. + * @param newExtraAmount The new extra amount of the payment. + * @param confirmationAmount The amount to confirm for the payment. + */ + function updateLazyAndConfirmPayment( + bytes32 paymentId, + uint256 newBaseAmount, + uint256 newExtraAmount, + uint256 confirmationAmount + ) external; + + /** + * @dev Makes a refund for a previously made card payment. + * + * Emits a {PaymentRefunded} event. + * Emits a {PaymentConfirmedAmountChanged} event if the confirmed amount of the payment is changed. + * + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + * @param refundingAmount The amount of tokens to refund. + */ + function refundPayment( + bytes32 paymentId, + uint256 refundingAmount + ) external; + + /** + * @dev Makes a refund for an account where the refund cannot be associated with any card payment. + * + * During this operation the needed amount of tokens is transferred from the cash-out account to the target account. + * + * Emits a {AccountRefunded} event. + * + * @param account The address of the account to refund. + * @param refundingAmount The amount of tokens to refund. + */ + function refundAccount( + address account, + uint256 refundingAmount + ) external; + + // ------------------ View functions -------------------------- // + + /// @dev Returns the address of the underlying token. + function token() external view returns (address); + + /// @dev Returns the address of the cash-out account. + function cashOutAccount() external view returns (address); + + /** + * @dev Returns payment data for a card transaction payment ID. + * @param paymentId The card transaction payment ID from the off-chain card processing backend. + */ + function getPayment(bytes32 paymentId) external view returns (Payment memory); + + /// @dev Returns statistics of all payments. + function getPaymentStatistics() external view returns (PaymentStatistics memory); + + /// @dev Proves the contract is the card payment processor one. A marker function. + function proveCardPaymentProcessor() external pure; +} diff --git a/contracts/interfaces/IVersionable.sol b/contracts/interfaces/IVersionable.sol new file mode 100644 index 0000000..29a28eb --- /dev/null +++ b/contracts/interfaces/IVersionable.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title IVersionable interface + * @author CloudWalk Inc. (See https://cloudwalk.io) + * @dev Defines the function of getting the contract version. + */ +interface IVersionable { + /** + * @dev The struct for the contract version. + */ + struct Version { + uint16 major; // -- The major version of contract + uint16 minor; // -- The minor version of contract + uint16 patch; // -- The patch version of contract + } + + /** + * @dev Returns the version of the contract. + */ + function $__VERSION() external pure returns (Version memory); +} diff --git a/contracts/mocks/base/AccessControlExtUpgradeableMock.sol b/contracts/mocks/base/AccessControlExtUpgradeableMock.sol new file mode 100644 index 0000000..473596f --- /dev/null +++ b/contracts/mocks/base/AccessControlExtUpgradeableMock.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import { AccessControlExtUpgradeable } from "../../base/AccessControlExtUpgradeable.sol"; + +/** + * @title AccessControlExtUpgradeableMock contract + * @author CloudWalk Inc. + * @dev An implementation of the {AccessControlExtUpgradeable} contract for test purposes. + */ +contract AccessControlExtUpgradeableMock is AccessControlExtUpgradeable, UUPSUpgradeable { + /// @dev The role of this contract owner. + bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); + bytes32 public constant USER_ROLE = keccak256("USER_ROLE"); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The initialize function of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function initialize() public initializer { + _grantRole(OWNER_ROLE, _msgSender()); + _setRoleAdmin(USER_ROLE, OWNER_ROLE); + __AccessControlExt_init(); + + // Only to provide the 100 % test coverage + _authorizeUpgrade(address(0)); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Needed to check that the initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize() public { + __AccessControlExt_init(); + } + + /** + * @dev Needed to check that the unchained initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize_unchained() public { + __AccessControlExt_init_unchained(); + } + + // ------------------ Internal functions ---------------------- // + + /// @dev The upgrade authorization function for UUPSProxy. + function _authorizeUpgrade(address newImplementation) internal pure override { + newImplementation; // Suppresses a compiler warning about the unused variable + } +} diff --git a/contracts/mocks/base/BlocklistableUpgradeableMock.sol b/contracts/mocks/base/BlocklistableUpgradeableMock.sol new file mode 100644 index 0000000..16d5351 --- /dev/null +++ b/contracts/mocks/base/BlocklistableUpgradeableMock.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import { BlocklistableUpgradeable } from "../../base/BlocklistableUpgradeable.sol"; + +/** + * @title BlocklistableUpgradeableMock contract + * @author CloudWalk Inc. + * @dev An implementation of the {BlocklistableUpgradeable} contract for test purposes. + */ +contract BlocklistableUpgradeableMock is BlocklistableUpgradeable, UUPSUpgradeable { + /// @dev The role of this contract owner. + bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); + + // ------------------ Events ---------------------------------- // + + /// @dev Emitted when a test function of the `notBlocklisted` modifier executes successfully. + event TestNotBlocklistedModifierSucceeded(); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The initialize function of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function initialize() public initializer { + _grantRole(OWNER_ROLE, _msgSender()); + __Blocklistable_init(OWNER_ROLE); + + // Only to provide the 100 % test coverage + _authorizeUpgrade(address(0)); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Needed to check that the initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize() public { + __Blocklistable_init(OWNER_ROLE); + } + + /** + * @dev Needed to check that the unchained initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize_unchained() public { + __Blocklistable_init_unchained(OWNER_ROLE); + } + + /** + * @dev Checks the execution of the {notBlocklisted} modifier. + * If that modifier executed without reverting emits an event {TestNotBlocklistedModifierSucceeded}. + */ + function testNotBlocklistedModifier() external notBlocklisted(_msgSender()) { + emit TestNotBlocklistedModifierSucceeded(); + } + + // ------------------ Internal functions ---------------------- // + + /// @dev The upgrade authorization function for UUPSProxy. + function _authorizeUpgrade(address newImplementation) internal pure override { + newImplementation; // Suppresses a compiler warning about the unused variable + } +} diff --git a/contracts/mocks/base/PausableExtUpgradeableMock.sol b/contracts/mocks/base/PausableExtUpgradeableMock.sol new file mode 100644 index 0000000..563d334 --- /dev/null +++ b/contracts/mocks/base/PausableExtUpgradeableMock.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import { PausableExtUpgradeable } from "../../base/PausableExtUpgradeable.sol"; + +/** + * @title PausableExtUpgradeableMock contract + * @author CloudWalk Inc. + * @dev An implementation of the {PausableExtUpgradeable} contract for test purposes. + */ +contract PausableExtUpgradeableMock is PausableExtUpgradeable, UUPSUpgradeable { + /// @dev The role of this contract owner. + bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The initialize function of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function initialize() public initializer { + _grantRole(OWNER_ROLE, _msgSender()); + __PausableExt_init(OWNER_ROLE); + + // Only to provide the 100 % test coverage + _authorizeUpgrade(address(0)); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Needed to check that the initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize() public { + __PausableExt_init(OWNER_ROLE); + } + + /** + * @dev Needed to check that the unchained initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize_unchained() public { + __PausableExt_init_unchained(OWNER_ROLE); + } + + // ------------------ Internal functions ---------------------- // + + /// @dev The upgrade authorization function for UUPSProxy. + function _authorizeUpgrade(address newImplementation) internal pure override { + newImplementation; // Suppresses a compiler warning about the unused variable + } +} diff --git a/contracts/mocks/base/RescuableUpgradeableMock.sol b/contracts/mocks/base/RescuableUpgradeableMock.sol new file mode 100644 index 0000000..194cc82 --- /dev/null +++ b/contracts/mocks/base/RescuableUpgradeableMock.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import { RescuableUpgradeable } from "../../base/RescuableUpgradeable.sol"; + +/** + * @title RescuableUpgradeableMock contract + * @author CloudWalk Inc. + * @dev An implementation of the {RescuableUpgradeable} contract for test purposes. + */ +contract RescuableUpgradeableMock is RescuableUpgradeable, UUPSUpgradeable { + /// @dev The role of this contract owner. + bytes32 public constant OWNER_ROLE = keccak256("OWNER_ROLE"); + + // ------------------ Initializers ---------------------------- // + + /** + * @dev The initialize function of the upgradable contract. + * + * See details https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable. + */ + function initialize() public initializer { + _grantRole(OWNER_ROLE, _msgSender()); + __Rescuable_init(OWNER_ROLE); + + // Only to provide the 100 % test coverage + _authorizeUpgrade(address(0)); + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Needed to check that the initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize() public { + __Rescuable_init(OWNER_ROLE); + } + + /** + * @dev Needed to check that the unchained initialize function of the ancestor contract + * has the 'onlyInitializing' modifier. + */ + function call_parent_initialize_unchained() public { + __Rescuable_init_unchained(OWNER_ROLE); + } + + // ------------------ Internal functions ---------------------- // + + /// @dev The upgrade authorization function for UUPSProxy. + function _authorizeUpgrade(address newImplementation) internal pure override { + newImplementation; // Suppresses a compiler warning about the unused variable + } +} diff --git a/contracts/mocks/base/UUPSExtUpgradableMock.sol b/contracts/mocks/base/UUPSExtUpgradableMock.sol new file mode 100644 index 0000000..0e5dcc6 --- /dev/null +++ b/contracts/mocks/base/UUPSExtUpgradableMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { UUPSExtUpgradeable } from "../../base/UUPSExtUpgradeable.sol"; + +/** + * @title UUPSExtUpgradableMock contract + * @author CloudWalk Inc. (See https://www.cloudwalk.io) + * @dev An implementation of the {UUPSExtUpgradable} contract for test purposes. + */ +contract UUPSExtUpgradeableMock is UUPSExtUpgradeable { + /// @dev Emitted when the internal `_validateUpgrade()` function is called with the parameters of the function. + event MockValidateUpgradeCall(address newImplementation); + + /** + * @dev Executes further validation steps of the upgrade including authorization and implementation address checks. + * @param newImplementation The address of the new implementation. + */ + function _validateUpgrade(address newImplementation) internal virtual override { + emit MockValidateUpgradeCall(newImplementation); + } +} diff --git a/contracts/mocks/tokens/ERC20TokenMock.sol b/contracts/mocks/tokens/ERC20TokenMock.sol new file mode 100644 index 0000000..d62f496 --- /dev/null +++ b/contracts/mocks/tokens/ERC20TokenMock.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title ERC20TokenMock contract + * @dev An implementation of the {ERC20Upgradeable} contract for testing purposes + */ +contract ERC20TokenMock is ERC20 { + /// @dev A special amount when the transfer functions should return `false`. + uint256 public specialAmountToReturnFalse; + + /// @dev A special amount when the transfer functions should revert. + uint256 public specialAmountToRevert; + + // ------------------ Constructor ----------------------------- // + + /** + * @dev The initialize function of the upgradable contract. + * @param name_ The name of the token to set for this ERC20-comparable contract. + * @param symbol_ The symbol of the token to set for this ERC20-comparable contract. + */ + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { + specialAmountToReturnFalse = type(uint256).max; + specialAmountToRevert = type(uint256).max; + } + + // ------------------ Functions ------------------------------- // + + /** + * @dev Calls the appropriate internal function to mint needed amount of tokens for an account. + * @param account The address of an account to mint for. + * @param amount The amount of tokens to mint. + */ + function mint(address account, uint256 amount) external { + _mint(account, amount); + } + + /// @dev The variation of the standard transfer function that returns `false` if the special amount is passed. + function transfer(address to, uint256 amount) public override returns (bool) { + if (amount == specialAmountToRevert) { + revert("ERC20TokenMock: The special amount has been used inside the 'transfer()' function"); + } else if (amount == specialAmountToReturnFalse) { + return false; + } else { + return super.transfer(to, amount); + } + } + + /// @dev The variation of the standard transfer from function that returns `false` if the special amount is passed. + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { + if (amount == specialAmountToRevert) { + revert("ERC20TokenMock: The special amount has been used inside the 'transferFrom()' function"); + } else if (amount == specialAmountToReturnFalse) { + return false; + } else { + return super.transferFrom(from, to, amount); + } + } + + /// @dev Configures the special amount when the transfer functions should return `false`. + function setSpecialAmountToReturnFalse(uint256 newSpecialAmount) external { + specialAmountToReturnFalse = newSpecialAmount; + } + + /// @dev Configures the special amount when the transfer functions should revert. + function setSpecialAmountToRevert(uint256 newSpecialAmount) external { + specialAmountToRevert = newSpecialAmount; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..40817f5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9268 @@ +{ + "name": "brlc-card-payment-processor-v2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@openzeppelin/contracts-upgradeable": "^5.1.0" + }, + "devDependencies": { + "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "@openzeppelin/hardhat-upgrades": "^3.5.0", + "@stylistic/eslint-plugin": "^1.8.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.1", + "hardhat-contract-sizer": "^2.10.0", + "prettier": "^3.3.3", + "prettier-plugin-solidity": "^1.4.1" + } + }, + "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==", + "dev": true, + "peer": true + }, + "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, + "dependencies": { + "@aws-crypto/util": "^1.2.2", + "@aws-sdk/types": "^3.1.0", + "tslib": "^1.11.1" + } + }, + "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, + "dependencies": { + "@aws-sdk/types": "^3.1.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.686.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.686.0.tgz", + "integrity": "sha512-xFnrb3wxOoJcW2Xrh63ZgFo5buIu9DF7bOHnwoUxHdNpUXicUh0AHw85TjXxyxIAd0d1psY/DU7QHoNI3OswgQ==", + "dev": true, + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.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 + }, + "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, + "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 + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "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, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "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, + "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, + "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, + "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, + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/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, + "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/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.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, + "peer": true + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "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" + } + ], + "peer": true + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.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.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "peer": true + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "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==", + "dev": true, + "peer": true, + "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" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/eth-sig-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, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@metamask/eth-sig-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, + "peer": true + }, + "node_modules/@metamask/eth-sig-util/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, + "peer": 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/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dev": true, + "peer": true, + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "peer": true + }, + "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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nomicfoundation/edr": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.6.4.tgz", + "integrity": "sha512-YgrSuT3yo5ZQkbvBGqQ7hG+RDvz3YygSkddg4tb1Z0Y6pLXFzwrcEwWaJCFAVeeZxdxGfCgGMUYgRVneK+WXkw==", + "dev": true, + "peer": true, + "dependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.6.4", + "@nomicfoundation/edr-darwin-x64": "0.6.4", + "@nomicfoundation/edr-linux-arm64-gnu": "0.6.4", + "@nomicfoundation/edr-linux-arm64-musl": "0.6.4", + "@nomicfoundation/edr-linux-x64-gnu": "0.6.4", + "@nomicfoundation/edr-linux-x64-musl": "0.6.4", + "@nomicfoundation/edr-win32-x64-msvc": "0.6.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-darwin-arm64": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.6.4.tgz", + "integrity": "sha512-QNQErISLgssV9+qia8sIjRANqtbW8snSDvjspixT/kSQ5ZSGxxctTg7x72wPSrcu8+EBEveIe5uqENIp5GH8HQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-darwin-x64": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.6.4.tgz", + "integrity": "sha512-cjVmREiwByyc9+oGfvAh49IAw+oVJHF9WWYRD+Tm/ZlSpnEVWxrGNBak2bd/JSYjn+mZE7gmWS4SMRi4nKaLUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.6.4.tgz", + "integrity": "sha512-96o9kRIVD6W5VkgKvUOGpWyUGInVQ5BRlME2Fa36YoNsRQMaKtmYJEU0ACosYES6ZTpYC8U5sjMulvPtVoEfOA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-musl": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.6.4.tgz", + "integrity": "sha512-+JVEW9e5plHrUfQlSgkEj/UONrIU6rADTEk+Yp9pbe+mzNkJdfJYhs5JYiLQRP4OjxH4QOrXI97bKU6FcEbt5Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-gnu": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.6.4.tgz", + "integrity": "sha512-nzYWW+fO3EZItOeP4CrdMgDXfaGBIBkKg0Y/7ySpUxLqzut40O4Mb0/+quqLAFkacUSWMlFp8nsmypJfOH5zoA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-musl": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.6.4.tgz", + "integrity": "sha512-QFRoE9qSQ2boRrVeQ1HdzU+XN7NUgwZ1SIy5DQt4d7jCP+5qTNsq8LBNcqhRBOATgO63nsweNUhxX/Suj5r1Sw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nomicfoundation/edr-win32-x64-msvc": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.6.4.tgz", + "integrity": "sha512-2yopjelNkkCvIjUgBGhrn153IBPLwnsDeNiq6oA0WkeM8tGmQi4td+PGi9jAriUDAkc59Yoi2q9hYA6efiY7Zw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 18" + } + }, + "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==", + "dev": true, + "peer": true, + "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, + "peer": true, + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "peer": true, + "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" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@nomicfoundation/ethereumjs-rlp": "5.0.4", + "ethereum-cryptography": "0.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "c-kzg": "^2.1.2" + }, + "peerDependenciesMeta": { + "c-kzg": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "lodash.isequal": "^4.5.0" + }, + "peerDependencies": { + "ethers": "^6.1.0", + "hardhat": "^2.0.0" + } + }, + "node_modules/@nomicfoundation/hardhat-ignition": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-0.15.7.tgz", + "integrity": "sha512-RFhGazR0/JqHxuuIxjjMmM+nWFqEvA7wcVqcX7vUqqmAIGuok4HhnWQH8aOvBaVguiXvvlFDJL0PIlxmkFgIUg==", + "dev": true, + "peer": true, + "dependencies": { + "@nomicfoundation/ignition-core": "^0.15.7", + "@nomicfoundation/ignition-ui": "^0.15.7", + "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.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-ethers/-/hardhat-ignition-ethers-0.15.7.tgz", + "integrity": "sha512-pUZWQeFNMwDe6F/yKIJsCo+87elk/M/Edjp6AnWWIBplRyPa13Nh63+yOqMSSd9Mx9lLuBaEGnYXoI2Uz2wYZA==", + "dev": true, + "peer": true, + "peerDependencies": { + "@nomicfoundation/hardhat-ethers": "^3.0.4", + "@nomicfoundation/hardhat-ignition": "^0.15.7", + "@nomicfoundation/ignition-core": "^0.15.7", + "ethers": "^6.7.0", + "hardhat": "^2.18.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "ethereumjs-util": "^7.1.4" + }, + "peerDependencies": { + "hardhat": "^2.9.5" + } + }, + "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==", + "dev": true, + "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.11", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.11.tgz", + "integrity": "sha512-lGIo4dNjVQFdsiEgZp3KP6ntLiF7xJEJsbNHfSyIiFCyI0Yv0518ElsFtMC5uCuHEChiBBMrib9jWQvHHT+X3Q==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@ethersproject/address": "^5.0.2", + "cbor": "^8.1.0", + "chalk": "^2.4.2", + "debug": "^4.1.1", + "lodash.clonedeep": "^4.5.0", + "semver": "^6.3.0", + "table": "^6.8.0", + "undici": "^5.14.0" + }, + "peerDependencies": { + "hardhat": "^2.0.4" + } + }, + "node_modules/@nomicfoundation/hardhat-verify/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/@nomicfoundation/hardhat-verify/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/@nomicfoundation/hardhat-verify/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/@nomicfoundation/hardhat-verify/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/@nomicfoundation/hardhat-verify/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/@nomicfoundation/hardhat-verify/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/hardhat-verify/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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nomicfoundation/ignition-core": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-0.15.7.tgz", + "integrity": "sha512-C4/0V/q2gNxKDt88cMr+Oxlf4NINQ7QgmJyciQ1/6UdCRUg+/Pgdgpd3vgGXQVTotq50Q/BU4ofNUAD/8HRqtg==", + "dev": true, + "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" + } + }, + "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==", + "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" + } + ], + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@nomicfoundation/ignition-ui": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.7.tgz", + "integrity": "sha512-pj2LmXylgbHOTNrkFqFrre/FAOjcwYl4VKIKVH/QMMBH/DatbiT8aC5n9o2fbLD8uwlPEesD+uXZuKCE71KFBg==", + "dev": true, + "peer": true + }, + "node_modules/@nomicfoundation/slang": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang/-/slang-0.17.0.tgz", + "integrity": "sha512-1GlkGRcGpVnjFw9Z1vvDKOKo2mzparFt7qrl2pDxWp+jrVtlvej98yCMX52pVyrYE7ZeOSZFnx/DtsSgoukStQ==", + "dev": true, + "dependencies": { + "@nomicfoundation/slang-darwin-arm64": "0.17.0", + "@nomicfoundation/slang-darwin-x64": "0.17.0", + "@nomicfoundation/slang-linux-arm64-gnu": "0.17.0", + "@nomicfoundation/slang-linux-arm64-musl": "0.17.0", + "@nomicfoundation/slang-linux-x64-gnu": "0.17.0", + "@nomicfoundation/slang-linux-x64-musl": "0.17.0", + "@nomicfoundation/slang-win32-arm64-msvc": "0.17.0", + "@nomicfoundation/slang-win32-ia32-msvc": "0.17.0", + "@nomicfoundation/slang-win32-x64-msvc": "0.17.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-darwin-arm64": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-darwin-arm64/-/slang-darwin-arm64-0.17.0.tgz", + "integrity": "sha512-O0q94EUtoWy9A5kOTOa9/khtxXDYnLqmuda9pQELurSiwbQEVCPQL8kb34VbOW+ifdre66JM/05Xw9JWhIZ9sA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-darwin-x64": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-darwin-x64/-/slang-darwin-x64-0.17.0.tgz", + "integrity": "sha512-IaDbHzvT08sBK2HyGzonWhq1uu8IxdjmTqAWHr25Oh/PYnamdi8u4qchZXXYKz/DHLoYN3vIpBXoqLQIomhD/g==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-arm64-gnu": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-arm64-gnu/-/slang-linux-arm64-gnu-0.17.0.tgz", + "integrity": "sha512-Lj4anvOsQZxs1SycG8VyT2Rl2oqIhyLSUCgGepTt3CiJ/bM+8r8bLJIgh8vKkki4BWz49YsYIgaJB2IPv8FFTw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-arm64-musl": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-arm64-musl/-/slang-linux-arm64-musl-0.17.0.tgz", + "integrity": "sha512-/xkTCa9d5SIWUBQE3BmLqDFfJRr4yUBwbl4ynPiGUpRXrD69cs6pWKkwjwz/FdBpXqVo36I+zY95qzoTj/YhOA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-x64-gnu": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-x64-gnu/-/slang-linux-x64-gnu-0.17.0.tgz", + "integrity": "sha512-oe5IO5vntOqYvTd67deCHPIWuSuWm6aYtT2/0Kqz2/VLtGz4ClEulBSRwfnNzBVtw2nksWipE1w8BzhImI7Syg==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-linux-x64-musl": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-linux-x64-musl/-/slang-linux-x64-musl-0.17.0.tgz", + "integrity": "sha512-PpYCI5K/kgLAMXaPY0V4VST5gCDprEOh7z/47tbI8kJQumI5odjsj/Cs8MpTo7/uRH6flKYbVNgUzcocWVYrAQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-arm64-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-arm64-msvc/-/slang-win32-arm64-msvc-0.17.0.tgz", + "integrity": "sha512-u/Mkf7OjokdBilP7QOJj6QYJU4/mjkbKnTX21wLyCIzeVWS7yafRPYpBycKIBj2pRRZ6ceAY5EqRpb0aiCq+0Q==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-ia32-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-ia32-msvc/-/slang-win32-ia32-msvc-0.17.0.tgz", + "integrity": "sha512-XJBVQfNnZQUv0tP2JSJ573S+pmgrLWgqSZOGaMllnB/TL1gRci4Z7dYRJUF2s82GlRJE+FHSI2Ro6JISKmlXCg==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nomicfoundation/slang-win32-x64-msvc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/slang-win32-x64-msvc/-/slang-win32-x64-msvc-0.17.0.tgz", + "integrity": "sha512-zPGsAeiTfqfPNYHD8BfrahQmYzA78ZraoHKTGraq/1xwJwzBK4bu/NtvVA4pJjBV+B4L6DCxVhSbpn40q26JQA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@openzeppelin/contracts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-5.1.0.tgz", + "integrity": "sha512-p1ULhl7BXzjjbha5aqst+QMLY+4/LCWADXOCsmLHRM77AqiPjnd9vvUN9sosUfhL9JGKpZ0TjEGxgvnizmWGSA==", + "peer": true + }, + "node_modules/@openzeppelin/contracts-upgradeable": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-5.1.0.tgz", + "integrity": "sha512-AIElwP5Ck+cslNE+Hkemf5SxjJoF4wBvvjxc27Rp+9jaPs/CLIaUBMYe1FNzhdiN0cYuwGRmYaRHmmntuiju4Q==", + "peerDependencies": { + "@openzeppelin/contracts": "5.1.0" + } + }, + "node_modules/@openzeppelin/defender-sdk-base-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-base-client/-/defender-sdk-base-client-1.15.1.tgz", + "integrity": "sha512-z3ZoDDRgRAlkaOFrY1SoHK/hn6LWlnfuFvs7WAA+nahlltS9UN7ro4v6P2aUq4ZQH2kZg5JeNfHCkpkRFaGa5Q==", + "dev": true, + "dependencies": { + "amazon-cognito-identity-js": "^6.3.6", + "async-retry": "^1.3.3" + } + }, + "node_modules/@openzeppelin/defender-sdk-deploy-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-deploy-client/-/defender-sdk-deploy-client-1.15.1.tgz", + "integrity": "sha512-seJajiWFCM+dbMIv3290TOEsygeWyGa9DQxPESpFwXvlLxfPcKN/o8g+4bs98BmC9v6d0q5ckoWA8iEuzEBLpA==", + "dev": true, + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.15.1", + "axios": "^1.7.2", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/defender-sdk-network-client": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@openzeppelin/defender-sdk-network-client/-/defender-sdk-network-client-1.15.1.tgz", + "integrity": "sha512-X09to21R7UjWMstDTmY+F8B6N+4c0B/hNio++fRsCs8kgO/ZcBLAQ3HDFgCBRVmhRI8+Qpa2uqc673aU6hW10A==", + "dev": true, + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.15.1", + "axios": "^1.7.2", + "lodash": "^4.17.21" + } + }, + "node_modules/@openzeppelin/hardhat-upgrades": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-3.5.0.tgz", + "integrity": "sha512-Ju/JnT7NRiOMi5m5Y0dGiz37d8wnjVBep1v5Vr7+6+MFNuQa1yddUEVWhWhoEw4udI3/mYwyw4Sfz3sq7vhicQ==", + "dev": true, + "dependencies": { + "@openzeppelin/defender-sdk-base-client": "^1.14.4", + "@openzeppelin/defender-sdk-deploy-client": "^1.14.4", + "@openzeppelin/defender-sdk-network-client": "^1.14.4", + "@openzeppelin/upgrades-core": "^1.40.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/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@openzeppelin/upgrades-core": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/upgrades-core/-/upgrades-core-1.40.0.tgz", + "integrity": "sha512-4bPSXdEqHsNRL5T1ybPLneWGYjzGl6XWGWkv7aUoFFgz8mOdarstRBX1Wi4XJFw6IeHPUI7mMSQr2jdz8Y2ypQ==", + "dev": true, + "dependencies": { + "@nomicfoundation/slang": "^0.17.0", + "cbor": "^9.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/cbor": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", + "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "dev": true, + "dependencies": { + "nofilter": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "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, + "peer": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "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/@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, + "peer": true, + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "peer": true, + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": 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, + "peer": true, + "dependencies": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@smithy/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.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 + }, + "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, + "peer": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-1.8.1.tgz", + "integrity": "sha512-64My6I7uCcmSQ//427Pfg2vjSf9SDzfsGIWohNFgISMLYdC5BzJqDo647iDDJzSxINh3WTC0Ql46ifiKuOoTyA==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "1.8.1", + "@stylistic/eslint-plugin-jsx": "1.8.1", + "@stylistic/eslint-plugin-plus": "1.8.1", + "@stylistic/eslint-plugin-ts": "1.8.1", + "@types/eslint": "^8.56.10" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.1.tgz", + "integrity": "sha512-c5c2C8Mos5tTQd+NWpqwEu7VT6SSRooAguFPMj1cp2RkTYl1ynKoXo8MWy3k4rkbzoeYHrqC2UlUzsroAN7wtQ==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.8.1.tgz", + "integrity": "sha512-k1Eb6rcjMP+mmjvj+vd9y5KUdWn1OBkkPLHXhsrHt5lCDFZxJEs0aVQzE5lpYrtVZVkpc5esTtss/cPJux0lfA==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^1.8.1", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.8.1.tgz", + "integrity": "sha512-4+40H3lHYTN8OWz+US8CamVkO+2hxNLp9+CAjorI7top/lHqemhpJvKA1LD9Uh+WMY9DYWiWpL2+SZ2wAXY9fQ==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@stylistic/eslint-plugin-ts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-1.8.1.tgz", + "integrity": "sha512-/q1m+ZuO1JHfiSF16EATFzv7XSJkc5W6DocfvH5o9oB6WWYFMF77fVoBWnKT3wGptPOc2hkRupRKhmeFROdfWA==", + "dev": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "1.8.1", + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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/@types/bn.js": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.6.tgz", + "integrity": "sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==", + "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, + "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, + "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, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "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, + "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, + "peer": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "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, + "peer": 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, + "peer": true + }, + "node_modules/@types/mocha": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", + "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "dev": true, + "peer": true + }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "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, + "peer": true + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, + "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/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "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, + "peer": true + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.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, + "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, + "peer": 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==", + "dev": true, + "peer": true + }, + "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, + "peer": 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, + "peer": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.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, + "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/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, + "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, + "peer": 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, + "peer": 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, + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/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, + "peer": true, + "engines": { + "node": ">=10" + }, + "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": "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/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, + "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, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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 + }, + "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, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dev": true, + "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" + } + ] + }, + "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, + "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, + "peer": 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/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, + "peer": 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/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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, + "peer": 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, + "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, + "peer": 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, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": 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, + "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, + "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, + "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, + "peer": true, + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" + } + }, + "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/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, + "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, + "peer": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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, + "peer": 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, + "peer": 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, + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "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, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "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/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/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "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, + "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, + "peer": 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, + "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, + "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/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/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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/command-line-usage/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/command-line-usage/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/command-line-usage/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/command-line-usage/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/command-line-usage/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/command-line-usage/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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "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, + "peer": 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 + }, + "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 + }, + "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" + ], + "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, + "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, + "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, + "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, + "peer": 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, + "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, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "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.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "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, + "peer": 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, + "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, + "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 + }, + "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, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "peer": 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, + "peer": 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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "peer": true, + "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, + "peer": 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/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, + "peer": 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, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "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, + "peer": true, + "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, + "peer": true, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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/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/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/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==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.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, + "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, + "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/@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/" + } + ], + "peer": true + }, + "node_modules/eth-gas-reporter/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/" + } + ], + "peer": true, + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/eth-gas-reporter/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/" + } + ], + "peer": true, + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "dependencies": { + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "colors": "^1.1.2" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "node_modules/eth-gas-reporter/node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "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" + } + ], + "peer": true, + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/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, + "peer": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "peer": true, + "dependencies": { + "@noble/hashes": "^1.4.0" + } + }, + "node_modules/ethereum-bloom-filters/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/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==", + "deprecated": "This library has been deprecated and usage is discouraged.", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "node_modules/ethereumjs-abi/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, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "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, + "peer": true + }, + "node_modules/ethereumjs-abi/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, + "peer": 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": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "dev": 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" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethers": { + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "peer": true, + "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/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dev": true, + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "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==", + "dev": true, + "peer": 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==", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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==", + "dev": true, + "peer": true + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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_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 + }, + "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==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": 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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.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==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "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, + "peer": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "peer": true + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.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==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^2.4.2", + "node-emoji": "^1.10.0" + }, + "bin": { + "testrpc-sc": "index.js" + } + }, + "node_modules/ghost-testrpc/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/ghost-testrpc/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/ghost-testrpc/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/ghost-testrpc/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/ghost-testrpc/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/ghost-testrpc/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ghost-testrpc/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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": 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/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "peer": 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" + } + }, + "node_modules/handlebars/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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hardhat": { + "version": "2.22.15", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.15.tgz", + "integrity": "sha512-BpTGa9PE/sKAaHi4s/S1e9WGv63DR1m7Lzfd60C8gSEchDPfAJssVRSq0MZ2v2k76ig9m0kHAwVLf5teYwu/Mw==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.1.2", + "@metamask/eth-sig-util": "^4.0.0", + "@nomicfoundation/edr": "^0.6.4", + "@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", + "chalk": "^2.4.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": "^2.1.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "glob": "7.2.0", + "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", + "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", + "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 + } + } + }, + "node_modules/hardhat-contract-sizer": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/hardhat-contract-sizer/-/hardhat-contract-sizer-2.10.0.tgz", + "integrity": "sha512-QiinUgBD5MqJZJh1hl1jc9dNnpJg7eE/w4/4GEnrcmZJJTDbVFNe3+/3Ep24XqISSkYxRz36czcPHKHd/a0dwA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "cli-table3": "^0.6.0", + "strip-ansi": "^6.0.0" + }, + "peerDependencies": { + "hardhat": "^2.0.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "array-uniq": "1.0.3", + "eth-gas-reporter": "^0.2.25", + "sha1": "^1.1.1" + }, + "peerDependencies": { + "hardhat": "^2.0.2" + } + }, + "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/" + } + ], + "peer": true + }, + "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/" + } + ], + "peer": true, + "dependencies": { + "@noble/hashes": "~1.2.0", + "@noble/secp256k1": "~1.7.0", + "@scure/base": "~1.1.0" + } + }, + "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/" + } + ], + "peer": true, + "dependencies": { + "@noble/hashes": "~1.2.0", + "@scure/base": "~1.1.0" + } + }, + "node_modules/hardhat/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/hardhat/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/hardhat/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/hardhat/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/hardhat/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/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, + "peer": true, + "dependencies": { + "@noble/hashes": "1.2.0", + "@noble/secp256k1": "1.7.1", + "@scure/bip32": "1.1.5", + "@scure/bip39": "1.1.1" + } + }, + "node_modules/hardhat/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/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, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/hardhat/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/hardhat/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hardhat/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/hardhat/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "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/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, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "peer": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true, + "peer": true + }, + "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, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.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==", + "dev": true, + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "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==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "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==", + "dev": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true, + "peer": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "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, + "peer": true + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, + "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, + "peer": true, + "dependencies": { + "fp-ts": "^1.0.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": 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==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.1", + "unfetch": "^4.2.0" + } + }, + "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==", + "dev": true + }, + "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, + "peer": 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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "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==", + "dev": true, + "peer": true, + "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, + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", + "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", + "dev": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "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, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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==", + "dev": true, + "peer": true + }, + "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==", + "dev": true, + "peer": true + }, + "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==", + "dev": true, + "peer": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "peer": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "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, + "peer": true + }, + "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, + "peer": true + }, + "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==", + "dev": true, + "peer": 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==", + "dev": true, + "dependencies": { + "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, + "peer": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "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, + "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==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "engines": { + "node": ">= 0.6" + } + }, + "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, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.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/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": { + "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==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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, + "peer": true, + "dependencies": { + "obliterator": "^2.0.0" + } + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "peer": 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" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/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, + "peer": 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/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", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/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==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "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, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "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==", + "dev": true, + "peer": true, + "dependencies": { + "json-stringify-safe": "^5.0.1", + "minimist": "^1.2.5", + "readable-stream": "^3.6.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "ndjson": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "peer": true + }, + "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/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==", + "dev": true, + "peer": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "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, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "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==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "dev": true, + "engines": { + "node": ">=12.19" + } + }, + "node_modules/nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "dev": true, + "peer": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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, + "peer": 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.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ordinal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", + "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", + "dev": true, + "peer": true + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "peer": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "peer": true + }, + "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, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "peer": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.4.1.tgz", + "integrity": "sha512-Mq8EtfacVZ/0+uDKTtHZGW3Aa7vEbX/BNx63hmVg6YTiTXSiuKP0amj0G6pGwjmLaOfymWh3QgXEZkjQbU8QRg==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.18.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "prettier": ">=2.3.0" + } + }, + "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true + }, + "node_modules/prettier-plugin-solidity/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "peer": true + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "asap": "~2.0.6" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "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==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "peer": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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/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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/recursive-readdir/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/recursive-readdir/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "req-from": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/req-from/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "peer": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "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, + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "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==", + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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, + "peer": 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==", + "dev": true, + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/sc-istanbul/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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", + "dev": true, + "peer": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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==", + "dev": true, + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sc-istanbul/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sc-istanbul/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "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/secp256k1": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/secp256k1/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/secp256k1/node_modules/elliptic": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", + "dev": true, + "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/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 + }, + "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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "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, + "peer": true, + "dependencies": { + "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": ">= 0.4" + } + }, + "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, + "peer": 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==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", + "dev": true, + "peer": true, + "dependencies": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + }, + "engines": { + "node": "*" + } + }, + "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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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 + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": 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==", + "dev": true, + "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_modules/solc": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", + "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", + "dev": true, + "peer": 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" + }, + "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, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/solidity-ast": { + "version": "0.4.59", + "resolved": "https://registry.npmjs.org/solidity-ast/-/solidity-ast-0.4.59.tgz", + "integrity": "sha512-I+CX0wrYUN9jDfYtcgWSe+OAowaXy8/1YQy7NS4ni5IBDmIYBq7ZzaP/7QqouLjzZapmQtvGLqCaYgoUWqBo5g==", + "dev": true + }, + "node_modules/solidity-coverage": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.13.tgz", + "integrity": "sha512-RiBoI+kF94V3Rv0+iwOj3HQVSqNzA9qm/qDP1ZDXK5IX0Cvho1qiz8hAXTsAo6KOIUeP73jfscq0KlLqVxzGWA==", + "dev": true, + "peer": true, + "dependencies": { + "@ethersproject/abi": "^5.0.9", + "@solidity-parser/parser": "^0.18.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" + } + }, + "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", + "dev": true, + "peer": true + }, + "node_modules/solidity-coverage/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/solidity-coverage/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/solidity-coverage/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/solidity-coverage/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/solidity-coverage/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/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==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/solidity-coverage/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==", + "dev": true, + "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/solidity-coverage/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/solidity-coverage/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solidity-coverage/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==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/solidity-coverage/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "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/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, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "peer": true, + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "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, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "peer": true, + "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_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, + "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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "is-hex-prefixed": "1.0.0" + }, + "engines": { + "node": ">=6.5.0", + "npm": ">=3" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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==", + "dev": true, + "peer": true, + "dependencies": { + "http-response-object": "^3.0.1", + "sync-rpc": "^1.2.1", + "then-request": "^6.0.0" + }, + "engines": { + "node": ">=8.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==", + "dev": true, + "peer": true, + "dependencies": { + "get-port": "^3.1.0" + } + }, + "node_modules/table": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", + "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "dev": true, + "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" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "array-back": "^4.0.1", + "deep-extend": "~0.6.0", + "typical": "^5.2.0", + "wordwrapjs": "^4.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/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, + "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/table/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==", + "dev": true, + "peer": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "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==", + "dev": true, + "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_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==", + "dev": true, + "peer": true + }, + "node_modules/then-request/node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "peer": true, + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "peer": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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, + "peer": true, + "engines": { + "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 + }, + "node_modules/ts-api-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "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==", + "dev": true, + "peer": true, + "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" + } + }, + "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, + "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==", + "dev": true, + "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" + }, + "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" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "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, + "peer": 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, + "peer": 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, + "peer": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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/typechain": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", + "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", + "dev": true, + "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/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/typechain/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, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "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, + "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/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/typechain/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typechain/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/typechain/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "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, + "peer": true + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dev": true, + "peer": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "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==", + "dev": true + }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "dev": true + }, + "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, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.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, + "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 + }, + "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==", + "dev": true, + "peer": true, + "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, + "peer": 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, + "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, + "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, + "peer": true, + "engines": { + "node": ">= 16" + }, + "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, + "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", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "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, + "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, + "peer": 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, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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, + "peer": true + }, + "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, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "peer": true, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "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, + "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", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/scripts/deployProxy.ts b/scripts/deployProxy.ts new file mode 100644 index 0000000..789d38a --- /dev/null +++ b/scripts/deployProxy.ts @@ -0,0 +1,21 @@ +import { ethers, upgrades } from "hardhat"; + +async function main() { + const CONTRACT_NAME: string = ""; // TBD: Enter contract name + const TOKEN_ADDRESS: string = ""; // TBD: Enter token contract address + + const factory = await ethers.getContractFactory(CONTRACT_NAME); + const proxy = await upgrades.deployProxy( + factory, + [TOKEN_ADDRESS], + { kind: "uups" } + ); + + await proxy.waitForDeployment(); + + console.log("Proxy deployed:", await proxy.getAddress()); +} + +main().then().catch(err => { + throw err; +}); diff --git a/scripts/prepareUpgrade.ts b/scripts/prepareUpgrade.ts new file mode 100644 index 0000000..edf2afc --- /dev/null +++ b/scripts/prepareUpgrade.ts @@ -0,0 +1,18 @@ +import { ethers, upgrades } from "hardhat"; + +async function main() { + const CONTRACT_NAME: string = ""; // TBD: Enter contract name + const PROXY_ADDRESS: string = ""; // TBD: Enter proxy address + + const factory = await ethers.getContractFactory(CONTRACT_NAME); + const response = await upgrades.prepareUpgrade(PROXY_ADDRESS, factory, { + unsafeAllowRenames: false, + unsafeSkipStorageCheck: false + }); + + console.log("Upgrade prepared:", response); +} + +main().then().catch(err => { + throw err; +}); diff --git a/scripts/upgradeProxy.ts b/scripts/upgradeProxy.ts new file mode 100644 index 0000000..90cea1d --- /dev/null +++ b/scripts/upgradeProxy.ts @@ -0,0 +1,18 @@ +import { ethers, upgrades } from "hardhat"; + +async function main() { + const CONTRACT_NAME: string = ""; // TBD: Enter contract name + const PROXY_ADDRESS: string = ""; // TBD: Enter proxy address + + const factory = await ethers.getContractFactory(CONTRACT_NAME); + await upgrades.upgradeProxy(PROXY_ADDRESS, factory, { + unsafeAllowRenames: false, + unsafeSkipStorageCheck: false + }); + + console.log("Proxy upgraded"); +} + +main().then().catch(err => { + throw err; +}); diff --git a/scripts/validateUpgrade.ts b/scripts/validateUpgrade.ts new file mode 100644 index 0000000..40b72cf --- /dev/null +++ b/scripts/validateUpgrade.ts @@ -0,0 +1,18 @@ +import { ethers, upgrades } from "hardhat"; + +async function main() { + const CONTRACT_NAME: string = ""; // TBD: Enter contract name + const PROXY_ADDRESS: string = ""; // TBD: Enter proxy address + + const factory = await ethers.getContractFactory(CONTRACT_NAME); + await upgrades.validateUpgrade(PROXY_ADDRESS, factory, { + unsafeAllowRenames: false, + unsafeSkipStorageCheck: false + }); + + console.log("Successfully validated"); +} + +main().then().catch(err => { + throw err; +}); diff --git a/test-utils/README.md b/test-utils/README.md new file mode 100644 index 0000000..524e078 --- /dev/null +++ b/test-utils/README.md @@ -0,0 +1,3 @@ +## Test utils directory + +This directory contains custom utilities used for smart contracts testing. diff --git a/test-utils/checkers.ts b/test-utils/checkers.ts new file mode 100644 index 0000000..f67c3d5 --- /dev/null +++ b/test-utils/checkers.ts @@ -0,0 +1,82 @@ +import { expect } from "chai"; + +export interface EventParameterCheckingOptions { + showValuesInErrorMessage?: boolean; + caseInsensitiveComparison?: boolean; + convertToJson?: boolean; +} + +interface Stringable { + toString(): string; +} + +function checkEventParameter( + fieldName: string, + expectedValue: T | string | undefined | null, + options: EventParameterCheckingOptions = {} +): (value: T) => boolean { + const f = function (value: T | string): boolean { + if (options.convertToJson) { + value = JSON.stringify(value); + expectedValue = JSON.stringify(expectedValue); + } + let errorMessage = `The "${fieldName}" field of the event is wrong`; + if (options.showValuesInErrorMessage) { + errorMessage += ` (actual: ${value} ; expected: ${expectedValue})`; + } + if (options.caseInsensitiveComparison) { + value = value.toString().toLowerCase(); + expectedValue = expectedValue?.toString()?.toLowerCase(); + } + expect(value).to.equal(expectedValue, errorMessage); + return true; + }; + Object.defineProperty(f, "name", { value: `checkEventField_${fieldName}`, writable: false }); + return f; +} + +function checkEventParameterNotEqual( + fieldName: string, + notExpectedValue: T | string | undefined | null, + options: EventParameterCheckingOptions = {} +): (value: T | string) => boolean { + const f = function (value: T | string): boolean { + if (options.convertToJson) { + value = JSON.stringify(value); + notExpectedValue = JSON.stringify(notExpectedValue); + } + let errorMessage = + `The "${fieldName}" field of the event is wrong because it is equal ${notExpectedValue} but should not`; + if (options.showValuesInErrorMessage) { + errorMessage += ` (actual: ${value} ; not expected: ${notExpectedValue})`; + } + if (options.caseInsensitiveComparison) { + value = value.toString().toLowerCase(); + notExpectedValue = notExpectedValue?.toString()?.toLowerCase(); + } + expect(value).not.to.equal(notExpectedValue, errorMessage); + return true; + }; + Object.defineProperty(f, "name", { value: `checkEventFieldNot_${fieldName}`, writable: false }); + return f; +} + +function checkEquality>(actualObject: T, expectedObject: T, index?: number) { + const indexString = !index ? "" : ` with index: ${index}`; + Object.keys(expectedObject).forEach(property => { + const value = actualObject[property]; + if (typeof value === "undefined" || typeof value === "function" || typeof value === "object") { + throw Error(`Property "${property}" is not found in the actual object` + indexString); + } + expect(value).to.eq( + expectedObject[property], + `Mismatch in the "${property}" property between the actual object and expected one` + indexString + ); + }); +} + +export { + checkEventParameter, + checkEventParameterNotEqual, + checkEquality +}; diff --git a/test-utils/eth.ts b/test-utils/eth.ts new file mode 100644 index 0000000..365c392 --- /dev/null +++ b/test-utils/eth.ts @@ -0,0 +1,82 @@ +import { ethers, upgrades, network } from "hardhat"; +import { BaseContract, BlockTag, Contract, ContractFactory, TransactionReceipt, TransactionResponse } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { time } from "@nomicfoundation/hardhat-network-helpers"; +import { expect } from "chai"; + +export async function checkContractUupsUpgrading( + contract: Contract, + contractFactory: ContractFactory, + upgradeFunctionSignature: string = "upgradeToAndCall(address,bytes)" +) { + const contractAddress = await contract.getAddress(); + const oldImplementationAddress = await upgrades.erc1967.getImplementationAddress(contractAddress); + const newImplementation = await contractFactory.deploy(); + await newImplementation.waitForDeployment(); + const expectedNewImplementationAddress = await newImplementation.getAddress(); + + if (upgradeFunctionSignature === "upgradeToAndCall(address,bytes)") { + await proveTx(contract[upgradeFunctionSignature](expectedNewImplementationAddress, "0x")); + } else { + await proveTx(contract[upgradeFunctionSignature](expectedNewImplementationAddress)); + } + + const actualNewImplementationAddress = await upgrades.erc1967.getImplementationAddress(contractAddress); + expect(actualNewImplementationAddress).to.eq(expectedNewImplementationAddress); + expect(actualNewImplementationAddress).not.to.eq(oldImplementationAddress); +} + +export function connect(contract: BaseContract, signer: HardhatEthersSigner): Contract { + return contract.connect(signer) as Contract; +} + +export function getAddress(contract: Contract): string { + const address = contract.target; + if (typeof address !== "string" || address.length != 42 || !address.startsWith("0x")) { + throw new Error("The '.target' field of the contract is not an address string"); + } + return address; +} + +export async function getBlockTimestamp(blockTag: BlockTag): Promise { + const block = await ethers.provider.getBlock(blockTag); + return block?.timestamp ?? 0; +} + +export async function getLatestBlockTimestamp(): Promise { + return getBlockTimestamp("latest"); +} + +export async function getTxTimestamp(tx: Promise): Promise { + const receipt = await proveTx(tx); + const block = await ethers.provider.getBlock(receipt.blockNumber); + return Number(block?.timestamp ?? 0); +} + +export async function increaseBlockTimestampTo(targetTimestamp: number) { + if (network.name === "hardhat") { + await time.increaseTo(targetTimestamp); + } else if (network.name === "stratus") { + await ethers.provider.send("evm_setNextBlockTimestamp", [targetTimestamp]); + await ethers.provider.send("evm_mine", []); + } else { + throw new Error(`Setting block timestamp for the current blockchain is not supported: ${network.name}`); + } +} + +export async function increaseBlockTimestamp(increaseInSeconds: number) { + if (increaseInSeconds <= 0) { + throw new Error(`The block timestamp increase must be greater than zero, but it equals: ${increaseInSeconds}`); + } + const currentTimestamp = await getLatestBlockTimestamp(); + await increaseBlockTimestampTo(currentTimestamp + increaseInSeconds); +} + +export async function proveTx(txResponsePromise: Promise): Promise { + const txResponse = await txResponsePromise; + const txReceipt = await txResponse.wait(); + if (!txReceipt) { + throw new Error("The transaction receipt is empty"); + } + return txReceipt as TransactionReceipt; +} diff --git a/test-utils/misc.ts b/test-utils/misc.ts new file mode 100644 index 0000000..d5138e9 --- /dev/null +++ b/test-utils/misc.ts @@ -0,0 +1,22 @@ +function createBytesString(baseString: string | number | undefined, byteLength: number) { + baseString = !baseString ? "" : baseString.toString(); + if (baseString.length > byteLength * 2) { + throw new Error( + `Creating of bytes string failed. ` + + `The length of the base string if greater than allowed maximum length. ` + + `The base string: '${baseString}'. ` + + `The target byte length: '${byteLength}'` + ); + } + if (!(/^[0-9a-fA-F]+$/).test(baseString)) { + throw new Error( + `Creating of bytes string failed. ` + + `The base string content is incorrect. ` + + `The base string: '${baseString}'` + ); + } + + return "0x" + "0".repeat(byteLength * 2 - baseString.length) + baseString.toLowerCase(); +} + +export { createBytesString }; diff --git a/test/CardPaymentProcessor.test.ts b/test/CardPaymentProcessor.test.ts new file mode 100644 index 0000000..b55f728 --- /dev/null +++ b/test/CardPaymentProcessor.test.ts @@ -0,0 +1,4897 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory, TransactionReceipt, TransactionResponse } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { + checkContractUupsUpgrading, + connect, + getAddress, + getTxTimestamp, + increaseBlockTimestamp, + proveTx +} from "../test-utils/eth"; +import { createBytesString } from "../test-utils/misc"; +import { + checkEquality, + checkEventParameter, + checkEventParameterNotEqual, + EventParameterCheckingOptions +} from "../test-utils/checkers"; + +const MAX_UINT256 = ethers.MaxUint256; +const MAX_INT256 = ethers.MaxInt256; +const MAX_UINT64 = BigInt("0xffffffffffffffff"); +const ZERO_ADDRESS = ethers.ZeroAddress; +const ZERO_CONFIRMATION_AMOUNT = 0; +const ZERO_PAYER_ADDRESS = ethers.ZeroAddress; +const ZERO_REFUND_AMOUNT = 0; +const ZERO_SPONSOR_ADDRESS = ethers.ZeroAddress; +const ZERO_SUBSIDY_LIMIT = 0; +const BYTES32_LENGTH: number = 32; +const CASHBACK_RATE_AS_IN_CONTRACT = -1; +const TOKE_DECIMALS = 6; +const CASHBACK_ROUNDING_COEF = 10 ** (TOKE_DECIMALS - 2); +const DIGITS_COEF = 10 ** TOKE_DECIMALS; +const INITIAL_USER_BALANCE = 1000_000 * DIGITS_COEF; +const INITIAL_SPONSOR_BALANCE = INITIAL_USER_BALANCE * 2; +const CASHBACK_FACTOR = 1000; +const CASHBACK_CAP_RESET_PERIOD = 30 * 24 * 60 * 60; +const MAX_CASHBACK_FOR_CAP_PERIOD = 300 * 10 ** TOKE_DECIMALS; + +const EVENT_ADDENDUM_VERSION_DEFAULT_VALUE = "01"; +const EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED = "00"; +const EVENT_ADDENDUM_FLAGS_SUBSIDIZED = "01"; +const EVENT_ADDENDUM_CHARS_FOR_AMOUNT = 16; + +const eventAddendumCheckingOptions: EventParameterCheckingOptions = { + showValuesInErrorMessage: true, + caseInsensitiveComparison: true +}; + +const EVENT_NAME_ACCOUNT_REFUNDED = "AccountRefunded"; +const EVENT_NAME_CASH_OUT_ACCOUNT_CHANGED = "CashOutAccountChanged"; +const EVENT_NAME_CASHBACK_ENABLED = "CashbackEnabled"; +const EVENT_NAME_CASHBACK_DISABLED = "CashbackDisabled"; +const EVENT_NAME_CASHBACK_INCREASED = "CashbackIncreased"; +const EVENT_NAME_CASHBACK_RATE_CHANGED = "CashbackRateChanged"; +const EVENT_NAME_CASHBACK_REVOKED = "CashbackRevoked"; +const EVENT_NAME_CASHBACK_TREASURY_CHANGED = "CashbackTreasuryChanged"; +const EVENT_NAME_CASHBACK_SENT = "CashbackSent"; +const EVENT_NAME_PAYMENT_CONFIRMED_AMOUNT_CHANGED = "PaymentConfirmedAmountChanged"; +const EVENT_NAME_PAYMENT_MADE = "PaymentMade"; +const EVENT_NAME_PAYMENT_REFUNDED = "PaymentRefunded"; +const EVENT_NAME_PAYMENT_REVERSED = "PaymentReversed"; +const EVENT_NAME_PAYMENT_REVOKED = "PaymentRevoked"; +const EVENT_NAME_PAYMENT_UPDATED = "PaymentUpdated"; + +enum PaymentStatus { + // Nonexistent = 0, is not used in the tests. + Active = 1, + Revoked = 2, + Reversed = 3 +} + +enum CashbackOperationStatus { + Undefined = 0, + Success = 1, + Partial = 2, + Capped = 3, + Failed = 4 +} + +interface PaymentConfirmation { + paymentId: string; + amount: number; +} + +interface TestPayment { + id: string; + payer: HardhatEthersSigner; + baseAmount: number; + extraAmount: number; +} + +interface PaymentModel { + paymentId: string; // Does not exist in the contract + status: PaymentStatus; + payer: HardhatEthersSigner; + cashbackEnabled: boolean; + cashbackRate: number; + confirmedAmount: number; + sponsor?: HardhatEthersSigner; + subsidyLimit: number; + baseAmount: number; + extraAmount: number; + cashbackAmount: number; + refundAmount: number; +} + +interface AccountCashbackState { + totalAmount: bigint; + capPeriodStartAmount: bigint; + capPeriodStartTime: number; +} + +enum CashbackOperationKind { + Undefined = 0, + None = 1, + Sending = 2, + Increase = 3, + Revocation = 4 +} + +enum OperationKind { + Undefined = 0, + Making = 1, + Updating = 2, + Revoking = 3, + Reversing = 4, + Confirming = 5, + Refunding = 6 +} + +enum UpdatingOperationKind { + Full = 0, + Lazy = 1 +} + +interface PaymentOperation { + kind: OperationKind; + paymentId: string; + paymentStatus: PaymentStatus; + sender?: HardhatEthersSigner; + payer: HardhatEthersSigner; + oldBaseAmount: number; + newBaseAmount: number; + oldExtraAmount: number; + newExtraAmount: number; + oldConfirmationAmount: number; + newConfirmationAmount: number; + oldRefundAmount: number; + newRefundAmount: number; + oldRemainder: number; + newRemainder: number; + oldCashbackAmount: number; + oldPayerSumAmount: number; + newPayerSumAmount: number; + oldPayerRefundAmount: number; + newPayerRefundAmount: number; + oldPayerRemainder: number; + newPayerRemainder: number; + cashbackEnabled: boolean; + cashbackOperationKind: CashbackOperationKind; + cashbackOperationStatus: CashbackOperationStatus; + cashbackRequestedChange: number; // From the user point of view, if "+" then the user earns cashback + cashbackActualChange: number; // From the user point of view, if "+" then the user earns cashback + cashbackRate: number; + sponsor?: HardhatEthersSigner; + subsidyLimit: number; + oldSponsorSumAmount: number; + newSponsorSumAmount: number; + oldSponsorRefundAmount: number; + newSponsorRefundAmount: number; + oldSponsorRemainder: number; + newSponsorRemainder: number; + senderBalanceChange: number; + cardPaymentProcessorBalanceChange: number; + cashOutAccountBalanceChange: number; + payerBalanceChange: number; + sponsorBalanceChange: number; + updatingOperationKind: UpdatingOperationKind; + relatedPaymentIds: string[]; +} + +interface Fixture { + cardPaymentProcessor: Contract; + tokenMock: Contract; +} + +interface AmountParts { + payerBaseAmount: number; + payerExtraAmount: number; + payerSumAmount: number; + sponsorBaseAmount: number; + sponsorExtraAmount: number; + sponsorSumAmount: number; +} + +interface RefundParts { + payerRefundAmount: number; + sponsorRefundAmount: number; +} + +enum CashbackConditionType { + CashbackEnabled = 0, + CashbackDisabledBeforePaymentMaking = 1, + CashbackDisabledAfterPaymentMaking = 2, + CashbackEnabledButRevokingFails = 3, + CashbackEnabledButRevokingReverts = 4, + CashbackEnabledButIncreasingFails = 5, + CashbackEnabledButIncreasingReverts = 6, + CashbackEnabledButIncreasingPartial = 7 +} + +class CardPaymentProcessorModel { + #cashbackEnabled: boolean; + readonly #cashbackRate: number; + #paymentPerId: Map = new Map(); + #totalBalance: number = 0; + #totalConfirmedAmount: number = 0; + #totalUnconfirmedRemainder: number = 0; + #paymentMakingOperations: PaymentOperation[] = []; + #paymentOperations: PaymentOperation[] = []; + #cashbackIncreaseStatus: CashbackOperationStatus = CashbackOperationStatus.Success; + #cashbackIncreaseAmountResult: number = -1; // As expected by default cashback calculation + #cashbackRevocationStatus: CashbackOperationStatus = CashbackOperationStatus.Success; + #cashbackSendingStatus: CashbackOperationStatus = CashbackOperationStatus.Success; + #cashbackSendingAmountResult: number = -1; // As expected by default cashback calculation + #cashbackTotalPerAccount: Map = new Map(); + #capPeriodStartAmount: number = 0; + + constructor(props: { + cashbackRate: number; + cashbackEnabled: boolean; + }) { + this.#cashbackRate = props.cashbackRate; + this.#cashbackEnabled = props.cashbackEnabled; + } + + makePayment( + payment: TestPayment, + props: { + sponsor?: HardhatEthersSigner; + subsidyLimit?: number; + cashbackRate?: number; + confirmationAmount?: number; + sender?: HardhatEthersSigner; + } = {} + ): number { + const paymentModel: PaymentModel = this.#createPayment(payment); + const operation: PaymentOperation = this.#createPaymentOperation(paymentModel, OperationKind.Making); + operation.sender = props.sender ?? payment.payer; + operation.oldBaseAmount = 0; + operation.oldExtraAmount = 0; + operation.oldRemainder = 0; + operation.sponsor = props.sponsor; + operation.subsidyLimit = !operation.sponsor ? 0 : (props.subsidyLimit ?? 0); + operation.cashbackOperationKind = CashbackOperationKind.Sending; + operation.newConfirmationAmount = props.confirmationAmount ?? 0; + if (!!props.cashbackRate && props.cashbackRate > 0) { + operation.cashbackRate = props.cashbackRate ?? 0; + } else if (props.cashbackRate === 0) { + operation.cashbackEnabled = false; + operation.cashbackRate = 0; + } + this.#checkPaymentConfirming(operation); + this.#definePaymentOperation(operation); + return this.#registerPaymentMakingOperation(operation, paymentModel); + } + + updatePayment( + paymentId: string, + newBaseAmount: number, + newExtraAmount: number, + updatingOperationKind: UpdatingOperationKind = UpdatingOperationKind.Full + ): number { + const payment: PaymentModel = this.getPaymentById(paymentId); + const operation: PaymentOperation = this.#createPaymentOperation(payment, OperationKind.Updating); + operation.newBaseAmount = newBaseAmount; + operation.newExtraAmount = newExtraAmount; + operation.updatingOperationKind = updatingOperationKind; + this.#checkPaymentUpdating(operation); + this.#definePaymentOperation(operation); + return this.#registerPaymentOperation(operation, payment); + } + + revokePayment(paymentId: string): number { + const payment: PaymentModel = this.getPaymentById(paymentId); + const operation: PaymentOperation = this.#createPaymentOperation(payment, OperationKind.Revoking); + operation.newBaseAmount = 0; + operation.newExtraAmount = 0; + operation.newConfirmationAmount = 0; + operation.newRefundAmount = 0; + this.#checkPaymentCanceling(operation); + this.#definePaymentOperation(operation); + this.#updateModelDueToPaymentCancelingOperation(operation); + return this.#registerPaymentCancelingOperation(operation, payment, PaymentStatus.Revoked); + } + + reversePayment(paymentId: string): number { + const payment: PaymentModel = this.getPaymentById(paymentId); + const operation: PaymentOperation = this.#createPaymentOperation(payment, OperationKind.Reversing); + operation.newBaseAmount = 0; + operation.newExtraAmount = 0; + operation.newConfirmationAmount = 0; + operation.newRefundAmount = 0; + this.#checkPaymentCanceling(operation); + this.#definePaymentOperation(operation); + this.#updateModelDueToPaymentCancelingOperation(operation); + return this.#registerPaymentCancelingOperation(operation, payment, PaymentStatus.Reversed); + } + + confirmPayment(paymentId: string, confirmationAmount: number): number { + const payment: PaymentModel = this.getPaymentById(paymentId); + const operation: PaymentOperation = this.#createPaymentOperation(payment, OperationKind.Confirming); + operation.cardPaymentProcessorBalanceChange = -confirmationAmount; + operation.cashOutAccountBalanceChange = confirmationAmount; + operation.newConfirmationAmount = operation.oldConfirmationAmount + confirmationAmount; + this.#checkPaymentConfirming(operation); + return this.#registerPaymentOperation(operation, payment); + } + + refundPayment( + paymentId: string, + refundingAmount: number + ): number { + const payment: PaymentModel = this.getPaymentById(paymentId); + const operation: PaymentOperation = this.#createPaymentOperation(payment, OperationKind.Refunding); + operation.newRefundAmount = operation.oldRefundAmount + refundingAmount; + this.#checkPaymentRefunding(operation); + this.#definePaymentOperation(operation); + return this.#registerPaymentOperation(operation, payment); + } + + disableCashback() { + this.#cashbackEnabled = false; + } + + set capPeriodStartAmount(startAmount: number) { + this.#capPeriodStartAmount = startAmount; + } + + getPaymentModelsInMakingOrder(): PaymentModel[] { + const paymentNumber = this.#paymentMakingOperations.length; + const paymentModels: PaymentModel[] = []; + for (let i = 0; i < paymentNumber; ++i) { + const paymentModel: PaymentModel = this.#getPaymentByMakingOperationIndex(i); + paymentModels.push(paymentModel); + } + return paymentModels; + } + + getPaymentById(paymentId: string): PaymentModel { + const payment = this.#paymentPerId.get(paymentId); + if (!payment) { + throw Error(`A payment is not in the model. Payment id = ${paymentId}`); + } + return payment; + } + + get totalBalance(): number { + return this.#totalBalance; + } + + get totalConfirmedAmount(): number { + return this.#totalConfirmedAmount; + } + + get totalUnconfirmedRemainder(): number { + return this.#totalUnconfirmedRemainder; + } + + get cashbackRate(): number { + return this.#cashbackRate; + } + + get capPeriodStartAmount(): number { + return this.#capPeriodStartAmount; + } + + getPaymentOperation(operationIndex: number): PaymentOperation { + return this.#getOperationByIndex(this.#paymentOperations, operationIndex, ""); + } + + setCashbackIncreaseStatus(newStatus: CashbackOperationStatus) { + this.#cashbackIncreaseStatus = newStatus; + } + + setCashbackIncreaseAmountResult(newAmountResult: number) { + this.#cashbackIncreaseAmountResult = newAmountResult; + } + + setCashbackRevocationStatus(newStatus: CashbackOperationStatus) { + this.#cashbackRevocationStatus = newStatus; + } + + setCashbackSendingStatus(newStatus: CashbackOperationStatus) { + this.#cashbackSendingStatus = newStatus; + } + + setCashbackSendingAmountResult(newAmountResult: number) { + this.#cashbackSendingAmountResult = newAmountResult; + } + + calculateCashback(baseAmount: number, refundAmount?: number, cashbackRate?: number) { + refundAmount = refundAmount ?? 0; + cashbackRate = cashbackRate ?? this.#cashbackRate; + if (baseAmount < refundAmount) { + return 0; + } + const amount = baseAmount - refundAmount; + const cashback = Math.floor(amount * cashbackRate / CASHBACK_FACTOR); + return this.#roundCashback(cashback, CASHBACK_ROUNDING_COEF); + } + + getCashbackTotalForAccount(account: string): number { + return this.#cashbackTotalPerAccount.get(account) ?? 0; + } + + #createPayment(payment: TestPayment): PaymentModel { + const currentPayment: PaymentModel | undefined = this.#paymentPerId.get(payment.id); + if (currentPayment && currentPayment.status != PaymentStatus.Revoked) { + throw new Error( + `A payment with the provided ID already exists in the model and its status is not "Revoked".` + + `Payment id=${payment.id}` + ); + } + return { + paymentId: payment.id, + status: PaymentStatus.Active, + payer: payment.payer, + cashbackEnabled: this.#cashbackEnabled, + cashbackRate: this.#cashbackRate, + confirmedAmount: 0, + sponsor: undefined, + subsidyLimit: 0, + baseAmount: payment.baseAmount, + extraAmount: payment.extraAmount, + cashbackAmount: 0, + refundAmount: 0 + }; + } + + #createPaymentOperation(payment: PaymentModel, kind: OperationKind): PaymentOperation { + const remainder = payment.baseAmount + payment.extraAmount - payment.refundAmount; + const amountParts: AmountParts = + this.#defineAmountParts(payment.baseAmount, payment.extraAmount, payment.subsidyLimit); + const refundParts: RefundParts = + this.#defineRefundParts(payment.refundAmount, payment.baseAmount, payment.subsidyLimit); + const sponsorRemainder: number = amountParts.sponsorSumAmount - refundParts.sponsorRefundAmount; + return { + kind, + paymentId: payment.paymentId, + paymentStatus: payment.status, + sender: undefined, + payer: payment.payer, + oldBaseAmount: payment.baseAmount, + newBaseAmount: payment.baseAmount, + oldExtraAmount: payment.extraAmount, + newExtraAmount: payment.extraAmount, + oldConfirmationAmount: payment.confirmedAmount, + newConfirmationAmount: payment.confirmedAmount, + oldRefundAmount: payment.refundAmount, + newRefundAmount: payment.refundAmount, + oldRemainder: remainder, + newRemainder: remainder, + oldPayerSumAmount: amountParts.payerSumAmount, + newPayerSumAmount: amountParts.sponsorSumAmount, + oldPayerRefundAmount: refundParts.payerRefundAmount, + newPayerRefundAmount: refundParts.payerRefundAmount, + oldPayerRemainder: remainder - sponsorRemainder, + newPayerRemainder: remainder - sponsorRemainder, + cashbackEnabled: payment.cashbackEnabled, + oldCashbackAmount: payment.cashbackAmount, + cashbackOperationKind: CashbackOperationKind.Undefined, + cashbackOperationStatus: CashbackOperationStatus.Undefined, + cashbackRequestedChange: 0, + cashbackActualChange: 0, + cashbackRate: payment.cashbackRate, + sponsor: payment.sponsor, + subsidyLimit: payment.subsidyLimit, + oldSponsorSumAmount: amountParts.sponsorSumAmount, + newSponsorSumAmount: amountParts.sponsorSumAmount, + oldSponsorRefundAmount: refundParts.sponsorRefundAmount, + newSponsorRefundAmount: refundParts.sponsorRefundAmount, + oldSponsorRemainder: sponsorRemainder, + newSponsorRemainder: sponsorRemainder, + senderBalanceChange: 0, + cardPaymentProcessorBalanceChange: 0, + cashOutAccountBalanceChange: 0, + payerBalanceChange: 0, + sponsorBalanceChange: 0, + updatingOperationKind: UpdatingOperationKind.Full, + relatedPaymentIds: [] + }; + } + + #definePaymentOperation(operation: PaymentOperation) { + const newAmountParts: AmountParts = + this.#defineAmountParts(operation.newBaseAmount, operation.newExtraAmount, operation.subsidyLimit); + const newRefundParts: RefundParts = + this.#defineRefundParts(operation.newRefundAmount, operation.newBaseAmount, operation.subsidyLimit); + const oldPayerRemainder: number = operation.oldRemainder - operation.oldSponsorRemainder; + const newPayerRemainder: number = newAmountParts.payerSumAmount - newRefundParts.payerRefundAmount; + const newSponsorRemainder: number = newAmountParts.sponsorSumAmount - newRefundParts.sponsorRefundAmount; + const newRemainder: number = operation.newBaseAmount + operation.newExtraAmount - operation.newRefundAmount; + if (newRemainder < operation.newConfirmationAmount) { + operation.newConfirmationAmount = newRemainder; + } + + operation.newRemainder = newRemainder; + operation.newPayerSumAmount = newAmountParts.payerSumAmount; + operation.newSponsorSumAmount = newAmountParts.sponsorSumAmount; + operation.newPayerRefundAmount = newRefundParts.payerRefundAmount; + operation.newSponsorRefundAmount = newRefundParts.sponsorRefundAmount; + operation.newPayerRemainder = newPayerRemainder; + operation.newSponsorRemainder = newSponsorRemainder; + operation.payerBalanceChange = oldPayerRemainder - newPayerRemainder; + operation.sponsorBalanceChange = operation.oldSponsorRemainder - newSponsorRemainder; + operation.cardPaymentProcessorBalanceChange -= (operation.payerBalanceChange + operation.sponsorBalanceChange); + operation.cardPaymentProcessorBalanceChange -= + (operation.newConfirmationAmount - operation.oldConfirmationAmount); + operation.cashOutAccountBalanceChange = operation.newConfirmationAmount - operation.oldConfirmationAmount; + + this.#defineCashbackOperation(operation); + + if (!operation.cashbackEnabled) { + return; + } + + if ( + operation.cashbackOperationKind == CashbackOperationKind.Increase || + operation.cashbackOperationKind == CashbackOperationKind.Sending + ) { + operation.payerBalanceChange += operation.cashbackActualChange; + } + if (operation.cashbackOperationKind == CashbackOperationKind.Revocation) { + operation.payerBalanceChange += operation.cashbackRequestedChange; + operation.cardPaymentProcessorBalanceChange -= + (operation.cashbackRequestedChange - operation.cashbackActualChange); + } + + if (operation.sender === operation.payer) { + operation.senderBalanceChange = operation.payerBalanceChange; + } + } + + #defineCashbackOperation(operation: PaymentOperation) { + if (!operation.cashbackEnabled) { + operation.cashbackRate = 0; + operation.cashbackOperationKind = CashbackOperationKind.None; + operation.cashbackOperationStatus = CashbackOperationStatus.Undefined; + operation.cashbackRequestedChange = 0; + operation.cashbackActualChange = 0; + return; + } + if (operation.cashbackOperationKind !== CashbackOperationKind.None) { + const newAmountParts: AmountParts = + this.#defineAmountParts(operation.newBaseAmount, operation.newExtraAmount, operation.subsidyLimit); + const refundParts: RefundParts = + this.#defineRefundParts(operation.newRefundAmount, operation.newBaseAmount, operation.subsidyLimit); + const newCashbackAmount = this.calculateCashback( + newAmountParts.payerBaseAmount, + refundParts.payerRefundAmount, + operation.cashbackRate + ); + operation.cashbackRequestedChange = newCashbackAmount - operation.oldCashbackAmount; + } + if (operation.cashbackOperationKind === CashbackOperationKind.Undefined) { + if (operation.cashbackRequestedChange > 0) { + operation.cashbackOperationKind = CashbackOperationKind.Increase; + } else if (operation.cashbackRequestedChange < 0) { + operation.cashbackOperationKind = CashbackOperationKind.Revocation; + } else { + operation.cashbackOperationKind = CashbackOperationKind.None; + } + } + operation.cashbackActualChange = 0; + operation.cashbackOperationStatus = CashbackOperationStatus.Undefined; + if (operation.cashbackOperationKind == CashbackOperationKind.Increase) { + operation.cashbackOperationStatus = this.#cashbackIncreaseStatus; + if ( + operation.cashbackOperationStatus === CashbackOperationStatus.Success || + operation.cashbackOperationStatus === CashbackOperationStatus.Partial + ) { + if (this.#cashbackIncreaseAmountResult < 0) { + operation.cashbackActualChange = operation.cashbackRequestedChange; + } else { + operation.cashbackActualChange = this.#cashbackIncreaseAmountResult; + if (operation.cashbackRequestedChange > operation.cashbackActualChange) { + operation.cashbackOperationStatus = CashbackOperationStatus.Partial; + } + } + } + } else if (operation.cashbackOperationKind == CashbackOperationKind.Revocation) { + operation.cashbackOperationStatus = this.#cashbackRevocationStatus; + if (operation.cashbackOperationStatus === CashbackOperationStatus.Success) { + operation.cashbackActualChange = operation.cashbackRequestedChange; + } + } else if (operation.cashbackOperationKind == CashbackOperationKind.Sending) { + if (operation.cashbackRequestedChange < 0) { + throw new Error("The cashback amount change is negative during the cashback sending operation"); + } + operation.cashbackOperationStatus = this.#cashbackSendingStatus; + if ( + operation.cashbackOperationStatus === CashbackOperationStatus.Success || + operation.cashbackOperationStatus === CashbackOperationStatus.Partial + ) { + if (this.#cashbackSendingAmountResult < 0) { + operation.cashbackActualChange = operation.cashbackRequestedChange; + } else { + operation.cashbackActualChange = this.#cashbackSendingAmountResult; + if (operation.cashbackRequestedChange > operation.cashbackActualChange) { + operation.cashbackOperationStatus = CashbackOperationStatus.Partial; + } + } + } + } + + // Check the periodical cap + if ( + operation.cashbackOperationStatus === CashbackOperationStatus.Success || + operation.cashbackOperationStatus === CashbackOperationStatus.Partial + ) { + const totalCashback = + (this.#cashbackTotalPerAccount.get(operation.payer.address) ?? 0) - this.#capPeriodStartAmount; + if (totalCashback + operation.cashbackActualChange > MAX_CASHBACK_FOR_CAP_PERIOD) { + if (totalCashback >= MAX_CASHBACK_FOR_CAP_PERIOD) { + operation.cashbackActualChange = 0; + operation.cashbackOperationStatus = CashbackOperationStatus.Capped; + } else { + operation.cashbackActualChange = MAX_CASHBACK_FOR_CAP_PERIOD - totalCashback; + operation.cashbackOperationStatus = CashbackOperationStatus.Partial; + } + } + } + } + + #registerPaymentOperation(operation: PaymentOperation, payment: PaymentModel): number { + payment.baseAmount = operation.newBaseAmount; + payment.extraAmount = operation.newExtraAmount; + payment.refundAmount = operation.newRefundAmount; + payment.confirmedAmount = operation.newConfirmationAmount; + payment.cashbackAmount += operation.cashbackActualChange; + this.#totalBalance += operation.cardPaymentProcessorBalanceChange; + this.#totalConfirmedAmount += operation.cashOutAccountBalanceChange; + this.#totalUnconfirmedRemainder += (operation.newRemainder - operation.oldRemainder); + this.#totalUnconfirmedRemainder -= (operation.newConfirmationAmount - operation.oldConfirmationAmount); + const totalCashback = this.#cashbackTotalPerAccount.get(operation.payer.address) ?? 0; + this.#cashbackTotalPerAccount.set(operation.payer.address, totalCashback + operation.cashbackActualChange); + return this.#paymentOperations.push(operation) - 1; + } + + #registerPaymentMakingOperation(operation: PaymentOperation, payment: PaymentModel): number { + payment.cashbackEnabled = operation.cashbackEnabled; + payment.cashbackRate = operation.cashbackRate; + if (operation.sponsor) { + payment.sponsor = operation.sponsor; + payment.subsidyLimit = operation.subsidyLimit; + } + this.#paymentPerId.set(payment.paymentId, payment); + this.#paymentMakingOperations.push(operation); + return this.#registerPaymentOperation(operation, payment); + } + + #roundCashback(cashback: number, roundingCoefficient: number): number { + return Math.floor(Math.floor(cashback + roundingCoefficient / 2) / roundingCoefficient) * roundingCoefficient; + } + + #getPaymentByMakingOperationIndex(paymentMakingOperationIndex: number): PaymentModel { + const paymentOperation: PaymentOperation = this.#paymentMakingOperations[paymentMakingOperationIndex]; + const paymentId = paymentOperation.paymentId; + return this.getPaymentById(paymentId); + } + + #checkPaymentUpdating(operation: PaymentOperation) { + if (operation.paymentStatus !== PaymentStatus.Active) { + throw new Error( + `The payment has inappropriate status: ${operation.paymentStatus}` + ); + } + if (operation.newRefundAmount > operation.newBaseAmount + operation.newExtraAmount) { + throw new Error( + `The new sum amount is wrong for the payment with the id=${operation.paymentId}. ` + + `The new sum amount: ${operation.newBaseAmount + operation.newExtraAmount}. ` + + `The old sum amount: ${operation.oldBaseAmount + operation.oldExtraAmount}. ` + + `The payment refund amount: ${operation.newRefundAmount}` + ); + } + } + + #getOperationByIndex(operations: PaymentOperation[], index: number, kind: string): PaymentOperation { + if (index < 0) { + index = operations.length + index; + } + if (index >= operations.length) { + throw new Error( + `A payment ${kind} operation with index ${index} does not exist. ` + ); + } + return operations[index]; + } + + #checkPaymentCanceling(operation: PaymentOperation) { + if (operation.paymentStatus !== PaymentStatus.Active) { + throw new Error( + `The payment has inappropriate status: ${operation.paymentStatus}` + ); + } + } + + #updateModelDueToPaymentCancelingOperation(operation: PaymentOperation) { + this.#totalBalance += operation.cardPaymentProcessorBalanceChange; + this.#totalConfirmedAmount += operation.cashOutAccountBalanceChange; + this.#totalUnconfirmedRemainder += (operation.newRemainder - operation.oldRemainder); + this.#totalUnconfirmedRemainder -= (operation.newConfirmationAmount - operation.oldConfirmationAmount); + const totalCashback = this.#cashbackTotalPerAccount.get(operation.payer.address) ?? 0; + this.#cashbackTotalPerAccount.set(operation.payer.address, totalCashback + operation.cashbackActualChange); + } + + #registerPaymentCancelingOperation(operation: PaymentOperation, payment: PaymentModel, targetStatus: PaymentStatus) { + payment.status = targetStatus; + return this.#paymentOperations.push(operation) - 1; + } + + #checkPaymentConfirming(operation: PaymentOperation) { + if (operation.paymentStatus !== PaymentStatus.Active) { + throw new Error( + `The payment has inappropriate status: ${operation.paymentStatus}` + ); + } + const remainder: number = operation.newBaseAmount + operation.newExtraAmount - operation.newRefundAmount; + if (operation.newConfirmationAmount > remainder) { + throw new Error( + `The confirmation amount is wrong for the payment with id=${operation.paymentId}. ` + + `The old payment confirmed amount: ${operation.oldConfirmationAmount}. ` + + `The new confirmation amount: ${operation.newConfirmationAmount}. ` + + `The payment remainder: ${remainder}.` + ); + } + } + + #checkPaymentRefunding(operation: PaymentOperation) { + if (operation.paymentStatus !== PaymentStatus.Active) { + throw new Error( + `The payment has inappropriate status: ${operation.paymentStatus}` + ); + } + if (operation.newRefundAmount > (operation.newBaseAmount + operation.newExtraAmount)) { + throw new Error( + `The new refund amount is wrong for the payment with id=${operation.paymentId}. ` + + `The old refund amount: ${operation.oldRefundAmount}. ` + + `The new refund amount: ${operation.newRefundAmount}. ` + + `The payment sum amount: ${operation.newBaseAmount + operation.newExtraAmount}` + ); + } + } + + #defineAmountParts(paymentBaseAmount: number, paymentExtraAmount: number, subsidyLimit: number): AmountParts { + const result: AmountParts = { + payerBaseAmount: paymentBaseAmount, + payerExtraAmount: paymentExtraAmount, + payerSumAmount: paymentBaseAmount + paymentExtraAmount, + sponsorBaseAmount: 0, + sponsorExtraAmount: 0, + sponsorSumAmount: 0 + }; + if (subsidyLimit >= (paymentBaseAmount + paymentExtraAmount)) { + result.sponsorBaseAmount = paymentBaseAmount; + result.payerBaseAmount = 0; + result.sponsorExtraAmount = paymentExtraAmount; + result.payerExtraAmount = 0; + } else if (subsidyLimit >= paymentBaseAmount) { + result.sponsorBaseAmount = paymentBaseAmount; + result.payerBaseAmount = 0; + result.sponsorExtraAmount = subsidyLimit - paymentBaseAmount; + result.payerExtraAmount = paymentExtraAmount - result.sponsorExtraAmount; + } else { + result.sponsorBaseAmount = subsidyLimit; + result.payerBaseAmount = paymentBaseAmount - result.sponsorBaseAmount; + result.sponsorExtraAmount = 0; + result.payerExtraAmount = paymentExtraAmount; + } + result.payerSumAmount = result.payerBaseAmount + result.payerExtraAmount; + result.sponsorSumAmount = result.sponsorBaseAmount + result.sponsorExtraAmount; + return result; + } + + #defineRefundParts(paymentRefundAmount: number, paymentBaseAmount: number, subsidyLimit: number): RefundParts { + let sponsorRefundAmount; + if (subsidyLimit === 0) { + sponsorRefundAmount = 0; + } else if (subsidyLimit >= paymentBaseAmount) { + sponsorRefundAmount = paymentRefundAmount; + } else { + sponsorRefundAmount = Math.floor(paymentRefundAmount * subsidyLimit / paymentBaseAmount); + if (sponsorRefundAmount > subsidyLimit) { + sponsorRefundAmount = subsidyLimit; + } + } + return { + payerRefundAmount: paymentRefundAmount - sponsorRefundAmount, + sponsorRefundAmount + }; + } +} + +interface OperationResult { + operationIndex: number; + tx: Promise; + txReceipt: TransactionReceipt; +} + +interface OperationConditions { + confirmationAmountChangedInAnyOperation: boolean; + cashbackSendingRequestedInAnyOperation: boolean; + cashbackIncreaseRequestedInAnyOperation: boolean; + cashbackRevocationRequestedInAnyOperation: boolean; +} + +interface Version { + major: number; + minor: number; + patch: number; + + [key: string]: number; // Indexing signature to ensure that fields are iterated over in a key-value style +} + +class CardPaymentProcessorShell { + contract: Contract; + model: CardPaymentProcessorModel; + executor: HardhatEthersSigner; + + constructor(props: { + cardPaymentProcessorContract: Contract; + cardPaymentProcessorModel: CardPaymentProcessorModel; + executor: HardhatEthersSigner; + }) { + this.contract = props.cardPaymentProcessorContract; + this.model = props.cardPaymentProcessorModel; + this.executor = props.executor; + } + + async disableCashback() { + this.model.disableCashback(); + await proveTx(this.contract.disableCashback()); + } + + async makeCommonPayments( + payments: TestPayment[], + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationResults: OperationResult[] = []; + for (const payment of payments) { + const operationIndex = this.model.makePayment(payment, { sender }); + const tx = (this.contract.connect(sender) as Contract).makeCommonPaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount + ); + const txReceipt: TransactionReceipt = await proveTx(tx); + operationResults.push({ + operationIndex, + tx, + txReceipt + }); + } + return operationResults; + } + + async makePaymentFor( + payment: TestPayment, + props: { + sponsor?: HardhatEthersSigner; + subsidyLimit?: number; + cashbackRate?: number; + confirmationAmount?: number; + sender?: HardhatEthersSigner; + } = {} + ): Promise { + if (!props.sender) { + props.sender = this.executor; + } + if (!props.subsidyLimit) { + props.sponsor = undefined; + } + const operationIndex = this.model.makePayment(payment, props); + const tx = (this.contract.connect(props.sender) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + props.sponsor?.address ?? ZERO_SPONSOR_ADDRESS, + props.subsidyLimit ?? 0, + props.cashbackRate ?? CASHBACK_RATE_AS_IN_CONTRACT, + props.confirmationAmount ?? 0 + ); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } + + async updatePayment( + payment: TestPayment, + newBaseAmount: number, + newExtraAmount: number = payment.extraAmount, + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationIndex = this.model.updatePayment( + payment.id, + newBaseAmount, + newExtraAmount, + UpdatingOperationKind.Full + ); + const tx = (this.contract.connect(sender) as Contract).updatePayment( + payment.id, + newBaseAmount, + newExtraAmount + ); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } + + async revokePayment( + payment: TestPayment, + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationIndex = this.model.revokePayment(payment.id); + const tx = (this.contract.connect(sender) as Contract).revokePayment(payment.id); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } + + async reversePayment( + payment: TestPayment, + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationIndex = this.model.reversePayment(payment.id); + const tx = (this.contract.connect(sender) as Contract).reversePayment(payment.id); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } + + async confirmPayment( + payment: TestPayment, + confirmationAmount: number, + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationIndex = this.model.confirmPayment(payment.id, confirmationAmount); + const tx = (this.contract.connect(sender) as Contract).confirmPayment(payment.id, confirmationAmount); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } + + async refundPayment( + payment: TestPayment, + refundAmount: number, + sender: HardhatEthersSigner = this.executor + ): Promise { + const operationIndex = this.model.refundPayment( + payment.id, + refundAmount + ); + const contractConnected = this.contract.connect(sender) as Contract; + const tx = contractConnected.refundPayment(payment.id, refundAmount); + const txReceipt: TransactionReceipt = await proveTx(tx); + return { + operationIndex, + tx, + txReceipt + }; + } +} + +function encodeEventAddendumFieldForAmount(amount: number): string { + if (!Number.isSafeInteger(amount)) { + throw new Error(`The provided amount is not valid for encoding as an addendum field of an event: ${amount}`); + } + return amount.toString(16).padStart(EVENT_ADDENDUM_CHARS_FOR_AMOUNT, "0"); +} + +function encodeEventAddendumFieldForAddress(address: string | undefined): string { + if (!address) { + throw new Error(`The provided address is not valid for encoding as an addendum field of an event: ${address}`); + } + if (address.startsWith("0x")) { + if (address.length != 42) { + throw new Error(`The provided address is not valid for encoding as an addendum field of an event: ${address}`); + } + return address.slice(2); + } else { + if (address.length != 40) { + throw new Error(`The provided address is not valid for encoding as an addendum field of an event: ${address}`); + } + return address; + } +} + +function defineEventAddendum(...parts: string[]): string { + return "0x" + parts.join(""); +} + +class TestContext { + tokenMock: Contract; + cardPaymentProcessorShell: CardPaymentProcessorShell; + cashOutAccount: HardhatEthersSigner; + cashbackTreasury: HardhatEthersSigner; + payments: TestPayment[]; + + constructor(props: { + fixture: Fixture; + cashbackRateInPermil: number; + cashbackEnabled: boolean; + cashOutAccount: HardhatEthersSigner; + cashbackTreasury: HardhatEthersSigner; + cardPaymentProcessorExecutor: HardhatEthersSigner; + payments: TestPayment[]; + }) { + this.tokenMock = props.fixture.tokenMock; + this.cardPaymentProcessorShell = new CardPaymentProcessorShell({ + cardPaymentProcessorContract: props.fixture.cardPaymentProcessor, + cardPaymentProcessorModel: new CardPaymentProcessorModel({ + cashbackRate: props.cashbackRateInPermil, + cashbackEnabled: props.cashbackEnabled + }), + executor: props.cardPaymentProcessorExecutor + }); + this.cashOutAccount = props.cashOutAccount; + this.cashbackTreasury = props.cashbackTreasury; + this.payments = props.payments; + } + + async setUpContractsForPayments(payments: TestPayment[] = this.payments) { + const accounts: Set = new Set(payments.map(payment => payment.payer)); + for (const account of accounts) { + await proveTx(this.tokenMock.mint(account.address, INITIAL_USER_BALANCE)); + const allowance: bigint = await this.tokenMock.allowance( + account.address, + getAddress(this.cardPaymentProcessorShell.contract) + ); + if (allowance < MAX_UINT256) { + await proveTx( + (this.tokenMock.connect(account) as Contract).approve( + getAddress(this.cardPaymentProcessorShell.contract), + MAX_UINT256 + ) + ); + } + } + } + + async checkPaymentOperationsForTx(tx: Promise, paymentOperationIndexes: number[] = [-1]) { + const operations: PaymentOperation[] = paymentOperationIndexes.map( + index => this.cardPaymentProcessorShell.model.getPaymentOperation(index) + ); + const operationConditions: OperationConditions = this.defineOperationConditions(operations); + + for (const operation of operations) { + await this.checkMainEvents(tx, operation); + await this.checkConfirmationEvents(tx, operation, operationConditions); + await this.checkCashbackEvents( + operation, + tx, + operationConditions + ); + } + + await this.checkBalanceChanges(tx, operations); + } + + defineOperationConditions(operations: PaymentOperation[]): OperationConditions { + const result: OperationConditions = { + cashbackIncreaseRequestedInAnyOperation: false, + cashbackRevocationRequestedInAnyOperation: false, + cashbackSendingRequestedInAnyOperation: false, + confirmationAmountChangedInAnyOperation: false + }; + operations.forEach(operation => { + if (operation.cashbackOperationKind == CashbackOperationKind.Sending) { + result.cashbackSendingRequestedInAnyOperation = true; + } else if (operation.cashbackOperationKind == CashbackOperationKind.Increase) { + result.cashbackIncreaseRequestedInAnyOperation = true; + } else if (operation.cashbackOperationKind == CashbackOperationKind.Revocation) { + result.cashbackRevocationRequestedInAnyOperation = true; + } + if (operation.newConfirmationAmount != operation.oldConfirmationAmount) { + result.confirmationAmountChangedInAnyOperation = true; + } + }); + + return result; + } + + async checkMainEvents( + tx: Promise, + operation: PaymentOperation + ) { + switch (operation.kind) { + case OperationKind.Undefined: + break; + case OperationKind.Making: + await this.checkMakingEvents(tx, operation); + break; + case OperationKind.Updating: + await this.checkUpdatingEvents(tx, operation); + break; + case OperationKind.Revoking: + await this.checkCancelingEvents(tx, operation); + break; + case OperationKind.Reversing: + await this.checkCancelingEvents(tx, operation); + break; + case OperationKind.Confirming: + // Do nothing. It will be checked in another function. + break; + case OperationKind.Refunding: + await this.checkRefundingEvents(tx, operation); + break; + default: + throw new Error( + `An unknown operation kind was found: ${operation.kind}` + ); + } + } + + async checkConfirmationEvents( + tx: Promise, + operation: PaymentOperation, + operationConditions: OperationConditions + ) { + const expectedAddendum: string = defineEventAddendum( + EVENT_ADDENDUM_VERSION_DEFAULT_VALUE, + !operation.sponsor ? EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED : EVENT_ADDENDUM_FLAGS_SUBSIDIZED, + encodeEventAddendumFieldForAmount(operation.oldConfirmationAmount), + encodeEventAddendumFieldForAmount(operation.newConfirmationAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAddress(operation.sponsor?.address) + ); + + if (operation.newConfirmationAmount != operation.oldConfirmationAmount) { + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_CONFIRMED_AMOUNT_CHANGED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameter("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } else if (operationConditions.confirmationAmountChangedInAnyOperation) { + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_CONFIRMED_AMOUNT_CHANGED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameterNotEqual("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } else { + await expect(tx).not.to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_CONFIRMED_AMOUNT_CHANGED + ); + } + } + + async checkCashbackEvents( + operation: PaymentOperation, + tx: Promise, + operationConditions: OperationConditions + ) { + if (operation.cashbackOperationKind === CashbackOperationKind.Sending) { + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_SENT + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("recipient", operation.payer.address), + checkEventParameter("status", operation.cashbackOperationStatus), + checkEventParameter("amount", operation.cashbackActualChange) + ); + } + + if (operation.cashbackOperationKind === CashbackOperationKind.Revocation) { + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_REVOKED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("recipient", operation.payer.address), + checkEventParameter("status", operation.cashbackOperationStatus), + checkEventParameter("oldCashbackAmount", operation.oldCashbackAmount), + checkEventParameter("oldCashbackAmount", operation.oldCashbackAmount + operation.cashbackActualChange) + ); + } + + if (operation.cashbackOperationKind === CashbackOperationKind.Increase) { + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_INCREASED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("recipient", operation.payer.address), + checkEventParameter("status", operation.cashbackOperationStatus), + checkEventParameter("oldCashbackAmount", operation.oldCashbackAmount), + checkEventParameter("oldCashbackAmount", operation.oldCashbackAmount + operation.cashbackActualChange) + ); + } + + if (!operationConditions.cashbackSendingRequestedInAnyOperation) { + await expect(tx).not.to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_SENT + ); + } + + if (!operationConditions.cashbackIncreaseRequestedInAnyOperation) { + await expect(tx).not.to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_INCREASED + ); + } + + if (!operationConditions.cashbackRevocationRequestedInAnyOperation) { + await expect(tx).not.to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_CASHBACK_REVOKED + ); + } + } + + private async checkBalanceChanges(tx: Promise, operations: PaymentOperation[]) { + const cardPaymentProcessorBalanceChange = operations + .map(operation => operation.cardPaymentProcessorBalanceChange) + .reduce((sum: number, currentValue: number) => sum + currentValue); + const cashbackTreasuryBalanceChange = operations + .map(operation => -operation.cashbackActualChange) + .reduce((sum: number, currentValue: number) => sum + currentValue); + const cashOutAccountBalanceChange = operations + .map(operation => operation.cashOutAccountBalanceChange) + .reduce((sum: number, currentValue: number) => sum + currentValue); + const balanceChangePerAccount: Map = this.#getBalanceChangePerAccount(operations); + const accounts: HardhatEthersSigner[] = Array.from(balanceChangePerAccount.keys()); + const accountBalanceChanges: number[] = accounts.map(user => balanceChangePerAccount.get(user) ?? 0); + + await expect(tx).to.changeTokenBalances( + this.tokenMock, + [ + this.cardPaymentProcessorShell.contract, + this.cashOutAccount, + this.cashbackTreasury, + ...accounts + ], + [ + cardPaymentProcessorBalanceChange, + cashOutAccountBalanceChange, + cashbackTreasuryBalanceChange, + ...accountBalanceChanges + ] + ); + } + + async checkCardPaymentProcessorState() { + await this.#checkPaymentStructures(); + await this.#checkTokenBalances(); + } + + async checkMakingEvents(tx: Promise, operation: PaymentOperation) { + const expectedAddendum: string = defineEventAddendum( + EVENT_ADDENDUM_VERSION_DEFAULT_VALUE, + !operation.sponsor ? EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED : EVENT_ADDENDUM_FLAGS_SUBSIDIZED, + encodeEventAddendumFieldForAmount(operation.newBaseAmount), + encodeEventAddendumFieldForAmount(operation.newExtraAmount), + encodeEventAddendumFieldForAmount(operation.newPayerSumAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAddress(operation.sponsor?.address), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.newSponsorSumAmount) + ); + + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_MADE + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameter("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } + + async checkUpdatingEvents(tx: Promise, operation: PaymentOperation) { + if ( + operation.updatingOperationKind === UpdatingOperationKind.Full || + operation.newBaseAmount !== operation.oldBaseAmount || + operation.newExtraAmount !== operation.oldExtraAmount + ) { + const expectedAddendum: string = defineEventAddendum( + EVENT_ADDENDUM_VERSION_DEFAULT_VALUE, + !operation.sponsor ? EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED : EVENT_ADDENDUM_FLAGS_SUBSIDIZED, + encodeEventAddendumFieldForAmount(operation.oldBaseAmount), + encodeEventAddendumFieldForAmount(operation.newBaseAmount), + encodeEventAddendumFieldForAmount(operation.oldExtraAmount), + encodeEventAddendumFieldForAmount(operation.newExtraAmount), + encodeEventAddendumFieldForAmount(operation.oldPayerSumAmount), + encodeEventAddendumFieldForAmount(operation.newPayerSumAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAddress(operation.sponsor?.address), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.oldSponsorSumAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.newSponsorSumAmount) + ); + + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_UPDATED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameter("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } else { + await expect(tx).not.to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_UPDATED + ); + } + } + + async checkCancelingEvents( + tx: Promise, + operation: PaymentOperation + ) { + const mainEventName: string = operation.kind === OperationKind.Revoking + ? EVENT_NAME_PAYMENT_REVOKED + : EVENT_NAME_PAYMENT_REVERSED; + + const expectedAddendum: string = defineEventAddendum( + EVENT_ADDENDUM_VERSION_DEFAULT_VALUE, + !operation.sponsor ? EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED : EVENT_ADDENDUM_FLAGS_SUBSIDIZED, + encodeEventAddendumFieldForAmount(operation.oldBaseAmount), + encodeEventAddendumFieldForAmount(operation.oldExtraAmount), + encodeEventAddendumFieldForAmount(operation.oldPayerRemainder), + !operation.sponsor ? "" : encodeEventAddendumFieldForAddress(operation.sponsor?.address ?? ZERO_ADDRESS), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.oldSponsorRemainder) + ); + + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + mainEventName + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameter("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } + + async checkRefundingEvents(tx: Promise, operation: PaymentOperation) { + const expectedAddendum: string = defineEventAddendum( + EVENT_ADDENDUM_VERSION_DEFAULT_VALUE, + !operation.sponsor ? EVENT_ADDENDUM_FLAGS_NON_SUBSIDIZED : EVENT_ADDENDUM_FLAGS_SUBSIDIZED, + encodeEventAddendumFieldForAmount(operation.oldPayerRefundAmount), + encodeEventAddendumFieldForAmount(operation.newPayerRefundAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAddress(operation.sponsor?.address ?? ZERO_ADDRESS), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.oldSponsorRefundAmount), + !operation.sponsor ? "" : encodeEventAddendumFieldForAmount(operation.newSponsorRefundAmount) + ); + + await expect(tx).to.emit( + this.cardPaymentProcessorShell.contract, + EVENT_NAME_PAYMENT_REFUNDED + ).withArgs( + checkEventParameter("paymentId", operation.paymentId), + checkEventParameter("payer", operation.payer.address), + checkEventParameter("addendum", expectedAddendum, eventAddendumCheckingOptions) + ); + } + + async presetCashbackForAccount(account: HardhatEthersSigner, cashbackAmount: number): Promise { + const accountCashbackOld: AccountCashbackState = + await this.cardPaymentProcessorShell.contract.getAccountCashbackState(account.address); + const cashbackAmountDiff = Number(BigInt(cashbackAmount) - accountCashbackOld.totalAmount); + if (cashbackAmountDiff < 0) { + throw new Error("Cannot set the expected cashback for account because current cashback is already higher"); + } + const preliminarilyPaymentAmount = Math.floor( + cashbackAmountDiff * CASHBACK_FACTOR / this.cardPaymentProcessorShell.model.cashbackRate + ); + const payment: TestPayment = { + id: ethers.id("cashbackPresetPayment"), + payer: account, + baseAmount: preliminarilyPaymentAmount, + extraAmount: 0 + }; + + const operationResult = await this.cardPaymentProcessorShell.makePaymentFor(payment); + const accountCashbackNew: AccountCashbackState = + await this.cardPaymentProcessorShell.contract.getAccountCashbackState(account.address); + expect(accountCashbackNew.totalAmount).to.equal(cashbackAmount); + + return operationResult; + } + + async #checkPaymentStructures() { + const expectedPayments: PaymentModel[] = this.cardPaymentProcessorShell.model.getPaymentModelsInMakingOrder(); + const paymentNumber = expectedPayments.length; + const checkedPaymentIds: Set = new Set(); + for (let i = 0; i < paymentNumber; ++i) { + const expectedPayment: PaymentModel = expectedPayments[i]; + if (checkedPaymentIds.has(expectedPayment.paymentId)) { + continue; + } + checkedPaymentIds.add(expectedPayment.paymentId); + const actualPayment = await this.cardPaymentProcessorShell.contract.getPayment(expectedPayment.paymentId); + this.#checkPaymentsEquality(actualPayment, expectedPayment, i); + const expectedTotalCashback = + this.cardPaymentProcessorShell.model.getCashbackTotalForAccount(expectedPayment.payer.address); + const actualAccountCashbackState: AccountCashbackState = + await this.cardPaymentProcessorShell.contract.getAccountCashbackState(expectedPayment.payer.address); + expect(actualAccountCashbackState.totalAmount).to.equal(expectedTotalCashback); + expect(actualAccountCashbackState.capPeriodStartAmount).to.equal( + this.cardPaymentProcessorShell.model.capPeriodStartAmount + ); + } + } + + #checkPaymentsEquality( + actualOnChainPayment: Record, + expectedPayment: PaymentModel, + paymentIndex: number + ) { + expect(actualOnChainPayment.status).to.equal( + expectedPayment.status, + `payment[${paymentIndex}].status is wrong` + ); + expect(actualOnChainPayment.reserve1).to.equal( + 0, + `payment[${paymentIndex}].reserve1 is wrong` + ); + expect(actualOnChainPayment.payer).to.equal( + expectedPayment.payer.address, + `payment[${paymentIndex}].payer is wrong` + ); + expect(actualOnChainPayment.cashbackRate).to.equal( + expectedPayment.cashbackRate, + `payment[${paymentIndex}].cashbackRate is wrong` + ); + expect(actualOnChainPayment.confirmedAmount).to.equal( + expectedPayment.confirmedAmount, + `payment[${paymentIndex}].confirmedAmount is wrong` + ); + expect(actualOnChainPayment.sponsor).to.equal( + expectedPayment.sponsor?.address ?? ZERO_ADDRESS, + `payment[${paymentIndex}].sponsor is wrong` + ); + expect(actualOnChainPayment.subsidyLimit).to.equal( + expectedPayment.subsidyLimit, + `payment[${paymentIndex}].subsidyLimit is wrong` + ); + expect(actualOnChainPayment.reserve2).to.equal( + 0, + `payment[${paymentIndex}].reserve2 is wrong` + ); + expect(actualOnChainPayment.baseAmount).to.equal( + expectedPayment.baseAmount, + `payment[${paymentIndex}].baseAmount is wrong` + ); + expect(actualOnChainPayment.extraAmount).to.equal( + expectedPayment.extraAmount, + `payment[${paymentIndex}].extraAmount is wrong` + ); + expect(actualOnChainPayment.cashbackAmount).to.equal( + expectedPayment.cashbackAmount, + `payment[${paymentIndex}].cashbackAmount is wrong` + ); + expect(actualOnChainPayment.refundAmount).to.equal( + expectedPayment.refundAmount, + `payment[${paymentIndex}].refundAmount is wrong` + ); + } + + async #checkTokenBalances() { + expect( + await this.tokenMock.balanceOf(getAddress(this.cardPaymentProcessorShell.contract)) + ).to.equal( + this.cardPaymentProcessorShell.model.totalBalance, + `The card payment processor token balance is wrong` + ); + + expect( + await this.tokenMock.balanceOf(this.cashOutAccount.address) + ).to.equal( + this.cardPaymentProcessorShell.model.totalConfirmedAmount, + `The cash-out account token balance is wrong` + ); + + expect( + (await this.cardPaymentProcessorShell.contract.getPaymentStatistics()).totalUnconfirmedRemainder + ).to.equal( + this.cardPaymentProcessorShell.model.totalUnconfirmedRemainder, + `The total unconfirmed remainder is wrong` + ); + } + + #getBalanceChangePerAccount(operations: PaymentOperation[]) { + const result: Map = new Map(); + operations.forEach(operation => { + let balanceChange: number = result.get(operation.payer) ?? 0; + balanceChange += operation.payerBalanceChange; + result.set(operation.payer, balanceChange); + + const sponsor = operation.sponsor; + if (sponsor) { + balanceChange = result.get(sponsor) ?? 0; + balanceChange += operation.sponsorBalanceChange; + result.set(sponsor, balanceChange); + } + }); + return result; + } +} + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contract 'CardPaymentProcessor'", async () => { + const ZERO_PAYMENT_ID: string = createBytesString("00", BYTES32_LENGTH); + const CASHBACK_TREASURY_ADDRESS_STUB1 = "0x0000000000000000000000000000000000000001"; + const CASHBACK_TREASURY_ADDRESS_STUB2 = "0x0000000000000000000000000000000000000002"; + const CASHBACK_RATE_MAX = 500; // 50% + const CASHBACK_RATE_DEFAULT = 100; // 10% + const CASHBACK_RATE_ZERO = 0; + const EXPECTED_VERSION: Version = { + major: 2, + minor: 1, + patch: 0 + }; + + const REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID = "InvalidInitialization"; + const REVERT_ERROR_IF_CONTRACT_IS_PAUSED = "EnforcedPause"; + const REVERT_ERROR_IF_ERC20_TOKEN_TRANSFER_AMOUNT_EXCEEDS_BALANCE = "ERC20InsufficientBalance"; + const REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT = "AccessControlUnauthorizedAccount"; + + const REVERT_ERROR_IF_ACCOUNT_ZERO_ADDRESS = "AccountZeroAddress"; + const REVERT_ERROR_IF_CASHBACK_ALREADY_ENABLED = "CashbackAlreadyEnabled"; + const REVERT_ERROR_IF_CASHBACK_ALREADY_DISABLED = "CashbackAlreadyDisabled"; + const REVERT_ERROR_IF_CASHBACK_TREASURY_UNCHANGED = "CashbackTreasuryUnchanged"; + const REVERT_ERROR_IF_CASHBACK_TREASURY_NOT_CONFIGURED = "CashbackTreasuryNotConfigured"; + const REVERT_ERROR_IF_CASHBACK_TREASURY_ZERO_ADDRESS = "CashbackTreasuryZeroAddress"; + const REVERT_ERROR_IF_CASHBACK_RATE_EXCESS = "CashbackRateExcess"; + const REVERT_ERROR_IF_CASHBACK_RATE_UNCHANGED = "CashbackRateUnchanged"; + const REVERT_ERROR_IF_CASH_OUT_ACCOUNT_NOT_CONFIGURED = "CashOutAccountNotConfigured"; + const REVERT_ERROR_IF_CASH_OUT_ACCOUNT_UNCHANGED = "CashOutAccountUnchanged"; + const REVERT_ERROR_IF_IMPLEMENTATION_ADDRESS_INVALID = "ImplementationAddressInvalid"; + const REVERT_ERROR_IF_INAPPROPRIATE_CONFIRMATION_AMOUNT = "InappropriateConfirmationAmount"; + const REVERT_ERROR_IF_INAPPROPRIATE_REFUNDING_AMOUNT = "InappropriateRefundingAmount"; + const REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS = "InappropriatePaymentStatus"; + const REVERT_ERROR_IF_INAPPROPRIATE_SUM_AMOUNT = "InappropriateSumAmount"; + const REVERT_ERROR_IF_OVERFLOW_OF_SUBSIDY_LIMIT = "OverflowOfSubsidyLimit"; + const REVERT_ERROR_IF_OVERFLOW_OF_SUM_AMOUNT = "OverflowOfSumAmount"; + const REVERT_ERROR_IF_PAYER_ZERO_ADDRESS = "PayerZeroAddress"; + const REVERT_ERROR_IF_PAYMENT_ALREADY_EXISTENT = "PaymentAlreadyExistent"; + const REVERT_ERROR_IF_PAYMENT_CONFIRMATION_ARRAY_EMPTY = "PaymentConfirmationArrayEmpty"; + const REVERT_ERROR_IF_PAYMENT_NON_EXISTENT = "PaymentNonExistent"; + const REVERT_ERROR_IF_PAYMENT_ZERO_ID = "PaymentZeroId"; + const REVERT_ERROR_IF_SPONSOR_ZERO_ADDRESS = "SponsorZeroAddress"; + const REVERT_ERROR_IF_TOKEN_ZERO_ADDRESS = "TokenZeroAddress"; + + const ownerRole: string = ethers.id("OWNER_ROLE"); + const blocklisterRole: string = ethers.id("BLOCKLISTER_ROLE"); + const pauserRole: string = ethers.id("PAUSER_ROLE"); + const rescuerRole: string = ethers.id("RESCUER_ROLE"); + const executorRole: string = ethers.id("EXECUTOR_ROLE"); + + let cardPaymentProcessorFactory: ContractFactory; + let tokenMockFactory: ContractFactory; + + let deployer: HardhatEthersSigner; + let cashOutAccount: HardhatEthersSigner; + let cashbackTreasury: HardhatEthersSigner; + let executor: HardhatEthersSigner; + let sponsor: HardhatEthersSigner; + let user1: HardhatEthersSigner; + let user2: HardhatEthersSigner; + + before(async () => { + [deployer, cashOutAccount, cashbackTreasury, executor, sponsor, user1, user2] = await ethers.getSigners(); + + // Contract factories with the explicitly specified deployer account + cardPaymentProcessorFactory = await ethers.getContractFactory("CardPaymentProcessor"); + cardPaymentProcessorFactory = cardPaymentProcessorFactory.connect(deployer); + tokenMockFactory = await ethers.getContractFactory("ERC20TokenMock"); + tokenMockFactory = tokenMockFactory.connect(deployer); + }); + + async function deployTokenMock(): Promise<{ tokenMock: Contract }> { + const name = "ERC20 Test"; + const symbol = "TEST"; + + let tokenMock: Contract = await tokenMockFactory.deploy(name, symbol) as Contract; + await tokenMock.waitForDeployment(); + tokenMock = connect(tokenMock, deployer); // Explicitly specifying the initial account + + return { tokenMock }; + } + + async function deployTokenMockAndCardPaymentProcessor(): Promise<{ + cardPaymentProcessor: Contract; + tokenMock: Contract; + }> { + const { tokenMock } = await deployTokenMock(); + + let cardPaymentProcessor: Contract = + await upgrades.deployProxy(cardPaymentProcessorFactory, [getAddress(tokenMock)]); + await cardPaymentProcessor.waitForDeployment(); + cardPaymentProcessor = connect(cardPaymentProcessor, deployer); // Explicitly specifying the initial account + + return { + cardPaymentProcessor, + tokenMock + }; + } + + async function deployAndConfigureAllContracts(): Promise { + const { cardPaymentProcessor, tokenMock } = await deployTokenMockAndCardPaymentProcessor(); + + await proveTx(cardPaymentProcessor.grantRole(executorRole, executor.address)); + await proveTx(cardPaymentProcessor.grantRole(pauserRole, deployer.address)); + await proveTx(cardPaymentProcessor.setCashbackTreasury(cashbackTreasury.address)); + await proveTx(connect(tokenMock, cashbackTreasury).approve(getAddress(cardPaymentProcessor), MAX_UINT256)); + await proveTx(cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_DEFAULT)); + + await proveTx(cardPaymentProcessor.setCashOutAccount(cashOutAccount.address)); + await proveTx(connect(tokenMock, cashOutAccount).approve(getAddress(cardPaymentProcessor), MAX_UINT256)); + + await proveTx(tokenMock.mint(cashbackTreasury.address, MAX_INT256)); + await proveTx(tokenMock.mint(sponsor.address, INITIAL_SPONSOR_BALANCE)); + await proveTx(connect(tokenMock, sponsor).approve(getAddress(cardPaymentProcessor), MAX_UINT256)); + + await proveTx(cardPaymentProcessor.enableCashback()); + + return { + cardPaymentProcessor, + tokenMock + }; + } + + async function pauseContract(contract: Contract) { + if (!(await contract.hasRole(pauserRole, deployer.address))) { + await proveTx(contract.grantRole(pauserRole, deployer.address)); + } + await proveTx(contract.pause()); + } + + function createTestPayments(numberOfPayments: number = 1): TestPayment[] { + const testPayments: TestPayment[] = []; + for (let i = 0; i < numberOfPayments; ++i) { + const payment: TestPayment = { + id: createBytesString(i + 1, BYTES32_LENGTH), + payer: (i % 2 > 0) ? user1 : user2, + baseAmount: Math.floor(123.456789 * DIGITS_COEF + i * 123.456789 * DIGITS_COEF), + extraAmount: Math.floor(132.456789 * DIGITS_COEF + i * 132.456789 * DIGITS_COEF) + }; + expect(payment.baseAmount).greaterThan(10 * DIGITS_COEF); + expect(payment.extraAmount).greaterThan(10 * DIGITS_COEF); + testPayments.push(payment); + } + return testPayments; + } + + async function prepareForPayments(props: { paymentNumber: number } = { paymentNumber: 1 }): Promise { + const fixture: Fixture = await setUpFixture(deployAndConfigureAllContracts); + const payments = createTestPayments(props.paymentNumber); + return new TestContext({ + fixture, + cashbackRateInPermil: CASHBACK_RATE_DEFAULT, + cashbackEnabled: true, + cashOutAccount, + cashbackTreasury, + cardPaymentProcessorExecutor: executor, + payments + }); + } + + async function beforeMakingPayments(props: { paymentNumber: number } = { paymentNumber: 1 }): Promise { + const context = await prepareForPayments(props); + await context.setUpContractsForPayments(); + return context; + } + + describe("Function 'initialize()'", async () => { + it("Configures the contract as expected", async () => { + const { cardPaymentProcessor, tokenMock } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + // The underlying contract address + expect(await cardPaymentProcessor.token()).to.equal(getAddress(tokenMock)); + + // The admins of roles + expect(await cardPaymentProcessor.getRoleAdmin(ownerRole)).to.equal(ownerRole); + expect(await cardPaymentProcessor.getRoleAdmin(blocklisterRole)).to.equal(ownerRole); + expect(await cardPaymentProcessor.getRoleAdmin(pauserRole)).to.equal(ownerRole); + expect(await cardPaymentProcessor.getRoleAdmin(rescuerRole)).to.equal(ownerRole); + expect(await cardPaymentProcessor.getRoleAdmin(executorRole)).to.equal(ownerRole); + + // The deployer should have the owner role, but not the other roles + expect(await cardPaymentProcessor.hasRole(ownerRole, deployer.address)).to.equal(true); + expect(await cardPaymentProcessor.hasRole(blocklisterRole, deployer.address)).to.equal(false); + expect(await cardPaymentProcessor.hasRole(pauserRole, deployer.address)).to.equal(false); + expect(await cardPaymentProcessor.hasRole(rescuerRole, deployer.address)).to.equal(false); + expect(await cardPaymentProcessor.hasRole(executorRole, deployer.address)).to.equal(false); + + // The initial contract state is unpaused + expect(await cardPaymentProcessor.paused()).to.equal(false); + + // Cashback related values + expect(await cardPaymentProcessor.cashbackTreasury()).to.equal(ZERO_ADDRESS); + expect(await cardPaymentProcessor.cashbackEnabled()).to.equal(false); + expect(await cardPaymentProcessor.cashbackRate()).to.equal(0); + expect(await cardPaymentProcessor.MAX_CASHBACK_RATE()).to.equal(CASHBACK_RATE_MAX); + expect(await cardPaymentProcessor.CASHBACK_FACTOR()).to.equal(CASHBACK_FACTOR); + expect(await cardPaymentProcessor.TOKE_DECIMALS()).to.equal(TOKE_DECIMALS); + expect(await cardPaymentProcessor.CASHBACK_ROUNDING_COEF()).to.equal(CASHBACK_ROUNDING_COEF); + expect(await cardPaymentProcessor.CASHBACK_CAP_RESET_PERIOD()).to.equal(CASHBACK_CAP_RESET_PERIOD); + expect(await cardPaymentProcessor.MAX_CASHBACK_FOR_CAP_PERIOD()).to.equal(MAX_CASHBACK_FOR_CAP_PERIOD); + + // The cash-out account + expect(await cardPaymentProcessor.cashOutAccount()).to.equal(ZERO_ADDRESS); + + // Additional constrains + expect(await cardPaymentProcessor.MAX_CASHBACK_RATE()).to.be.lessThanOrEqual(0xFFFF); + }); + + it("Is reverted if it is called a second time", async () => { + const { cardPaymentProcessor, tokenMock } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.initialize(getAddress(tokenMock)) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID); + }); + + it("Is reverted if the passed token address is zero", async () => { + const anotherCardPaymentProcessor: Contract = + await upgrades.deployProxy(cardPaymentProcessorFactory, [], { initializer: false }); + + await expect( + anotherCardPaymentProcessor.initialize(ZERO_ADDRESS) + ).to.be.revertedWithCustomError(cardPaymentProcessorFactory, REVERT_ERROR_IF_TOKEN_ZERO_ADDRESS); + }); + }); + + describe("Function '$__VERSION()'", async () => { + it("Returns expected values", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + const cardPaymentProcessorVersion = await cardPaymentProcessor.$__VERSION(); + checkEquality(cardPaymentProcessorVersion, EXPECTED_VERSION); + }); + }); + + describe("Function 'upgradeToAndCall()'", async () => { + it("Executes as expected", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await checkContractUupsUpgrading(cardPaymentProcessor, cardPaymentProcessorFactory); + }); + + it("Is reverted if the caller is not the owner", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect(connect(cardPaymentProcessor, user1).upgradeToAndCall(cardPaymentProcessor, "0x")) + .to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT); + }); + + it("Is reverted if the provided implementation address is not a card payment processor contract", async () => { + const { cardPaymentProcessor, tokenMock } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect(cardPaymentProcessor.upgradeToAndCall(getAddress(tokenMock), "0x")) + .to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_IMPLEMENTATION_ADDRESS_INVALID); + }); + }); + + describe("Function 'upgradeTo()'", async () => { + it("Executes as expected", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await checkContractUupsUpgrading(cardPaymentProcessor, cardPaymentProcessorFactory, "upgradeTo(address)"); + }); + + it("Is reverted if the caller is not the owner", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect(connect(cardPaymentProcessor, user1).upgradeTo(cardPaymentProcessor)) + .to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT); + }); + + it("Is reverted if the provided implementation address is not a card payment processor contract", async () => { + const { cardPaymentProcessor, tokenMock } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect(cardPaymentProcessor.upgradeTo(getAddress(tokenMock))) + .to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_IMPLEMENTATION_ADDRESS_INVALID); + }); + }); + + describe("Function 'setCashOutAccount()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect( + cardPaymentProcessor.setCashOutAccount(cashOutAccount.address) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASH_OUT_ACCOUNT_CHANGED + ).withArgs( + ZERO_ADDRESS, + cashOutAccount.address + ); + + expect(await cardPaymentProcessor.cashOutAccount()).to.equal(cashOutAccount.address); + + // Can be set to the zero address + await expect( + cardPaymentProcessor.setCashOutAccount(ZERO_ADDRESS) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASH_OUT_ACCOUNT_CHANGED + ).withArgs( + cashOutAccount.address, + ZERO_ADDRESS + ); + + expect(await cardPaymentProcessor.cashOutAccount()).to.equal(ZERO_ADDRESS); + }); + + it("Is reverted if the caller does not have the owner role", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + connect(cardPaymentProcessor, user1).setCashOutAccount(cashOutAccount.address) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(user1.address, ownerRole); + }); + + it("Is reverted if the new cash-out account is the same as the previous set one", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.setCashOutAccount(ZERO_ADDRESS) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASH_OUT_ACCOUNT_UNCHANGED); + + await proveTx(cardPaymentProcessor.setCashOutAccount(cashOutAccount.address)); + + await expect( + cardPaymentProcessor.setCashOutAccount(cashOutAccount.address) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASH_OUT_ACCOUNT_UNCHANGED); + }); + }); + + describe("Function 'setCashbackTreasury()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { cardPaymentProcessor, tokenMock } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + expect( + await tokenMock.allowance(getAddress(cardPaymentProcessor), CASHBACK_TREASURY_ADDRESS_STUB1) + ).to.equal(0); + + await expect( + cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_TREASURY_CHANGED + ).withArgs( + ZERO_ADDRESS, + CASHBACK_TREASURY_ADDRESS_STUB1 + ); + + expect(await cardPaymentProcessor.cashbackTreasury()).to.equal(CASHBACK_TREASURY_ADDRESS_STUB1); + + await expect( + cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB2) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_TREASURY_CHANGED + ).withArgs( + CASHBACK_TREASURY_ADDRESS_STUB1, + CASHBACK_TREASURY_ADDRESS_STUB2 + ); + + expect(await cardPaymentProcessor.cashbackTreasury()).to.equal(CASHBACK_TREASURY_ADDRESS_STUB2); + }); + + it("Is reverted if the caller does not have the owner role", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + connect(cardPaymentProcessor, user1).setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(user1.address, ownerRole); + }); + + it("Is reverted if the new cashback treasury address is zero", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.setCashbackTreasury(ZERO_ADDRESS) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_TREASURY_ZERO_ADDRESS); + }); + + it("Is reverted if the cashback treasury is not changed", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await proveTx(cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1)); + + await expect( + cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_TREASURY_UNCHANGED); + }); + }); + + describe("Function 'setCashbackRate()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + + await expect( + cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_DEFAULT) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_RATE_CHANGED + ).withArgs( + CASHBACK_RATE_ZERO, + CASHBACK_RATE_DEFAULT + ); + + expect(await cardPaymentProcessor.cashbackRate()).to.equal(CASHBACK_RATE_DEFAULT); + + await expect( + cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_ZERO) + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_RATE_CHANGED + ).withArgs( + CASHBACK_RATE_DEFAULT, + CASHBACK_RATE_ZERO + ); + + expect(await cardPaymentProcessor.cashbackRate()).to.equal(CASHBACK_RATE_ZERO); + }); + + it("Is reverted if the caller does not have the owner role", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + connect(cardPaymentProcessor, user1).setCashbackRate(CASHBACK_RATE_DEFAULT) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(user1.address, ownerRole); + }); + + it("Is reverted if the new rate exceeds the allowable maximum", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_MAX + 1) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_RATE_EXCESS); + }); + + it("Is reverted if called with the same argument twice", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await proveTx(cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_DEFAULT)); + + await expect( + cardPaymentProcessor.setCashbackRate(CASHBACK_RATE_DEFAULT) + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_RATE_UNCHANGED); + }); + }); + + describe("Function 'enableCashback()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await proveTx(cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1)); + + await expect( + cardPaymentProcessor.enableCashback() + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_ENABLED + ); + + expect(await cardPaymentProcessor.cashbackEnabled()).to.equal(true); + }); + + it("Is reverted if the caller does not have the owner role", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + connect(cardPaymentProcessor, user1).enableCashback() + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(user1.address, ownerRole); + }); + + it("Is reverted if the cashback treasury was not configured", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.enableCashback() + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_TREASURY_NOT_CONFIGURED); + }); + + it("Is reverted if the cashback operations are already enabled", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await proveTx(cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1)); + await proveTx(cardPaymentProcessor.enableCashback()); + + await expect( + cardPaymentProcessor.enableCashback() + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_ALREADY_ENABLED); + }); + }); + + describe("Function 'disableCashback()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await proveTx(cardPaymentProcessor.setCashbackTreasury(CASHBACK_TREASURY_ADDRESS_STUB1)); + await proveTx(cardPaymentProcessor.enableCashback()); + expect(await cardPaymentProcessor.cashbackEnabled()).to.equal(true); + + await expect( + cardPaymentProcessor.disableCashback() + ).to.emit( + cardPaymentProcessor, + EVENT_NAME_CASHBACK_DISABLED + ); + + expect(await cardPaymentProcessor.cashbackEnabled()).to.equal(false); + }); + + it("Is reverted if the caller does not have the owner role", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + connect(cardPaymentProcessor, user1).disableCashback() + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(user1.address, ownerRole); + }); + + it("Is reverted if the cashback operations are already disabled", async () => { + const { cardPaymentProcessor } = await setUpFixture(deployTokenMockAndCardPaymentProcessor); + await expect( + cardPaymentProcessor.disableCashback() + ).to.be.revertedWithCustomError(cardPaymentProcessor, REVERT_ERROR_IF_CASHBACK_ALREADY_DISABLED); + }); + }); + + describe("Function 'makePaymentFor()'", async () => { + async function checkPaymentMakingFor( + context: TestContext, + props: { + sponsor?: HardhatEthersSigner; + subsidyLimit?: number; + cashbackEnabled?: boolean; + cashbackRate?: number; + confirmationAmount?: number; + } = {} + ) { + const { cardPaymentProcessorShell, payments: [payment] } = context; + + if (props.cashbackEnabled === false) { + await cardPaymentProcessorShell.disableCashback(); + } + + cardPaymentProcessorShell.model.makePayment( + payment, + { + sponsor: props.sponsor, + subsidyLimit: props.subsidyLimit, + cashbackRate: props.cashbackRate, + confirmationAmount: props.confirmationAmount, + sender: executor + } + ); + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + props.sponsor?.address ?? ZERO_SPONSOR_ADDRESS, + props.subsidyLimit ?? ZERO_SUBSIDY_LIMIT, + props.cashbackRate ?? CASHBACK_RATE_AS_IN_CONTRACT, + props.confirmationAmount ?? ZERO_CONFIRMATION_AMOUNT + ); + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The payment is not immediately confirmed, and", async () => { + describe("The payment is not sponsored, and", async () => { + describe("The cashback rate is determined by the contract settings, and", async () => { + describe("Cashback is enabled, and the base and extra payment amounts are", async () => { + it("Both nonzero", async () => { + const context = await beforeMakingPayments(); + await checkPaymentMakingFor(context); + }); + + it("Both nonzero, and if the subsidy limit argument is zero", async () => { + const context = await beforeMakingPayments(); + await checkPaymentMakingFor(context); + }); + + it("Both zero", async () => { + const context = await beforeMakingPayments(); + context.payments[0].baseAmount = 0; + context.payments[0].extraAmount = 0; + await checkPaymentMakingFor(context); + }); + + it("Different: base is zero, extra is nonzero", async () => { + const context = await beforeMakingPayments(); + context.payments[0].baseAmount = 0; + await checkPaymentMakingFor(context); + }); + + it("Different: base is nonzero, extra is zero", async () => { + const context = await beforeMakingPayments(); + context.payments[0].extraAmount = 0; + await checkPaymentMakingFor(context); + }); + + it("Both nonzero, and cashback is partially sent with non-zero amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment], cardPaymentProcessorShell } = context; + const sentCashbackAmount = 2 * CASHBACK_ROUNDING_COEF; + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - sentCashbackAmount; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + cardPaymentProcessorShell.model.setCashbackSendingAmountResult(sentCashbackAmount); + await checkPaymentMakingFor(context); + }); + }); + describe("Cashback is disabled, and the base and extra payment amounts are", async () => { + it("Both nonzero", async () => { + const context = await beforeMakingPayments(); + await checkPaymentMakingFor(context, { cashbackEnabled: false }); + }); + }); + describe("Cashback is enabled, and the base and extra payment amounts are nonzero, and", async () => { + it("The cashback transfer function returns 'false'", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment], cardPaymentProcessorShell, tokenMock } = context; + const cashbackAmount = cardPaymentProcessorShell.model.calculateCashback(payment.baseAmount); + await proveTx(tokenMock.setSpecialAmountToReturnFalse(cashbackAmount)); + cardPaymentProcessorShell.model.setCashbackSendingStatus(CashbackOperationStatus.Failed); + await checkPaymentMakingFor(context); + }); + it("The cashback transfer is reverted", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment], cardPaymentProcessorShell, tokenMock } = context; + const cashbackAmount = cardPaymentProcessorShell.model.calculateCashback(payment.baseAmount); + await proveTx(tokenMock.setSpecialAmountToRevert(cashbackAmount)); + cardPaymentProcessorShell.model.setCashbackSendingStatus(CashbackOperationStatus.Failed); + await checkPaymentMakingFor(context); + }); + it("The cashback is capped", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + await context.presetCashbackForAccount(payment.payer, MAX_CASHBACK_FOR_CAP_PERIOD); + await checkPaymentMakingFor(context); + }); + }); + }); + }); + describe("The payment is sponsored, cashback is enabled, and", async () => { + describe("The cashback rate is determined by the contract settings, and", async () => { + describe("The base and extra payment amounts are both nonzero, and the subsidy limit is", async () => { + it("Zero", async () => { + const context = await beforeMakingPayments(); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit: 0 }); + }); + + it("Less than the base amount", async () => { + const context = await beforeMakingPayments(); + const subsidyLimit = Math.floor(context.payments[0].baseAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Less than the payment sum amount but higher than the base amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = payment.baseAmount + Math.floor(payment.extraAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("The same as the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = payment.baseAmount + payment.extraAmount; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Greater than the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const sumAmount = payment.baseAmount + payment.extraAmount; + const subsidyLimit = Math.floor(sumAmount * 1.1); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + }); + describe("The base and extra payment amounts are both zero, and the subsidy limit is", async () => { + it("Non-zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = payment.baseAmount; + payment.baseAmount = 0; + payment.extraAmount = 0; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + payment.baseAmount = 0; + payment.extraAmount = 0; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit: 0 }); + }); + }); + describe("The base amount is nonzero, the extra amount is zero, and the subsidy limit is", async () => { + it("The same as the payment sum amount", async () => { + const context = await beforeMakingPayments(); + context.payments[0].extraAmount = 0; + const subsidyLimit = context.payments[0].baseAmount; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Less than the payment sum amount", async () => { + const context = await beforeMakingPayments(); + context.payments[0].extraAmount = 0; + const subsidyLimit = Math.floor(context.payments[0].baseAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Zero", async () => { + const context = await beforeMakingPayments(); + context.payments[0].extraAmount = 0; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit: 0 }); + }); + }); + describe("The base amount is zero, the extra amount is nonzero, and the subsidy limit is", async () => { + it("The same as the payment sum amount", async () => { + const context = await beforeMakingPayments(); + context.payments[0].baseAmount = 0; + const subsidyLimit = context.payments[0].extraAmount; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Less than the payment sum amount", async () => { + const context = await beforeMakingPayments(); + context.payments[0].baseAmount = 0; + const subsidyLimit = Math.floor(context.payments[0].extraAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit }); + }); + + it("Zero", async () => { + const context = await beforeMakingPayments(); + context.payments[0].baseAmount = 0; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit: 0 }); + }); + }); + }); + describe("The cashback rate is requested to be zero, and", async () => { + const cashbackRate = 0; + describe("The base and extra payment amounts are both nonzero, and the subsidy limit is ", async () => { + it("Less than the base amount", async () => { + const context = await beforeMakingPayments(); + const subsidyLimit = Math.floor(context.payments[0].baseAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit, cashbackRate }); + }); + }); + }); + describe("The cashback is requested to be a special value, and", async () => { + const cashbackRate = CASHBACK_RATE_DEFAULT * 2; + describe("The base and extra payment amounts are both nonzero, and the subsidy limit is ", async () => { + it("Less than the base amount", async () => { + const context = await beforeMakingPayments(); + const subsidyLimit = Math.floor(context.payments[0].baseAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit, cashbackRate }); + }); + + it("Zero", async () => { + const context = await beforeMakingPayments(); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit: 0, cashbackRate }); + }); + }); + }); + }); + }); + describe("The payment is immediately confirmed, sponsored, with some amounts, usual cashback, and", async () => { + describe("The confirmation amount is", async () => { + it("Less than the base amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount / 2); + const confirmationAmount = Math.floor(payment.baseAmount / 4); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit, confirmationAmount }); + }); + + it("Less than the payment sum amount but higher than the base amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount / 2); + const confirmationAmount = payment.baseAmount + Math.floor(payment.extraAmount / 2); + await checkPaymentMakingFor(context, { sponsor, subsidyLimit, confirmationAmount }); + }); + + it("The same as the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount / 2); + const confirmationAmount = payment.baseAmount + payment.extraAmount; + await checkPaymentMakingFor(context, { sponsor, subsidyLimit, confirmationAmount }); + }); + }); + }); + }); + + describe("Is reverted if", async () => { + const subsidyLimit = INITIAL_SPONSOR_BALANCE; + + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(payment.payer) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(payment.payer.address, executorRole); + }); + + it("The payer address is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + ZERO_PAYER_ADDRESS, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_PAYER_ZERO_ADDRESS); + }); + + it("The payment ID is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + ZERO_PAYMENT_ID, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_ZERO_ID + ); + }); + + it("The account has not enough token balance", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, tokenMock, payments: [payment] } = context; + + payment.baseAmount = INITIAL_USER_BALANCE + 1; + payment.extraAmount = 0; + const subsidyLimitLocal = 0; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimitLocal, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + tokenMock, + REVERT_ERROR_IF_ERC20_TOKEN_TRANSFER_AMOUNT_EXCEEDS_BALANCE + ).withArgs(payment.payer.address, anyValue, anyValue); + }); + + it("The sponsor has not enough token balance", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, tokenMock, payments: [payment] } = context; + + const subsidyLimitLocal = INITIAL_SPONSOR_BALANCE + 1; + payment.baseAmount = 0; + payment.extraAmount = subsidyLimitLocal; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimitLocal, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + tokenMock, + REVERT_ERROR_IF_ERC20_TOKEN_TRANSFER_AMOUNT_EXCEEDS_BALANCE + ).withArgs(sponsor.address, anyValue, anyValue); + }); + + it("The payment with the provided ID already exists", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_PAYMENT_ALREADY_EXISTENT); + }); + + it("The requested cashback rate exceeds the maximum allowed value", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_MAX + 1, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CASHBACK_RATE_EXCESS); + }); + + it("The payment sum amount is greater than 64-bit unsigned integer", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + const paymentBaseAmount = MAX_UINT64; + const paymentExtraAmount: bigint = 1n; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + paymentBaseAmount, + paymentExtraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_OVERFLOW_OF_SUM_AMOUNT); + }); + + it("The payment subsidy limit is not zero, but the sponsor address is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + ZERO_SPONSOR_ADDRESS, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_SPONSOR_ZERO_ADDRESS); + }); + + it("The payment subsidy limit is greater than 64-bit unsigned integer", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + const subsidyLimitOverflowed = MAX_UINT64 + 1n; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimitOverflowed, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_OVERFLOW_OF_SUBSIDY_LIMIT); + }); + + it("The confirmation amount for the payment is greater than the sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const excessConfirmationAmount = payment.baseAmount + payment.extraAmount + 1; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount, + sponsor.address, + subsidyLimit, + CASHBACK_RATE_AS_IN_CONTRACT, + excessConfirmationAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_INAPPROPRIATE_CONFIRMATION_AMOUNT + ); + }); + }); + }); + + describe("Function 'makeCommonPaymentFor()'", async () => { + /* Since the function under test uses the same common internal function to make a payment, + * the complete set of checks are provided in the test section for the 'makePaymentFor()' function. + * In this section, only specific checks are provided. + */ + describe("Executes as expected if", async () => { + it("Cashback is enabled, and the base and extra payment amounts are both nonzero", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + cardPaymentProcessorShell.model.makePayment(payment, { sender: executor }); + + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).makeCommonPaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount + ); + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makeCommonPaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(payment.payer) as Contract).makeCommonPaymentFor( + payment.id, + payment.payer.address, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(payment.payer.address, executorRole); + }); + + it("The payer address is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makeCommonPaymentFor( + payment.id, + ZERO_PAYER_ADDRESS, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_PAYER_ZERO_ADDRESS); + }); + }); + }); + + describe("Function 'updatePayment()'", async () => { + async function checkUpdating( + context: TestContext, + props: { + cashbackCondition?: CashbackConditionType; + newBaseAmount?: number; + newExtraAmount?: number; + refundAmount?: number; + confirmedAmount?: number; + subsidyLimit?: number; + } = { cashbackCondition: CashbackConditionType.CashbackEnabled } + ) { + const { cardPaymentProcessorShell, tokenMock, payments: [payment] } = context; + const { cashbackCondition, subsidyLimit, confirmedAmount: confirmedAmount } = props; + const newBaseAmount = props.newBaseAmount ?? payment.baseAmount; + const newExtraAmount = props.newExtraAmount ?? payment.extraAmount; + + if (cashbackCondition === CashbackConditionType.CashbackDisabledBeforePaymentMaking) { + await cardPaymentProcessorShell.disableCashback(); + } + await cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, confirmationAmount: confirmedAmount } + ); + if (cashbackCondition === CashbackConditionType.CashbackDisabledAfterPaymentMaking) { + await cardPaymentProcessorShell.disableCashback(); + } + + if (props.refundAmount === 0 || props.refundAmount) { + await cardPaymentProcessorShell.refundPayment(payment, props.refundAmount ?? 0); + } else { + const refundAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + } + if (cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingPartial) { + const actualCashbackChange = 2 * CASHBACK_ROUNDING_COEF; + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - actualCashbackChange; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + cardPaymentProcessorShell.model.setCashbackIncreaseAmountResult(actualCashbackChange); + } + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingFails || + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingReverts + ) { + cardPaymentProcessorShell.model.setCashbackRevocationStatus(CashbackOperationStatus.Failed); + } + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingFails || + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingReverts + ) { + cardPaymentProcessorShell.model.setCashbackIncreaseStatus(CashbackOperationStatus.Failed); + } + + await context.checkCardPaymentProcessorState(); + + const operationIndex = cardPaymentProcessorShell.model.updatePayment( + payment.id, + newBaseAmount, + newExtraAmount + ); + + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingFails || + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingFails + ) { + const operation = cardPaymentProcessorShell.model.getPaymentOperation(operationIndex); + await proveTx(tokenMock.setSpecialAmountToReturnFalse(Math.abs(operation.cashbackRequestedChange))); + } + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingReverts || + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingReverts + ) { + const operation = cardPaymentProcessorShell.model.getPaymentOperation(operationIndex); + await proveTx(tokenMock.setSpecialAmountToRevert(Math.abs(operation.cashbackRequestedChange))); + } + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + payment.id, + newBaseAmount, + newExtraAmount + ); + + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The payment is not subsidized and not confirmed, and", async () => { + describe("The base amount decreases, and", async () => { + describe("The extra amount remains the same, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkUpdating(context, { newBaseAmount }); + }); + + it("Disabled before payment making", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledBeforePaymentMaking + }); + }); + + it("Disabled after payment making", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledAfterPaymentMaking + }); + }); + + it("Enabled but cashback transfer function returns 'false'", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButRevokingFails + }); + }); + it("Enabled but cashback transfer function reverts", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButRevokingReverts + }); + }); + }); + + describe("The extra amount decreases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = Math.floor(payment.baseAmount * 0.9); + const newExtraAmount = Math.floor(payment.extraAmount * 0.5); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount increases but the sum amount decreases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = Math.floor(payment.baseAmount * 0.7); + const newExtraAmount = Math.floor(payment.extraAmount * 1.1); + const oldSumAmount = payment.baseAmount + payment.extraAmount; + const newSumAmount = newBaseAmount + newExtraAmount; + expect(newSumAmount - oldSumAmount).to.be.lessThan(0); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount increases and the sum amount increases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = Math.floor(payment.baseAmount * 0.7); + const newExtraAmount = Math.floor(payment.extraAmount * 2); + const oldSumAmount = payment.baseAmount + payment.extraAmount; + const newSumAmount = newBaseAmount + newExtraAmount; + expect(newSumAmount - oldSumAmount).to.be.greaterThan(0); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount becomes zero, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + const newExtraAmount = 0; + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + }); + + describe("The base amount remains the same, and cashback is enabled, and", async () => { + it("The extra amount remains the same", async () => { + const context = await beforeMakingPayments(); + await checkUpdating(context); + }); + + it("The extra amount decreases", async () => { + const context = await beforeMakingPayments(); + const newExtraAmount = Math.floor(context.payments[0].extraAmount * 0.5); + await checkUpdating(context, { newExtraAmount }); + }); + + it("The extra amount increases", async () => { + const context = await beforeMakingPayments(); + const newExtraAmount = Math.floor(context.payments[0].extraAmount * 2); + await checkUpdating(context, { newExtraAmount }); + }); + + it("The extra amount becomes zero", async () => { + const context = await beforeMakingPayments(); + const newExtraAmount = 0; + await checkUpdating(context, { newExtraAmount }); + }); + }); + + describe("The base amount increases, and", async () => { + describe("The extra amount remains the same, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { newBaseAmount }); + }); + + it("Disabled before payment making", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledBeforePaymentMaking + }); + }); + + it("Disabled after payment making", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledAfterPaymentMaking + }); + }); + + it("Enabled but cashback transfer function returns 'false'", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButIncreasingFails + }); + }); + + it("Enabled but cashback transfer function reverts", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButIncreasingReverts + }); + }); + + it("Enabled but cashback operation executes partially", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + await checkUpdating(context, { + newBaseAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButIncreasingPartial + }); + }); + }); + + describe("The extra amount decreases and the sum amount decreases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = Math.floor(payment.baseAmount * 1.1); + const newExtraAmount = Math.floor(payment.extraAmount * 0.5); + const oldSumAmount = payment.baseAmount + payment.extraAmount; + const newSumAmount = newBaseAmount + newExtraAmount; + expect(newSumAmount - oldSumAmount).to.be.lessThan(0); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount decreases but the sum amount increases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = Math.floor(payment.baseAmount * 1.5); + const newExtraAmount = Math.floor(payment.extraAmount * 0.9); + const oldSumAmount = payment.baseAmount + payment.extraAmount; + const newSumAmount = newBaseAmount + newExtraAmount; + expect(newSumAmount - oldSumAmount).to.be.greaterThan(0); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount increases, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 1.1); + const newExtraAmount = Math.floor(context.payments[0].extraAmount * 2); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + + describe("The extra amount becomes zero, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + const newExtraAmount = 0; + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + }); + }); + + describe("The base amount becomes zero, and cashback is enabled, and", async () => { + it("The extra amount remains the same", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = 0; + await checkUpdating(context, { newBaseAmount }); + }); + + it("The extra amount decreases", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = 0; + const newExtraAmount = Math.floor(payment.extraAmount * 0.5); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + + it("The extra amount increases", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const newBaseAmount = 0; + const newExtraAmount = Math.floor(payment.extraAmount * 2); + await checkUpdating(context, { newBaseAmount, newExtraAmount }); + }); + + it("The extra amount becomes zero", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = 0; + const newExtraAmount = 0; + await checkUpdating(context, { newBaseAmount, newExtraAmount, refundAmount: 0 }); + }); + }); + }); + + describe("The payment is subsidized but not confirmed, and", async () => { + describe("The initial subsidy limit (SL) is less than the base amount, and", async () => { + it("The base amount becomes zero, but the extra amount does not change", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = 0; + await checkUpdating(context, { newBaseAmount, subsidyLimit }); + }); + + it("The base and extra amounts both become zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = 0; + const newExtraAmount = 0; + await checkUpdating(context, { newBaseAmount, newExtraAmount, refundAmount: 0, subsidyLimit }); + }); + + it("The base amount decreases but it is still above SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.9); + await checkUpdating(context, { newBaseAmount, subsidyLimit }); + }); + + it("The base amount decreases bellow SL but the sum amount is still above SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.3); + expect(newBaseAmount + payment.extraAmount).to.be.greaterThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, subsidyLimit }); + }); + + it("The base amount decreases and the sum amount becomes bellow SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.2); + const newExtraAmount = Math.floor(payment.extraAmount * 0.2); + expect(newBaseAmount + newExtraAmount).to.be.lessThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + }); + + describe("The initial subsidy limit (SL) is between the base amount and the sum amount, and", async () => { + it("The base amount becomes zero, but the extra amount does not change", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + const newBaseAmount = 0; + await checkUpdating(context, { newBaseAmount, subsidyLimit }); + }); + + it("The base and extra amounts both become zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + const newBaseAmount = 0; + const newExtraAmount = 0; + await checkUpdating(context, { newBaseAmount, newExtraAmount, refundAmount: 0, subsidyLimit }); + }); + + it("The base amount decreases but the sum amount is still above SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.9); + const newExtraAmount = Math.floor(payment.extraAmount * 0.9); + expect(newBaseAmount + newExtraAmount).to.be.greaterThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + + it("The base amount decreases and the sum amount becomes bellow SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.2); + const newExtraAmount = Math.floor(payment.extraAmount * 0.2); + expect(newBaseAmount + newExtraAmount).to.be.lessThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + + it("The base amount increases above SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 2); + expect(newBaseAmount).to.be.greaterThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, subsidyLimit }); + }); + }); + + describe("The initial subsidy limit (SL) is above the payment sum amount, and", async () => { + it("The payment sum amount decreases", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor((payment.baseAmount + payment.extraAmount) * 1.2); + const newBaseAmount = Math.floor(payment.baseAmount * 0.9); + const newExtraAmount = Math.floor(payment.extraAmount * 0.9); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + + it("The payment sum amount increases but it is still below SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor((payment.baseAmount + payment.extraAmount) * 1.2); + const newBaseAmount = Math.floor(payment.baseAmount * 1.1); + const newExtraAmount = Math.floor(payment.extraAmount * 1.1); + expect(newBaseAmount + newExtraAmount).to.be.lessThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + + it("The payment sum amount increases above SL but the base amount is still below SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor((payment.baseAmount + payment.extraAmount) * 1.2); + const newBaseAmount = Math.floor(payment.baseAmount * 1.1); + const newExtraAmount = Math.floor(payment.extraAmount * 1.5); + expect(newBaseAmount + newExtraAmount).to.be.greaterThan(subsidyLimit); + expect(newBaseAmount).to.be.lessThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + + it("The payment sum amount increases above SL and the base amount becomes above SL", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor((payment.baseAmount + payment.extraAmount) * 1.2); + const newBaseAmount = Math.floor(payment.baseAmount * 2.5); + const newExtraAmount = payment.extraAmount; + expect(newBaseAmount + newExtraAmount).to.be.greaterThan(subsidyLimit); + expect(newBaseAmount).to.be.greaterThan(subsidyLimit); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit }); + }); + }); + }); + + describe("The payment is subsidized and confirmed, and", async () => { + it("The payment sum amount is increased", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 1.1); + const newExtraAmount = Math.floor(payment.extraAmount * 1.1); + const confirmedAmount = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + await checkUpdating(context, { newBaseAmount, newExtraAmount, subsidyLimit, confirmedAmount }); + }); + + it("The payment sum amount is decreased but the remainder is still above the confirmed amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.9); + const newExtraAmount = Math.floor(payment.extraAmount * 0.9); + const refundAmount = Math.floor(payment.baseAmount * 0.1); + const remainder = newBaseAmount + newExtraAmount - refundAmount; + const confirmedAmount = Math.floor(remainder * 0.9); + await checkUpdating( + context, + { newBaseAmount, newExtraAmount, subsidyLimit, refundAmount, confirmedAmount } + ); + }); + + it("The payment sum amount decreased and the remainder becomes below the confirmed amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.7); + const newExtraAmount = Math.floor(payment.extraAmount * 0.7); + const refundAmount = Math.floor(payment.baseAmount * 0.1); + const remainder = newBaseAmount + newExtraAmount - refundAmount; + const confirmedAmount = Math.floor(remainder * 1.1); + await checkUpdating( + context, + { newBaseAmount, newExtraAmount, subsidyLimit, refundAmount, confirmedAmount } + ); + }); + + it("The payment sum amount becomes zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const newBaseAmount = 0; + const newExtraAmount = 0; + const refundAmount = 0; + const confirmedAmount = Math.floor(payment.baseAmount * 0.1); + await checkUpdating( + context, + { newBaseAmount, newExtraAmount, subsidyLimit, refundAmount, confirmedAmount } + ); + }); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + payment.id, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(deployer) as Contract).updatePayment( + payment.id, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("The payment ID is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + ZERO_PAYMENT_ID, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_ZERO_ID + ); + }); + + it("The payment with the provided ID does not exist", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + payment.id, + payment.baseAmount, + payment.extraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_NON_EXISTENT + ).withArgs(payment.id); + }); + + it("The new sum amount is less than the refund amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + const refundAmount = Math.floor((payment.baseAmount + payment.extraAmount) * 0.5); + const newBaseAmount = Math.floor(payment.baseAmount * 0.4); + const newExtraAmount = Math.floor(payment.baseAmount * 0.4); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + payment.id, + newBaseAmount, + newExtraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_INAPPROPRIATE_SUM_AMOUNT + ); + }); + + it("The new payment sum amount is greater than 64-bit unsigned integer", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + const newBaseAmount = MAX_UINT64; + const newExtraAmount = 1n; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updatePayment( + payment.id, + newBaseAmount, + newExtraAmount + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_OVERFLOW_OF_SUM_AMOUNT); + }); + }); + }); + + describe("Function 'revokePayment()'", async () => { + async function checkRevocation(context: TestContext, props: { cashbackRevocationFailure?: boolean } = {}) { + const { cardPaymentProcessorShell, payments: [payment] } = context; + const cashbackRevocationFailure = props.cashbackRevocationFailure ?? false; + + // To be sure that the `refundAmount` field is taken into account + const refundAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + + if (cashbackRevocationFailure) { + cardPaymentProcessorShell.model.setCashbackRevocationStatus(CashbackOperationStatus.Failed); + } + const operationIndex = cardPaymentProcessorShell.model.revokePayment(payment.id); + if (cashbackRevocationFailure) { + const operation = cardPaymentProcessorShell.model.getPaymentOperation(operationIndex); + await proveTx(context.tokenMock.setSpecialAmountToReturnFalse(Math.abs(operation.cashbackRequestedChange))); + } + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).revokePayment(payment.id); + + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The payment is not subsidized and not confirmed, and", async () => { + it("Cashback operations are enabled", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + await checkRevocation(context); + }); + + it("Cashback operations are enabled but cashback revoking fails", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + await checkRevocation(context, { cashbackRevocationFailure: true }); + }); + + it("Cashback operations are disabled before sending", async () => { + const context = await beforeMakingPayments(); + await context.cardPaymentProcessorShell.disableCashback(); + await context.cardPaymentProcessorShell.makeCommonPayments([context.payments[0]]); + await checkRevocation(context); + }); + + it("Cashback operations are disabled after sending", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + await cardPaymentProcessorShell.disableCashback(); + + await checkRevocation(context); + }); + }); + + describe("The payment is subsidized, but not confirmed, and cashback operations are enabled, and", async () => { + describe("The initial subsidy limit (SL) is", async () => { + it("Less than the payment base amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = Math.floor(payment.baseAmount / 2); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit }); + await checkRevocation(context); + }); + + it("Between the payment base amount and the sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = payment.baseAmount + Math.floor(payment.extraAmount / 2); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit }); + await checkRevocation(context); + }); + + it("Above the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = Math.floor((payment.baseAmount + payment.extraAmount) * 1.1); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit }); + await checkRevocation(context); + }); + }); + }); + + describe("The payment is subsidized and confirmed, and cashback operations are enabled, and", async () => { + describe("The confirmed amount is", async () => { + it("Less than the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = Math.floor(payment.baseAmount / 2); + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount / 2); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, confirmationAmount }); + await checkRevocation(context); + }); + + it("Equal to the payment sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = Math.floor(payment.baseAmount / 2); + const confirmationAmount = payment.baseAmount + payment.extraAmount; + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, confirmationAmount }); + await checkRevocation(context); + }); + }); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).revokePayment(payment.id) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(deployer) as Contract).revokePayment(payment.id) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("The payment ID is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).revokePayment(ZERO_PAYMENT_ID) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_ZERO_ID + ); + }); + + it("The payment with the provided ID does not exist", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).revokePayment(payment.id) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_NON_EXISTENT + ).withArgs(payment.id); + }); + }); + }); + + describe("Function 'reversePayment()'", async () => { + /* Since the function under test uses the same common internal function to cancel a payment, + * the complete set of checks are provided in the test section for the 'revokePayment()' function. + * In this section, only specific checks are provided. + */ + it("Executes as expected if the payment is subsidized, confirmed, and cashback is enabled", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { + sponsor, + subsidyLimit: Math.floor(payment.baseAmount * 0.25), + confirmationAmount: Math.floor((payment.baseAmount + payment.extraAmount) * 0.75) + } + ); + + // To be sure that the `refundAmount` field is taken into account + const refundAmount = Math.floor(payment.baseAmount * 0.15); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + + cardPaymentProcessorShell.model.reversePayment(payment.id); + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).reversePayment(payment.id); + + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + }); + + it("Is reverted if the contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).reversePayment(payment.id) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("Is reverted if the caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(deployer) as Contract).reversePayment(payment.id) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + }); + + describe("Function 'confirmPayment()'", async () => { + async function checkConfirmation(context: TestContext, confirmationAmount: number, refundAmount?: number) { + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit }); + if (refundAmount) { + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + } + + cardPaymentProcessorShell.model.confirmPayment(payment.id, confirmationAmount); + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + const tx = contractConnected.confirmPayment(payment.id, confirmationAmount); + + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The refund amount is zero, and", async () => { + it("The confirmation amount is less than the remainder", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + await checkConfirmation(context, confirmationAmount); + }); + + it("The confirmation amount equals the remainder", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount); + await checkConfirmation(context, confirmationAmount); + }); + }); + + describe("The refund amount is non-zero, and", async () => { + it("The confirmation amount is less than the remainder", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.1); + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount * 0.5); + await checkConfirmation(context, confirmationAmount, refundAmount); + }); + + it("The confirmation amount equals the remainder", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.1); + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount - refundAmount); + await checkConfirmation(context, confirmationAmount, refundAmount); + }); + + it("The confirmation is zero", async () => { + const context = await beforeMakingPayments(); + const confirmationAmount = 0; + await checkConfirmation(context, confirmationAmount); + }); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayment(payment.id, ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(deployer) as Contract; + await expect( + contractConnected.confirmPayment(payment.id, ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("The payment ID is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayment(ZERO_PAYMENT_ID, ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_ZERO_ID + ); + }); + + it("The payment with the provided ID does not exist", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayment(payment.id, ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_NON_EXISTENT + ).withArgs(payment.id); + }); + + it("The confirmation amount is greater than the remainder", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit }); + const refundAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + const confirmationAmount = Math.floor(payment.baseAmount + payment.extraAmount - refundAmount) + 1; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayment(payment.id, confirmationAmount) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_INAPPROPRIATE_CONFIRMATION_AMOUNT + ); + }); + + it("The cash-out account is the zero address", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await cardPaymentProcessorShell.makeCommonPayments([payment]); + + await proveTx(cardPaymentProcessorShell.contract.setCashOutAccount(ZERO_ADDRESS)); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayment(payment.id, ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_CASH_OUT_ACCOUNT_NOT_CONFIGURED + ); + }); + }); + }); + + describe("Function 'confirmPayments()'", async () => { + /* Since the function under test uses the same common internal function to confirm payments, + * the complete set of checks are provided in the test section for the 'confirmPayment()' function. + * In this section, only specific checks are provided. + */ + it("Executes as expected", async () => { + const context = await beforeMakingPayments({ paymentNumber: 2 }); + const { cardPaymentProcessorShell, payments } = context; + await cardPaymentProcessorShell.makeCommonPayments([payments[0]]); + const subsidyLimit = Math.floor(payments[1].baseAmount / 2); + await cardPaymentProcessorShell.makePaymentFor(payments[1], { sponsor, subsidyLimit }); + const paymentConfirmations: PaymentConfirmation[] = [ + { + paymentId: payments[0].id, + amount: Math.floor(payments[0].baseAmount + payments[0].extraAmount * 0.5) + }, + { + paymentId: payments[1].id, + amount: Math.floor(payments[1].baseAmount + payments[1].extraAmount) + } + ]; + const operationIndex1 = cardPaymentProcessorShell.model.confirmPayment( + paymentConfirmations[0].paymentId, + paymentConfirmations[0].amount + ); + const operationIndex2 = cardPaymentProcessorShell.model.confirmPayment( + paymentConfirmations[1].paymentId, + paymentConfirmations[1].amount + ); + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + const tx = contractConnected.confirmPayments(paymentConfirmations); + await context.checkPaymentOperationsForTx(tx, [operationIndex1, operationIndex2]); + await context.checkCardPaymentProcessorState(); + }); + + it("Is reverted if the contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayments([{ paymentId: payment.id, amount: 0 }]) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("Is reverted if the caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(deployer) as Contract; + await expect( + contractConnected.confirmPayments([{ paymentId: payment.id, amount: 0 }]) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("Is reverted if the payment confirmation array is empty", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.confirmPayments([]) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_CONFIRMATION_ARRAY_EMPTY + ); + }); + }); + + describe("Function 'updateLazyAndConfirmPayment()'", async () => { + /* Since the function under test uses the same common internal functions to update and confirm a payment, + * the complete set of checks are provided in the test sections for + * the 'updatePayment()' and `confirmPayment()` function. + * In this section, only specific checks are provided. + */ + async function checkLazyUpdating( + context: TestContext, + props: { + newBaseAmount?: number; + newExtraAmount?: number; + confirmationAmount?: number; + } = {} + ) { + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makePaymentFor(payment); + + await context.checkCardPaymentProcessorState(); + + const newBaseAmount = props.newBaseAmount ?? payment.baseAmount; + const newExtraAmount = props.newExtraAmount ?? payment.extraAmount; + + const operationIndex1 = cardPaymentProcessorShell.model.updatePayment( + payment.id, + newBaseAmount, + newExtraAmount, + UpdatingOperationKind.Lazy + ); + const operationIndex2 = cardPaymentProcessorShell.model.confirmPayment( + payment.id, + props.confirmationAmount ?? ZERO_CONFIRMATION_AMOUNT + ); + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).updateLazyAndConfirmPayment( + payment.id, + newBaseAmount, + newExtraAmount, + props.confirmationAmount ?? ZERO_CONFIRMATION_AMOUNT + ); + + await context.checkPaymentOperationsForTx(tx, [operationIndex1, operationIndex2]); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The confirmation amount is zero, and", async () => { + it("The base amount and extra amount are both not changed", async () => { + const context = await beforeMakingPayments(); + await checkLazyUpdating(context); + }); + + it("The base amount is changed, but the extra amount is not changed", async () => { + const context = await beforeMakingPayments(); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkLazyUpdating(context, { newBaseAmount }); + }); + + it("The base amount is not changed, but the extra amount is changed", async () => { + const context = await beforeMakingPayments(); + const newExtraAmount = Math.floor(context.payments[0].extraAmount * 0.9); + await checkLazyUpdating(context, { newExtraAmount }); + }); + }); + + describe("The confirmation amount is non-zero, and", async () => { + it("The base amount and extra amount are both not changed", async () => { + const context = await beforeMakingPayments(); + const confirmationAmount = Math.floor(context.payments[0].baseAmount * 0.5); + await checkLazyUpdating(context, { confirmationAmount }); + }); + + it("The base amount is changed, but the extra amount is not changed", async () => { + const context = await beforeMakingPayments(); + const confirmationAmount = Math.floor(context.payments[0].baseAmount * 0.5); + const newBaseAmount = Math.floor(context.payments[0].baseAmount * 0.9); + await checkLazyUpdating(context, { newBaseAmount, confirmationAmount }); + }); + + it("The base amount is not changed, but the extra amount is changed", async () => { + const context = await beforeMakingPayments(); + const confirmationAmount = Math.floor(context.payments[0].baseAmount * 0.5); + const newExtraAmount = Math.floor(context.payments[0].extraAmount * 0.9); + await checkLazyUpdating(context, { newExtraAmount, confirmationAmount }); + }); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).updateLazyAndConfirmPayment( + payment.id, + payment.baseAmount, + payment.extraAmount, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await expect( + (cardPaymentProcessorShell.contract.connect(deployer) as Contract).updateLazyAndConfirmPayment( + payment.id, + payment.baseAmount, + payment.extraAmount, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + }); + }); + + describe("Function 'refundPayment()'", async () => { + async function checkRefunding( + context: TestContext, + props: { + refundAmount: number; + cashbackCondition?: CashbackConditionType; + confirmedAmount?: number; + subsidyLimit?: number; + } = { refundAmount: 0, cashbackCondition: CashbackConditionType.CashbackEnabled } + ) { + const { cardPaymentProcessorShell, tokenMock, payments: [payment] } = context; + const { cashbackCondition, subsidyLimit, confirmedAmount } = props; + + if (cashbackCondition === CashbackConditionType.CashbackDisabledBeforePaymentMaking) { + await cardPaymentProcessorShell.disableCashback(); + } + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, confirmationAmount: confirmedAmount } + ); + + if (cashbackCondition === CashbackConditionType.CashbackDisabledAfterPaymentMaking) { + await cardPaymentProcessorShell.disableCashback(); + } + + if (cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingPartial) { + const actualCashbackChange = 2 * CASHBACK_ROUNDING_COEF; + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - actualCashbackChange; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + cardPaymentProcessorShell.model.setCashbackIncreaseAmountResult(actualCashbackChange); + } + if (cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingFails) { + cardPaymentProcessorShell.model.setCashbackRevocationStatus(CashbackOperationStatus.Failed); + } + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingReverts || + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingFails || + cashbackCondition === CashbackConditionType.CashbackEnabledButIncreasingReverts + ) { + throw new Error(`Unexpected cashback condition type: ${cashbackCondition}`); + } + + await context.checkCardPaymentProcessorState(); + + const operationIndex = cardPaymentProcessorShell.model.refundPayment( + payment.id, + props.refundAmount + ); + if ( + cashbackCondition === CashbackConditionType.CashbackEnabledButRevokingFails + ) { + const operation = cardPaymentProcessorShell.model.getPaymentOperation(operationIndex); + await proveTx(tokenMock.setSpecialAmountToReturnFalse(Math.abs(operation.cashbackRequestedChange))); + } + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + const tx = contractConnected.refundPayment(payment.id, props.refundAmount); + + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + } + + describe("Executes as expected if", async () => { + describe("The payment is not subsidized and not confirmed, and", async () => { + describe("The refund amount is less than base amount, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.9); + await checkRefunding(context, { refundAmount }); + }); + + it("Disabled before payment making", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.9); + await checkRefunding(context, { + refundAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledBeforePaymentMaking + }); + }); + + it("Disabled after payment making", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.9); + await checkRefunding(context, { + refundAmount, + cashbackCondition: CashbackConditionType.CashbackDisabledAfterPaymentMaking + }); + }); + + it("Enabled but cashback operation fails", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.9); + await checkRefunding(context, { + refundAmount, + cashbackCondition: CashbackConditionType.CashbackEnabledButRevokingFails + }); + }); + }); + + describe("The refund amount equals the sum amount, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount + payment.extraAmount); + await checkRefunding(context, { refundAmount }); + }); + }); + + describe("The refund amount is zero, and cashback is", async () => { + it("Enabled, and cashback operation is executed successfully", async () => { + const context = await beforeMakingPayments(); + const refundAmount = 0; + await checkRefunding(context, { refundAmount }); + }); + }); + }); + + describe("The payment is subsidized but not confirmed, and cashback is enabled, and", async () => { + it("The refund amount is less than base amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount * 0.9); + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit }); + }); + + it("The refund amount equals the sum amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = Math.floor(payment.baseAmount + payment.extraAmount); + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit }); + }); + + it("The refund amount is zero, and cashback is", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const refundAmount = 0; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit }); + }); + }); + + describe("The payment is subsidized and confirmed, and cashback is enabled, and", async () => { + it("The refund amount is less than non-confirmed amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const confirmedAmount = Math.floor(payment.baseAmount * 0.2); + const refundAmount = Math.floor(payment.baseAmount * 0.3); + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit, confirmedAmount }); + }); + + it("The refund amount is the same as non-confirmed amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const sumAmount = Math.floor(payment.baseAmount + payment.extraAmount); + const confirmedAmount = Math.floor(payment.baseAmount * 0.2); + const refundAmount = sumAmount - confirmedAmount; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit, confirmedAmount }); + }); + + it("The refund amount is greater than non-confirmed amount", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const sumAmount = Math.floor(payment.baseAmount + payment.extraAmount); + const confirmedAmount = Math.floor(payment.baseAmount * 0.2); + const refundAmount = Math.floor(sumAmount - payment.baseAmount * 0.1); + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit, confirmedAmount }); + }); + + it("The refund amount is zero", async () => { + const context = await beforeMakingPayments(); + const { payments: [payment] } = context; + const confirmedAmount = Math.floor(payment.baseAmount * 0.2); + const refundAmount = 0; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + await checkRefunding(context, { refundAmount, subsidyLimit, confirmedAmount }); + }); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + await pauseContract(cardPaymentProcessorShell.contract); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundPayment(payment.id, ZERO_REFUND_AMOUNT) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(deployer) as Contract; + await expect( + contractConnected.refundPayment(payment.id, ZERO_REFUND_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("The payment ID is zero", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundPayment(ZERO_PAYMENT_ID, ZERO_REFUND_AMOUNT) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_PAYMENT_ZERO_ID); + }); + + it("The payment with the provided ID does not exist", async () => { + const context = await prepareForPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundPayment(payment.id, ZERO_REFUND_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_PAYMENT_NON_EXISTENT + ).withArgs(payment.id); + }); + + it("The refund amount exceeds the sum amount", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + const subsidyLimit = Math.floor(payment.baseAmount * 0.5); + const confirmationAmount = Math.floor(payment.baseAmount * 0.2); + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, confirmationAmount }); + const refundAmount = payment.baseAmount + payment.extraAmount + 1; + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundPayment(payment.id, refundAmount) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_INAPPROPRIATE_REFUNDING_AMOUNT + ); + }); + }); + }); + + describe("Function 'refundAccount()'", async () => { + const nonZeroTokenAmount = 123; + const zeroTokenAmount = 0; + + async function checkRefundingAccount(tokenAmount: number) { + const { cardPaymentProcessorShell, tokenMock } = await prepareForPayments(); + await proveTx(tokenMock.mint(cashOutAccount.address, tokenAmount)); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + const tx = await contractConnected.refundAccount(user1.address, tokenAmount); + + await expect(tx).to.emit( + cardPaymentProcessorShell.contract, + EVENT_NAME_ACCOUNT_REFUNDED + ).withArgs( + user1.address, + tokenAmount, + "0x" + ); + + await expect(tx).to.changeTokenBalances( + tokenMock, + [user1, cashOutAccount, cardPaymentProcessorShell.contract], + [+tokenAmount, -tokenAmount, 0] + ); + } + + describe("Executes as expected and emits the correct events if the refund amount is", async () => { + it("Non-zero", async () => { + await checkRefundingAccount(nonZeroTokenAmount); + }); + + it("Zero", async () => { + await checkRefundingAccount(zeroTokenAmount); + }); + }); + + describe("Is reverted if", async () => { + it("The contract is paused", async () => { + const { cardPaymentProcessorShell } = await prepareForPayments(); + await pauseContract(cardPaymentProcessorShell.contract); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundAccount(user1.address, nonZeroTokenAmount) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_CONTRACT_IS_PAUSED); + }); + + it("The caller does not have the executor role", async () => { + const { cardPaymentProcessorShell } = await prepareForPayments(); + + const contractConnected = cardPaymentProcessorShell.contract.connect(deployer) as Contract; + await expect( + contractConnected.refundAccount(user1.address, nonZeroTokenAmount) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, executorRole); + }); + + it("The account address is zero", async () => { + const { cardPaymentProcessorShell } = await prepareForPayments(); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundAccount(ZERO_ADDRESS, nonZeroTokenAmount) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_ACCOUNT_ZERO_ADDRESS); + }); + + it("The cash-out account does not configured", async () => { + const { cardPaymentProcessorShell } = await prepareForPayments(); + const tokenAmount = nonZeroTokenAmount; + await proveTx(cardPaymentProcessorShell.contract.setCashOutAccount(ZERO_ADDRESS)); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundAccount(user1.address, tokenAmount) + ).to.be.revertedWithCustomError( + cardPaymentProcessorShell.contract, + REVERT_ERROR_IF_CASH_OUT_ACCOUNT_NOT_CONFIGURED + ); + }); + + it("The cash-out account does not have enough token balance", async () => { + const { cardPaymentProcessorShell, tokenMock } = await prepareForPayments(); + const tokenAmount = nonZeroTokenAmount; + await proveTx(tokenMock.mint(cashOutAccount.address, tokenAmount - 1)); + + const contractConnected = cardPaymentProcessorShell.contract.connect(executor) as Contract; + await expect( + contractConnected.refundAccount(user1.address, tokenAmount) + ).to.be.revertedWithCustomError( + tokenMock, + REVERT_ERROR_IF_ERC20_TOKEN_TRANSFER_AMOUNT_EXCEEDS_BALANCE + ).withArgs(cashOutAccount.address, anyValue, anyValue); + }); + }); + }); + + describe("Complex scenarios", async () => { + async function checkRevertingOfAllPaymentProcessingFunctionsExceptMaking( + cardPaymentProcessor: Contract, + payments: TestPayment[], + status: PaymentStatus + ) { + const paymentIds = payments.map(payment => payment.id); + + await expect( + connect(cardPaymentProcessor, executor).revokePayment(paymentIds[0]) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + await expect( + connect(cardPaymentProcessor, executor).reversePayment(paymentIds[0]) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + await expect( + connect(cardPaymentProcessor, executor).confirmPayment(paymentIds[0], ZERO_CONFIRMATION_AMOUNT) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + const paymentConfirmations: PaymentConfirmation[] = paymentIds.map(id => { + return { + paymentId: id, + amount: ZERO_CONFIRMATION_AMOUNT + }; + }); + + await expect( + connect(cardPaymentProcessor, executor).confirmPayments(paymentConfirmations) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + await expect( + connect(cardPaymentProcessor, executor).updatePayment( + paymentIds[0], + payments[0].baseAmount, + payments[0].extraAmount + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + await expect( + connect(cardPaymentProcessor, executor).updateLazyAndConfirmPayment( + paymentIds[0], + payments[0].baseAmount, + payments[0].extraAmount, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + + await expect( + connect(cardPaymentProcessor, executor).refundPayment( + paymentIds[0], + ZERO_REFUND_AMOUNT + ) + ).to.be.revertedWithCustomError( + cardPaymentProcessor, + REVERT_ERROR_IF_INAPPROPRIATE_PAYMENT_STATUS + ).withArgs( + paymentIds[0], + status + ); + } + + it("All payment processing functions except making are reverted if a payment was revoked", async () => { + const context = await beforeMakingPayments({ paymentNumber: 2 }); + const { cardPaymentProcessorShell, payments } = context; + + await cardPaymentProcessorShell.makeCommonPayments(payments); + await cardPaymentProcessorShell.revokePayment(payments[0]); + + await context.checkCardPaymentProcessorState(); + + await checkRevertingOfAllPaymentProcessingFunctionsExceptMaking( + cardPaymentProcessorShell.contract, + payments, + PaymentStatus.Revoked + ); + + cardPaymentProcessorShell.model.makePayment(payments[0], { sender: executor }); + const tx = (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payments[0].id, + payments[0].payer.address, + payments[0].baseAmount, + payments[0].extraAmount, + ZERO_SPONSOR_ADDRESS, + ZERO_SUBSIDY_LIMIT, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ); + await context.checkPaymentOperationsForTx(tx); + await context.checkCardPaymentProcessorState(); + }); + + it("All payment processing functions are reverted if a payment was reversed", async () => { + const context = await beforeMakingPayments({ paymentNumber: 2 }); + const { cardPaymentProcessorShell, payments } = context; + + await cardPaymentProcessorShell.makeCommonPayments(payments); + await cardPaymentProcessorShell.reversePayment(payments[0]); + + await expect( + (cardPaymentProcessorShell.contract.connect(executor) as Contract).makePaymentFor( + payments[0].id, + payments[0].payer.address, + payments[0].baseAmount, + payments[0].extraAmount, + ZERO_SPONSOR_ADDRESS, + ZERO_SUBSIDY_LIMIT, + CASHBACK_RATE_AS_IN_CONTRACT, + ZERO_CONFIRMATION_AMOUNT + ) + ).to.be.revertedWithCustomError(cardPaymentProcessorShell.contract, REVERT_ERROR_IF_PAYMENT_ALREADY_EXISTENT); + + await checkRevertingOfAllPaymentProcessingFunctionsExceptMaking( + cardPaymentProcessorShell.contract, + payments, + PaymentStatus.Reversed + ); + await context.checkCardPaymentProcessorState(); + }); + + it("Several refund and payment updating operations execute as expected if cashback is enabled", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makeCommonPayments([payment]); + + await context.checkCardPaymentProcessorState(); + + await cardPaymentProcessorShell.updatePayment(payment, Math.floor(payment.baseAmount * 2)); + await cardPaymentProcessorShell.refundPayment(payment, Math.floor(payment.baseAmount * 0.1)); + await cardPaymentProcessorShell.updatePayment( + payment, + Math.floor(payment.baseAmount * 0.9), + Math.floor(payment.extraAmount * 1.1) + ); + await cardPaymentProcessorShell.refundPayment( + payment, + Math.floor(payment.baseAmount * 0.2) + ); + await cardPaymentProcessorShell.updatePayment(payment, Math.floor(payment.baseAmount * 1.5)); + await context.checkCardPaymentProcessorState(); + + await cardPaymentProcessorShell.refundPayment(payment, Math.floor(payment.baseAmount * 0.3)); + await context.checkCardPaymentProcessorState(); + + let currentPayment: PaymentModel = cardPaymentProcessorShell.model.getPaymentById(payment.id); + const confirmationAmount = + currentPayment.baseAmount + currentPayment.extraAmount - currentPayment.refundAmount - CASHBACK_ROUNDING_COEF; + const operationResult: OperationResult = + await cardPaymentProcessorShell.confirmPayment(payment, confirmationAmount); + await context.checkPaymentOperationsForTx(operationResult.tx); + await context.checkCardPaymentProcessorState(); + + await cardPaymentProcessorShell.refundPayment(payment, Math.floor(payment.baseAmount * 0.4)); + currentPayment = cardPaymentProcessorShell.model.getPaymentById(payment.id); + const refundAmount = + currentPayment.baseAmount + currentPayment.extraAmount - currentPayment.refundAmount; + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + await context.checkCardPaymentProcessorState(); + }); + + it("Revocation execute correctly for a payment with and without extra amount, cashback, subsidy", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { + sponsor, + subsidyLimit: Math.floor(payment.baseAmount * 0.75), + confirmationAmount: Math.floor(payment.baseAmount * 0.25) + } + ); + await cardPaymentProcessorShell.revokePayment(payment); + await context.checkCardPaymentProcessorState(); + + payment.extraAmount = 0; + await cardPaymentProcessorShell.disableCashback(); + await cardPaymentProcessorShell.makePaymentFor(payment); + await context.checkCardPaymentProcessorState(); + await cardPaymentProcessorShell.revokePayment(payment); + await context.checkCardPaymentProcessorState(); + }); + + it("Refunding executes as expected if the initial cashback was capped", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - CASHBACK_ROUNDING_COEF; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + await cardPaymentProcessorShell.makePaymentFor(payment); + const refundAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.refundPayment(payment, refundAmount); + await context.checkCardPaymentProcessorState(); + }); + + it("Updating with cashback decreasing executes as expected if the initial cashback was capped", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - CASHBACK_ROUNDING_COEF; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + await cardPaymentProcessorShell.makePaymentFor(payment); + const newBaseAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.updatePayment(payment, newBaseAmount); + await context.checkCardPaymentProcessorState(); + }); + + it("Updating with cashback increasing executes as expected if the initial cashback was capped", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - CASHBACK_ROUNDING_COEF; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + await cardPaymentProcessorShell.makePaymentFor(payment); + const newBaseAmount = Math.floor(payment.baseAmount * 1.5); + await cardPaymentProcessorShell.updatePayment(payment, newBaseAmount); + await context.checkCardPaymentProcessorState(); + }); + + it("Updating with cashback decreasing executes as expected if the initial cashback was capped", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const presetCashbackAmount = MAX_CASHBACK_FOR_CAP_PERIOD - CASHBACK_ROUNDING_COEF; + await context.presetCashbackForAccount(payment.payer, presetCashbackAmount); + await cardPaymentProcessorShell.makePaymentFor(payment); + const newBaseAmount = Math.floor(payment.baseAmount * 0.1); + await cardPaymentProcessorShell.updatePayment(payment, newBaseAmount); + await context.checkCardPaymentProcessorState(); + }); + }); + + describe("Token transfer demonstration scenarios", async () => { + interface ExpectedBalanceChanges { + payer?: number; + sponsor?: number; + cardPaymentProcessor?: number; + cashOutAccount?: number; + cashbackTreasury?: number; + } + + async function checkBalanceChanges( + context: TestContext, + operationResult: Promise, + expectedBalanceChanges: ExpectedBalanceChanges + ) { + const tx = (await operationResult).tx; + await expect(tx).to.changeTokenBalances( + context.tokenMock, + [ + context.payments[0].payer, + sponsor, + context.cardPaymentProcessorShell.contract, + context.cashOutAccount, + context.cashbackTreasury + ], + [ + expectedBalanceChanges.payer ?? 0, + expectedBalanceChanges.sponsor ?? 0, + expectedBalanceChanges.cardPaymentProcessor ?? 0, + expectedBalanceChanges.cashOutAccount ?? 0, + expectedBalanceChanges.cashbackTreasury ?? 0 + ] + ); + } + + it("Making a payment with cashback, example 1: a partially subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + + const opResult = cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + + const cashbackChange = 40 * DIGITS_COEF; + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -600 * DIGITS_COEF + cashbackChange, + sponsor: -800 * DIGITS_COEF, + cardPaymentProcessor: 1400 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Making a payment with cashback, example 2: a fully subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 2000 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + + const opResult = cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + + const cashbackChange = 0; + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -0 + cashbackChange, + sponsor: -1400 * DIGITS_COEF, + cardPaymentProcessor: 1400 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Making a payment with cashback, example 3: cashback rounding up", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const cashbackRate = 200; // 20 % + payment.baseAmount = Math.floor(2.5 * CASHBACK_ROUNDING_COEF / (cashbackRate / 1000)); + payment.extraAmount = 0; + + const opResult = cardPaymentProcessorShell.makePaymentFor(payment, { cashbackRate }); + + const cashbackChange = 3 * CASHBACK_ROUNDING_COEF; + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -payment.baseAmount + cashbackChange, + sponsor: -0, + cardPaymentProcessor: payment.baseAmount, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Making a payment with cashback, example 4: cashback rounding down", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + const cashbackRate = 200; // 20 % + payment.baseAmount = Math.floor(2.49999 * CASHBACK_ROUNDING_COEF / (cashbackRate / 1000)); + payment.extraAmount = 0; + + const opResult = cardPaymentProcessorShell.makePaymentFor(payment, { cashbackRate }); + + const cashbackChange = 2 * CASHBACK_ROUNDING_COEF; + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -payment.baseAmount + cashbackChange, + sponsor: -0, + cardPaymentProcessor: payment.baseAmount, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Making a payment with cashback, example 1: a partially subsidized payment with confirmation", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const confirmationAmount = 1100 * DIGITS_COEF; + + const opResult = cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, cashbackRate, confirmationAmount } + ); + + const cashbackChange = 40 * DIGITS_COEF; + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -600 * DIGITS_COEF + cashbackChange, + sponsor: -800 * DIGITS_COEF, + cardPaymentProcessor: 1400 * DIGITS_COEF - confirmationAmount, + cashbackTreasury: -cashbackChange, + cashOutAccount: confirmationAmount + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Updating a payment with cashback, example 1: increasing a partially subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const newBaseAmount = 1200 * DIGITS_COEF; + const newExtraAmount = 600 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + const opResult = cardPaymentProcessorShell.updatePayment(payment, newBaseAmount, newExtraAmount); + + const oldCashback = 40 * DIGITS_COEF; + const newCashback = 80 * DIGITS_COEF; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -400 * DIGITS_COEF + cashbackChange, + sponsor: 0, + cardPaymentProcessor: +400 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Updating a payment with cashback, example 2: decreasing a partially subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1200 * DIGITS_COEF; + payment.extraAmount = 600 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const newBaseAmount = 400 * DIGITS_COEF; + const newExtraAmount = 200 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + const opResult = cardPaymentProcessorShell.updatePayment(payment, newBaseAmount, newExtraAmount); + + const oldCashback = 80 * DIGITS_COEF; + const newCashback = 0; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: +1000 * DIGITS_COEF + cashbackChange, + sponsor: +200 * DIGITS_COEF, + cardPaymentProcessor: -1200 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Updating a payment with cashback, example 3: increasing a fully subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 400 * DIGITS_COEF; + payment.extraAmount = 200 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const newBaseAmount = 1200 * DIGITS_COEF; + const newExtraAmount = 600 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + const opResult = cardPaymentProcessorShell.updatePayment(payment, newBaseAmount, newExtraAmount); + + const oldCashback = 0; + const newCashback = 80 * DIGITS_COEF; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: -1000 * DIGITS_COEF + cashbackChange, + sponsor: -200 * DIGITS_COEF, + cardPaymentProcessor: +1200 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Refunding a payment with cashback, example 1: the confirmed amount is zero", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 600 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const refundAmount = 400 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + const opResult = cardPaymentProcessorShell.refundPayment(payment, refundAmount); + + const oldCashback = 40 * DIGITS_COEF; + const newCashback = 24 * DIGITS_COEF; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: +80 * DIGITS_COEF + cashbackChange, + sponsor: +320 * DIGITS_COEF, + cardPaymentProcessor: -400 * DIGITS_COEF, + cashbackTreasury: -cashbackChange + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Refunding a payment with cashback, example 2: the confirmed amount is non-zero", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 600 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const refundAmount = 400 * DIGITS_COEF; + const confirmationAmount = 1500 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, cashbackRate, confirmationAmount } + ); + const opResult = cardPaymentProcessorShell.refundPayment(payment, refundAmount); + + const oldCashback = 40 * DIGITS_COEF; + const newCashback = 24 * DIGITS_COEF; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: +80 * DIGITS_COEF + cashbackChange, + sponsor: +320 * DIGITS_COEF, + cardPaymentProcessor: -100 * DIGITS_COEF, + cashbackTreasury: -cashbackChange, + cashOutAccount: -300 * DIGITS_COEF + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Revoking a payment with cashback, example 1: a partially subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const confirmationAmount = 1200 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, cashbackRate, confirmationAmount } + ); + const opResult = cardPaymentProcessorShell.revokePayment(payment); + + const oldCashback = 40 * DIGITS_COEF; + const newCashback = 0; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: +600 * DIGITS_COEF + cashbackChange, + sponsor: +800 * DIGITS_COEF, + cardPaymentProcessor: -200 * DIGITS_COEF, + cashbackTreasury: -cashbackChange, + cashOutAccount: -1200 * DIGITS_COEF + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Revoking a payment with cashback, example 2: a fully subsidized payment", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 2000 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const confirmationAmount = 1200 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor( + payment, + { sponsor, subsidyLimit, cashbackRate, confirmationAmount } + ); + const opResult = cardPaymentProcessorShell.revokePayment(payment); + + const oldCashback = 0; + const newCashback = 0; + const cashbackChange = newCashback - oldCashback; + + const expectedBalanceChanges: ExpectedBalanceChanges = { + payer: cashbackChange, + sponsor: +1400 * DIGITS_COEF, + cardPaymentProcessor: -200 * DIGITS_COEF, + cashbackTreasury: -cashbackChange, + cashOutAccount: -1200 * DIGITS_COEF + }; + await checkBalanceChanges(context, opResult, expectedBalanceChanges); + }); + + it("Confirming a payment with cashback, example 1", async () => { + const context = await beforeMakingPayments(); + const { cardPaymentProcessorShell, payments: [payment] } = context; + + payment.baseAmount = 1000 * DIGITS_COEF; + payment.extraAmount = 400 * DIGITS_COEF; + const subsidyLimit = 800 * DIGITS_COEF; + const cashbackRate = 200; // 20 % + const confirmationAmount = 1200 * DIGITS_COEF; + + await cardPaymentProcessorShell.makePaymentFor(payment, { sponsor, subsidyLimit, cashbackRate }); + const opResult = await cardPaymentProcessorShell.confirmPayment(payment, confirmationAmount); + + const expectedBalanceChanges: ExpectedBalanceChanges = { + cardPaymentProcessor: -1200 * DIGITS_COEF, + cashOutAccount: +1200 * DIGITS_COEF + }; + await checkBalanceChanges(context, Promise.resolve(opResult), expectedBalanceChanges); + }); + }); + + describe("Scenario with cashback periodical cap", async () => { + async function checkAccountCashbackState( + context: TestContext, + expectedCapPeriodStartTime: number + ) { + const { cardPaymentProcessorShell, payments: [payment] } = context; + const actualAccountCashbackState: AccountCashbackState = + await cardPaymentProcessorShell.contract.getAccountCashbackState(payment.payer.address); + const expectedTotalCashback = cardPaymentProcessorShell.model.getCashbackTotalForAccount(payment.payer.address); + const expectedCapPeriodStartAmount = cardPaymentProcessorShell.model.capPeriodStartAmount; + expect(actualAccountCashbackState.totalAmount).to.equal(expectedTotalCashback); + expect(actualAccountCashbackState.capPeriodStartAmount).to.equal(expectedCapPeriodStartAmount); + expect(actualAccountCashbackState.capPeriodStartTime).to.equal(expectedCapPeriodStartTime); + } + + it("Executes as expected", async () => { + const context = await beforeMakingPayments({ paymentNumber: 2 }); + const { cardPaymentProcessorShell } = context; + const payment: TestPayment = { ...context.payments[0] }; + + // Check initial cashback state for the account after the first cashback transfer + const operationResult1 = await cardPaymentProcessorShell.makePaymentFor(payment); + const expectedCapPeriodStartTime1 = await getTxTimestamp(operationResult1.tx); + await checkAccountCashbackState(context, expectedCapPeriodStartTime1); + + // Reach the cashback cap first time. + await context.presetCashbackForAccount(payment.payer, MAX_CASHBACK_FOR_CAP_PERIOD); + await checkAccountCashbackState(context, expectedCapPeriodStartTime1); + + // Revoke cashback and check that the cap-related values are changed properly + await cardPaymentProcessorShell.revokePayment(payment); + await checkAccountCashbackState(context, expectedCapPeriodStartTime1); + + // Reach the cashback cap again + await cardPaymentProcessorShell.makePaymentFor(payment); + await checkAccountCashbackState(context, expectedCapPeriodStartTime1); + + // The following part of the test is executed only for Hardhat network because we need to shift block time + if (network.name !== "hardhat" && network.name !== "stratus") { + return; + } + + // Shift next block time for a period of cap checking + await increaseBlockTimestamp(CASHBACK_CAP_RESET_PERIOD + 1); + + // Set new start amount for the cashback cap checking in the model + cardPaymentProcessorShell.model.capPeriodStartAmount = + cardPaymentProcessorShell.model.getCashbackTotalForAccount(payment.payer.address); + + // Check that next cashback sending executes successfully due to the cap period resets + payment.id = context.payments[1].id; + const operationResult2 = await cardPaymentProcessorShell.makePaymentFor(payment); + const expectedCapPeriodStartTime2 = await getTxTimestamp(operationResult2.tx); + await checkAccountCashbackState(context, expectedCapPeriodStartTime2); + + // Revoke the very first cashback and check that the cap-related values are changed properly + payment.id = context.payments[0].id; + await cardPaymentProcessorShell.revokePayment(payment); + await checkAccountCashbackState(context, expectedCapPeriodStartTime2); + }); + }); +}); diff --git a/test/base/AccessControlExtUpgradeable.test.ts b/test/base/AccessControlExtUpgradeable.test.ts new file mode 100644 index 0000000..d69115f --- /dev/null +++ b/test/base/AccessControlExtUpgradeable.test.ts @@ -0,0 +1,212 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory, TransactionResponse } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { connect, proveTx } from "../../test-utils/eth"; + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contract 'AccessControlExtUpgradeable'", async () => { + const EVENT_NAME_ROLE_GRANTED = "RoleGranted"; + const EVENT_NAME_ROLE_REVOKED = "RoleRevoked"; + + const REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID = "InvalidInitialization"; + const REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING = "NotInitializing"; + const REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT = "AccessControlUnauthorizedAccount"; + + const ownerRole: string = ethers.id("OWNER_ROLE"); + const userRole: string = ethers.id("USER_ROLE"); + + let accessControlExtMockFactory: ContractFactory; + let deployer: HardhatEthersSigner; + let attacker: HardhatEthersSigner; + let users: HardhatEthersSigner[]; + let userAddresses: string[]; + + before(async () => { + [deployer, attacker, ...users] = await ethers.getSigners(); + accessControlExtMockFactory = await ethers.getContractFactory("AccessControlExtUpgradeableMock"); + // Explicitly specifying the deployer account + accessControlExtMockFactory = accessControlExtMockFactory.connect(deployer); + + userAddresses = [users[0].address, users[1].address, users[2].address]; + }); + + async function deployAccessControlExtMock(): Promise<{ accessControlExtMock: Contract }> { + let accessControlExtMock: Contract = await upgrades.deployProxy(accessControlExtMockFactory); + await accessControlExtMock.waitForDeployment(); + accessControlExtMock = connect(accessControlExtMock, deployer); // Explicitly specifying the initial account + + return { accessControlExtMock }; + } + + describe("Function 'initialize()'", async () => { + it("The external initializer configures the contract as expected", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + + // The roles + expect((await accessControlExtMock.OWNER_ROLE()).toLowerCase()).to.equal(ownerRole); + expect((await accessControlExtMock.USER_ROLE()).toLowerCase()).to.equal(userRole); + + // The role admins + expect(await accessControlExtMock.getRoleAdmin(ownerRole)).to.equal(ethers.ZeroHash); + expect(await accessControlExtMock.getRoleAdmin(userRole)).to.equal(ownerRole); + + // The deployer should have the owner role, but not the other roles + expect(await accessControlExtMock.hasRole(ownerRole, deployer.address)).to.equal(true); + }); + + it("The external initializer is reverted if it is called a second time", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await expect( + accessControlExtMock.initialize() + ).to.be.revertedWithCustomError(accessControlExtMock, REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID); + }); + + it("The internal initializer is reverted if it is called outside the init process", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await expect( + accessControlExtMock.call_parent_initialize() + ).to.be.revertedWithCustomError(accessControlExtMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + + it("The internal unchained initializer is reverted if it is called outside the init process", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await expect( + accessControlExtMock.call_parent_initialize_unchained() + ).to.be.revertedWithCustomError(accessControlExtMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + }); + + describe("Function 'grantRoleBatch()'", async () => { + describe("Executes as expected if the input account array contains", async () => { + it("A single account without the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(false); + + await expect(accessControlExtMock.grantRoleBatch(userRole, [userAddresses[0]])) + .to.emit(accessControlExtMock, EVENT_NAME_ROLE_GRANTED) + .withArgs(userRole, userAddresses[0], deployer.address); + + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(true); + }); + + it("A single account with the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await proveTx(accessControlExtMock.grantRoleBatch(userRole, [userAddresses[0]])); + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(true); + + await expect( + accessControlExtMock.grantRoleBatch(userRole, [userAddresses[0]]) + ).not.to.emit(accessControlExtMock, EVENT_NAME_ROLE_GRANTED); + }); + + it("Multiple accounts without the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + for (const userAddress of userAddresses) { + expect(await accessControlExtMock.hasRole(userRole, userAddress)).to.equal(false); + } + + const tx: Promise = accessControlExtMock.grantRoleBatch(userRole, userAddresses); + + for (const userAddress of userAddresses) { + await expect(tx) + .to.emit(accessControlExtMock, EVENT_NAME_ROLE_GRANTED) + .withArgs(userRole, userAddress, deployer.address); + expect(await accessControlExtMock.hasRole(userRole, userAddress)).to.equal(true); + } + }); + + it("No accounts", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + + await expect( + accessControlExtMock.grantRoleBatch(userRole, []) + ).not.to.emit(accessControlExtMock, EVENT_NAME_ROLE_GRANTED); + }); + }); + + describe("Is reverted if", async () => { + it("The sender does not have the expected admin role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + + await expect( + connect(accessControlExtMock, attacker).grantRoleBatch(userRole, []) + ).to.be.revertedWithCustomError( + accessControlExtMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(attacker.address, ownerRole); + }); + }); + + describe("Function 'revokeRoleBatch()'", async () => { + describe("Executes as expected if the input account array contains", async () => { + it("A single account with the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await proveTx(accessControlExtMock.grantRoleBatch(userRole, [userAddresses[0]])); + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(true); + + await expect(accessControlExtMock.revokeRoleBatch(userRole, [userAddresses[0]])) + .to.emit(accessControlExtMock, EVENT_NAME_ROLE_REVOKED) + .withArgs(userRole, userAddresses[0], deployer.address); + + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(false); + }); + + it("A single account without the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + expect(await accessControlExtMock.hasRole(userRole, userAddresses[0])).to.equal(false); + + await expect( + accessControlExtMock.revokeRoleBatch(userRole, [userAddresses[0]]) + ).not.to.emit(accessControlExtMock, EVENT_NAME_ROLE_REVOKED); + }); + + it("Multiple accounts with the previously granted role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + await proveTx(accessControlExtMock.grantRoleBatch(userRole, userAddresses)); + for (const userAddress of userAddresses) { + expect(await accessControlExtMock.hasRole(userRole, userAddress)).to.equal(true); + } + + const tx: Promise = accessControlExtMock.revokeRoleBatch(userRole, userAddresses); + + for (const userAddress of userAddresses) { + await expect(tx) + .to.emit(accessControlExtMock, EVENT_NAME_ROLE_REVOKED) + .withArgs(userRole, userAddress, deployer.address); + expect(await accessControlExtMock.hasRole(userRole, userAddress)).to.equal(false); + } + }); + + it("No accounts", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + + await expect( + accessControlExtMock.revokeRoleBatch(userRole, []) + ).not.to.emit(accessControlExtMock, EVENT_NAME_ROLE_REVOKED); + }); + }); + + describe("Is reverted if", async () => { + it("The sender does not have the expected admin role", async () => { + const { accessControlExtMock } = await setUpFixture(deployAccessControlExtMock); + + await expect( + connect(accessControlExtMock, attacker).revokeRoleBatch(userRole, []) + ).to.be.revertedWithCustomError( + accessControlExtMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(attacker.address, ownerRole); + }); + }); + }); + }); +}); diff --git a/test/base/BlocklistableUpgradeable.test.ts b/test/base/BlocklistableUpgradeable.test.ts new file mode 100644 index 0000000..fb4b87f --- /dev/null +++ b/test/base/BlocklistableUpgradeable.test.ts @@ -0,0 +1,188 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { connect, proveTx } from "../../test-utils/eth"; + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contract 'BlocklistableUpgradeable'", async () => { + const EVENT_NAME_BLOCKLISTED = "Blocklisted"; + const EVENT_NAME_SELFBLOCKLISTED = "SelfBlocklisted"; + const EVENT_NAME_TEST_NOT_BLOCKLISTED_MODIFIER_SUCCEEDED = "TestNotBlocklistedModifierSucceeded"; + const EVENT_NAME_UNBLOCKLISTED = "UnBlocklisted"; + + const REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID = "InvalidInitialization"; + const REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING = "NotInitializing"; + const REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT = "AccessControlUnauthorizedAccount"; + + const REVERT_ERROR_IF_ACCOUNT_IS_BLOCKLISTED = "BlocklistedAccount"; + + const ownerRole: string = ethers.id("OWNER_ROLE"); + const blocklisterRole: string = ethers.id("BLOCKLISTER_ROLE"); + + let blocklistableMockFactory: ContractFactory; + + let deployer: HardhatEthersSigner; + let blocklister: HardhatEthersSigner; + let user: HardhatEthersSigner; + + before(async () => { + [deployer, blocklister, user] = await ethers.getSigners(); + blocklistableMockFactory = await ethers.getContractFactory("BlocklistableUpgradeableMock"); + // Explicitly specifying the deployer account + blocklistableMockFactory = blocklistableMockFactory.connect(deployer); + }); + + async function deployBlocklistableMock(): Promise<{ blocklistableMock: Contract }> { + let blocklistableMock: Contract = await upgrades.deployProxy(blocklistableMockFactory); + await blocklistableMock.waitForDeployment(); + blocklistableMock = connect(blocklistableMock, deployer); // Explicitly specifying the initial account + + return { blocklistableMock }; + } + + async function deployAndConfigureBlocklistableMock(): Promise<{ blocklistableMock: Contract }> { + const { blocklistableMock } = await deployBlocklistableMock(); + await proveTx(blocklistableMock.grantRole(blocklisterRole, blocklister.address)); + return { blocklistableMock }; + } + + describe("Initializers", async () => { + it("The external initializer configures the contract as expected", async () => { + const { blocklistableMock } = await setUpFixture(deployBlocklistableMock); + + // The roles + expect(await blocklistableMock.OWNER_ROLE()).to.equal(ownerRole); + expect(await blocklistableMock.BLOCKLISTER_ROLE()).to.equal(blocklisterRole); + + // The role admins + expect(await blocklistableMock.getRoleAdmin(ownerRole)).to.equal(ethers.ZeroHash); + expect(await blocklistableMock.getRoleAdmin(blocklisterRole)).to.equal(ownerRole); + + // The deployer should have the owner role, but not the other roles + expect(await blocklistableMock.hasRole(ownerRole, deployer.address)).to.equal(true); + expect(await blocklistableMock.hasRole(blocklisterRole, deployer.address)).to.equal(false); + }); + + it("The external initializer is reverted if it is called a second time", async () => { + const { blocklistableMock } = await setUpFixture(deployBlocklistableMock); + await expect( + blocklistableMock.initialize() + ).to.be.revertedWithCustomError(blocklistableMock, REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID); + }); + + it("The internal initializer is reverted if it is called outside the init process", async () => { + const { blocklistableMock } = await setUpFixture(deployBlocklistableMock); + await expect( + blocklistableMock.call_parent_initialize() + ).to.be.revertedWithCustomError(blocklistableMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + + it("The internal unchained initializer is reverted if it is called outside the init process", async () => { + const { blocklistableMock } = await setUpFixture(deployBlocklistableMock); + await expect( + blocklistableMock.call_parent_initialize_unchained() + ).to.be.revertedWithCustomError(blocklistableMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + }); + + describe("Function 'blocklist()'", async () => { + it("Executes as expected and emits the correct event if it is called by a blocklister", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(false); + + await expect(connect(blocklistableMock, blocklister).blocklist(user.address)) + .to.emit(blocklistableMock, EVENT_NAME_BLOCKLISTED) + .withArgs(user.address); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(true); + + // Second call with the same argument should not emit an event + await expect( + connect(blocklistableMock, blocklister).blocklist(user.address) + ).not.to.emit(blocklistableMock, EVENT_NAME_BLOCKLISTED); + }); + }); + + it("Is reverted if it is called by an account without the blocklister role", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + await expect( + blocklistableMock.blocklist(user.address) + ).to.be.revertedWithCustomError( + blocklistableMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, blocklisterRole); + }); + + describe("Function 'unBlocklist()'", async () => { + it("Executes as expected and emits the correct event if it is called by a blocklister", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + await proveTx(connect(blocklistableMock, blocklister).blocklist(user.address)); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(true); + + await expect(connect(blocklistableMock, blocklister).unBlocklist(user.address)) + .to.emit(blocklistableMock, EVENT_NAME_UNBLOCKLISTED) + .withArgs(user.address); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(false); + + // The second call with the same argument should not emit an event + await expect( + connect(blocklistableMock, blocklister).unBlocklist(user.address) + ).not.to.emit(blocklistableMock, EVENT_NAME_UNBLOCKLISTED); + }); + + it("Is reverted if it is called by an account without the blocklister role", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + await expect( + blocklistableMock.unBlocklist(user.address) + ).to.be.revertedWithCustomError( + blocklistableMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, blocklisterRole); + }); + }); + + describe("Function 'selfBlocklist()'", async () => { + it("Executes as expected and emits the correct events if it is called by any account", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(false); + + await expect(connect(blocklistableMock, user).selfBlocklist()) + .to.emit(blocklistableMock, EVENT_NAME_BLOCKLISTED) + .withArgs(user.address) + .and.to.emit(blocklistableMock, EVENT_NAME_SELFBLOCKLISTED) + .withArgs(user.address); + expect(await blocklistableMock.isBlocklisted(user.address)).to.equal(true); + + // Second call should not emit an event + await expect( + connect(blocklistableMock, user).selfBlocklist() + ).not.to.emit(blocklistableMock, EVENT_NAME_SELFBLOCKLISTED); + }); + }); + + describe("Modifier 'notBlocklisted'", async () => { + it("Reverts the target function if the caller is blocklisted", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + + await proveTx(connect(blocklistableMock, blocklister).blocklist(deployer.address)); + await expect( + blocklistableMock.testNotBlocklistedModifier() + ).to.be.revertedWithCustomError(blocklistableMock, REVERT_ERROR_IF_ACCOUNT_IS_BLOCKLISTED); + }); + + it("Does not revert the target function if the caller is not blocklisted", async () => { + const { blocklistableMock } = await setUpFixture(deployAndConfigureBlocklistableMock); + await expect( + connect(blocklistableMock, user).testNotBlocklistedModifier() + ).to.emit(blocklistableMock, EVENT_NAME_TEST_NOT_BLOCKLISTED_MODIFIER_SUCCEEDED); + }); + }); +}); diff --git a/test/base/PausableExtUpgradeable.test.ts b/test/base/PausableExtUpgradeable.test.ts new file mode 100644 index 0000000..109f077 --- /dev/null +++ b/test/base/PausableExtUpgradeable.test.ts @@ -0,0 +1,135 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { connect, proveTx } from "../../test-utils/eth"; + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contract 'PausableExtUpgradeable'", async () => { + const REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID = "InvalidInitialization"; + const REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING = "NotInitializing"; + const REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT = "AccessControlUnauthorizedAccount"; + + const ownerRole: string = ethers.id("OWNER_ROLE"); + const pauserRole: string = ethers.id("PAUSER_ROLE"); + + let pausableExtMockFactory: ContractFactory; + + let deployer: HardhatEthersSigner; + let pauser: HardhatEthersSigner; + + before(async () => { + [deployer, pauser] = await ethers.getSigners(); + pausableExtMockFactory = await ethers.getContractFactory("PausableExtUpgradeableMock"); + pausableExtMockFactory = pausableExtMockFactory.connect(deployer); // Explicitly specifying the deployer account + }); + + async function deployPausableExtMock(): Promise<{ pausableExtMock: Contract }> { + let pausableExtMock: Contract = await upgrades.deployProxy(pausableExtMockFactory); + await pausableExtMock.waitForDeployment(); + pausableExtMock = connect(pausableExtMock, deployer); // Explicitly specifying the initial account + return { pausableExtMock }; + } + + async function deployAndConfigurePausableExtMock(): Promise<{ pausableExtMock: Contract }> { + const { pausableExtMock } = await deployPausableExtMock(); + await proveTx(pausableExtMock.grantRole(pauserRole, pauser.address)); + + return { pausableExtMock }; + } + + describe("Function 'initialize()'", async () => { + it("The external initializer configures the contract as expected", async () => { + const { pausableExtMock } = await setUpFixture(deployPausableExtMock); + + // The roles + expect((await pausableExtMock.OWNER_ROLE()).toLowerCase()).to.equal(ownerRole); + expect((await pausableExtMock.PAUSER_ROLE()).toLowerCase()).to.equal(pauserRole); + + // The role admins + expect(await pausableExtMock.getRoleAdmin(ownerRole)).to.equal(ethers.ZeroHash); + expect(await pausableExtMock.getRoleAdmin(pauserRole)).to.equal(ownerRole); + + // The deployer should have the owner role, but not the other roles + expect(await pausableExtMock.hasRole(ownerRole, deployer.address)).to.equal(true); + expect(await pausableExtMock.hasRole(pauserRole, deployer.address)).to.equal(false); + + // The initial contract state is unpaused + expect(await pausableExtMock.paused()).to.equal(false); + }); + + it("The external initializer is reverted if it is called a second time", async () => { + const { pausableExtMock } = await setUpFixture(deployPausableExtMock); + await expect( + pausableExtMock.initialize() + ).to.be.revertedWithCustomError(pausableExtMock, REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID); + }); + + it("The internal initializer is reverted if it is called outside the init process", async () => { + const { pausableExtMock } = await setUpFixture(deployPausableExtMock); + await expect( + pausableExtMock.call_parent_initialize() + ).to.be.revertedWithCustomError(pausableExtMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + + it("The internal unchained initializer is reverted if it is called outside the init process", async () => { + const { pausableExtMock } = await setUpFixture(deployPausableExtMock); + await expect( + pausableExtMock.call_parent_initialize_unchained() + ).to.be.revertedWithCustomError(pausableExtMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + }); + + describe("Function 'pause()'", async () => { + it("Executes successfully and emits the correct event", async () => { + const { pausableExtMock } = await setUpFixture(deployAndConfigurePausableExtMock); + + await expect(connect(pausableExtMock, pauser).pause()) + .to.emit(pausableExtMock, "Paused") + .withArgs(pauser.address); + + expect(await pausableExtMock.paused()).to.equal(true); + }); + + it("Is reverted if it is called by an account without the pauser role", async () => { + const { pausableExtMock } = await setUpFixture(deployAndConfigurePausableExtMock); + await expect( + pausableExtMock.pause() + ).to.be.revertedWithCustomError( + pausableExtMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, pauserRole); + }); + }); + + describe("Function 'unpause()'", async () => { + it("Executes successfully and emits the correct event", async () => { + const { pausableExtMock } = await setUpFixture(deployAndConfigurePausableExtMock); + await proveTx(connect(pausableExtMock, pauser).pause()); + + await expect(connect(pausableExtMock, pauser).unpause()) + .to.emit(pausableExtMock, "Unpaused") + .withArgs(pauser.address); + + expect(await pausableExtMock.paused()).to.equal(false); + }); + + it("Is reverted if it is called by an account without the pauser role", async () => { + const { pausableExtMock } = await setUpFixture(deployAndConfigurePausableExtMock); + await expect( + pausableExtMock.unpause() + ).to.be.revertedWithCustomError( + pausableExtMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, pauserRole); + }); + }); +}); diff --git a/test/base/RescuableUpgradeable.test.ts b/test/base/RescuableUpgradeable.test.ts new file mode 100644 index 0000000..f031271 --- /dev/null +++ b/test/base/RescuableUpgradeable.test.ts @@ -0,0 +1,141 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { connect, getAddress, proveTx } from "../../test-utils/eth"; + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contract 'RescuableUpgradeable'", async () => { + const TOKEN_AMOUNT = 123; + + const EVENT_NAME_TRANSFER = "Transfer"; + + const REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID = "InvalidInitialization"; + const REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING = "NotInitializing"; + const REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT = "AccessControlUnauthorizedAccount"; + + const ownerRole: string = ethers.id("OWNER_ROLE"); + const rescuerRole: string = ethers.id("RESCUER_ROLE"); + + let rescuableMockFactory: ContractFactory; + let tokenMockFactory: ContractFactory; + + let deployer: HardhatEthersSigner; + let rescuer: HardhatEthersSigner; + + before(async () => { + [deployer, rescuer] = await ethers.getSigners(); + + // Contract factories with the explicitly specified deployer account + rescuableMockFactory = await ethers.getContractFactory("RescuableUpgradeableMock"); + rescuableMockFactory = rescuableMockFactory.connect(deployer); + tokenMockFactory = await ethers.getContractFactory("ERC20TokenMock"); + tokenMockFactory = tokenMockFactory.connect(deployer); + }); + + async function deployRescuableMock(): Promise<{ rescuableMock: Contract }> { + let rescuableMock: Contract = await upgrades.deployProxy(rescuableMockFactory); + await rescuableMock.waitForDeployment(); + rescuableMock = connect(rescuableMock, deployer); // Explicitly specifying the initial account + + return { rescuableMock }; + } + + async function deployTokenMock(): Promise<{ tokenMock: Contract }> { + let tokenMock: Contract = await tokenMockFactory.deploy("ERC20 Test", "TEST") as Contract; + await tokenMock.waitForDeployment(); + tokenMock = connect(tokenMock, deployer); // Explicitly specifying the initial account + + return { tokenMock }; + } + + async function deployAndConfigureAllContracts(): Promise<{ + rescuableMock: Contract; + tokenMock: Contract; + }> { + const { rescuableMock } = await deployRescuableMock(); + const { tokenMock } = await deployTokenMock(); + + await proveTx(tokenMock.mint(getAddress(rescuableMock), TOKEN_AMOUNT)); + await proveTx(rescuableMock.grantRole(rescuerRole, rescuer.address)); + + return { + rescuableMock, + tokenMock + }; + } + + describe("Function 'initialize()'", async () => { + it("The external initializer configures the contract as expected", async () => { + const { rescuableMock } = await setUpFixture(deployRescuableMock); + + // The roles + expect((await rescuableMock.OWNER_ROLE()).toLowerCase()).to.equal(ownerRole); + expect((await rescuableMock.RESCUER_ROLE()).toLowerCase()).to.equal(rescuerRole); + + // The role admins + expect(await rescuableMock.getRoleAdmin(ownerRole)).to.equal(ethers.ZeroHash); + expect(await rescuableMock.getRoleAdmin(rescuerRole)).to.equal(ownerRole); + + // The deployer should have the owner role, but not the other roles + expect(await rescuableMock.hasRole(ownerRole, deployer.address)).to.equal(true); + expect(await rescuableMock.hasRole(rescuerRole, deployer.address)).to.equal(false); + }); + + it("The external initializer is reverted if it is called a second time", async () => { + const { rescuableMock } = await setUpFixture(deployRescuableMock); + await expect( + rescuableMock.initialize() + ).to.be.revertedWithCustomError(rescuableMock, REVERT_ERROR_IF_CONTRACT_INITIALIZATION_IS_INVALID); + }); + + it("The internal initializer is reverted if it is called outside the init process", async () => { + const { rescuableMock } = await setUpFixture(deployRescuableMock); + await expect( + rescuableMock.call_parent_initialize() + ).to.be.revertedWithCustomError(rescuableMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + + it("The internal unchained initializer is reverted if it is called outside the init process", async () => { + const { rescuableMock } = await setUpFixture(deployRescuableMock); + await expect( + rescuableMock.call_parent_initialize_unchained() + ).to.be.revertedWithCustomError(rescuableMock, REVERT_ERROR_IF_CONTRACT_IS_NOT_INITIALIZING); + }); + }); + + describe("Function 'rescueERC20()'", async () => { + it("Executes as expected and emits the correct event", async () => { + const { rescuableMock, tokenMock } = await setUpFixture(deployAndConfigureAllContracts); + + const rescuableMockConnected = connect(rescuableMock, rescuer); + const tx = rescuableMockConnected.rescueERC20(getAddress(tokenMock), deployer.address, TOKEN_AMOUNT); + await expect(tx).to.changeTokenBalances( + tokenMock, + [rescuableMock, deployer, rescuer], + [-TOKEN_AMOUNT, +TOKEN_AMOUNT, 0] + ); + await expect(tx) + .to.emit(tokenMock, EVENT_NAME_TRANSFER) + .withArgs(getAddress(rescuableMock), deployer.address, TOKEN_AMOUNT); + }); + + it("Is reverted if it is called by an account without the rescuer role", async () => { + const { rescuableMock, tokenMock } = await setUpFixture(deployAndConfigureAllContracts); + await expect( + rescuableMock.rescueERC20(getAddress(tokenMock), deployer.address, TOKEN_AMOUNT) + ).to.be.revertedWithCustomError( + rescuableMock, + REVERT_ERROR_IF_UNAUTHORIZED_ACCOUNT + ).withArgs(deployer.address, rescuerRole); + }); + }); +}); diff --git a/test/base/UUPSExtUbgradable.test.ts b/test/base/UUPSExtUbgradable.test.ts new file mode 100644 index 0000000..5288026 --- /dev/null +++ b/test/base/UUPSExtUbgradable.test.ts @@ -0,0 +1,70 @@ +import { ethers, network, upgrades } from "hardhat"; +import { expect } from "chai"; +import { Contract, ContractFactory } from "ethers"; +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import { connect } from "../../test-utils/eth"; + +const ADDRESS_ZERO = ethers.ZeroAddress; + +async function setUpFixture(func: () => Promise): Promise { + if (network.name === "hardhat") { + return loadFixture(func); + } else { + return func(); + } +} + +describe("Contracts 'UUPSExtUpgradeable'", async () => { + // Errors of the lib contracts + const REVERT_ERROR_IMPLEMENTATION_ADDRESS_NOT_CONTRACT = "UUPSExtUpgradeable_ImplementationAddressNotContract"; + const REVERT_ERROR_IMPLEMENTATION_ADDRESS_ZERO = "UUPSExtUpgradeable_ImplementationAddressZero"; + + // Events of the contracts under test + const EVENT_NAME_MOCK_VALIDATE_UPGRADE_CALL = "MockValidateUpgradeCall"; + + let uupsExtensionFactory: ContractFactory; + let deployer: HardhatEthersSigner; + + before(async () => { + [deployer] = await ethers.getSigners(); + + // The contract factory with the explicitly specified deployer account + uupsExtensionFactory = await ethers.getContractFactory("UUPSExtUpgradeableMock"); + uupsExtensionFactory = uupsExtensionFactory.connect(deployer); + }); + + async function deployContract(): Promise<{ uupsExtension: Contract }> { + let uupsExtension: Contract = await upgrades.deployProxy(uupsExtensionFactory, [], { initializer: false }); + await uupsExtension.waitForDeployment(); + uupsExtension = connect(uupsExtension, deployer); // Explicitly specifying the initial account + + return { uupsExtension }; + } + + describe("Function 'upgradeToAndCall()'", async () => { + it("Executes as expected", async () => { + const { uupsExtension } = await setUpFixture(deployContract); + + const newImplementation = await uupsExtensionFactory.deploy(); + await newImplementation.waitForDeployment(); + const newImplementationAddress = await newImplementation.getAddress(); + + await expect(uupsExtension.upgradeToAndCall(newImplementationAddress, "0x")) + .to.emit(uupsExtension, EVENT_NAME_MOCK_VALIDATE_UPGRADE_CALL) + .withArgs(newImplementationAddress); + }); + + it("Is reverted if the new implementation address is zero", async () => { + const { uupsExtension } = await setUpFixture(deployContract); + await expect(uupsExtension.upgradeToAndCall(ADDRESS_ZERO, "0x")) + .to.be.revertedWithCustomError(uupsExtension, REVERT_ERROR_IMPLEMENTATION_ADDRESS_ZERO); + }); + + it("Is reverted if the new implementation address is not a contract", async () => { + const { uupsExtension } = await setUpFixture(deployContract); + await expect(uupsExtension.upgradeToAndCall(deployer.address, "0x")) + .to.be.revertedWithCustomError(uupsExtension, REVERT_ERROR_IMPLEMENTATION_ADDRESS_NOT_CONTRACT); + }); + }); +});