diff --git a/.dockerignore b/.dockerignore index 615da49..7260bd2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,7 +6,6 @@ cache/ artifacts/ .openzeppelin/ .husky/ -contracts/mocks/ contracts/upgrades/ scripts/log.txt diff --git a/.openzeppelin/unknown-421614.json b/.openzeppelin/unknown-421614.json index 187c610..2bfea81 100644 --- a/.openzeppelin/unknown-421614.json +++ b/.openzeppelin/unknown-421614.json @@ -84,6 +84,11 @@ "address": "0x6e692DB944162f8b4250aA25eCEe80608457D7a7", "txHash": "0xd6e5936315424a44930fa67eade8a23a84a4d2bad9edf5221b843a990a28a68c", "kind": "transparent" + }, + { + "address": "0xc0Fd850bE474e21449aF001782F11a0e9FC049C7", + "txHash": "0x84b0e6664753f61a3d608c050aa5e381427077f1562fbeb2e97f7fe04fb00428", + "kind": "transparent" } ], "impls": { @@ -11229,6 +11234,2378 @@ }, "namespaces": {} } + }, + "1a486ddbfcb238bad9524bd895f8e98a0694a27b3a619359be1620033ebcfa1f": { + "address": "0x39a8Ac54Ba0Ff8ee4CB83B5d5FC32C6b9d7E425F", + "txHash": "0x7dd69e990c7080895702c8fb7b6948af862786527be6d91cd454990d5a50482c", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)852_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260" + }, + { + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43" + }, + { + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44" + }, + { + "label": "_currentDelay", + "offset": 26, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46" + }, + { + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "152", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47" + }, + { + "label": "_pendingDelay", + "offset": 20, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50" + }, + { + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394" + }, + { + "label": "_paused", + "offset": 0, + "slot": "201", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "_index", + "offset": 0, + "slot": "251", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:49" + }, + { + "label": "dlcs", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_uint256,t_struct(DLC)8807_storage)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:50" + }, + { + "label": "dlcIDsByUUID", + "offset": 0, + "slot": "253", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:51" + }, + { + "label": "_minimumThreshold", + "offset": 0, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:53" + }, + { + "label": "_threshold", + "offset": 2, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:54" + }, + { + "label": "_signerCount", + "offset": 4, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:55" + }, + { + "label": "tssCommitment", + "offset": 0, + "slot": "255", + "type": "t_bytes32", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:56" + }, + { + "label": "attestorGroupPubKey", + "offset": 0, + "slot": "256", + "type": "t_string_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:57" + }, + { + "label": "dlcBTC", + "offset": 0, + "slot": "257", + "type": "t_contract(DLCBTC)8765", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:59" + }, + { + "label": "btcFeeRecipient", + "offset": 0, + "slot": "258", + "type": "t_string_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:60" + }, + { + "label": "minimumDeposit", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:61" + }, + { + "label": "maximumDeposit", + "offset": 0, + "slot": "260", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:62" + }, + { + "label": "btcMintFeeRate", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:63" + }, + { + "label": "btcRedeemFeeRate", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:64" + }, + { + "label": "whitelistingEnabled", + "offset": 0, + "slot": "263", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:65" + }, + { + "label": "userVaults", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_address,t_array(t_bytes32)dyn_storage)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:67" + }, + { + "label": "_whitelistedAddresses", + "offset": 0, + "slot": "265", + "type": "t_mapping(t_address,t_bool)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:68" + }, + { + "label": "porEnabled", + "offset": 0, + "slot": "266", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:69" + }, + { + "label": "dlcBTCPoRFeed", + "offset": 1, + "slot": "266", + "type": "t_contract(AggregatorV3Interface)104", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:70" + }, + { + "label": "_seenSigners", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_address,t_mapping(t_bytes32,t_bool))", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:71" + }, + { + "label": "skipSignatureVerification", + "offset": 0, + "slot": "268", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:74" + }, + { + "label": "__gap", + "offset": 0, + "slot": "269", + "type": "t_array(t_uint256)38_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:76" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)38_storage": { + "label": "uint256[38]", + "numberOfBytes": "1216" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(AggregatorV3Interface)104": { + "label": "contract AggregatorV3Interface", + "numberOfBytes": "20" + }, + "t_contract(DLCBTC)8765": { + "label": "contract DLCBTC", + "numberOfBytes": "20" + }, + "t_enum(DLCStatus)8777": { + "label": "enum DLCLink.DLCStatus", + "members": [ + "READY", + "FUNDED", + "CLOSING", + "CLOSED", + "AUX_STATE_1", + "AUX_STATE_2", + "AUX_STATE_3", + "AUX_STATE_4", + "AUX_STATE_5" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_array(t_bytes32)dyn_storage)": { + "label": "mapping(address => bytes32[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_bytes32,t_bool))": { + "label": "mapping(address => mapping(bytes32 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bool)": { + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)852_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(DLC)8807_storage)": { + "label": "mapping(uint256 => struct DLCLink.DLC)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(DLC)8807_storage": { + "label": "struct DLCLink.DLC", + "members": [ + { + "label": "uuid", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "protocolContract", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "timestamp", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "valueLocked", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "creator", + "type": "t_address", + "offset": 0, + "slot": "4" + }, + { + "label": "status", + "type": "t_enum(DLCStatus)8777", + "offset": 20, + "slot": "4" + }, + { + "label": "fundingTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "5" + }, + { + "label": "closingTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "6" + }, + { + "label": "btcFeeRecipient", + "type": "t_string_storage", + "offset": 0, + "slot": "7" + }, + { + "label": "btcMintFeeBasisPoints", + "type": "t_uint256", + "offset": 0, + "slot": "8" + }, + { + "label": "btcRedeemFeeBasisPoints", + "type": "t_uint256", + "offset": 0, + "slot": "9" + }, + { + "label": "taprootPubKey", + "type": "t_string_storage", + "offset": 0, + "slot": "10" + }, + { + "label": "valueMinted", + "type": "t_uint256", + "offset": 0, + "slot": "11" + }, + { + "label": "wdTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "12" + } + ], + "numberOfBytes": "416" + }, + "t_struct(RoleData)852_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + }, + "4e1821fdf8bfbd89ea21f91fc350f2775462ea450594d20c3c642f065c283109": { + "address": "0x965567Ef8a9AD6B10858Af32db80e0E083c6550E", + "txHash": "0xe77cf2fc43d886383921d8ea2e4ef5a83a55ecc9ebfc637b3ba537982e8d61de", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)852_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260" + }, + { + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43" + }, + { + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44" + }, + { + "label": "_currentDelay", + "offset": 26, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46" + }, + { + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "152", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47" + }, + { + "label": "_pendingDelay", + "offset": 20, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50" + }, + { + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394" + }, + { + "label": "_paused", + "offset": 0, + "slot": "201", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "_status", + "offset": 0, + "slot": "251", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88" + }, + { + "label": "dlcManager", + "offset": 0, + "slot": "301", + "type": "t_contract(IDLCManager)12735", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:55" + }, + { + "label": "dlcBTC", + "offset": 0, + "slot": "302", + "type": "t_contract(IERC20)7802", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:56" + }, + { + "label": "_vaults", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_bytes32,t_struct(VaultInfo)12800_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:97" + }, + { + "label": "vaultsByTaprootPubKey", + "offset": 0, + "slot": "304", + "type": "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:98" + }, + { + "label": "integrations", + "offset": 0, + "slot": "305", + "type": "t_mapping(t_address,t_struct(Integration)12839_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:99" + }, + { + "label": "rewardTokens", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_struct(RewardToken)12781_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:100" + }, + { + "label": "uuidByTaprootAndIntegration", + "offset": 0, + "slot": "307", + "type": "t_mapping(t_bytes32,t_bytes32)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:101" + }, + { + "label": "activeIntegrations", + "offset": 0, + "slot": "308", + "type": "t_array(t_address)dyn_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:102" + }, + { + "label": "__gap", + "offset": 0, + "slot": "309", + "type": "t_array(t_uint256)50_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:104" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IDLCManager)12735": { + "label": "contract IDLCManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)7802": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IIntegration)16156": { + "label": "contract IIntegration", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Integration)12839_storage)": { + "label": "mapping(address => struct PoolMerchant.Integration)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(RewardToken)12781_storage)": { + "label": "mapping(address => struct PoolMerchant.RewardToken)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(UserReward)12786_storage)": { + "label": "mapping(address => struct PoolMerchant.UserReward)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)852_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(VaultInfo)12800_storage)": { + "label": "mapping(bytes32 => struct PoolMerchant.VaultInfo)", + "numberOfBytes": "32" + }, + "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)": { + "label": "mapping(string => bytes32[])", + "numberOfBytes": "32" + }, + "t_string_memory_ptr": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Integration)12839_storage": { + "label": "struct PoolMerchant.Integration", + "members": [ + { + "label": "strategy", + "type": "t_contract(IIntegration)16156", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + }, + { + "label": "totalShares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "supportedRewardTokens", + "type": "t_array(t_address)dyn_storage", + "offset": 0, + "slot": "2" + }, + { + "label": "vaults", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(RewardToken)12781_storage": { + "label": "struct PoolMerchant.RewardToken", + "members": [ + { + "label": "tokenAddress", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)852_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(UserReward)12786_storage": { + "label": "struct PoolMerchant.UserReward", + "members": [ + { + "label": "lastClaimedAt", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "harvestedAmount", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(VaultInfo)12800_storage": { + "label": "struct PoolMerchant.VaultInfo", + "members": [ + { + "label": "integration", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "shares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allocated", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "rewards", + "type": "t_mapping(t_address,t_struct(UserReward)12786_storage)", + "offset": 0, + "slot": "3" + }, + { + "label": "integrationIndex", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_string_storage": { + "label": "string" + } + }, + "namespaces": {} + } + }, + "1a49c8802e770b7ee2328b952cb3af4ae9a7d3cd0a3496e5155ab81dfb5ef2b7": { + "address": "0x9b6eE1041ED0616bACD077a919fAB97Ef55cd068", + "txHash": "0x7ad3b43e588c935fa63288904c3adde3be147cfedd69f2c321da041ea8262862", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)747_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260" + }, + { + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43" + }, + { + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44" + }, + { + "label": "_currentDelay", + "offset": 26, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46" + }, + { + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "152", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47" + }, + { + "label": "_pendingDelay", + "offset": 20, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50" + }, + { + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394" + }, + { + "label": "_paused", + "offset": 0, + "slot": "201", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "_status", + "offset": 0, + "slot": "251", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88" + }, + { + "label": "dlcManager", + "offset": 0, + "slot": "301", + "type": "t_contract(IDLCManager)5547", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:55" + }, + { + "label": "dlcBTC", + "offset": 0, + "slot": "302", + "type": "t_contract(IERC20)5006", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:56" + }, + { + "label": "_vaults", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_bytes32,t_struct(VaultInfo)5612_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:97" + }, + { + "label": "vaultsByTaprootPubKey", + "offset": 0, + "slot": "304", + "type": "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:98" + }, + { + "label": "integrations", + "offset": 0, + "slot": "305", + "type": "t_mapping(t_address,t_struct(Integration)5651_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:99" + }, + { + "label": "rewardTokens", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_struct(RewardToken)5593_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:100" + }, + { + "label": "uuidByTaprootAndIntegration", + "offset": 0, + "slot": "307", + "type": "t_mapping(t_bytes32,t_bytes32)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:101" + }, + { + "label": "activeIntegrations", + "offset": 0, + "slot": "308", + "type": "t_array(t_address)dyn_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:102" + }, + { + "label": "__gap", + "offset": 0, + "slot": "309", + "type": "t_array(t_uint256)50_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:104" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IDLCManager)5547": { + "label": "contract IDLCManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)5006": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IIntegration)7495": { + "label": "contract IIntegration", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Integration)5651_storage)": { + "label": "mapping(address => struct PoolMerchant.Integration)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(RewardToken)5593_storage)": { + "label": "mapping(address => struct PoolMerchant.RewardToken)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(UserReward)5598_storage)": { + "label": "mapping(address => struct PoolMerchant.UserReward)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)747_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(VaultInfo)5612_storage)": { + "label": "mapping(bytes32 => struct PoolMerchant.VaultInfo)", + "numberOfBytes": "32" + }, + "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)": { + "label": "mapping(string => bytes32[])", + "numberOfBytes": "32" + }, + "t_string_memory_ptr": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Integration)5651_storage": { + "label": "struct PoolMerchant.Integration", + "members": [ + { + "label": "strategy", + "type": "t_contract(IIntegration)7495", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + }, + { + "label": "totalShares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "supportedRewardTokens", + "type": "t_array(t_address)dyn_storage", + "offset": 0, + "slot": "2" + }, + { + "label": "vaults", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(RewardToken)5593_storage": { + "label": "struct PoolMerchant.RewardToken", + "members": [ + { + "label": "tokenAddress", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)747_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(UserReward)5598_storage": { + "label": "struct PoolMerchant.UserReward", + "members": [ + { + "label": "lastClaimedAt", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "harvestedAmount", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(VaultInfo)5612_storage": { + "label": "struct PoolMerchant.VaultInfo", + "members": [ + { + "label": "integration", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "shares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allocated", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "rewards", + "type": "t_mapping(t_address,t_struct(UserReward)5598_storage)", + "offset": 0, + "slot": "3" + }, + { + "label": "integrationIndex", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_string_storage": { + "label": "string" + } + }, + "namespaces": {} + } + }, + "043edcc90cd1034788103111af244217dd31b89372e473e9163e08681f047622": { + "address": "0x2b0600467e6E9406c5dbF4772430aBb8d2b2C327", + "txHash": "0x8db41d3750e92fb3b3b22633c8b0a8ea29a82072d6de8dbf682cc58c08dab614", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)793_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260" + }, + { + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43" + }, + { + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44" + }, + { + "label": "_currentDelay", + "offset": 26, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46" + }, + { + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "152", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47" + }, + { + "label": "_pendingDelay", + "offset": 20, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50" + }, + { + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394" + }, + { + "label": "_paused", + "offset": 0, + "slot": "201", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "_index", + "offset": 0, + "slot": "251", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:49" + }, + { + "label": "dlcs", + "offset": 0, + "slot": "252", + "type": "t_mapping(t_uint256,t_struct(DLC)8307_storage)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:50" + }, + { + "label": "dlcIDsByUUID", + "offset": 0, + "slot": "253", + "type": "t_mapping(t_bytes32,t_uint256)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:51" + }, + { + "label": "_minimumThreshold", + "offset": 0, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:53" + }, + { + "label": "_threshold", + "offset": 2, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:54" + }, + { + "label": "_signerCount", + "offset": 4, + "slot": "254", + "type": "t_uint16", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:55" + }, + { + "label": "tssCommitment", + "offset": 0, + "slot": "255", + "type": "t_bytes32", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:56" + }, + { + "label": "attestorGroupPubKey", + "offset": 0, + "slot": "256", + "type": "t_string_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:57" + }, + { + "label": "dlcBTC", + "offset": 0, + "slot": "257", + "type": "t_contract(DLCBTC)8263", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:59" + }, + { + "label": "btcFeeRecipient", + "offset": 0, + "slot": "258", + "type": "t_string_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:60" + }, + { + "label": "minimumDeposit", + "offset": 0, + "slot": "259", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:61" + }, + { + "label": "maximumDeposit", + "offset": 0, + "slot": "260", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:62" + }, + { + "label": "btcMintFeeRate", + "offset": 0, + "slot": "261", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:63" + }, + { + "label": "btcRedeemFeeRate", + "offset": 0, + "slot": "262", + "type": "t_uint256", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:64" + }, + { + "label": "whitelistingEnabled", + "offset": 0, + "slot": "263", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:65" + }, + { + "label": "userVaults", + "offset": 0, + "slot": "264", + "type": "t_mapping(t_address,t_array(t_bytes32)dyn_storage)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:67" + }, + { + "label": "_whitelistedAddresses", + "offset": 0, + "slot": "265", + "type": "t_mapping(t_address,t_bool)", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:68" + }, + { + "label": "porEnabled", + "offset": 0, + "slot": "266", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:69" + }, + { + "label": "dlcBTCPoRFeed", + "offset": 1, + "slot": "266", + "type": "t_contract(AggregatorV3Interface)45", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:70" + }, + { + "label": "_seenSigners", + "offset": 0, + "slot": "267", + "type": "t_mapping(t_address,t_mapping(t_bytes32,t_bool))", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:71" + }, + { + "label": "skipSignatureVerification", + "offset": 0, + "slot": "268", + "type": "t_bool", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:74" + }, + { + "label": "__gap", + "offset": 0, + "slot": "269", + "type": "t_array(t_uint256)38_storage", + "contract": "DLCManager", + "src": "contracts/DLCManager.sol:76" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)38_storage": { + "label": "uint256[38]", + "numberOfBytes": "1216" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(AggregatorV3Interface)45": { + "label": "contract AggregatorV3Interface", + "numberOfBytes": "20" + }, + "t_contract(DLCBTC)8263": { + "label": "contract DLCBTC", + "numberOfBytes": "20" + }, + "t_enum(DLCStatus)8275": { + "label": "enum DLCLink.DLCStatus", + "members": [ + "READY", + "FUNDED", + "CLOSING", + "CLOSED", + "AUX_STATE_1", + "AUX_STATE_2", + "AUX_STATE_3", + "AUX_STATE_4", + "AUX_STATE_5" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_array(t_bytes32)dyn_storage)": { + "label": "mapping(address => bytes32[])", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_bytes32,t_bool))": { + "label": "mapping(address => mapping(bytes32 => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bool)": { + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)793_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(DLC)8307_storage)": { + "label": "mapping(uint256 => struct DLCLink.DLC)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(DLC)8307_storage": { + "label": "struct DLCLink.DLC", + "members": [ + { + "label": "uuid", + "type": "t_bytes32", + "offset": 0, + "slot": "0" + }, + { + "label": "protocolContract", + "type": "t_address", + "offset": 0, + "slot": "1" + }, + { + "label": "timestamp", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "valueLocked", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "creator", + "type": "t_address", + "offset": 0, + "slot": "4" + }, + { + "label": "status", + "type": "t_enum(DLCStatus)8275", + "offset": 20, + "slot": "4" + }, + { + "label": "fundingTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "5" + }, + { + "label": "closingTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "6" + }, + { + "label": "btcFeeRecipient", + "type": "t_string_storage", + "offset": 0, + "slot": "7" + }, + { + "label": "btcMintFeeBasisPoints", + "type": "t_uint256", + "offset": 0, + "slot": "8" + }, + { + "label": "btcRedeemFeeBasisPoints", + "type": "t_uint256", + "offset": 0, + "slot": "9" + }, + { + "label": "taprootPubKey", + "type": "t_string_storage", + "offset": 0, + "slot": "10" + }, + { + "label": "valueMinted", + "type": "t_uint256", + "offset": 0, + "slot": "11" + }, + { + "label": "wdTxId", + "type": "t_string_storage", + "offset": 0, + "slot": "12" + }, + { + "label": "icyIntegrationAddress", + "type": "t_address", + "offset": 0, + "slot": "13" + } + ], + "numberOfBytes": "448" + }, + "t_struct(RoleData)793_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } + }, + "a617e58166f08b435f9f273bbd5fcf1f1b5a938732c347567e5ca64327c818e0": { + "address": "0xaC345969c553DD637C9A42c110A18A36c233cfe7", + "txHash": "0x4c105ab35022aa0cf5ddbfc964857bfe53292ae73db7c38f19eb1abfd7f6cfd7", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_roles", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_bytes32,t_struct(RoleData)793_storage)", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62" + }, + { + "label": "__gap", + "offset": 0, + "slot": "102", + "type": "t_array(t_uint256)49_storage", + "contract": "AccessControlUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260" + }, + { + "label": "_pendingDefaultAdmin", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43" + }, + { + "label": "_pendingDefaultAdminSchedule", + "offset": 20, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44" + }, + { + "label": "_currentDelay", + "offset": 26, + "slot": "151", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46" + }, + { + "label": "_currentDefaultAdmin", + "offset": 0, + "slot": "152", + "type": "t_address", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47" + }, + { + "label": "_pendingDelay", + "offset": 20, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50" + }, + { + "label": "_pendingDelaySchedule", + "offset": 26, + "slot": "152", + "type": "t_uint48", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51" + }, + { + "label": "__gap", + "offset": 0, + "slot": "153", + "type": "t_array(t_uint256)48_storage", + "contract": "AccessControlDefaultAdminRulesUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394" + }, + { + "label": "_paused", + "offset": 0, + "slot": "201", + "type": "t_bool", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "PausableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116" + }, + { + "label": "_status", + "offset": 0, + "slot": "251", + "type": "t_uint256", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:38" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)49_storage", + "contract": "ReentrancyGuardUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol:88" + }, + { + "label": "dlcManager", + "offset": 0, + "slot": "301", + "type": "t_contract(IDLCManager)10285", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:56" + }, + { + "label": "dlcBTC", + "offset": 0, + "slot": "302", + "type": "t_contract(IERC20)6875", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:57" + }, + { + "label": "_vaults", + "offset": 0, + "slot": "303", + "type": "t_mapping(t_bytes32,t_struct(VaultInfo)10350_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:98" + }, + { + "label": "vaultsByTaprootPubKey", + "offset": 0, + "slot": "304", + "type": "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:99" + }, + { + "label": "integrations", + "offset": 0, + "slot": "305", + "type": "t_mapping(t_address,t_struct(Integration)10389_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:100" + }, + { + "label": "rewardTokens", + "offset": 0, + "slot": "306", + "type": "t_mapping(t_address,t_struct(RewardToken)10331_storage)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:101" + }, + { + "label": "uuidByTaprootAndIntegration", + "offset": 0, + "slot": "307", + "type": "t_mapping(t_bytes32,t_bytes32)", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:102" + }, + { + "label": "activeIntegrations", + "offset": 0, + "slot": "308", + "type": "t_array(t_address)dyn_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:103" + }, + { + "label": "__gap", + "offset": 0, + "slot": "309", + "type": "t_array(t_uint256)50_storage", + "contract": "PoolMerchant", + "src": "contracts/PoolMerchant.sol:105" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "label": "address[]", + "numberOfBytes": "32" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)48_storage": { + "label": "uint256[48]", + "numberOfBytes": "1536" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IDLCManager)10285": { + "label": "contract IDLCManager", + "numberOfBytes": "20" + }, + "t_contract(IERC20)6875": { + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_contract(IIntegration)12289": { + "label": "contract IIntegration", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(Integration)10389_storage)": { + "label": "mapping(address => struct PoolMerchant.Integration)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(RewardToken)10331_storage)": { + "label": "mapping(address => struct PoolMerchant.RewardToken)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(UserReward)10336_storage)": { + "label": "mapping(address => struct PoolMerchant.UserReward)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_bytes32)": { + "label": "mapping(bytes32 => bytes32)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(RoleData)793_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)", + "numberOfBytes": "32" + }, + "t_mapping(t_bytes32,t_struct(VaultInfo)10350_storage)": { + "label": "mapping(bytes32 => struct PoolMerchant.VaultInfo)", + "numberOfBytes": "32" + }, + "t_mapping(t_string_memory_ptr,t_array(t_bytes32)dyn_storage)": { + "label": "mapping(string => bytes32[])", + "numberOfBytes": "32" + }, + "t_string_memory_ptr": { + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(Integration)10389_storage": { + "label": "struct PoolMerchant.Integration", + "members": [ + { + "label": "strategy", + "type": "t_contract(IIntegration)12289", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + }, + { + "label": "totalShares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "supportedRewardTokens", + "type": "t_array(t_address)dyn_storage", + "offset": 0, + "slot": "2" + }, + { + "label": "vaults", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "3" + } + ], + "numberOfBytes": "128" + }, + "t_struct(RewardToken)10331_storage": { + "label": "struct PoolMerchant.RewardToken", + "members": [ + { + "label": "tokenAddress", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "isActive", + "type": "t_bool", + "offset": 20, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(RoleData)793_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_mapping(t_address,t_bool)", + "offset": 0, + "slot": "0" + }, + { + "label": "adminRole", + "type": "t_bytes32", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(UserReward)10336_storage": { + "label": "struct PoolMerchant.UserReward", + "members": [ + { + "label": "lastClaimedAt", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "harvestedAmount", + "type": "t_uint256", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_struct(VaultInfo)10350_storage": { + "label": "struct PoolMerchant.VaultInfo", + "members": [ + { + "label": "integration", + "type": "t_address", + "offset": 0, + "slot": "0" + }, + { + "label": "shares", + "type": "t_uint256", + "offset": 0, + "slot": "1" + }, + { + "label": "allocated", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "rewards", + "type": "t_mapping(t_address,t_struct(UserReward)10336_storage)", + "offset": 0, + "slot": "3" + }, + { + "label": "integrationIndex", + "type": "t_uint256", + "offset": 0, + "slot": "4" + } + ], + "numberOfBytes": "160" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint48": { + "label": "uint48", + "numberOfBytes": "6" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + }, + "t_string_storage": { + "label": "string" + } + }, + "namespaces": {} + } } } } diff --git a/contracts/DLCLinkLibrary.sol b/contracts/DLCLinkLibrary.sol index 0d7c469..9139efc 100644 --- a/contracts/DLCLinkLibrary.sol +++ b/contracts/DLCLinkLibrary.sol @@ -35,5 +35,6 @@ library DLCLink { string taprootPubKey; uint256 valueMinted; string wdTxId; + address icyIntegrationAddress; } } diff --git a/contracts/DLCManager.sol b/contracts/DLCManager.sol index 37ce286..85da4dd 100644 --- a/contracts/DLCManager.sol +++ b/contracts/DLCManager.sol @@ -69,7 +69,11 @@ contract DLCManager is bool public porEnabled; AggregatorV3Interface public dlcBTCPoRFeed; mapping(address => mapping(bytes32 => bool)) private _seenSigners; - uint256[39] __gap; + + // TODO: FIXME: NOTE: Remove this for production + bool public skipSignatureVerification; // Add state variable + + uint256[38] __gap; //////////////////////////////////////////////////////////////// // ERRORS // @@ -84,6 +88,7 @@ contract DLCManager is error DLCNotPending(); error DLCNotReadyOrFunded(); error DLCNotFunded(); + error DLCAlreadyExists(bytes32 uuid); error ThresholdMinimumReached(uint16 _minimumThreshold); error ThresholdTooLow(uint16 _minimumThreshold); @@ -205,10 +210,10 @@ contract DLCManager is // INTERNAL FUNCTIONS // //////////////////////////////////////////////////////////////// - function _generateUUID( + function generateUUID( address sender, uint256 nonce - ) private view returns (bytes32) { + ) public view returns (bytes32) { return keccak256( abi.encodePacked( @@ -232,6 +237,10 @@ contract DLCManager is bytes memory message, bytes[] memory signatures ) internal { + // TODO: FIXME: NOTE: Remove this for production + if (skipSignatureVerification) { + return; + } if (signatures.length < _threshold) revert NotEnoughSignatures(); bytes32 prefixedMessageHash = ECDSAUpgradeable.toEthSignedMessageHash( @@ -330,9 +339,9 @@ contract DLCManager is onlyWhitelisted returns (bytes32) { - bytes32 _uuid = _generateUUID(msg.sender, _index); + bytes32 _uuid = generateUUID(msg.sender, _index); - dlcs[_index] = DLCLink.DLC({ + DLCLink.DLC memory newDLC = DLCLink.DLC({ uuid: _uuid, protocolContract: msg.sender, // deprecated valueLocked: 0, @@ -346,8 +355,46 @@ contract DLCManager is btcFeeRecipient: btcFeeRecipient, btcMintFeeBasisPoints: btcMintFeeRate, btcRedeemFeeBasisPoints: btcRedeemFeeRate, - taprootPubKey: "" + taprootPubKey: "", + icyIntegrationAddress: address(0) + }); + + dlcs[_index] = newDLC; + + emit CreateDLC(_uuid, msg.sender, block.timestamp); + + dlcIDsByUUID[_uuid] = _index; + userVaults[msg.sender].push(_uuid); + _index++; + + return _uuid; + } + + // TODO: auth + function setupPendingVault( + string calldata _taprootPubKey, + string calldata _wdTxId, + address icyIntegrationAddress + ) public onlyWhitelisted whenNotPaused returns (bytes32) { + bytes32 _uuid = generateUUID(msg.sender, _index); + DLCLink.DLC memory newDLC = DLCLink.DLC({ + uuid: _uuid, + protocolContract: msg.sender, // deprecated + valueLocked: 0, + valueMinted: 0, + timestamp: block.timestamp, + creator: msg.sender, + status: DLCLink.DLCStatus.AUX_STATE_1, + fundingTxId: "", + closingTxId: "", + wdTxId: _wdTxId, + btcFeeRecipient: btcFeeRecipient, + btcMintFeeBasisPoints: btcMintFeeRate, + btcRedeemFeeBasisPoints: btcRedeemFeeRate, + taprootPubKey: _taprootPubKey, + icyIntegrationAddress: icyIntegrationAddress }); + dlcs[_index] = newDLC; emit CreateDLC(_uuid, msg.sender, block.timestamp); @@ -725,4 +772,9 @@ contract DLCManager is dlcBTCPoRFeed = feed; emit SetDlcBTCPoRFeed(feed); } + + // Add function to toggle (only owner/admin) + function setSkipSignatureVerification(bool skip) external onlyAdmin { + skipSignatureVerification = skip; + } } diff --git a/contracts/PoolMerchant.sol b/contracts/PoolMerchant.sol new file mode 100644 index 0000000..788ae47 --- /dev/null +++ b/contracts/PoolMerchant.sol @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: MIT +// ___ __ ___ __ _ _ +// / \/ / / __\ / /(_)_ __ | | __ +// / /\ / / / / / / | | '_ \| |/ / +// / /_// /__/ /____/ /__| | | | | < +// /___,'\____|____(_)____/_|_| |_|_|\_\ + +pragma solidity 0.8.18; + +import "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "./DLCLinkLibrary.sol"; +import "./interfaces/IIntegration.sol"; +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; + +interface IDLCManager { + function setupPendingVault( + string calldata _taprootPubKey, + string calldata _wdTxId, + address _integration + ) external returns (bytes32); + + function withdraw(bytes32 uuid, uint256 amount) external; + + function getDLC(bytes32 uuid) external view returns (DLCLink.DLC memory); +} + +contract PoolMerchant is + Initializable, + ERC165Upgradeable, + AccessControlDefaultAdminRulesUpgradeable, + PausableUpgradeable, + ReentrancyGuardUpgradeable, + IERC721Receiver, + IERC1155Receiver +{ + using DLCLink for DLCLink.DLC; + using DLCLink for DLCLink.DLCStatus; + using SafeMath for uint256; + + //////////////////////////////////////////////////////////////// + // STATE VARIABLES // + //////////////////////////////////////////////////////////////// + + bytes32 public constant ATTESTOR_ROLE = keccak256("ATTESTOR_ROLE"); + bytes32 public constant HARVESTER_ROLE = keccak256("HARVESTER_ROLE"); + + IDLCManager public dlcManager; + IERC20 public dlcBTC; + + struct RewardToken { + address tokenAddress; + bool isActive; + } + + struct UserReward { + uint256 lastClaimedAt; + uint256 harvestedAmount; + } + + struct VaultInfo { + address integration; + uint256 shares; + uint256 allocated; + mapping(address => UserReward) rewards; // token -> reward + uint256 integrationIndex; // Index in the integration's vault array + 1 (0 means not in array) + } + + struct VaultDetails { + bytes32 uuid; + address integration; + uint256 shares; + uint256 valueMinted; + uint256 allocated; + uint256 unallocated; + address[] rewardTokens; + uint256[] lastClaimedAt; + uint256[] harvestedRewards; // Already harvested rewards + uint256[] pendingRewards; // Rewards that can be harvested now + } + + struct Integration { + IIntegration strategy; + bool isActive; + uint256 totalShares; + address[] supportedRewardTokens; + bytes32[] vaults; // Array of vault UUIDs using this integration + } + + mapping(bytes32 => VaultInfo) internal _vaults; + mapping(string => bytes32[]) public vaultsByTaprootPubKey; + mapping(address => Integration) public integrations; + mapping(address => RewardToken) public rewardTokens; + mapping(bytes32 => bytes32) public uuidByTaprootAndIntegration; // taproot-integration -> uuid + address[] public activeIntegrations; + + uint256[50] private __gap; + + //////////////////////////////////////////////////////////////// + // EVENTS // + //////////////////////////////////////////////////////////////// + + event PendingVaultCreated( + bytes32 indexed uuid, + string taprootPubKey, + string wdPSBT, + address integration + ); + event VaultWithdrawn(bytes32 indexed uuid, uint256 amount); + event IntegrationAdded( + address indexed integration, + address[] supportedRewards + ); + event RewardTokenAdded(address indexed token); + event SharesAllocated( + bytes32 indexed uuid, + address indexed integration, + uint256 shares + ); + event RewardsHarvested( + address indexed integration, + address indexed rewardToken, + address indexed harvester, + uint256 amount + ); + event RewardsClaimed( + bytes32 indexed uuid, + address indexed rewardToken, + uint256 amount + ); + + //////////////////////////////////////////////////////////////// + // CONSTRUCTOR // + //////////////////////////////////////////////////////////////// + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize( + address _dlcManager, + address _dlcBTC, + address defaultAdmin + ) public initializer { + __AccessControlDefaultAdminRules_init(2 days, defaultAdmin); + __Pausable_init(); + __ReentrancyGuard_init(); + __ERC165_init(); + + dlcManager = IDLCManager(_dlcManager); + dlcBTC = IERC20(_dlcBTC); + } + + //////////////////////////////////////////////////////////////// + // MAIN FUNCTIONS // + //////////////////////////////////////////////////////////////// + + function createPendingVault( + string calldata taprootPubKey, + string calldata wdPSBT, + address integration + ) + external + onlyRole(ATTESTOR_ROLE) + nonReentrant + whenNotPaused + returns (bytes32) + { + return _createPendingVault(taprootPubKey, wdPSBT, integration); + } + + function _createPendingVault( + string memory taprootPubKey, + string memory wdPSBT, + address integration + ) internal returns (bytes32) { + require(integrations[integration].isActive, "Integration not active"); + + bytes32 mappingKey = _createMappingKey(taprootPubKey, integration); + require( + uuidByTaprootAndIntegration[mappingKey] == bytes32(0), + "Vault already exists for this taproot-integration pair" + ); + + // Create vault separately to manage stack + bytes32 _uuid = _setupVaultInDLCManager( + taprootPubKey, + wdPSBT, + integration + ); + + // Initialize vault data separately + _initializeVault(_uuid, integration, taprootPubKey, mappingKey); + + emit PendingVaultCreated(_uuid, taprootPubKey, wdPSBT, integration); + return _uuid; + } + + function _setupVaultInDLCManager( + string memory taprootPubKey, + string memory wdPSBT, + address integration + ) internal returns (bytes32) { + return dlcManager.setupPendingVault(taprootPubKey, wdPSBT, integration); + } + + function _initializeVault( + bytes32 _uuid, + address integration, + string memory taprootPubKey, + bytes32 mappingKey + ) internal { + _vaults[_uuid].integration = integration; + _addVaultToIntegration(_uuid, integration); + + vaultsByTaprootPubKey[taprootPubKey].push(_uuid); + uuidByTaprootAndIntegration[mappingKey] = _uuid; + } + + function withdrawFromVault( + string calldata taprootPubKey, + address integration, + uint256 amount + ) external onlyRole(ATTESTOR_ROLE) nonReentrant whenNotPaused { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + DLCLink.DLC memory dlc = dlcManager.getDLC(uuid); + require(dlc.uuid != bytes32(0), "Vault does not exist"); + + VaultInfo storage vault = _vaults[uuid]; + Integration storage integ = integrations[integration]; + + require(amount <= dlc.valueMinted, "Amount exceeds minted value"); + + uint256 received; + + if (vault.allocated > 0) { + // If there are allocated funds, handle integration withdrawal + require(amount <= vault.allocated, "Amount exceeds allocation"); + + // Harvest any pending rewards + if (vault.shares > 0) { + _harvestRewardsForVault(uuid); + } + + // Withdraw from integration + received = integ.strategy.withdraw(amount); + + vault.shares = vault.shares.sub(received, "Insufficient shares"); + vault.allocated = vault.allocated.sub( + received, + "Insufficient allocation" + ); + integrations[integration].totalShares = integrations[integration] + .totalShares + .sub(received, "Insufficient total shares"); + + // Clean up if fully withdrawn + if (vault.shares == 0) { + _removeVaultFromIntegration(uuid, integration); + } + } else { + // If no funds are allocated, simply withdraw the requested amount + received = amount; + } + + // Withdraw from DLCManager with adjusted received amount + dlcManager.withdraw(uuid, received); + emit VaultWithdrawn(uuid, received); + } + + function _harvestRewardsForVault(bytes32 uuid) internal { + VaultInfo storage vault = _vaults[uuid]; + address integration = vault.integration; + Integration storage integ = integrations[integration]; + + // Get and claim rewards from integration + uint256[] memory amounts = integ.strategy.claimRewards(); + address[] memory rewardAddresses = integ.strategy.getRewardTokens(); + require( + amounts.length == rewardAddresses.length, + "Invalid reward data" + ); + + // Process each reward token + for (uint256 i = 0; i < amounts.length; i++) { + address rewardAddress = rewardAddresses[i]; + uint256 amount = amounts[i]; + + if (!rewardTokens[rewardAddress].isActive) { + continue; + } + + // Calculate this vault's share of the rewards + if (amount > 0 && vault.shares > 0) { + UserReward storage reward = vault.rewards[rewardAddress]; + uint256 vaultReward = amount.mul(vault.shares).div( + integ.totalShares + ); + reward.harvestedAmount = reward.harvestedAmount.add( + vaultReward + ); + + emit RewardsHarvested( + integration, + rewardAddress, + address(this), + vaultReward + ); + } + } + } + + function harvestRewardsForIntegration( + address integration + ) external onlyRole(HARVESTER_ROLE) nonReentrant whenNotPaused { + require(integrations[integration].isActive, "Integration not active"); + + bytes32[] memory activeVaults = _getVaultsForIntegration(integration); + for (uint256 i = 0; i < activeVaults.length; i++) { + if (_vaults[activeVaults[i]].shares > 0) { + _harvestRewardsForVault(activeVaults[i]); + } + } + } + + // Claim rewards (ERC20s only) + // TODO: add auth/a way for users to claim their rewards + // So, it would not be msg.sender who gets this + function claimRewards( + string calldata taprootPubKey, + address integration, + address rewardToken + ) external nonReentrant whenNotPaused { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + VaultInfo storage vault = _vaults[uuid]; + UserReward storage reward = vault.rewards[rewardToken]; + require(reward.harvestedAmount > 0, "No rewards to claim"); + + uint256 amount = reward.harvestedAmount; + reward.harvestedAmount = 0; + reward.lastClaimedAt = block.timestamp; + + require( + IERC20(rewardToken).transfer(msg.sender, amount), + "Reward transfer failed" + ); + + emit RewardsClaimed(uuid, rewardToken, amount); + } + + //////////////////////////////////////////////////////////////// + // INTEGRATIONS // + //////////////////////////////////////////////////////////////// + + function setIntegration( + address integration, + address[] calldata supportedRewards + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + // Validate reward tokens + for (uint256 i = 0; i < supportedRewards.length; i++) { + require( + rewardTokens[supportedRewards[i]].isActive, + "Invalid reward token" + ); + } + + integrations[integration] = Integration({ + strategy: IIntegration(integration), + isActive: true, + totalShares: integrations[integration].totalShares, // Preserve existing shares if any + supportedRewardTokens: supportedRewards, + vaults: _getVaultsForIntegration(integration) + }); + + // Update active integrations list if new + if (!_isInActiveIntegrations(integration)) { + activeIntegrations.push(integration); + } + + emit IntegrationAdded(integration, supportedRewards); + } + + // Allocate dlcBTC to the vault's integration + function allocateToIntegration( + string calldata taprootPubKey, + address integration + ) public nonReentrant whenNotPaused { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + VaultInfo storage vault = _vaults[uuid]; + require(integrations[integration].isActive, "Integration not active"); + + DLCLink.DLC memory dlc = dlcManager.getDLC(uuid); + require(dlc.valueMinted > 0, "Vault not funded"); + + uint256 unallocated = dlc.valueMinted.sub( + vault.allocated, + "Already fully allocated" + ); + require(unallocated > 0, "Nothing to allocate"); + + require(dlcBTC.approve(integration, unallocated), "Approval failed"); + + uint256 shares = integrations[integration].strategy.deposit( + unallocated + ); + + vault.shares = vault.shares.add(shares); + vault.allocated = vault.allocated.add(unallocated); + integrations[integration].totalShares = integrations[integration] + .totalShares + .add(shares); + + emit SharesAllocated(uuid, integration, shares); + } + + function _addVaultToIntegration( + bytes32 uuid, + address integration + ) internal { + VaultInfo storage vault = _vaults[uuid]; + if (vault.integrationIndex == 0) { + integrations[integration].vaults.push(uuid); + vault.integrationIndex = integrations[integration].vaults.length; + } + } + + function _removeVaultFromIntegration( + bytes32 uuid, + address integration + ) internal { + VaultInfo storage vault = _vaults[uuid]; + uint256 index = vault.integrationIndex; + + if (index > 0) { + index--; // Convert from 1-based to 0-based + + // Get the array of vaults for this integration + bytes32[] storage vaults = integrations[integration].vaults; + + // If this isn't the last element, move the last element to this position + if (index != vaults.length - 1) { + bytes32 lastVault = vaults[vaults.length - 1]; + vaults[index] = lastVault; + _vaults[lastVault].integrationIndex = index + 1; // Update to 1-based index + } + + // Remove the last element + vaults.pop(); + vault.integrationIndex = 0; + } + } + + function _isInActiveIntegrations( + address integration + ) internal view returns (bool) { + for (uint256 i = 0; i < activeIntegrations.length; i++) { + if (activeIntegrations[i] == integration) { + return true; + } + } + return false; + } + + function getIntegrationVaults( + address integration + ) external view returns (bytes32[] memory) { + return integrations[integration].vaults; + } + + function _getVaultsForIntegration( + address integration + ) internal view returns (bytes32[] memory) { + return integrations[integration].vaults; + } + + function getVaultByTaprootAndIntegration( + string calldata taprootPubKey, + address integration + ) external view returns (bytes32) { + return + uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + } + + function getAllActiveIntegrations() + external + view + returns (address[] memory) + { + return activeIntegrations; + } + + //////////////////////////////////////////////////////////////// + // VAULT FUNCTIONS // + //////////////////////////////////////////////////////////////// + + function getVaultShares(bytes32 uuid) external view returns (uint256) { + return _vaults[uuid].shares; + } + + function getVaultSharesByTaprootAndIntegration( + string calldata taprootPubKey, + address integration + ) external view returns (uint256) { + return + _vaults[ + uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ] + ].shares; + } + + function getVaultIntegration(bytes32 uuid) external view returns (address) { + return _vaults[uuid].integration; + } + + function getVaultIntegrationByTaprootAndIntegration( + string calldata taprootPubKey, + address integration + ) external view returns (address) { + return + _vaults[ + uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ] + ].integration; + } + + function getVaultReward( + bytes32 uuid, + address rewardToken + ) external view returns (uint256 lastClaimedAt, uint256 harvestedAmount) { + UserReward storage reward = _vaults[uuid].rewards[rewardToken]; + return (reward.lastClaimedAt, reward.harvestedAmount); + } + + function getVaultRewardByTaprootAndIntegration( + string calldata taprootPubKey, + address integration, + address rewardToken + ) external view returns (uint256 lastClaimedAt, uint256 harvestedAmount) { + UserReward storage reward = _vaults[ + uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ] + ].rewards[rewardToken]; + return (reward.lastClaimedAt, reward.harvestedAmount); + } + + function getVaultAllocationDetails( + bytes32 uuid + ) + public + view + returns (uint256 valueMinted, uint256 allocated, uint256 unallocated) + { + DLCLink.DLC memory dlc = dlcManager.getDLC(uuid); + VaultInfo storage vault = _vaults[uuid]; + + valueMinted = dlc.valueMinted; + allocated = vault.allocated; + unallocated = valueMinted.sub( + allocated, + "Allocation exceeds minted value" + ); + } + + function getVaultAllocationDetailsByTaprootAndIntegration( + string calldata taprootPubKey, + address integration + ) + external + view + returns (uint256 valueMinted, uint256 allocated, uint256 unallocated) + { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + return getVaultAllocationDetails(uuid); + } + + function getUnallocatedAmount(bytes32 uuid) public view returns (uint256) { + DLCLink.DLC memory dlc = dlcManager.getDLC(uuid); + return + dlc.valueMinted.sub( + _vaults[uuid].allocated, + "Allocation exceeds minted value" + ); + } + + function getUnallocatedAmountByTaprootAndIntegration( + string calldata taprootPubKey, + address integration + ) external view returns (uint256) { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, integration) + ]; + return getUnallocatedAmount(uuid); + } + + function getPendingIntegrationRewards( + address integration + ) + external + view + returns (address[] memory tokens, uint256[] memory amounts) + { + require(integrations[integration].isActive, "Integration not active"); + Integration storage integ = integrations[integration]; + + tokens = integ.strategy.getRewardTokens(); + amounts = integ.strategy.getPendingRewards(); + } + + //////////////////////////////////////////////////////////////// + // VAULT QUERIES // + //////////////////////////////////////////////////////////////// + + function _calculateVaultPendingRewards( + bytes32 uuid, + uint256[] memory integrationPendingRewards, + address[] memory _rewardTokens + ) internal view returns (uint256[] memory) { + VaultInfo storage vault = _vaults[uuid]; + Integration storage integ = integrations[vault.integration]; + + uint256[] memory vaultPendingRewards = new uint256[]( + _rewardTokens.length + ); + + // Only calculate if vault has shares + if (vault.shares > 0 && integ.totalShares > 0) { + for (uint256 i = 0; i < _rewardTokens.length; i++) { + if (integrationPendingRewards[i] > 0) { + vaultPendingRewards[i] = integrationPendingRewards[i] + .mul(vault.shares) + .div(integ.totalShares); + } + } + } + + return vaultPendingRewards; + } + + // Get all vault UUIDs for a taproot public key + function getVaultsByTaprootPubKey( + string calldata taprootPubKey + ) external view returns (bytes32[] memory) { + return vaultsByTaprootPubKey[taprootPubKey]; + } + + // Get details for a specific vault by UUID + function getVaultDetails( + bytes32 uuid + ) public view returns (VaultDetails memory details) { + require(uuid != bytes32(0), "Invalid UUID"); + VaultInfo storage vault = _vaults[uuid]; + require(vault.integration != address(0), "Vault not found"); + + DLCLink.DLC memory dlc = dlcManager.getDLC(uuid); + Integration storage integ = integrations[vault.integration]; + + // Get reward tokens and pending rewards from integration + address[] memory _rewardTokens = integ.strategy.getRewardTokens(); + uint256[] memory integrationPendingRewards = integ + .strategy + .getPendingRewards(); + + // Calculate vault's share of unharvested rewards + uint256[] memory pendingRewards = _calculateVaultPendingRewards( + uuid, + integrationPendingRewards, + _rewardTokens + ); + + // Get harvested rewards waiting to be claimed + uint256[] memory harvestedRewards = new uint256[](_rewardTokens.length); + uint256[] memory lastClaimedAt = new uint256[](_rewardTokens.length); + + for (uint256 i = 0; i < _rewardTokens.length; i++) { + UserReward storage reward = vault.rewards[_rewardTokens[i]]; + harvestedRewards[i] = reward.harvestedAmount; // Rewards harvested but not yet claimed + lastClaimedAt[i] = reward.lastClaimedAt; // Last time rewards were claimed + } + + return + VaultDetails({ + uuid: uuid, + integration: vault.integration, + shares: vault.shares, + valueMinted: dlc.valueMinted, + allocated: vault.allocated, + unallocated: dlc.valueMinted - vault.allocated, + rewardTokens: _rewardTokens, + lastClaimedAt: lastClaimedAt, + harvestedRewards: harvestedRewards, // Ready to be claimed + pendingRewards: pendingRewards // Still in integration + }); + } + + function getVaultDetailsByTaprootAndIntegration( + string calldata taprootPubKey, + address _integration + ) external view returns (VaultDetails memory details) { + bytes32 uuid = uuidByTaprootAndIntegration[ + _createMappingKey(taprootPubKey, _integration) + ]; + return getVaultDetails(uuid); + } + + //////////////////////////////////////////////////////////////// + // ADMIN FUNCTIONS // + //////////////////////////////////////////////////////////////// + + // Reward token management + function addRewardToken( + address token + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(!rewardTokens[token].isActive, "Token already added"); + + // Verify it's an ERC20 + IERC20(token).totalSupply(); // Will revert if not ERC20 + + rewardTokens[token] = RewardToken({ + tokenAddress: token, + isActive: true + }); + + emit RewardTokenAdded(token); + } + + function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { + _pause(); + } + + function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { + _unpause(); + } + + //////////////////////////////////////////////////////////////// + // UTILITIES // + //////////////////////////////////////////////////////////////// + + function _createMappingKey( + string memory taprootPubKey, + address integration + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(taprootPubKey, integration)); + } + + // Required interface implementations + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } + + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + function supportsInterface( + bytes4 interfaceId + ) + public + view + virtual + override( + ERC165Upgradeable, + AccessControlDefaultAdminRulesUpgradeable, + IERC165 + ) + returns (bool) + { + return + interfaceId == type(IERC721Receiver).interfaceId || + interfaceId == type(IERC1155Receiver).interfaceId || + super.supportsInterface(interfaceId); + } +} diff --git a/contracts/integrations/curve.md b/contracts/integrations/curve.md new file mode 100644 index 0000000..7839f3d --- /dev/null +++ b/contracts/integrations/curve.md @@ -0,0 +1,114 @@ +# CurveIntegration Deposit Flow + +## Initial State + +- PoolMerchant has dlcBTC tokens +- PoolMerchant approves CurveIntegration to spend dlcBTC + +## Step 1: Add Liquidity to Pool + +```solidity +// Prepare amounts array for curve pool (e.g., for a 2-token pool) +uint256[] memory amounts = new uint256[](2); +amounts[uint256(uint128(dlcBTCIndex))] = amount; +// If dlcBTC is the second token (index 1), this means: +// amounts = [0, amount] + +// Approve curve pool to spend our dlcBTC +IERC20(dlcBTC).approve(curvePoolAddress, amount); + +// Add liquidity to pool +// Returns LP tokens representing our share of the pool +shares = curvePool.add_liquidity(amounts, 0, address(this)); +``` + +## Step 2: Deposit LP Tokens to Gauge + +```solidity +// Approve gauge to spend LP tokens +IERC20(curvePoolAddress).approve(curveGaugeAddress, shares); + +// Deposit LP tokens into gauge +// This is what enables us to earn CRV rewards +curveGauge.deposit(shares, address(this)); +``` + +## Flow of Tokens + +1. dlcBTC: PoolMerchant -> CurveIntegration -> Curve Pool +2. LP Tokens: Curve Pool -> CurveIntegration -> Curve Gauge + +## Results + +- Our dlcBTC is in the Curve Pool +- Our LP tokens are in the Curve Gauge +- We'll earn CRV rewards based on our gauge deposit +- The shares returned represent our proportional ownership of the pool + +## Key Points + +- All token movements require prior approvals +- We use minMintAmount = 0 (no slippage protection currently) +- We keep LP tokens in the gauge to earn rewards +- Our shares accounting in PoolMerchant matches the LP tokens we have in the gauge + +# CurveIntegration Withdraw Flow + +## Initial State + +- Our LP tokens are in the Curve Gauge +- We want to get back dlcBTC +- PoolMerchant calls withdraw with the number of shares (LP tokens) to withdraw + +## Step 1: Withdraw from Gauge + +```solidity +// Withdraw LP tokens from gauge +// claim_rewards = true means we'll also get any pending CRV rewards +curveGauge.withdraw(shares, true); +``` + +## Step 2: Remove Liquidity from Pool + +```solidity +// Remove liquidity for a single coin (dlcBTC) +amount = curvePool.remove_liquidity_one_coin( + shares, // amount of LP tokens to burn + dlcBTCIndex, // index of token we want back (dlcBTC) + 0, // minimum amount to accept + msg.sender // recipient (PoolMerchant) +); +``` + +## Flow of Tokens + +1. LP Tokens: Curve Gauge -> CurveIntegration -> Curve Pool +2. dlcBTC: Curve Pool -> PoolMerchant + +## Results + +- We get back dlcBTC tokens +- Our LP tokens are burned +- We may receive any accrued CRV rewards +- The `amount` returned is how many dlcBTC tokens we received + +## Key Points + +- No approvals needed for withdrawals +- We use minReceived = 0 (no slippage protection currently) +- We might get fewer dlcBTC back than we put in due to: + - Pool fees + - Price changes + - Slippage +- Using remove_liquidity_one_coin is more gas efficient than remove_liquidity +- Any pending rewards are claimed automatically if claim_rewards = true + +## Notes on Accounting + +- PoolMerchant updates its accounting based on the returned amount: + +```solidity +_vaults[uuid].integrationShares[integration] -= withdrawAmount; +_vaults[uuid].totalAllocated -= received; +integ.totalShares -= withdrawAmount; +``` diff --git a/contracts/integrations/curve.sol b/contracts/integrations/curve.sol new file mode 100644 index 0000000..5095cc6 --- /dev/null +++ b/contracts/integrations/curve.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../interfaces/IIntegration.sol"; + +interface ICurvePool { + function add_liquidity( + uint256[] memory _amounts, + uint256 _min_mint_amount, + address _receiver + ) external returns (uint256); + + function remove_liquidity_one_coin( + uint256 _burn_amount, + int128 i, + uint256 _min_received, + address _receiver + ) external returns (uint256); + + function coins(uint256 i) external view returns (address); +} + +// NOTE: I've altered deposit态withdraw态claim_rewards make up to date. +interface ICurveGauge { + function deposit( + uint256 _value, + address _user, + bool _claim_rewards + ) external; + + function withdraw( + uint256 _value, + address _user, + bool _claim_rewards + ) external; + + function claim_rewards(address _addr, address _receiver) external; + + function reward_tokens(uint256 i) external view returns (address); + + function reward_count() external view returns (uint256); + + function claimable_reward( + address _user, + address _reward_token + ) external view returns (uint256); +} + +contract CurveIntegration is IIntegration, Ownable { + address public curvePoolAddress; + address public curveGaugeAddress; + ICurvePool public curvePool; + ICurveGauge public curveGauge; + address public dlcBTC; + int128 public dlcBTCIndex; + + constructor( + address _curvePoolAddress, + address _curveGaugeAddress, + address _poolMerchant, + address _dlcBTC + ) Ownable() { + curvePoolAddress = _curvePoolAddress; + curveGaugeAddress = _curveGaugeAddress; + curvePool = ICurvePool(curvePoolAddress); + curveGauge = ICurveGauge(curveGaugeAddress); + dlcBTC = _dlcBTC; + + // Find dlcBTC index in pool + if (curvePool.coins(0) == dlcBTC) { + dlcBTCIndex = 0; + } else if (curvePool.coins(1) == dlcBTC) { + dlcBTCIndex = 1; + } else { + revert("dlcBTC not found in pool"); + } + + transferOwnership(_poolMerchant); + } + + // NOTE: we might have to change how shares/amounts translate... + // since dlcBTC is 8 decimals and the curve pool is 18 decimals + // NOTE: decimals changed + function deposit( + uint256 amount + ) external override onlyOwner returns (uint256 shares) { + // The dlcBTC tokens are still in the PoolMerchant, which has approved us to spend them + + // First approve the curve pool to spend the dlcBTC that we'll transferFrom + IERC20(dlcBTC).approve(curvePoolAddress, amount); + + uint256[] memory amounts = new uint256[](2); + amounts[uint256(uint128(dlcBTCIndex))] = amount; + + // Transfer the dlcBTC from PoolMerchant and add liquidity to the pool + IERC20(dlcBTC).transferFrom(msg.sender, address(this), amount); + shares = curvePool.add_liquidity(amounts, 0, address(this)); + + // Approve and deposit LP tokens into gauge + IERC20(curvePoolAddress).approve(curveGaugeAddress, shares); + curveGauge.deposit(shares, address(this), false); + shares = _fromPoolDecimals(shares); + + return shares; + } + + function withdraw( + uint256 shares + ) external override onlyOwner returns (uint256 amount) { + shares = _toPoolDecimals(shares); + // Step 1: Withdraw LP tokens from gauge + curveGauge.withdraw(shares, address(this), true); // Claim rewards during withdrawal + + // Step 2: Remove liquidity for dlcBTC + amount = curvePool.remove_liquidity_one_coin( + shares, + dlcBTCIndex, + 0, + msg.sender + ); + + return amount; + } + + function claimRewards() + external + override + onlyOwner + returns (uint256[] memory amounts) + { + curveGauge.claim_rewards(address(this), msg.sender); + + uint256 rewardCount = curveGauge.reward_count(); + amounts = new uint256[](rewardCount); + + for (uint256 i = 0; i < rewardCount; i++) { + address rewardToken = curveGauge.reward_tokens(i); + amounts[i] = IERC20(rewardToken).balanceOf(address(this)); + if (amounts[i] > 0) { + IERC20(rewardToken).transfer(msg.sender, amounts[i]); + } + } + } + + function getRewardTokens() + external + view + override + returns (address[] memory tokens) + { + uint256 rewardCount = curveGauge.reward_count(); + tokens = new address[](rewardCount); + + for (uint256 i = 0; i < rewardCount; i++) { + tokens[i] = curveGauge.reward_tokens(i); + } + } + + function getPendingRewards() + external + view + override + returns (uint256[] memory amounts) + { + uint256 rewardCount = curveGauge.reward_count(); + amounts = new uint256[](rewardCount); + + for (uint256 i = 0; i < rewardCount; i++) { + address rewardToken = curveGauge.reward_tokens(i); + amounts[i] = curveGauge.claimable_reward( + address(this), + rewardToken + ); + } + } + + function _toPoolDecimals(uint256 amount) internal pure returns (uint256) { + return amount * 1e10; + } + + function _fromPoolDecimals(uint256 amount) internal pure returns (uint256) { + return amount / 1e10; + } +} diff --git a/contracts/integrations/enzyme.sol b/contracts/integrations/enzyme.sol new file mode 100644 index 0000000..0a00f07 --- /dev/null +++ b/contracts/integrations/enzyme.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../interfaces/IIntegration.sol"; + +interface IGlobalConfigLibComptrollerV4 { + function buyShares( + uint256 _investmentAmount, + uint256 _minSharesQuantity + ) external returns (uint256 sharesReceived_); + + function getDenominationAsset() + external + view + returns (address denominationAsset_); + + function redeemSharesForSpecificAssets( + address _recipient, + uint256 _sharesQuantity, + address[] calldata _payoutAssets, + uint256[] calldata _payoutAssetPercentages + ) external returns (uint256[] memory payoutAmounts_); +} + +contract EnzymeIntegration is IIntegration, Ownable { + address public enzymeVaultAddress; + IGlobalConfigLibComptrollerV4 public enzymeVault; + address public dlcBTC; + + constructor( + address _enzymeVaultAddress, + address _poolMerchant, + address _dlcBTC + ) Ownable() { + enzymeVaultAddress = _enzymeVaultAddress; + enzymeVault = IGlobalConfigLibComptrollerV4(enzymeVaultAddress); + dlcBTC = _dlcBTC; + + // Verify that vault's denomination asset is dlcBTC + require( + enzymeVault.getDenominationAsset() == dlcBTC, + "Vault denomination asset must be dlcBTC" + ); + + transferOwnership(_poolMerchant); + } + + function deposit( + uint256 amount + ) external override onlyOwner returns (uint256 shares) { + // The dlcBTC tokens are still in the PoolMerchant, which has approved us to spend them + + // Transfer the dlcBTC from PoolMerchant and approve Enzyme vault to spend it + IERC20(dlcBTC).transferFrom(msg.sender, address(this), amount); + IERC20(dlcBTC).approve(enzymeVaultAddress, amount); + + // Buy shares in the Enzyme vault + shares = enzymeVault.buyShares(amount, 1); // minimum 1 share + + return shares; + } + + function withdraw( + uint256 shares + ) external override onlyOwner returns (uint256 amount) { + // Setup redemption parameters for dlcBTC only + address[] memory payoutAssets = new address[](1); + payoutAssets[0] = dlcBTC; + + uint256[] memory payoutPercentages = new uint256[](1); + payoutPercentages[0] = 10000; // 100% in basis points + + // Redeem shares for dlcBTC + uint256[] memory amounts = enzymeVault.redeemSharesForSpecificAssets( + msg.sender, // Send directly to PoolMerchant + shares, + payoutAssets, + payoutPercentages + ); + + return amounts[0]; + } + + // Since Enzyme doesn't have a direct reward claiming mechanism like Curve, + // we'll implement empty reward functions to maintain interface compatibility + + function claimRewards() + external + view + override + onlyOwner + returns (uint256[] memory amounts) + { + amounts = new uint256[](0); + return amounts; + } + + function getRewardTokens() + external + pure + override + returns (address[] memory tokens) + { + tokens = new address[](0); + return tokens; + } + + function getPendingRewards() + external + pure + override + returns (uint256[] memory amounts) + { + amounts = new uint256[](0); + return amounts; + } +} diff --git a/contracts/integrations/intergrationSample.sol b/contracts/integrations/intergrationSample.sol new file mode 100644 index 0000000..da3cc00 --- /dev/null +++ b/contracts/integrations/intergrationSample.sol @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "../interfaces/IIntegration.sol"; +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract IntegrationSample is IIntegration, Ownable { + using SafeERC20 for IERC20; + using SafeERC20 for IERC4626; + + uint256 public rewardRatePerSecond; + uint256 public lastRewardTime; + uint256 public accumulatedRewardPerShare; + uint256 public rewardBalance; // Track reward token balance to distribute + + mapping(address => uint256) public userRewardDebt; + + address[] public rewardTokens; + IERC4626 public vault; + IERC20 public rewardToken; + address public poolMerchant; + address public dlcBTC; + + event RewardsUpdated( + uint256 timeElapsed, + uint256 rewardsAdded, + uint256 newAccumulatedRewardPerShare + ); + + event RewardsClaimed(address user, uint256 amount); + constructor( + IERC4626 _vault, + IERC20 _rewardToken, + uint256 _rewardRatePerSecond, + address _poolMerchant, + address _dlcBTC + ) { + vault = _vault; + rewardToken = _rewardToken; + rewardRatePerSecond = _rewardRatePerSecond; + lastRewardTime = block.timestamp; + rewardTokens.push(address(_rewardToken)); + poolMerchant = _poolMerchant; + transferOwnership(_poolMerchant); + dlcBTC = _dlcBTC; + } + + function deposit( + uint256 amount + ) external override onlyOwner returns (uint256 shares) { + require(amount > 0, "Amount must be greater than 0"); + _updateRewards(); + IERC20 _dlcBTC = IERC20(dlcBTC); + + require( + _dlcBTC.allowance(msg.sender, address(this)) >= amount, + "Not enough allowance" + ); + require(_dlcBTC == IERC20(vault.asset()), "Invalid asset"); + + _dlcBTC.safeTransferFrom(msg.sender, address(this), amount); + _dlcBTC.safeApprove(address(vault), 0); // Clear any existing approval + _dlcBTC.safeApprove(address(vault), amount); + + shares = vault.deposit(amount, address(this)); + return shares; + } + + function withdraw( + uint256 shares + ) external override onlyOwner returns (uint256 assets) { + _updateRewards(); + require(shares > 0, "Shares must be greater than 0"); + require( + IERC20(address(vault)).balanceOf(address(this)) >= shares, + "Not enough shares" + ); + + // Just do the approval step + IERC20(address(vault)).safeApprove(address(vault), 0); + IERC20(address(vault)).safeApprove(address(vault), shares); + + assets = vault.redeem( + shares, + address(this), // receive assets here + address(this) // owner of shares + ); + + IERC20(vault.asset()).safeTransfer(poolMerchant, assets); + + return assets; + } + + function updateRewardRate(uint256 newRate) external onlyOwner { + _updateRewards(); // Update with old rate first + rewardRatePerSecond = newRate; + } + + // NOTE: normally, such logic would be in the vault side of the flow, + // and we would just call it from here. + // But for this demo, we are keeping it here. + function claimRewards() + external + override + onlyOwner + returns (uint256[] memory amounts) + { + _updateRewards(); + + uint256 totalShares = vault.balanceOf(address(this)); + require(totalShares > 0, "No shares"); + + uint256 pending = (totalShares * accumulatedRewardPerShare) / + 1e18 - + userRewardDebt[msg.sender]; + + if (pending > 0) { + require( + rewardToken.balanceOf(address(this)) >= pending, + "Insufficient reward balance" + ); + + userRewardDebt[msg.sender] = + (totalShares * accumulatedRewardPerShare) / + 1e18; + rewardToken.safeTransfer(msg.sender, pending); + + emit RewardsClaimed(msg.sender, pending); + } + + amounts = new uint256[](1); + amounts[0] = pending; + return amounts; + } + + function getPendingRewards() + external + view + override + returns (uint256[] memory amounts) + { + amounts = new uint256[](1); + + uint256 totalShares = vault.balanceOf(address(this)); + if (totalShares == 0) { + amounts[0] = 0; + return amounts; + } + + // Calculate rewards that would be added since last update + uint256 timeElapsed = block.timestamp - lastRewardTime; + uint256 currentBalance = rewardToken.balanceOf(address(this)); + uint256 maxRewardsToAdd = currentBalance > rewardBalance + ? timeElapsed * rewardRatePerSecond + : 0; + + uint256 currentAccRewardPerShare = accumulatedRewardPerShare; + if (maxRewardsToAdd > 0) { + currentAccRewardPerShare += (maxRewardsToAdd * 1e18) / totalShares; + } + + amounts[0] = + ((totalShares * currentAccRewardPerShare) / 1e18) - + userRewardDebt[msg.sender]; + return amounts; + } + + function _updateRewards() internal { + uint256 totalShares = vault.balanceOf(address(this)); + if (totalShares == 0) { + lastRewardTime = block.timestamp; + return; + } + + uint256 timeElapsed = block.timestamp - lastRewardTime; + if (timeElapsed == 0) return; + + uint256 currentBalance = rewardToken.balanceOf(address(this)); + + // Only distribute rewards if we have new tokens to distribute + if (currentBalance > rewardBalance) { + uint256 maxRewardsToAdd = timeElapsed * rewardRatePerSecond; + uint256 actualRewardsToAdd = currentBalance - rewardBalance; + + // Use the smaller of maxRewardsToAdd or actualRewardsToAdd + uint256 rewardsToAdd = maxRewardsToAdd < actualRewardsToAdd + ? maxRewardsToAdd + : actualRewardsToAdd; + + accumulatedRewardPerShare += (rewardsToAdd * 1e18) / totalShares; + rewardBalance = currentBalance; + + emit RewardsUpdated( + timeElapsed, + rewardsToAdd, + accumulatedRewardPerShare + ); + } + + lastRewardTime = block.timestamp; + } + + function getRewardState() + external + view + returns ( + uint256 totalShares, + uint256 currentRewardBalance, + uint256 trackedRewardBalance, + uint256 accRewardPerShare, + uint256 lastUpdate, + uint256 rewardRate + ) + { + return ( + vault.balanceOf(address(this)), + rewardToken.balanceOf(address(this)), + rewardBalance, + accumulatedRewardPerShare, + lastRewardTime, + rewardRatePerSecond + ); + } + + function getRewardTokens() + external + view + override + returns (address[] memory) + { + return rewardTokens; + } + + function _calculateRewards() internal view returns (uint256) { + uint256 timeElapsed = block.timestamp - lastRewardTime; + uint256 userShares = vault.balanceOf(address(this)); + uint256 totalShares = vault.totalSupply(); + if (totalShares == 0) { + return 0; + } + return (timeElapsed * rewardRatePerSecond * userShares) / totalShares; + } +} diff --git a/contracts/interfaces/IIntegration.sol b/contracts/interfaces/IIntegration.sol new file mode 100644 index 0000000..3637bdd --- /dev/null +++ b/contracts/interfaces/IIntegration.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; +interface IIntegration { + function deposit(uint256 amount) external returns (uint256 shares); + function withdraw(uint256 shares) external returns (uint256 amount); + function claimRewards() external returns (uint256[] memory amounts); + function getRewardTokens() external view returns (address[] memory); + function getPendingRewards() external view returns (uint256[] memory); +} diff --git a/contracts/mocks/MockERC20.sol b/contracts/mocks/MockERC20.sol new file mode 100644 index 0000000..5d26065 --- /dev/null +++ b/contracts/mocks/MockERC20.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + uint8 private _decimals; + + constructor( + string memory name, + string memory symbol, + uint8 decimalsValue + ) ERC20(name, symbol) { + _decimals = decimalsValue; + } + + function mint(address account, uint256 amount) public { + _mint(account, amount); + } + + function burn(address account, uint256 amount) public { + _burn(account, amount); + } + + function decimals() public view virtual override returns (uint8) { + return _decimals; + } +} diff --git a/contracts/mocks/MockERC4626.sol b/contracts/mocks/MockERC4626.sol new file mode 100644 index 0000000..f3603b7 --- /dev/null +++ b/contracts/mocks/MockERC4626.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract MockERC4626Vault is ERC4626 { + constructor( + IERC20Metadata _asset + ) ERC20("Mock Vault Token", "MVT") ERC4626(_asset) {} +} diff --git a/contracts/mocks/MockIntegration.sol b/contracts/mocks/MockIntegration.sol new file mode 100644 index 0000000..df3ca68 --- /dev/null +++ b/contracts/mocks/MockIntegration.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../interfaces/IIntegration.sol"; + +contract MockIntegration is IIntegration { + address[] public rewardTokens; + mapping(address => uint256) public pendingRewards; + + constructor(address[] memory _rewardTokens) { + rewardTokens = _rewardTokens; + } + + function deposit(uint256 amount) external override returns (uint256) { + return amount; + } + + function withdraw(uint256 shares) external override returns (uint256) { + return shares; + } + + function claimRewards() + external + override + returns (uint256[] memory amounts) + { + amounts = new uint256[](rewardTokens.length); + for (uint256 i = 0; i < rewardTokens.length; i++) { + amounts[i] = pendingRewards[rewardTokens[i]]; + pendingRewards[rewardTokens[i]] = 0; + + // Transfer rewards to caller + IERC20(rewardTokens[i]).transfer(msg.sender, amounts[i]); + } + } + + function getRewardTokens() + external + view + override + returns (address[] memory) + { + return rewardTokens; + } + + function getPendingRewards() + external + view + override + returns (uint256[] memory amounts) + { + amounts = new uint256[](rewardTokens.length); + for (uint256 i = 0; i < rewardTokens.length; i++) { + amounts[i] = pendingRewards[rewardTokens[i]]; + } + } + + // Test helper function + function mockRewards(uint256[] memory amounts) external { + require( + amounts.length == rewardTokens.length, + "Invalid amounts length" + ); + for (uint256 i = 0; i < rewardTokens.length; i++) { + pendingRewards[rewardTokens[i]] = amounts[i]; + } + } +} diff --git a/deploymentFiles/arbsepolia/DLCManager.json b/deploymentFiles/arbsepolia/DLCManager.json index c125abb..22bdd7c 100644 --- a/deploymentFiles/arbsepolia/DLCManager.json +++ b/deploymentFiles/arbsepolia/DLCManager.json @@ -1,7 +1,7 @@ { "network": "arbsepolia", - "updatedAt": "2024-09-26T17:49:57.073Z", - "gitSHA": "b4622fe", + "updatedAt": "2024-11-08T09:31:14.697Z", + "gitSHA": "f8bab99", "contract": { "name": "DLCManager", "address": "0xE8FA6399d1b60968f04888F23Ed13Ed499C1Fd51", @@ -10,6 +10,7 @@ "constructor()", "error ClosingFundedVault()", "error ContractNotWhitelisted()", + "error DLCAlreadyExists(bytes32 uuid)", "error DLCNotFound()", "error DLCNotFunded()", "error DLCNotPending()", @@ -27,6 +28,7 @@ "error NoSignerRenouncement()", "error NotCreatorContract()", "error NotDLCAdmin()", + "error NotEnoughReserves(uint256 reserves, uint256 amount)", "error NotEnoughSignatures()", "error NotOwner()", "error NotWhitelisted()", @@ -50,8 +52,10 @@ "event SetBtcFeeRecipient(string btcFeeRecipient)", "event SetBtcMintFeeRate(uint256 newBtcMintFeeRate)", "event SetBtcRedeemFeeRate(uint256 newBtcRedeemFeeRate)", + "event SetDlcBTCPoRFeed(address feed)", "event SetMaximumDeposit(uint256 newMaximumDeposit)", "event SetMinimumDeposit(uint256 newMinimumDeposit)", + "event SetPorEnabled(bool enabled)", "event SetStatusFunded(bytes32 uuid, string btcTxId, address creator, uint256 newValueLocked, uint256 amountToMint)", "event SetStatusPending(bytes32 uuid, string btcTxId, address creator, string taprootPubKey, uint256 newValueLocked)", "event SetThreshold(uint16 newThreshold)", @@ -77,18 +81,21 @@ "function defaultAdminDelay() view returns (uint48)", "function defaultAdminDelayIncreaseWait() view returns (uint48)", "function dlcBTC() view returns (address)", + "function dlcBTCPoRFeed() view returns (address)", "function dlcIDsByUUID(bytes32) view returns (uint256)", - "function dlcs(uint256) view returns (bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)", - "function getAllDLCs(uint256 startIndex, uint256 endIndex) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)[])", + "function dlcs(uint256) view returns (bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress)", + "function generateUUID(address sender, uint256 nonce) view returns (bytes32)", + "function getAllDLCs(uint256 startIndex, uint256 endIndex) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress)[])", "function getAllVaultUUIDsForAddress(address owner) view returns (bytes32[])", - "function getAllVaultsForAddress(address owner) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)[])", - "function getDLC(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))", - "function getDLCByIndex(uint256 index) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))", + "function getAllVaultsForAddress(address owner) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress)[])", + "function getDLC(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress))", + "function getDLCByIndex(uint256 index) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress))", "function getMinimumThreshold() view returns (uint16)", "function getRoleAdmin(bytes32 role) view returns (bytes32)", "function getSignerCount() view returns (uint16)", "function getThreshold() view returns (uint16)", - "function getVault(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))", + "function getTotalValueMintedInVaults() view returns (uint256)", + "function getVault(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId, address icyIntegrationAddress))", "function grantRole(bytes32 role, address account)", "function hasRole(bytes32 role, address account) view returns (bool)", "function initialize(address defaultAdmin, address dlcAdminRole, uint16 threshold, address tokenContract, string btcFeeRecipientToSet)", @@ -100,6 +107,7 @@ "function paused() view returns (bool)", "function pendingDefaultAdmin() view returns (address newAdmin, uint48 schedule)", "function pendingDefaultAdminDelay() view returns (uint48 newDelay, uint48 schedule)", + "function porEnabled() view returns (bool)", "function renounceRole(bytes32 role, address account)", "function revokeRole(bytes32 role, address account)", "function rollbackDefaultAdminDelay()", @@ -109,15 +117,20 @@ "function setBtcMintFeeRate(uint256 newBtcMintFeeRate)", "function setBtcRedeemFeeRate(uint256 newBtcRedeemFeeRate)", "function setBurnerOnTokenContract(address burner)", + "function setDlcBTCPoRFeed(address feed)", "function setMaximumDeposit(uint256 newMaximumDeposit)", "function setMinimumDeposit(uint256 newMinimumDeposit)", "function setMinterOnTokenContract(address minter)", + "function setPorEnabled(bool enabled)", + "function setSkipSignatureVerification(bool skip)", "function setStatusFunded(bytes32 uuid, string btcTxId, bytes[] signatures, uint256 newValueLocked)", "function setStatusPending(bytes32 uuid, string wdTxId, bytes[] signatures, string taprootPubKey, uint256 newValueLocked)", "function setTSSCommitment(bytes32 commitment)", "function setThreshold(uint16 newThreshold)", "function setWhitelistingEnabled(bool isWhitelistingEnabled)", + "function setupPendingVault(string _taprootPubKey, string _wdTxId, address icyIntegrationAddress) returns (bytes32)", "function setupVault() returns (bytes32)", + "function skipSignatureVerification() view returns (bool)", "function supportsInterface(bytes4 interfaceId) view returns (bool)", "function transferTokenContractOwnership(address newOwner)", "function tssCommitment() view returns (bytes32)", diff --git a/deploymentFiles/arbsepolia/PoolMerchant.json b/deploymentFiles/arbsepolia/PoolMerchant.json new file mode 100644 index 0000000..00f172e --- /dev/null +++ b/deploymentFiles/arbsepolia/PoolMerchant.json @@ -0,0 +1,88 @@ +{ + "network": "arbsepolia", + "updatedAt": "2024-11-08T09:31:33.116Z", + "gitSHA": "f8bab99", + "contract": { + "name": "PoolMerchant", + "address": "0xc0Fd850bE474e21449aF001782F11a0e9FC049C7", + "signerAddress": "0xBf7184178d610D7B0239a5CB8D64c1Df22d306a9", + "abi": [ + "constructor()", + "event DefaultAdminDelayChangeCanceled()", + "event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule)", + "event DefaultAdminTransferCanceled()", + "event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule)", + "event Initialized(uint8 version)", + "event IntegrationAdded(address indexed integration, address[] supportedRewards)", + "event Paused(address account)", + "event PendingVaultCreated(bytes32 indexed uuid, string taprootPubKey, string wdPSBT, address integration)", + "event RewardTokenAdded(address indexed token)", + "event RewardsClaimed(bytes32 indexed uuid, address indexed rewardToken, uint256 amount)", + "event RewardsHarvested(address indexed integration, address indexed rewardToken, address indexed harvester, uint256 amount)", + "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)", + "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)", + "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)", + "event SharesAllocated(bytes32 indexed uuid, address indexed integration, uint256 shares)", + "event Unpaused(address account)", + "event VaultWithdrawn(bytes32 indexed uuid, uint256 amount)", + "function ATTESTOR_ROLE() view returns (bytes32)", + "function DEFAULT_ADMIN_ROLE() view returns (bytes32)", + "function HARVESTER_ROLE() view returns (bytes32)", + "function acceptDefaultAdminTransfer()", + "function activeIntegrations(uint256) view returns (address)", + "function addRewardToken(address token)", + "function allocateToIntegration(string taprootPubKey, address integration)", + "function beginDefaultAdminTransfer(address newAdmin)", + "function cancelDefaultAdminTransfer()", + "function changeDefaultAdminDelay(uint48 newDelay)", + "function claimRewards(string taprootPubKey, address integration, address rewardToken)", + "function createPendingVault(string taprootPubKey, string wdPSBT, address integration) returns (bytes32)", + "function defaultAdmin() view returns (address)", + "function defaultAdminDelay() view returns (uint48)", + "function defaultAdminDelayIncreaseWait() view returns (uint48)", + "function dlcBTC() view returns (address)", + "function dlcManager() view returns (address)", + "function getAllActiveIntegrations() view returns (address[])", + "function getIntegrationVaults(address integration) view returns (bytes32[])", + "function getPendingIntegrationRewards(address integration) view returns (address[] tokens, uint256[] amounts)", + "function getRoleAdmin(bytes32 role) view returns (bytes32)", + "function getUnallocatedAmount(bytes32 uuid) view returns (uint256)", + "function getUnallocatedAmountByTaprootAndIntegration(string taprootPubKey, address integration) view returns (uint256)", + "function getVaultAllocationDetails(bytes32 uuid) view returns (uint256 valueMinted, uint256 allocated, uint256 unallocated)", + "function getVaultAllocationDetailsByTaprootAndIntegration(string taprootPubKey, address integration) view returns (uint256 valueMinted, uint256 allocated, uint256 unallocated)", + "function getVaultByTaprootAndIntegration(string taprootPubKey, address integration) view returns (bytes32)", + "function getVaultDetails(bytes32 uuid) view returns (tuple(bytes32 uuid, address integration, uint256 shares, uint256 valueMinted, uint256 allocated, uint256 unallocated, address[] rewardTokens, uint256[] lastClaimedAt, uint256[] harvestedRewards, uint256[] pendingRewards) details)", + "function getVaultDetailsByTaprootAndIntegration(string taprootPubKey, address _integration) view returns (tuple(bytes32 uuid, address integration, uint256 shares, uint256 valueMinted, uint256 allocated, uint256 unallocated, address[] rewardTokens, uint256[] lastClaimedAt, uint256[] harvestedRewards, uint256[] pendingRewards) details)", + "function getVaultIntegration(bytes32 uuid) view returns (address)", + "function getVaultIntegrationByTaprootAndIntegration(string taprootPubKey, address integration) view returns (address)", + "function getVaultReward(bytes32 uuid, address rewardToken) view returns (uint256 lastClaimedAt, uint256 harvestedAmount)", + "function getVaultRewardByTaprootAndIntegration(string taprootPubKey, address integration, address rewardToken) view returns (uint256 lastClaimedAt, uint256 harvestedAmount)", + "function getVaultShares(bytes32 uuid) view returns (uint256)", + "function getVaultSharesByTaprootAndIntegration(string taprootPubKey, address integration) view returns (uint256)", + "function getVaultsByTaprootPubKey(string taprootPubKey) view returns (bytes32[])", + "function grantRole(bytes32 role, address account)", + "function harvestRewardsForIntegration(address integration)", + "function hasRole(bytes32 role, address account) view returns (bool)", + "function initialize(address _dlcManager, address _dlcBTC, address defaultAdmin)", + "function integrations(address) view returns (address strategy, bool isActive, uint256 totalShares)", + "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)", + "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)", + "function onERC721Received(address, address, uint256, bytes) returns (bytes4)", + "function owner() view returns (address)", + "function pause()", + "function paused() view returns (bool)", + "function pendingDefaultAdmin() view returns (address newAdmin, uint48 schedule)", + "function pendingDefaultAdminDelay() view returns (uint48 newDelay, uint48 schedule)", + "function renounceRole(bytes32 role, address account)", + "function revokeRole(bytes32 role, address account)", + "function rewardTokens(address) view returns (address tokenAddress, bool isActive)", + "function rollbackDefaultAdminDelay()", + "function setIntegration(address integration, address[] supportedRewards)", + "function supportsInterface(bytes4 interfaceId) view returns (bool)", + "function unpause()", + "function uuidByTaprootAndIntegration(bytes32) view returns (bytes32)", + "function vaultsByTaprootPubKey(string, uint256) view returns (bytes32)", + "function withdrawFromVault(string taprootPubKey, address integration, uint256 amount)" + ] + } +} diff --git a/docker/hardhat.config.docker.js b/docker/hardhat.config.docker.js index 7347168..bcf89fb 100644 --- a/docker/hardhat.config.docker.js +++ b/docker/hardhat.config.docker.js @@ -9,6 +9,7 @@ module.exports = { solidity: { version: '0.8.18', settings: { + viaIR: true, optimizer: { enabled: true, runs: 200, diff --git a/docker/scripts/deploy-all.js b/docker/scripts/deploy-all.js index 71a3004..9b79297 100644 --- a/docker/scripts/deploy-all.js +++ b/docker/scripts/deploy-all.js @@ -16,6 +16,11 @@ async function main() { medium: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', critical: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', }; // Hardhat default deployer account + const defaultSigners = [ + '0x976EA74026E726554dB657fA54763abd0C3a0aa9', // account[6] + '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // account[7] + '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', // account[8] + ]; const contractConfigs = getContractConfigs( { @@ -37,20 +42,74 @@ async function main() { await contractConfig.deploy(reqs); } + console.log('\nšŸ“ Getting contract instances...'); + const proxyAddress = await loadContractAddress('DLCManager', network); + const dlcManager = await hardhat.ethers.getContractAt( + 'DLCManager', + proxyAddress + ); + const dlcBTCAddress = await loadContractAddress('DLCBTC', network); + const dlcBTC = await hardhat.ethers.getContractAt('DLCBTC', dlcBTCAddress); + const poolMerchantAddress = await loadContractAddress( + 'PoolMerchant', + network + ); + const poolMerchant = await hardhat.ethers.getContractAt( + 'PoolMerchant', + poolMerchantAddress + ); + + console.log('\nšŸ—ļø Deploying MockERC4626Vault...'); + const MockERC4626Vault = + await hardhat.ethers.getContractFactory('MockERC4626Vault'); + const mockVault = await MockERC4626Vault.deploy(dlcBTCAddress); + await mockVault.deployed(); + console.log('MockERC4626Vault deployed to:', mockVault.address); + + console.log('\nšŸ—ļø Deploying IntegrationSample...'); + const IntegrationSample = + await hardhat.ethers.getContractFactory('IntegrationSample'); + const rewardRatePerSecond = hardhat.ethers.BigNumber.from('317'); + const integrationSample = await IntegrationSample.deploy( + mockVault.address, + dlcBTCAddress, + rewardRatePerSecond, + poolMerchant.address, + dlcBTCAddress + ); + await integrationSample.deployed(); + console.log('IntegrationSample deployed to:', integrationSample.address); + console.log('Deployment complete'); + console.log('\nšŸ”‘ Setting up roles and integration...'); + await poolMerchant.grantRole( + await poolMerchant.ATTESTOR_ROLE(), + defaultSigners[0] + ); + await poolMerchant.grantRole( + await poolMerchant.HARVESTER_ROLE(), + deployer.address + ); + + console.log('\nšŸŖ™ Adding dlcBTC as reward token...'); + await poolMerchant.addRewardToken(dlcBTCAddress); + + console.log('\nšŸ”„ Setting up Integration...'); + await poolMerchant.setIntegration(integrationSample.address, [ + dlcBTCAddress, + ]); + // Adding signers - const defaultSigners = [ - '0x976EA74026E726554dB657fA54763abd0C3a0aa9', // account[6] - '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // account[7] - '0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f', // account[8] - ]; + for (const signer of defaultSigners) { await addSigner(signer); } // Set whitelisting await setWhitelisting('false'); + + console.log('\nāœ… Setup sequence complete'); } // make sure we catch all errors diff --git a/hardhat.config.js b/hardhat.config.js index c69737c..3829257 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -33,6 +33,7 @@ module.exports = { solidity: { version: '0.8.18', settings: { + viaIR: true, optimizer: { enabled: true, runs: 200, diff --git a/package-lock.json b/package-lock.json index 9a8122e..e691601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dlc-link-eth", - "version": "3.2.2", + "version": "3.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dlc-link-eth", - "version": "3.2.2", + "version": "3.3.0", "license": "ISC", "bin": { "dlc-link-eth": "scripts/index.js" diff --git a/package.json b/package.json index ef86f5f..d929d87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dlc-link-eth", - "version": "3.2.2", + "version": "3.3.0", "main": "scripts/index.js", "directories": { "test": "test" diff --git a/scripts/99_contract-configs.js b/scripts/99_contract-configs.js index fe0e0ab..4bb31d5 100644 --- a/scripts/99_contract-configs.js +++ b/scripts/99_contract-configs.js @@ -162,5 +162,63 @@ module.exports = function getContractConfigs(networkConfig, _btcFeeRecipient) { }); }, }, + { + name: 'PoolMerchant', + deployer: deployer.address, + upgradeable: true, + requirements: ['DLCManager', 'DLCBTC'], + deploy: async (requirementAddresses) => { + const DLCManagerAddress = requirementAddresses['DLCManager']; + const DLCBTCAddress = requirementAddresses['DLCBTC']; + if (!DLCManagerAddress) + throw new Error('DLCManager deployment not found.'); + if (!DLCBTCAddress) + throw new Error('DLCBTC deployment not found.'); + await beforeDeployment( + 'PoolMerchant', + `dlcManager: ${DLCManagerAddress}, \ + dlcBTC: ${DLCBTCAddress}, \ + deployer: ${deployer.address}`, + networkName + ); + const PoolMerchant = + await hardhat.ethers.getContractFactory('PoolMerchant'); + const poolMerchant = await hardhat.upgrades.deployProxy( + PoolMerchant, + [DLCManagerAddress, DLCBTCAddress, deployer.address] + ); + await poolMerchant.deployed(); + + await afterDeployment( + 'PoolMerchant', + poolMerchant, + networkName + ); + + try { + console.log('Whitelisting PoolMerchant...'); + const dlcManager = await hardhat.ethers.getContractAt( + 'DLCManager', + DLCManagerAddress + ); + await dlcManager + .connect(deployer) + .whitelistAddress(poolMerchant.address); + } catch (error) { + console.error(error); + } + + return poolMerchant.address; + }, + verify: async () => { + const address = await loadContractAddress( + 'PoolMerchant', + networkName + ); + await hardhat.run('verify:verify', { + address: address, + }); + }, + }, ]; }; diff --git a/scripts/enzyme-tests.js b/scripts/enzyme-tests.js new file mode 100644 index 0000000..97137eb --- /dev/null +++ b/scripts/enzyme-tests.js @@ -0,0 +1,293 @@ +// const hardhat = require('hardhat'); +// const { +// loadDeploymentInfo, +// } = require('./helpers/deployment-handlers_versioned'); +// const callManagerContractFunction = require('./helpers/00-call-dlc-manager-fn'); + +// const { getSignatures, setSigners } = require('../test/utils'); + +// // This script requires forking Arbitrum Mainnet from at least block 260000776 +// async function main() { +// await hardhat.run('compile'); +// const accounts = await hardhat.ethers.getSigners(); +// let attestor1, attestor2, attestor3; +// let attestors; +// attestor1 = accounts[6]; +// attestor2 = accounts[7]; +// attestor3 = accounts[8]; +// attestors = [attestor1, attestor2, attestor3]; +// let btcFeeRecipient = '0x000001'; + +// const dlcAdminSafe = await hardhat.ethers.getImpersonatedSigner( +// '0xaA2949C5285C2f2887ABD567865344240c29d619' +// ); +// const coordinator = await hardhat.ethers.getImpersonatedSigner( +// '0x3355977947F84C2b1CAE7D2903a72958aEE185e2' +// ); +// const enzymeManager = await hardhat.ethers.getImpersonatedSigner( +// '0x0DD4f29E21F10cb2E485cf9bDAb9F2dD1f240Bfa' +// ); + +// const enzymeComptrollerAddress = +// '0x0c164A2708477C8930E8964b3B8EB9038C86Ffee'; +// const enzymeVaultAddress = '0xe1f20e9855b2bce29f92579ba193230389795b46'; +// const admin = accounts[0]; +// const userAddress = accounts[1].address; + +// const deployInfoManager = await loadDeploymentInfo( +// hardhat.network.name, +// 'DLCManager' +// ); +// const dlcManagerExisting = new hardhat.ethers.Contract( +// '0x20157DBAbb84e3BBFE68C349d0d44E48AE7B5AD2', +// deployInfoManager.contract.abi, +// admin +// ); + +// const deployInfoToken = await loadDeploymentInfo( +// hardhat.network.name, +// 'DLCBTC' +// ); + +// const dlcBTC = new hardhat.ethers.Contract( +// '0x050C24dBf1eEc17babE5fc585F06116A259CC77A', +// deployInfoToken.contract.abi, +// admin +// ); +// let dlcManager; + +// const dlcManagerExists = await hardhat.ethers.getContractAt( +// 'DLCManager', +// '0xe4097Ee7b1AE8a7E5DdCaD820Ca31ee4d2d9Ef92', +// admin +// ); + +// // console.log('dlcManagerExists:', dlcManagerExists); + +// // TODO: this check doesnt really work +// if (process.env.FORCE_DEPLOY) { +// const DLCManager = +// await hardhat.ethers.getContractFactory('DLCManager'); +// dlcManager = await hardhat.upgrades.deployProxy(DLCManager, [ +// '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', +// '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', +// 2, +// dlcBTC.address, +// btcFeeRecipient, +// ]); +// await dlcManager.deployed(); +// await setSigners(dlcManager, attestors); + +// const txTranferOwnership = await dlcManagerExisting +// .connect(dlcAdminSafe) +// .transferTokenContractOwnership(dlcManager.address); +// await txTranferOwnership.wait(); +// } else { +// dlcManager = dlcManagerExists; +// } + +// console.log('dlcManager:', dlcManager.address); +// console.log('dlcBTC:', dlcBTC.address); +// console.log('dlcBTC owner:', await dlcBTC.owner()); + +// let poolMerchant; + +// const pmExists = await hardhat.ethers.getContractAt( +// 'PoolMerchant', +// '0xf96C190E181b38c840B7832BbA9E8D527250a5FB', +// admin +// ); + +// // console.log('pmExists:', pmExists); + +// if (process.env.FORCE_DEPLOY) { +// const PoolMerchant = +// await hardhat.ethers.getContractFactory('PoolMerchant'); +// poolMerchant = await hardhat.upgrades.deployProxy(PoolMerchant, [ +// '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', +// '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', +// dlcManager.address, +// dlcBTC.address, +// enzymeComptrollerAddress, +// ]); +// await poolMerchant.deployed(); +// } else { +// poolMerchant = pmExists; +// } + +// const pm = new hardhat.ethers.Contract( +// poolMerchant.address, +// // '0x8d6e07a6c15e1cdb881d665e0da4d7c2004a1929', +// // ['function getNewUUID(address) view returns (bytes32)'], +// poolMerchant.interface, +// admin +// ); +// console.log('pm:', pm.address); +// const uuid = await pm.getNewUUID(admin.address); +// console.log('uuid:', uuid); + +// const enzymeVault = new hardhat.ethers.Contract( +// enzymeComptrollerAddress, +// [ +// 'function buyShares(uint256, uint256) external returns (uint256)', +// 'function getDenominationAsset() external view returns (address)', +// 'function vaultCallOnContract(address,bytes4,bytes) external', +// ], +// admin +// ); +// const asset = await enzymeVault.getDenominationAsset(); +// console.log('asset on enzyme:', asset); + +// await enzymeVault +// .connect(enzymeManager) +// .vaultCallOnContract( +// '0x2C6bef68DAbf0494bB5F727E63c8FB54f7D2c287', +// '0x8da3d736', +// '0x000000000000000000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f96c190e181b38c840b7832bba9e8d527250a5fb' +// ); + +// // set up pending vault +// // set it to funded +// // PM should have the funds +// // call sweep +// // enzyme shoud have the funds + +// const fakeWdTxId = 'fakeWdTxId'; +// const fakeTapPK = 'fakeTapPK'; +// const deposit = 100000000; + +// try { +// await dlcManager.connect(admin).whitelistAddress(pm.address); +// console.log('whitelisted pm on dlcManager'); +// } catch (error) { +// console.error('error whitelisting pm on dlcManager:', error); +// } +// await pm +// .connect(admin) +// .createPendingVault(userAddress, uuid, fakeTapPK, fakeWdTxId); +// console.log('created pending vault through pm'); +// const dlc = await dlcManager.getDLC(uuid); +// // console.log('dlc:', dlc); +// const pmBalanceBefore = await dlcBTC.balanceOf(pm.address); +// console.log('pmBalanceBefore Funded:', pmBalanceBefore.toString()); + +// const signatureBytesForFunding = await getSignatures( +// { +// uuid, +// btcTxId: fakeWdTxId, +// functionString: 'set-status-funded', +// newLockedAmount: deposit, +// }, +// attestors, +// 3 +// ); +// const tx2 = await dlcManager +// .connect(attestor1) +// .setStatusFunded(uuid, fakeWdTxId, signatureBytesForFunding, deposit); +// await tx2.wait(); + +// const pmBalanceAfter = await dlcBTC.balanceOf(pm.address); +// console.log('pmBalanceAfter Funded:', pmBalanceAfter.toString()); + +// console.log( +// 'pmSweptBalanceForUUIDBefore:', +// (await pm.getSweptAmountForUUID(uuid)).toString() +// ); + +// const enzymeVaultBalanceBefore = await dlcBTC.balanceOf(enzymeVaultAddress); +// console.log( +// 'enzymeVaultBalanceBefore:', +// enzymeVaultBalanceBefore.toString() +// ); + +// console.log('sweeping deposits...'); +// const txSweep = await pm.connect(admin).sweepDeposit(); +// const txReceipt = await txSweep.wait(); + +// // console.log('txEvents:', txReceipt.events); + +// console.log( +// 'pmSweptBalanceForUUIDAfter:', +// (await pm.getSweptAmountForUUID(uuid)).toString() +// ); +// const pmBalanceAfterSweep = await dlcBTC.balanceOf(pm.address); +// console.log('pmBalanceAfterSweep:', pmBalanceAfterSweep.toString()); +// const enzymeVaultBalanceAfter = await dlcBTC.balanceOf(enzymeVaultAddress); +// console.log('enzymeVaultBalanceAfter:', enzymeVaultBalanceAfter.toString()); + +// console.log('withdrawing funds...'); +// const withdrawAmount = 50000; +// const txWithdraw = await pm +// .connect(admin) +// .withdraw(uuid, withdrawAmount, fakeTapPK, fakeWdTxId); +// const txReceiptWithdraw = await txWithdraw.wait(); + +// const pmBalanceAfterWithdraw = await dlcBTC.balanceOf(pm.address); +// console.log('pmBalanceAfterWithdraw:', pmBalanceAfterWithdraw.toString()); +// const enzymeVaultBalanceAfterWithdraw = +// await dlcBTC.balanceOf(enzymeVaultAddress); +// console.log( +// 'enzymeVaultBalanceAfterWithdraw:', +// enzymeVaultBalanceAfterWithdraw.toString() +// ); +// const sweptAmountsAfterWithdraw = await pm.getSweptAmountForUUID(uuid); +// console.log( +// 'sweptAmountsAfterWithdraw:', +// sweptAmountsAfterWithdraw.toString() +// ); + +// // withdraw funds +// // set pending +// // set funded with less amount +// // sweepredeem +// // check balances + +// // const signatureBytesForPending = await getSignatures( +// // { +// // uuid, +// // btcTxId: fakeWdTxId, +// // functionString: 'set-status-pending', +// // newLockedAmount: 0, +// // }, +// // attestors, +// // 3 +// // ); +// // const tx3 = await dlcManager +// // .connect(attestor1) +// // .setStatusPending( +// // uuid, +// // fakeWdTxId, +// // signatureBytesForPending, +// // fakeTapPK, +// // 0 +// // ); +// // await tx3.wait(); +// // const signatureBytesForFunding2 = await getSignatures( +// // { +// // uuid, +// // btcTxId: fakeWdTxId, +// // functionString: 'set-status-funded', +// // newLockedAmount: newAmountLocked, +// // }, +// // attestors, +// // 3 +// // ); +// // const tx4 = await dlcManager +// // .connect(attestor1) +// // .setStatusFunded( +// // uuid, +// // fakeWdTxId, +// // signatureBytesForFunding2, +// // newAmountLocked +// // ); +// // await tx4.wait(); +// } + +// main() +// .then(() => process.exit(0)) +// .catch((error) => { +// console.error(error); +// process.exit(1); +// }); + +// // diff --git a/scripts/helpers/local-setup.sh b/scripts/helpers/local-setup.sh index 53392f0..dcb2cee 100755 --- a/scripts/helpers/local-setup.sh +++ b/scripts/helpers/local-setup.sh @@ -10,4 +10,4 @@ dlc-link-eth add-signer 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC dlc-link-eth set-whitelisting 'false' # Vault setup and print dlcUUID -dlc-link-eth setup-vault | grep dlcUUID +dlc-link-eth setup-vault | grep uuid diff --git a/scripts/pool_merchant_flow_with_integrationSample.js b/scripts/pool_merchant_flow_with_integrationSample.js new file mode 100644 index 0000000..ed0409b --- /dev/null +++ b/scripts/pool_merchant_flow_with_integrationSample.js @@ -0,0 +1,492 @@ +const hre = require('hardhat'); +const { ethers, upgrades } = require('hardhat'); +const { getSignatures, whitelistAddress } = require('../test/utils'); + +async function fundAccount(address) { + await hre.network.provider.send('hardhat_setBalance', [ + address, + '0x2000000000000000000', + ]); +} + +async function main() { + console.log('\nšŸ”Ø Compiling contracts...'); + await hre.run('compile'); + + console.log('\nšŸš€ Starting happy path integration test...'); + + const MAINNET_ADDRESSES = { + DLC_MANAGER: '0x20157DBAbb84e3BBFE68C349d0d44E48AE7B5AD2', + DLC_BTC: '0x050C24dBf1eEc17babE5fc585F06116A259CC77A', + }; + + const accounts = await ethers.getSigners(); + const deployer = accounts[0]; + const dlcAdmin = await ethers.getImpersonatedSigner( + '0xaA2949C5285C2f2887ABD567865344240c29d619' + ); + const dlcCritical = await ethers.getImpersonatedSigner( + '0x24f75096ad315Ab617a3d0f2621aC3e9D391Aa77' + ); + const attestor_1 = await ethers.getImpersonatedSigner( + '0x989E9c4005ABc2a8E4b85544B44d2d95cfDe08de' + ); + const attestor_2 = await ethers.getImpersonatedSigner( + '0xBe4aAE47A62f67bdF93eA9f5F189ae51B1b54492' + ); + const attestor_3 = await ethers.getImpersonatedSigner( + '0x7B254D8C6eBd9662A52180B06920aEA4f23a8940' + ); + const attestor_4 = await ethers.getImpersonatedSigner( + '0x194c697e8343EaB3C53917BA7e597d02687f8BA0' + ); + const attestor_5 = await ethers.getImpersonatedSigner( + '0x2daef70747eb9E97E5f31A9EBDbda593918F8bE7' + ); + const operator = deployer; + const harvester = deployer; + + const attestors = [ + attestor_1, + attestor_2, + attestor_3, + attestor_4, + attestor_5, + ]; + + console.log('\nšŸ’° Funding impersonated accounts...'); + for (const account of [dlcAdmin, ...attestors]) { + await fundAccount(account.address); + } + + console.log('\nšŸ”„ Upgrading DLCManager...'); + const proxyAdminAddress = await upgrades.erc1967.getAdminAddress( + MAINNET_ADDRESSES.DLC_MANAGER + ); + console.log('Proxy admin address:', proxyAdminAddress); + const connectedProxyAdmin = new ethers.Contract( + proxyAdminAddress, + [ + 'function owner() view returns (address)', + 'function upgrade(address, address) external', + ], + dlcCritical + ); + + const DLCManager = await ethers.getContractFactory('DLCManager'); + const dlcManagerImpl = await DLCManager.deploy(); + await dlcManagerImpl.deployed(); + + await connectedProxyAdmin.upgrade( + MAINNET_ADDRESSES.DLC_MANAGER, + dlcManagerImpl.address + ); + + console.log('DLCManager upgraded'); + + console.log('\nšŸ“ Getting contract instances...'); + const dlcManager = await ethers.getContractAt( + 'DLCManager', + MAINNET_ADDRESSES.DLC_MANAGER + ); + const dlcBTC = await ethers.getContractAt( + 'DLCBTC', + MAINNET_ADDRESSES.DLC_BTC + ); + + await dlcManager.connect(dlcAdmin).setSkipSignatureVerification(true); + await dlcManager + .connect(dlcAdmin) + .setMinterOnTokenContract(deployer.address); + + console.log('\nšŸ—ļø Deploying PoolMerchant...'); + const PoolMerchant = await ethers.getContractFactory('PoolMerchant'); + const poolMerchant = await upgrades.deployProxy(PoolMerchant, [ + MAINNET_ADDRESSES.DLC_MANAGER, + MAINNET_ADDRESSES.DLC_BTC, + deployer.address, + ]); + await poolMerchant.deployed(); + console.log('PoolMerchant deployed to:', poolMerchant.address); + + await dlcManager.connect(dlcAdmin).whitelistAddress(poolMerchant.address); + + console.log('\nšŸ—ļø Deploying MockERC4626Vault...'); + const MockERC4626Vault = + await ethers.getContractFactory('MockERC4626Vault'); + const mockVault = await MockERC4626Vault.deploy(MAINNET_ADDRESSES.DLC_BTC); + await mockVault.deployed(); + console.log('MockERC4626Vault deployed to:', mockVault.address); + + console.log('\nšŸ—ļø Deploying IntegrationSample...'); + const IntegrationSample = + await ethers.getContractFactory('IntegrationSample'); + + const rewardRatePerSecond = ethers.BigNumber.from('317'); + + const integrationSample = await IntegrationSample.deploy( + mockVault.address, + MAINNET_ADDRESSES.DLC_BTC, + rewardRatePerSecond, + poolMerchant.address, + dlcBTC.address + ); + await integrationSample.deployed(); + console.log('IntegrationSample deployed to:', integrationSample.address); + + console.log('\nšŸ”‘ Setting up roles and integration...'); + await poolMerchant.grantRole( + await poolMerchant.ATTESTOR_ROLE(), + attestor_1.address + ); + await poolMerchant.grantRole( + await poolMerchant.ATTESTOR_ROLE(), + operator.address + ); + await poolMerchant.grantRole( + await poolMerchant.HARVESTER_ROLE(), + harvester.address + ); + + console.log('\nšŸŖ™ Adding DLCBTC as reward token...'); + await poolMerchant.addRewardToken(MAINNET_ADDRESSES.DLC_BTC); + + console.log('\nšŸ”„ Setting up IntegrationSample...'); + await poolMerchant.setIntegration(integrationSample.address, [ + MAINNET_ADDRESSES.DLC_BTC, + ]); + + console.log('\nšŸ“¦ Creating vault with IntegrationSample...'); + const mockBtcTxId = '0x123'; + const mockTaprootPubkey = '0x12345'; + + const tx = await poolMerchant + .connect(attestor_1) + .createPendingVault( + mockTaprootPubkey, + mockBtcTxId, + integrationSample.address, + { + gasLimit: 1000000, + } + ); + + const receipt = await tx.wait(); + const vaultId = await poolMerchant.getVaultByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + console.log('Vault created with ID:', vaultId); + + console.log('\nšŸ’° Funding vault...'); + const fundAmount = ethers.utils.parseUnits('1', 8); + console.log('Funding amount:', fundAmount.toString()); + + const tx3 = await dlcManager + .connect(attestor_1) + .setStatusFunded(vaultId, mockBtcTxId, [], fundAmount); + await tx3.wait(); + + console.log('\nšŸ“ˆ Allocating to IntegrationSample...'); + + const integrationSharesBefore = await mockVault.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample ERC4626 shares before allocation:', + integrationSharesBefore.toString() + ); + + await poolMerchant + .connect(operator) + .allocateToIntegration(mockTaprootPubkey, integrationSample.address); + + const shares = await poolMerchant.getVaultSharesByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + console.log('Allocated shares for vault:', shares.toString()); + + const integrationSharesAfter = await mockVault.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample ERC4626 shares after allocation:', + integrationSharesAfter.toString() + ); + + console.log('\nā³ Mining blocks to accrue rewards...'); + await hre.network.provider.send('hardhat_mine', ['0x100']); + + console.log( + '\nšŸ¦ Performing partial withdrawal to trigger reward harvest...' + ); + const withdrawAmount = fundAmount.div(2); + + console.log('\nšŸ” Testing withdrawal process...'); + + const vaultBefore = + await poolMerchant.getVaultAllocationDetailsByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + const sharesBefore = + await poolMerchant.getVaultSharesByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + console.log('\nInitial state:'); + console.log(' - Total minted:', vaultBefore.valueMinted.toString()); + console.log(' - Allocated:', vaultBefore.allocated.toString()); + console.log(' - Shares:', sharesBefore.toString()); + + const integrationBalance = await dlcBTC.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample dlcBTC balance:', + integrationBalance.toString() + ); + + const vaultBalance = await dlcBTC.balanceOf(mockVault.address); + console.log('MockERC4626Vault dlcBTC balance:', vaultBalance.toString()); + + console.log( + '\nAttempting withdrawal of shares:', + withdrawAmount.toString() + ); + + const previewRedeem = await mockVault.previewRedeem(withdrawAmount); + console.log('Preview redeem amount:', previewRedeem.toString()); + const maxRedeem = await mockVault.maxRedeem(integrationSample.address); + + try { + const integrationSampleSharesBalance = await mockVault.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample ERC4626 shares balance before withdrawal:', + integrationSampleSharesBalance.toString() + ); + + if (integrationSampleSharesBalance.lt(withdrawAmount)) { + console.log( + 'āš ļø IntegrationSample does not have enough balance to withdraw' + ); + } + const withdrawTx = await poolMerchant + .connect(operator) + .withdrawFromVault( + mockTaprootPubkey, + integrationSample.address, + withdrawAmount, + { + gasLimit: 2000000, + } + ); + + await withdrawTx.wait(); + console.log('Withdrawal successful!'); + + const vaultAfter = + await poolMerchant.getVaultAllocationDetailsByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + const sharesAfter = + await poolMerchant.getVaultSharesByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address + ); + console.log('\nPost-withdrawal state:'); + console.log(' - Total minted:', vaultAfter.valueMinted.toString()); + console.log(' - Allocated:', vaultAfter.allocated.toString()); + console.log(' - Shares:', sharesAfter.toString()); + + const dlcBTCBalance = await dlcBTC.balanceOf(poolMerchant.address); + console.log('\nDLCBTC balances:'); + console.log(' - PoolMerchant:', dlcBTCBalance.toString()); + + const integrationAssetBalanceAfter = await dlcBTC.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample asset balance after withdrawal:', + integrationAssetBalanceAfter.toString() + ); + + const vaultAssetBalanceAfter = await dlcBTC.balanceOf( + mockVault.address + ); + console.log( + 'MockERC4626Vault asset balance after withdrawal:', + vaultAssetBalanceAfter.toString() + ); + } catch (error) { + console.log('\nāŒ Withdrawal failed:', error.message); + + const integrationBalanceAfter = await dlcBTC.balanceOf( + integrationSample.address + ); + console.log( + 'IntegrationSample asset balance after withdrawal attempt:', + integrationBalanceAfter.toString() + ); + + const vaultBalanceAfter = await dlcBTC.balanceOf(mockVault.address); + console.log( + 'MockERC4626Vault asset balance after withdrawal attempt:', + vaultBalanceAfter.toString() + ); + } + // First mint some reward tokens to the integration + const rewardAmount = ethers.utils.parseUnits('1000', 8); // 1000 DLC + console.log( + '\nšŸ’° Minting rewards to IntegrationSample:', + rewardAmount.toString() + ); + await dlcBTC.mint(integrationSample.address, rewardAmount); + + // Check initial rewards state + console.log('\nšŸ“Š Checking initial rewards state...'); + const initialState = await integrationSample.getRewardState(); + console.log('Integration reward state:', { + totalShares: initialState.totalShares.toString(), + currentRewardBalance: initialState.currentRewardBalance.toString(), + trackedRewardBalance: initialState.trackedRewardBalance.toString(), + accRewardPerShare: initialState.accRewardPerShare.toString(), + }); + + // Check vault's initial reward state + console.log('\nšŸ“Š Checking vault initial rewards...'); + const [lastClaimedAt, pendingAmount] = + await poolMerchant.getVaultRewardByTaprootAndIntegration( + mockTaprootPubkey, + integrationSample.address, + MAINNET_ADDRESSES.DLC_BTC + ); + console.log('Vault reward state:', { + lastClaimedAt: lastClaimedAt.toString(), + pendingAmount: pendingAmount.toString(), + }); + + // Mine blocks to accrue rewards + console.log('\nā³ Mining blocks to accrue rewards...'); + await hre.network.provider.send('hardhat_mine', ['0x100']); + + // Check pending rewards for vault + console.log('\nšŸ” Checking pending integration rewards...'); + const pendingRewards = await integrationSample.getPendingRewards(); + console.log('Pending integration rewards:', pendingRewards[0].toString()); + + // Get vault's allocation details before harvest + console.log('\nšŸ“Š Vault allocation before harvest:'); + const vaultAllocationBefore = + await poolMerchant.getVaultAllocationDetails(vaultId); + console.log({ + valueMinted: vaultAllocationBefore.valueMinted.toString(), + allocated: vaultAllocationBefore.allocated.toString(), + unallocated: vaultAllocationBefore.unallocated.toString(), + }); + + // Check PoolMerchant's DLC balance before harvest + const poolMerchantBalanceBefore = await dlcBTC.balanceOf( + poolMerchant.address + ); + console.log( + '\nšŸ’° PoolMerchant DLC balance before harvest:', + poolMerchantBalanceBefore.toString() + ); + + // Harvest rewards + console.log('\nšŸŒ¾ Harvesting rewards...'); + const harvestTx = await poolMerchant + .connect(harvester) + .harvestRewardsForIntegration(integrationSample.address); + const harvestReceipt = await harvestTx.wait(); + + // Get harvest events + console.log('\nšŸ“œ Checking harvest events...'); + const harvestEvents = harvestReceipt.events.filter( + (e) => e.event === 'RewardsHarvested' + ); + for (const event of harvestEvents) { + console.log('Harvest event:', { + integration: event.args.integration, + rewardToken: event.args.rewardToken, + harvester: event.args.harvester, + amount: event.args.amount.toString(), + }); + } + + // Check final reward states + console.log('\nšŸ“Š Checking final states...'); + + // Integration final state + const finalState = await integrationSample.getRewardState(); + console.log('Integration final state:', { + totalShares: finalState.totalShares.toString(), + currentRewardBalance: finalState.currentRewardBalance.toString(), + trackedRewardBalance: finalState.trackedRewardBalance.toString(), + accRewardPerShare: finalState.accRewardPerShare.toString(), + }); + + // Vault's final reward state + const [finalLastClaimedAt, finalPendingAmount] = + await poolMerchant.getVaultReward(vaultId, MAINNET_ADDRESSES.DLC_BTC); + console.log('Vault final reward state:', { + lastClaimedAt: finalLastClaimedAt.toString(), + pendingAmount: finalPendingAmount.toString(), + }); + + // PoolMerchant's final balance + const poolMerchantBalanceAfter = await dlcBTC.balanceOf( + poolMerchant.address + ); + console.log( + '\nšŸ’° PoolMerchant DLC balance after harvest:', + poolMerchantBalanceAfter.toString() + ); + + // Check if rewards can be claimed + console.log('\nšŸŽÆ Attempting to claim rewards...'); + try { + const claimTx = await poolMerchant + .connect(operator) + .claimRewards( + mockTaprootPubkey, + integrationSample.address, + MAINNET_ADDRESSES.DLC_BTC + ); + const claimReceipt = await claimTx.wait(); + + const claimEvents = claimReceipt.events.filter( + (e) => e.event === 'RewardsClaimed' + ); + for (const event of claimEvents) { + console.log('Claim event:', { + uuid: event.args.uuid, + rewardToken: event.args.rewardToken, + amount: event.args.amount.toString(), + }); + } + } catch (error) { + console.log('āŒ Claim failed:', error.message); + } + + // Final balance check + console.log('\nšŸ“Š Final balance check:'); + const finalBalances = { + poolMerchant: await dlcBTC.balanceOf(poolMerchant.address), + integration: await dlcBTC.balanceOf(integrationSample.address), + vault: await dlcBTC.balanceOf(mockVault.address), + }; + console.log(finalBalances); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/pool_merchant_setup_flow.js b/scripts/pool_merchant_setup_flow.js new file mode 100644 index 0000000..1900930 --- /dev/null +++ b/scripts/pool_merchant_setup_flow.js @@ -0,0 +1,154 @@ +require('dotenv').config(); +const hre = require('hardhat'); +const { ethers, upgrades } = require('hardhat'); +const { loadContractAddress, promptUser } = require('./helpers/utils'); +const { + saveDeploymentInfo, + deploymentInfo, +} = require('./helpers/deployment-handlers_versioned'); + +async function main() { + const network = hre.network.name; + const shouldContinue = await promptUser( + `You are about to interact with ${network}.\n Continue?` + ); + if (!shouldContinue) { + throw new Error('Deployment aborted by user.'); + } + // Compile contracts + console.log('\nšŸ”Ø Compiling contracts...'); + await hre.run('compile'); + + console.log('\nšŸš€ Starting setup...'); + + const accounts = await ethers.getSigners(); + const deployer = accounts[0]; + const dlcAdmin = deployer; + const attestor_1 = '0x3355977947F84C2b1CAE7D2903a72958aEE185e2'; // TODO: devnet attestor 1 + const operator = deployer; + const harvester = deployer; + + // DLCManager upgrade remains the same + console.log('\nšŸ”„ Upgrading DLCManager...'); + const proxyAddress = await loadContractAddress('DLCManager', network); + // const newImplementation = await ethers.getContractFactory('DLCManager'); + // await upgrades.upgradeProxy(proxyAddress, newImplementation); + + // await hre.run('verify:verify', { + // address: proxyAddress, + // }); + // console.log('DLCManager upgraded'); + + console.log('\nšŸ“ Getting contract instances...'); + const dlcManager = await ethers.getContractAt('DLCManager', proxyAddress); + + // try { + // await saveDeploymentInfo( + // deploymentInfo(network, dlcManager, 'DLCManager') + // ); + // } catch (error) { + // console.error(error); + // } + + const dlcBTCAddress = await loadContractAddress('DLCBTC', network); + const dlcBTC = await ethers.getContractAt('DLCBTC', dlcBTCAddress); + + // Deploy PoolMerchant + console.log('\nšŸ—ļø Deploying PoolMerchant...'); + const poolMerchantAddress = await loadContractAddress( + 'PoolMerchant', + network + ); + const poolMerchant = await ethers.getContractAt( + 'PoolMerchant', + poolMerchantAddress + ); + // const PoolMerchant = await ethers.getContractFactory('PoolMerchant'); + // const poolMerchant = await upgrades.deployProxy(PoolMerchant, [ + // proxyAddress, + // dlcBTCAddress, + // deployer.address, + // ]); + // await poolMerchant.deployed(); + // console.log('PoolMerchant deployed to:', poolMerchant.address); + // try { + // await saveDeploymentInfo( + // deploymentInfo(network, poolMerchant, 'PoolMerchant') + // ); + // } catch (error) { + // console.error(error); + // } + // await hre.run('verify:verify', { + // address: poolMerchant.address, + // }); + + // // Whitelist PoolMerchant + // await dlcManager.connect(dlcAdmin).whitelistAddress(poolMerchant.address); + + console.log('\nšŸ—ļø Deploying MockERC4626Vault...'); + const MockERC4626Vault = + await ethers.getContractFactory('MockERC4626Vault'); + + const mockVault = await ethers.getContractAt( + 'MockERC4626Vault', + '0x7B4c1663945767426177277Fa04777912720D39A' + ); + // const mockVault = await MockERC4626Vault.deploy(dlcBTCAddress); + // await mockVault.deployed(); + // console.log('MockERC4626Vault deployed to:', mockVault.address); + // await hre.run('verify:verify', { + // address: mockVault.address, + // }); + + console.log('\nšŸ—ļø Deploying IntegrationSample...'); + const IntegrationSample = + await ethers.getContractFactory('IntegrationSample'); + + const rewardRatePerSecond = ethers.BigNumber.from('317'); + + const integrationSample = await IntegrationSample.deploy( + mockVault.address, + dlcBTCAddress, + rewardRatePerSecond, + poolMerchant.address, + dlcBTC.address + ); + await integrationSample.deployed(); + console.log('IntegrationSample deployed to:', integrationSample.address); + + try { + await hre.run('verify:verify', { + address: integrationSample.address, + }); + } catch (error) { + console.error(error); + } + + // Setup roles and integration + console.log('\nšŸ”‘ Setting up roles and integration...'); + await poolMerchant.grantRole( + await poolMerchant.ATTESTOR_ROLE(), + attestor_1 + ); + await poolMerchant.grantRole( + await poolMerchant.HARVESTER_ROLE(), + harvester.address + ); + + console.log('\nšŸŖ™ Adding dlcBTC as reward token...'); + await poolMerchant.addRewardToken(dlcBTCAddress); + + console.log('\nšŸ”„ Setting up Integration...'); + await poolMerchant.setIntegration(integrationSample.address, [ + dlcBTCAddress, + ]); + + console.log('\nāœ… Setup sequence complete'); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/test/DLCManager.test.js b/test/DLCManager.test.js index b10b8ab..f1a7427 100644 --- a/test/DLCManager.test.js +++ b/test/DLCManager.test.js @@ -7,12 +7,9 @@ const { getSignatures, setSigners, getMultipleSignaturesForSameAttestorAndMessage, + whitelistAddress, } = require('./utils'); -async function whitelistAddress(dlcManager, user) { - await dlcManager.whitelistAddress(user.address); -} - describe('DLCManager', () => { let dlcManager, dlcBtc, uuid; let accounts, deployer, user, randomAccount, anotherAccount, protocol; diff --git a/test/PoolMerchant.test.js b/test/PoolMerchant.test.js new file mode 100644 index 0000000..40df329 --- /dev/null +++ b/test/PoolMerchant.test.js @@ -0,0 +1,409 @@ +const { expect } = require('chai'); +const { ethers, upgrades } = require('hardhat'); +const hardhat = require('hardhat'); + +const { getSignatures, setSigners, whitelistAddress } = require('./utils'); + +async function getEventArg(tx, eventName, argName) { + const receipt = await tx.wait(); + const event = receipt.events.find((e) => e.event === eventName); + return event.args[argName]; +} + +describe('PoolMerchant', () => { + let poolMerchant; + let dlcManager; + let dlcBtc; + let deployer; + let attestor_1; + let attestor_2; + let attestor_3; + let attestors; + let harvester; + let user; + let mockIntegration; + let mockRewardToken; + const mockTaprootPubkey = 'taproot123'; + const mockWithdrawalTxId = 'tx123'; + + const ATTESTOR_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes('ATTESTOR_ROLE') + ); + const HARVESTER_ROLE = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes('HARVESTER_ROLE') + ); + + let btcFeeRecipient = '0x000001'; + + beforeEach(async function () { + [deployer, attestor_1, attestor_2, attestor_3, harvester, user] = + await ethers.getSigners(); + + attestors = [attestor_1, attestor_2, attestor_3]; + + const DLCBTC = await ethers.getContractFactory('DLCBTC', deployer); + dlcBtc = await hardhat.upgrades.deployProxy(DLCBTC); + await dlcBtc.deployed(); + + // DLCManager + const DLCManager = await ethers.getContractFactory('DLCManager'); + dlcManager = await hardhat.upgrades.deployProxy(DLCManager, [ + deployer.address, + deployer.address, + 3, + dlcBtc.address, + btcFeeRecipient, + ]); + await dlcManager.deployed(); + await dlcBtc.transferOwnership(dlcManager.address); + await setSigners(dlcManager, attestors); + + // Deploy Mock Integration + + // Deploy Mock Reward Token + const MockERC20 = await ethers.getContractFactory('MockERC20'); + mockRewardToken = await MockERC20.deploy('Mock Reward', 'MRWD', 18); + + const MockIntegration = + await ethers.getContractFactory('MockIntegration'); + mockIntegration = await MockIntegration.deploy([ + mockRewardToken.address, + ]); + + // Deploy PoolMerchant + const PoolMerchant = await ethers.getContractFactory('PoolMerchant'); + poolMerchant = await upgrades.deployProxy(PoolMerchant, [ + dlcManager.address, + dlcBtc.address, + deployer.address, + ]); + + // Setup roles + await poolMerchant.grantRole(ATTESTOR_ROLE, attestor_1.address); + await poolMerchant.grantRole(HARVESTER_ROLE, harvester.address); + + await whitelistAddress(dlcManager, poolMerchant); + }); + + describe('Initialization', function () { + it('should initialize with correct state', async function () { + expect(await poolMerchant.dlcManager()).to.equal( + dlcManager.address + ); + expect(await poolMerchant.dlcBTC()).to.equal(dlcBtc.address); + expect( + await poolMerchant.hasRole(ATTESTOR_ROLE, attestor_1.address) + ).to.be.true; + expect( + await poolMerchant.hasRole(HARVESTER_ROLE, harvester.address) + ).to.be.true; + }); + }); + + describe('Vault Creation and Integration', function () { + beforeEach(async function () { + await poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address); + await poolMerchant + .connect(deployer) + .setIntegration(mockIntegration.address, [ + mockRewardToken.address, + ]); + }); + + it('should create a pending vault with integration', async function () { + const tx = await poolMerchant + .connect(attestor_1) + .createPendingVault( + mockTaprootPubkey, + mockWithdrawalTxId, + mockIntegration.address + ); + + const uuid = await getEventArg(tx, 'PendingVaultCreated', 'uuid'); + + await expect(tx) + .to.emit(poolMerchant, 'PendingVaultCreated') + .withArgs( + uuid, + mockTaprootPubkey, + mockWithdrawalTxId, + mockIntegration.address + ); + + const vaultId = await poolMerchant.getVaultByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + expect(vaultId).to.equal(uuid); + + expect( + await poolMerchant.getVaultIntegrationByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ) + ).to.equal(mockIntegration.address); + }); + + it('should not create vault with inactive integration', async function () { + const MockIntegration = + await ethers.getContractFactory('MockIntegration'); + const newIntegration = await MockIntegration.deploy([ + mockRewardToken.address, + ]); + + await expect( + poolMerchant + .connect(attestor_1) + .createPendingVault( + mockTaprootPubkey, + mockWithdrawalTxId, + newIntegration.address + ) + ).to.be.revertedWith('Integration not active'); + }); + }); + + describe('Reward Token Management', function () { + it('should add reward token', async function () { + await expect( + poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address) + ) + .to.emit(poolMerchant, 'RewardTokenAdded') + .withArgs(mockRewardToken.address); + + const rewardToken = await poolMerchant.rewardTokens( + mockRewardToken.address + ); + expect(rewardToken.tokenAddress).to.equal(mockRewardToken.address); + expect(rewardToken.isActive).to.be.true; + }); + + it('should not add same reward token twice', async function () { + await poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address); + await expect( + poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address) + ).to.be.revertedWith('Token already added'); + }); + + it('should only allow admin to add reward tokens', async function () { + await expect( + poolMerchant + .connect(user) + .addRewardToken(mockRewardToken.address) + ).to.be.reverted; + }); + }); + + describe('Integration Management', function () { + beforeEach(async function () { + await poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address); + }); + + it('should set integration with supported rewards', async function () { + await expect( + poolMerchant + .connect(deployer) + .setIntegration(mockIntegration.address, [ + mockRewardToken.address, + ]) + ) + .to.emit(poolMerchant, 'IntegrationAdded') + .withArgs(mockIntegration.address, [mockRewardToken.address]); + + const integration = await poolMerchant.integrations( + mockIntegration.address + ); + expect(integration.isActive).to.be.true; + expect(integration.totalShares).to.equal(0); + expect(integration.strategy).to.equal(mockIntegration.address); + }); + + it('should not allow setting integration with unsupported reward token', async function () { + await expect( + poolMerchant + .connect(deployer) + .setIntegration(mockIntegration.address, [ + ethers.constants.AddressZero, + ]) + ).to.be.revertedWith('Invalid reward token'); + }); + + it('should only allow admin to set integration', async function () { + await expect( + poolMerchant + .connect(user) + .setIntegration(mockIntegration.address, [ + mockRewardToken.address, + ]) + ).to.be.reverted; + }); + }); + + describe('Allocation and Rewards', function () { + const initialFunding = ethers.utils.parseUnits('1', 8); + + beforeEach(async function () { + // Setup integration + await poolMerchant + .connect(deployer) + .addRewardToken(mockRewardToken.address); + await poolMerchant + .connect(deployer) + .setIntegration(mockIntegration.address, [ + mockRewardToken.address, + ]); + + // Create vault + const tx = await poolMerchant + .connect(attestor_1) + .createPendingVault( + mockTaprootPubkey, + mockWithdrawalTxId, + mockIntegration.address + ); + + const vaultId = await getEventArg( + tx, + 'PendingVaultCreated', + 'uuid' + ); + + // Fund vault + const signatureBytesForFunding = await getSignatures( + { + uuid: vaultId, + btcTxId: mockWithdrawalTxId, + functionString: 'set-status-funded', + newLockedAmount: initialFunding, + }, + attestors, + 3 + ); + await dlcManager + .connect(attestor_1) + .setStatusFunded( + vaultId, + mockWithdrawalTxId, + signatureBytesForFunding, + initialFunding + ); + }); + + it('should allocate to integration', async function () { + const vaultId = await poolMerchant.getVaultByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + + await expect( + poolMerchant + .connect(attestor_1) + .allocateToIntegration( + mockTaprootPubkey, + mockIntegration.address + ) + ) + .to.emit(poolMerchant, 'SharesAllocated') + .withArgs(vaultId, mockIntegration.address, initialFunding); + + const shares = + await poolMerchant.getVaultSharesByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + expect(shares).to.equal(initialFunding); + + const details = + await poolMerchant.getVaultAllocationDetailsByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + expect(details.allocated).to.equal(initialFunding); + expect(details.unallocated).to.equal(0); + }); + + it('should harvest rewards during withdrawal', async function () { + // First allocate + await poolMerchant + .connect(attestor_1) + .allocateToIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + + // Mock some rewards + const rewardAmount = ethers.utils.parseEther('10'); + await mockRewardToken.mint(mockIntegration.address, rewardAmount); + await mockIntegration.mockRewards([rewardAmount]); + + const withdrawAmount = initialFunding.div(2); + const vaultId = await poolMerchant.getVaultByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + + await expect( + poolMerchant + .connect(attestor_1) + .withdrawFromVault( + mockTaprootPubkey, + mockIntegration.address, + withdrawAmount + ) + ) + .to.emit(poolMerchant, 'RewardsHarvested') + .withArgs( + mockIntegration.address, + mockRewardToken.address, + poolMerchant.address, + rewardAmount + ) + .and.to.emit(poolMerchant, 'VaultWithdrawn') + .withArgs(vaultId, withdrawAmount); + + const [_, pendingAmount] = + await poolMerchant.getVaultRewardByTaprootAndIntegration( + mockTaprootPubkey, + mockIntegration.address, + mockRewardToken.address + ); + expect(pendingAmount).to.equal(rewardAmount); + }); + + it('should still allow manual reward harvesting by harvester', async function () { + await poolMerchant + .connect(attestor_1) + .allocateToIntegration( + mockTaprootPubkey, + mockIntegration.address + ); + + const rewardAmount = ethers.utils.parseEther('10'); + await mockRewardToken.mint(mockIntegration.address, rewardAmount); + await mockIntegration.mockRewards([rewardAmount]); + + await expect( + poolMerchant + .connect(harvester) + .harvestRewardsForIntegration(mockIntegration.address) + ) + .to.emit(poolMerchant, 'RewardsHarvested') + .withArgs( + mockIntegration.address, + mockRewardToken.address, + poolMerchant.address, + rewardAmount + ); + }); + }); +}); diff --git a/test/utils.js b/test/utils.js index eabe75a..c4c88b9 100644 --- a/test/utils.js +++ b/test/utils.js @@ -95,8 +95,13 @@ async function getMultipleSignaturesForSameAttestorAndMessage( return signatureBytes; } +async function whitelistAddress(dlcManager, user) { + await dlcManager.whitelistAddress(user.address); +} + module.exports = { getSignatures, setSigners, getMultipleSignaturesForSameAttestorAndMessage, + whitelistAddress, };