diff --git a/packages/protocol/.openzeppelin/mainnet.json b/packages/protocol/.openzeppelin/mainnet.json index 97bcde2ae..eb3c87a5a 100644 --- a/packages/protocol/.openzeppelin/mainnet.json +++ b/packages/protocol/.openzeppelin/mainnet.json @@ -11,11 +11,21 @@ "txHash": "0xabfdae678419c4b240b182e267471ee6e21c892454b5974b02962b0b68171405", "kind": "transparent" }, + { + "address": "0x0Cd73c18C085dEB287257ED2307eC713e9Af3460", + "txHash": "0x90a8a44b3ca62d71ead3477ba61a78b4177ee9ab4284f2350bbfd58648cceb64", + "kind": "transparent" + }, { "address": "0x6a445E9F40e0b97c92d0b8a3366cEF1d67F700BF", "txHash": "0x10f063c96096093eeb1064fad1d6f8cffffe04ecd1f5591d4372d40ee1a813d0", "kind": "transparent" }, + { + "address": "0x84AC02474c4656C88d4e08FCA63ff73070787C3d", + "txHash": "0x0d8b9df864dc4f44a881b68944b3f8022956e0571e13917fa621f8c883a5caac", + "kind": "transparent" + }, { "address": "0xaA425F8BfE82CD18f634e2Fe91E5DdEeFD98fDA1", "txHash": "0xdb231d4c6d0a07a634aa8288bb25f4329ae3a88c4d351de9121431ca90fcb7c8", @@ -50,16 +60,6 @@ "address": "0xba0439088dc1e75F58e0A7C107627942C15cbb41", "txHash": "0x517054372b36470ea012f6b23d165e5f3d3366d26329cd57b6e83a376ffb5614", "kind": "transparent" - }, - { - "address": "0x84AC02474c4656C88d4e08FCA63ff73070787C3d", - "txHash": "0x0d8b9df864dc4f44a881b68944b3f8022956e0571e13917fa621f8c883a5caac", - "kind": "transparent" - }, - { - "address": "0x0Cd73c18C085dEB287257ED2307eC713e9Af3460", - "txHash": "0x90a8a44b3ca62d71ead3477ba61a78b4177ee9ab4284f2350bbfd58648cceb64", - "kind": "transparent" } ], "impls": { @@ -2487,7 +2487,7 @@ { "contract": "PoolTokens", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/PoolTokens.sol:20" }, { @@ -2499,12 +2499,12 @@ { "contract": "PoolTokens", "label": "pools", - "type": "t_mapping(t_address,t_struct(PoolInfo)19715_storage)", + "type": "t_mapping(t_address,t_struct(PoolInfo)19766_storage)", "src": "contracts/protocol/core/PoolTokens.sol:32" } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_mapping(t_uint256,t_struct(TokenInfo)10129_storage)": { @@ -2541,10 +2541,10 @@ "t_address": { "label": "address" }, - "t_mapping(t_address,t_struct(PoolInfo)19715_storage)": { + "t_mapping(t_address,t_struct(PoolInfo)19766_storage)": { "label": "mapping(address => struct PoolTokens.PoolInfo)" }, - "t_struct(PoolInfo)19715_storage": { + "t_struct(PoolInfo)19766_storage": { "label": "struct PoolTokens.PoolInfo", "members": [ { @@ -2813,7 +2813,7 @@ { "contract": "SeniorPool", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/SeniorPool.sol:22" }, { @@ -2830,7 +2830,7 @@ } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_uint256": { @@ -3383,12 +3383,12 @@ { "contract": "GoldfinchFactory", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/GoldfinchFactory.sol:20" } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_array(t_uint256)50_storage": { @@ -3545,7 +3545,7 @@ { "contract": "BackerRewards", "label": "config", - "type": "t_contract(GoldfinchConfig)17306", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/rewards/BackerRewards.sol:32" }, { @@ -3575,27 +3575,27 @@ { "contract": "BackerRewards", "label": "tokens", - "type": "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28085_storage)", + "type": "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28109_storage)", "src": "contracts/rewards/BackerRewards.sol:50" }, { "contract": "BackerRewards", "label": "pools", - "type": "t_mapping(t_address,t_struct(BackerRewardsInfo)28080_storage)", + "type": "t_mapping(t_address,t_struct(BackerRewardsInfo)28104_storage)", "src": "contracts/rewards/BackerRewards.sol:52" } ], "types": { - "t_contract(GoldfinchConfig)17306": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_uint256": { "label": "uint256" }, - "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28085_storage)": { + "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28109_storage)": { "label": "mapping(uint256 => struct BackerRewards.BackerRewardsTokenInfo)" }, - "t_struct(BackerRewardsTokenInfo)28085_storage": { + "t_struct(BackerRewardsTokenInfo)28109_storage": { "label": "struct BackerRewards.BackerRewardsTokenInfo", "members": [ { @@ -3608,13 +3608,13 @@ } ] }, - "t_mapping(t_address,t_struct(BackerRewardsInfo)28080_storage)": { + "t_mapping(t_address,t_struct(BackerRewardsInfo)28104_storage)": { "label": "mapping(address => struct BackerRewards.BackerRewardsInfo)" }, "t_address": { "label": "address" }, - "t_struct(BackerRewardsInfo)28080_storage": { + "t_struct(BackerRewardsInfo)28104_storage": { "label": "struct BackerRewards.BackerRewardsInfo", "members": [ { @@ -4790,13 +4790,13 @@ { "contract": "Go", "label": "config", - "type": "t_contract(GoldfinchConfig)17548", + "type": "t_contract(GoldfinchConfig)17564", "src": "contracts/protocol/core/Go.sol:20" }, { "contract": "Go", "label": "legacyGoList", - "type": "t_contract(GoldfinchConfig)17548", + "type": "t_contract(GoldfinchConfig)17564", "src": "contracts/protocol/core/Go.sol:23" }, { @@ -4810,7 +4810,7 @@ "t_address": { "label": "address" }, - "t_contract(GoldfinchConfig)17548": { + "t_contract(GoldfinchConfig)17564": { "label": "contract GoldfinchConfig" }, "t_array(t_uint256)11_storage": { @@ -5030,7 +5030,7 @@ { "contract": "CommunityRewards", "label": "config", - "type": "t_contract(GoldfinchConfig)17548", + "type": "t_contract(GoldfinchConfig)17564", "src": "contracts/rewards/CommunityRewards.sol:33" }, { @@ -5048,21 +5048,21 @@ { "contract": "CommunityRewards", "label": "grants", - "type": "t_mapping(t_uint256,t_struct(Rewards)10815_storage)", + "type": "t_mapping(t_uint256,t_struct(Rewards)10831_storage)", "src": "contracts/rewards/CommunityRewards.sol:42" } ], "types": { - "t_contract(GoldfinchConfig)17548": { + "t_contract(GoldfinchConfig)17564": { "label": "contract GoldfinchConfig" }, "t_uint256": { "label": "uint256" }, - "t_mapping(t_uint256,t_struct(Rewards)10815_storage)": { + "t_mapping(t_uint256,t_struct(Rewards)10831_storage)": { "label": "mapping(uint256 => struct CommunityRewardsVesting.Rewards)" }, - "t_struct(Rewards)10815_storage": { + "t_struct(Rewards)10831_storage": { "label": "struct CommunityRewardsVesting.Rewards", "members": [ { diff --git a/packages/protocol/.openzeppelin/unknown-31337.json b/packages/protocol/.openzeppelin/unknown-31337.json index 52624e087..eb3c87a5a 100644 --- a/packages/protocol/.openzeppelin/unknown-31337.json +++ b/packages/protocol/.openzeppelin/unknown-31337.json @@ -21,6 +21,11 @@ "txHash": "0x10f063c96096093eeb1064fad1d6f8cffffe04ecd1f5591d4372d40ee1a813d0", "kind": "transparent" }, + { + "address": "0x84AC02474c4656C88d4e08FCA63ff73070787C3d", + "txHash": "0x0d8b9df864dc4f44a881b68944b3f8022956e0571e13917fa621f8c883a5caac", + "kind": "transparent" + }, { "address": "0xaA425F8BfE82CD18f634e2Fe91E5DdEeFD98fDA1", "txHash": "0xdb231d4c6d0a07a634aa8288bb25f4329ae3a88c4d351de9121431ca90fcb7c8", @@ -55,11 +60,6 @@ "address": "0xba0439088dc1e75F58e0A7C107627942C15cbb41", "txHash": "0x517054372b36470ea012f6b23d165e5f3d3366d26329cd57b6e83a376ffb5614", "kind": "transparent" - }, - { - "address": "0x84AC02474c4656C88d4e08FCA63ff73070787C3d", - "txHash": "0x0d8b9df864dc4f44a881b68944b3f8022956e0571e13917fa621f8c883a5caac", - "kind": "transparent" } ], "impls": { @@ -2487,7 +2487,7 @@ { "contract": "PoolTokens", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/PoolTokens.sol:20" }, { @@ -2499,12 +2499,12 @@ { "contract": "PoolTokens", "label": "pools", - "type": "t_mapping(t_address,t_struct(PoolInfo)19715_storage)", + "type": "t_mapping(t_address,t_struct(PoolInfo)19766_storage)", "src": "contracts/protocol/core/PoolTokens.sol:32" } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_mapping(t_uint256,t_struct(TokenInfo)10129_storage)": { @@ -2541,10 +2541,10 @@ "t_address": { "label": "address" }, - "t_mapping(t_address,t_struct(PoolInfo)19715_storage)": { + "t_mapping(t_address,t_struct(PoolInfo)19766_storage)": { "label": "mapping(address => struct PoolTokens.PoolInfo)" }, - "t_struct(PoolInfo)19715_storage": { + "t_struct(PoolInfo)19766_storage": { "label": "struct PoolTokens.PoolInfo", "members": [ { @@ -2813,7 +2813,7 @@ { "contract": "SeniorPool", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/SeniorPool.sol:22" }, { @@ -2830,7 +2830,7 @@ } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_uint256": { @@ -3383,12 +3383,12 @@ { "contract": "GoldfinchFactory", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/protocol/core/GoldfinchFactory.sol:20" } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_array(t_uint256)50_storage": { @@ -3545,7 +3545,7 @@ { "contract": "BackerRewards", "label": "config", - "type": "t_contract(GoldfinchConfig)17306", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/rewards/BackerRewards.sol:32" }, { @@ -3575,27 +3575,27 @@ { "contract": "BackerRewards", "label": "tokens", - "type": "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28085_storage)", + "type": "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28109_storage)", "src": "contracts/rewards/BackerRewards.sol:50" }, { "contract": "BackerRewards", "label": "pools", - "type": "t_mapping(t_address,t_struct(BackerRewardsInfo)28080_storage)", + "type": "t_mapping(t_address,t_struct(BackerRewardsInfo)28104_storage)", "src": "contracts/rewards/BackerRewards.sol:52" } ], "types": { - "t_contract(GoldfinchConfig)17306": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_uint256": { "label": "uint256" }, - "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28085_storage)": { + "t_mapping(t_uint256,t_struct(BackerRewardsTokenInfo)28109_storage)": { "label": "mapping(uint256 => struct BackerRewards.BackerRewardsTokenInfo)" }, - "t_struct(BackerRewardsTokenInfo)28085_storage": { + "t_struct(BackerRewardsTokenInfo)28109_storage": { "label": "struct BackerRewards.BackerRewardsTokenInfo", "members": [ { @@ -3608,13 +3608,13 @@ } ] }, - "t_mapping(t_address,t_struct(BackerRewardsInfo)28080_storage)": { + "t_mapping(t_address,t_struct(BackerRewardsInfo)28104_storage)": { "label": "mapping(address => struct BackerRewards.BackerRewardsInfo)" }, "t_address": { "label": "address" }, - "t_struct(BackerRewardsInfo)28080_storage": { + "t_struct(BackerRewardsInfo)28104_storage": { "label": "struct BackerRewards.BackerRewardsInfo", "members": [ { @@ -4293,7 +4293,7 @@ { "contract": "CommunityRewards", "label": "config", - "type": "t_contract(GoldfinchConfig)17305", + "type": "t_contract(GoldfinchConfig)17356", "src": "contracts/rewards/CommunityRewards.sol:33" }, { @@ -4316,7 +4316,7 @@ } ], "types": { - "t_contract(GoldfinchConfig)17305": { + "t_contract(GoldfinchConfig)17356": { "label": "contract GoldfinchConfig" }, "t_uint256": { @@ -4790,13 +4790,13 @@ { "contract": "Go", "label": "config", - "type": "t_contract(GoldfinchConfig)17548", + "type": "t_contract(GoldfinchConfig)17564", "src": "contracts/protocol/core/Go.sol:20" }, { "contract": "Go", "label": "legacyGoList", - "type": "t_contract(GoldfinchConfig)17548", + "type": "t_contract(GoldfinchConfig)17564", "src": "contracts/protocol/core/Go.sol:23" }, { @@ -4810,7 +4810,7 @@ "t_address": { "label": "address" }, - "t_contract(GoldfinchConfig)17548": { + "t_contract(GoldfinchConfig)17564": { "label": "contract GoldfinchConfig" }, "t_array(t_uint256)11_storage": { @@ -4877,6 +4877,368 @@ } } } + }, + "d57f23e97608988f68b0180cd473f276f1d55a804a264b24d7ac06562d3ab346": { + "address": "0xb91E2874C34C5A410Ff6baeCDd73a369119C4b03", + "txHash": "0x8fcc5ca1f79b6c60dd21b25fe439c1c84f39a1a48a0e913f52a9970e97e760d4", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol:21" + }, + { + "contract": "Initializable", + "label": "initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol:26" + }, + { + "contract": "Initializable", + "label": "______gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol:61" + }, + { + "contract": "ContextUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol:37" + }, + { + "contract": "AccessControlUpgradeSafe", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)260_storage)", + "src": "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol:58" + }, + { + "contract": "AccessControlUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol:210" + }, + { + "contract": "ERC165UpgradeSafe", + "label": "_supportedInterfaces", + "type": "t_mapping(t_bytes4,t_bool)", + "src": "@openzeppelin/contracts-ethereum-package/contracts/introspection/ERC165.sol:21" + }, + { + "contract": "ERC165UpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/introspection/ERC165.sol:63" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_holderTokens", + "type": "t_mapping(t_address,t_struct(UintSet)4313_storage)", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:32" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_tokenOwners", + "type": "t_struct(UintToAddressMap)3838_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:35" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_tokenApprovals", + "type": "t_mapping(t_uint256,t_address)", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:38" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_operatorApprovals", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:41" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_name", + "type": "t_string_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:44" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_symbol", + "type": "t_string_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:47" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_tokenURIs", + "type": "t_mapping(t_uint256,t_string_storage)", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:50" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "_baseURI", + "type": "t_string_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:53" + }, + { + "contract": "ERC721UpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)41_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721.sol:555" + }, + { + "contract": "PausableUpgradeSafe", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol:26" + }, + { + "contract": "PausableUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol:84" + }, + { + "contract": "ERC721PausableUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/token/ERC721/ERC721Pausable.sol:40" + }, + { + "contract": "ERC721PresetMinterPauserAutoIdUpgradeSafe", + "label": "_tokenIdTracker", + "type": "t_struct(Counter)3518_storage", + "src": "contracts/external/ERC721PresetMinterPauserAutoId.sol:48" + }, + { + "contract": "ERC721PresetMinterPauserAutoIdUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "contracts/external/ERC721PresetMinterPauserAutoId.sol:86" + }, + { + "contract": "ReentrancyGuardUpgradeSafe", + "label": "_notEntered", + "type": "t_bool", + "src": "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol:21" + }, + { + "contract": "ReentrancyGuardUpgradeSafe", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol:63" + }, + { + "contract": "CommunityRewards", + "label": "config", + "type": "t_contract(GoldfinchConfig)17564", + "src": "contracts/rewards/CommunityRewards.sol:33" + }, + { + "contract": "CommunityRewards", + "label": "rewardsAvailable", + "type": "t_uint256", + "src": "contracts/rewards/CommunityRewards.sol:36" + }, + { + "contract": "CommunityRewards", + "label": "tokenLaunchTimeInSeconds", + "type": "t_uint256", + "src": "contracts/rewards/CommunityRewards.sol:39" + }, + { + "contract": "CommunityRewards", + "label": "grants", + "type": "t_mapping(t_uint256,t_struct(Rewards)10831_storage)", + "src": "contracts/rewards/CommunityRewards.sol:42" + } + ], + "types": { + "t_contract(GoldfinchConfig)17564": { + "label": "contract GoldfinchConfig" + }, + "t_uint256": { + "label": "uint256" + }, + "t_mapping(t_uint256,t_struct(Rewards)10831_storage)": { + "label": "mapping(uint256 => struct CommunityRewardsVesting.Rewards)" + }, + "t_struct(Rewards)10831_storage": { + "label": "struct CommunityRewardsVesting.Rewards", + "members": [ + { + "label": "totalGranted", + "type": "t_uint256" + }, + { + "label": "totalClaimed", + "type": "t_uint256" + }, + { + "label": "startTime", + "type": "t_uint256" + }, + { + "label": "endTime", + "type": "t_uint256" + }, + { + "label": "cliffLength", + "type": "t_uint256" + }, + { + "label": "vestingInterval", + "type": "t_uint256" + }, + { + "label": "revokedAt", + "type": "t_uint256" + } + ] + }, + "t_bool": { + "label": "bool" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_struct(Counter)3518_storage": { + "label": "struct Counters.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256" + } + ] + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + }, + "t_mapping(t_address,t_struct(UintSet)4313_storage)": { + "label": "mapping(address => struct EnumerableSet.UintSet)" + }, + "t_address": { + "label": "address" + }, + "t_struct(UintSet)4313_storage": { + "label": "struct EnumerableSet.UintSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)4024_storage" + } + ] + }, + "t_struct(Set)4024_storage": { + "label": "struct EnumerableSet.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_struct(UintToAddressMap)3838_storage": { + "label": "struct EnumerableMap.UintToAddressMap", + "members": [ + { + "label": "_inner", + "type": "t_struct(Map)3574_storage" + } + ] + }, + "t_struct(Map)3574_storage": { + "label": "struct EnumerableMap.Map", + "members": [ + { + "label": "_entries", + "type": "t_array(t_struct(MapEntry)3566_storage)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_struct(MapEntry)3566_storage)dyn_storage": { + "label": "struct EnumerableMap.MapEntry[]" + }, + "t_struct(MapEntry)3566_storage": { + "label": "struct EnumerableMap.MapEntry", + "members": [ + { + "label": "_key", + "type": "t_bytes32" + }, + { + "label": "_value", + "type": "t_bytes32" + } + ] + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)" + }, + "t_mapping(t_address,t_mapping(t_address,t_bool))": { + "label": "mapping(address => mapping(address => bool))" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)" + }, + "t_string_storage": { + "label": "string" + }, + "t_mapping(t_uint256,t_string_storage)": { + "label": "mapping(uint256 => string)" + }, + "t_array(t_uint256)41_storage": { + "label": "uint256[41]" + }, + "t_mapping(t_bytes4,t_bool)": { + "label": "mapping(bytes4 => bool)" + }, + "t_bytes4": { + "label": "bytes4" + }, + "t_mapping(t_bytes32,t_struct(RoleData)260_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeSafe.RoleData)" + }, + "t_struct(RoleData)260_storage": { + "label": "struct AccessControlUpgradeSafe.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)4204_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)4204_storage": { + "label": "struct EnumerableSet.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)4024_storage" + } + ] + } + } + } } } } diff --git a/packages/protocol/blockchain_scripts/mainnetForkingHelpers.ts b/packages/protocol/blockchain_scripts/mainnetForkingHelpers.ts index 5081dc05b..8c9598dde 100644 --- a/packages/protocol/blockchain_scripts/mainnetForkingHelpers.ts +++ b/packages/protocol/blockchain_scripts/mainnetForkingHelpers.ts @@ -19,6 +19,7 @@ import { getEthersContract, getProtocolOwner, fixProvider, + isMainnetForking, } from "../blockchain_scripts/deployHelpers" import _ from "lodash" import {CONFIG_KEYS} from "./configKeys" @@ -130,8 +131,14 @@ async function upgradeContracts({ UpgradedImplAddress: upgradedImplAddress, } - await rewriteUpgradedDeployment(contractName) - await openzeppelin_saveDeploymentManifest(fixProvider(hre.network.provider), proxyDeployment, implDeployment) + // We don't want to re-write the upgrade manifest and deployments manifest + // when we deploy during a mainnet forking test. If we did, we would be + // checking if the upgrade was safe with the last thing that we deployed, + // not what's currently on mainnet + if (!isMainnetForking()) { + await rewriteUpgradedDeployment(contractName) + await openzeppelin_saveDeploymentManifest(fixProvider(hre.network.provider), proxyDeployment, implDeployment) + } } return upgradedContracts } diff --git a/packages/protocol/blockchain_scripts/migrations/v2.6.0/migrate.ts b/packages/protocol/blockchain_scripts/migrations/v2.6.0/migrate.ts index e7dec8d81..44a3c605b 100644 --- a/packages/protocol/blockchain_scripts/migrations/v2.6.0/migrate.ts +++ b/packages/protocol/blockchain_scripts/migrations/v2.6.0/migrate.ts @@ -1,15 +1,18 @@ -import {StakedPositionType} from "@goldfinch-eng/protocol/blockchain_scripts/deployHelpers" +import {StakedPositionType, TRANCHES} from "@goldfinch-eng/protocol/blockchain_scripts/deployHelpers" import { BackerRewards, GFI, Go, GoldfinchConfig, + PoolTokens, SeniorPool, StakingRewards, + TranchedPool, } from "@goldfinch-eng/protocol/typechain/ethers" +import {assertNonNullable} from "@goldfinch-eng/utils" +import _ from "lodash" import BigNumber from "bignumber.js" import hre from "hardhat" -import {bigVal} from "../../../test/testHelpers" import {deployFixedLeverageRatioStrategy} from "../../baseDeploy/deployFixedLeverageRatioStrategy" import {deployTranchedPool} from "../../baseDeploy/deployTranchedPool" import {deployZapper} from "../../baseDeploy/deployZapper" @@ -24,13 +27,25 @@ import { } from "../../deployHelpers" import {changeImplementations, getDeployEffects} from "../deployEffects" +const STRATOS_POOL_ADDR = "0x00c27fc71b159a346e179b4a1608a0865e8a7470" +const ALMA_6_POOL_ADDR = "0x418749e294cabce5a714efccc22a8aade6f9db57" +const ALMA_7_POOL_ADDR = "0x759f097f3153f5d62ff1c2d82ba78b6350f223e3" +const CAURIS_2_POOL_ADDR = "0xd09a57127bc40d680be7cb061c2a6629fe71abef" +const LEND_EAST_POOL_ADDR = "0xb26b42dd5771689d0a7faeea32825ff9710b9c11" +export const BACKER_REWARDS_PARAMS_POOL_ADDRS = [ + STRATOS_POOL_ADDR, + ALMA_6_POOL_ADDR, + ALMA_7_POOL_ADDR, + CAURIS_2_POOL_ADDR, + LEND_EAST_POOL_ADDR, +] + export type Migration260Params = { BackerRewards: { totalRewards: string - maxInterestDollarsEligible: string } StakingRewards: { - effectiveMultiplier: string + curveEffectiveMultiplier: string } } @@ -44,7 +59,36 @@ export async function main() { description: "https://github.com/warbler-labs/mono/pull/390", }) - console.log("Beginning v2.6.0 upgrade") + async function getPoolTokensThatRedeemedBeforeLocking(poolAddress: string): Promise<{[key: string]: string}> { + const tranchedPool = await getEthersContract("TranchedPool", {at: poolAddress}) + const lockEvents = await tranchedPool.queryFilter(tranchedPool.filters.TrancheLocked(tranchedPool.address)) + const isJuniorTrancheLockEvent = (event) => event.args.trancheId.toNumber() === TRANCHES.Junior + const juniorLockEvents = lockEvents.filter(isJuniorTrancheLockEvent) + if (juniorLockEvents.length !== 2) { + throw new Error(`Unexpected junior tranche lock events found.`) + } + + const lockBlockNumber = juniorLockEvents.reduce((acc, x) => Math.max(acc, x.blockNumber), 0) + if (!lockBlockNumber) { + throw new Error("Failed to identify lock block number.") + } + + const withdrawFilter = tranchedPool.filters.WithdrawalMade(undefined, TRANCHES.Junior) + const withdrawalEventsBeforeLocking = await tranchedPool.queryFilter(withdrawFilter, undefined, lockBlockNumber) + const withdrawEventWithdrewPrincipal = (event) => event.args.principalWithdrawn.toString() !== "0" + const withdrawalsOfPrincipalBeforeLocked = withdrawalEventsBeforeLocking.filter(withdrawEventWithdrewPrincipal) + + const balanceByTokenId: Record = {} + for (const event of withdrawalsOfPrincipalBeforeLocked) { + balanceByTokenId[event.args.tokenId.toString()] = new BigNumber( + balanceByTokenId[event.args.tokenId.toString()] || 0 + ) + .plus(event.args.principalWithdrawn.toString()) + .toFixed() + } + + return balanceByTokenId + } const owner = await getProtocolOwner() const gfi = await getEthersContract("GFI") @@ -52,6 +96,37 @@ export async function main() { const stakingRewards = await getEthersContract("StakingRewards") const seniorPool = await getEthersContract("SeniorPool") const go = await getEthersContract("Go") + const poolTokens = await getEthersContract("PoolTokens") + + const getRewardsParametersForPool = async (poolAddress: string): Promise => { + const tranchedPool = await getEthersContract("TranchedPool", {at: poolAddress}) + const drawdownEvents = await tranchedPool.queryFilter(tranchedPool.filters.DrawdownMade()) + // in some cases (stratos) they did a test drawdown. If we used the first drawdown + // as where to take our rewards param snapshot we would have incorrect values. So we need to find + // the latest block that they drewdown. + const lastDrawdownBlock = Math.max(...drawdownEvents.map((e) => e.blockNumber)) + if (!lastDrawdownBlock) { + throw new Error("Failed to Identify last drawdown block") + } + const trancheInfo = await tranchedPool.getTranche(TRANCHES.Junior, {blockTag: lastDrawdownBlock}) + const principalSharePrice = trancheInfo.principalSharePrice + const principalDeposited = trancheInfo.principalDeposited + const remaining = principalSharePrice.mul(principalDeposited).div(String(1e18)) + const backerCapitalDrawndown = principalDeposited.sub(remaining) + + const fiduSharePriceAtDrawdown = (await seniorPool.sharePrice({blockTag: lastDrawdownBlock})).toString() + const accumulatedRewardsPerToken = ( + await stakingRewards.accumulatedRewardsPerToken({blockTag: lastDrawdownBlock}) + ).toString() + + return { + principalDeployedAtDrawdown: backerCapitalDrawndown.toString(), + fiduSharePriceAtDrawdown, + accumulatedRewardsPerToken, + } + } + + console.log("Beginning v2.6.0 upgrade") // 1. Upgrade other contracts const upgradedContracts = await upgrader.upgrade({ @@ -69,23 +144,22 @@ export async function main() { const tranchedPool = await deployTranchedPool(deployer, {config, deployEffects}) // 4. deploy upgraded fixed leverage ratio strategy - const strategy = await deployFixedLeverageRatioStrategy(deployer, {config, deployEffects}) + const fixedLeverageRatioStrategy = await deployFixedLeverageRatioStrategy(deployer, {config, deployEffects}) // 5. deploy zapper const zapper = await deployZapper(deployer, {config, deployEffects}) const deployedContracts = { tranchedPool, - strategy, + fixedLeverageRatioStrategy, zapper, } const params: Migration260Params = { BackerRewards: { totalRewards: new BigNumber((await gfi.totalSupply()).toString()).multipliedBy("0.02").toString(), - maxInterestDollarsEligible: bigVal(100_000_000).toString(), }, StakingRewards: { - effectiveMultiplier: "750000000000000000", + curveEffectiveMultiplier: "750000000000000000", }, } @@ -93,9 +167,96 @@ export async function main() { `Transferring ${params.BackerRewards.totalRewards} GFI to BackerRewards at address ${backerRewards.address}` ) console.log("Setting StakingRewards parameters:") - console.log(` effectiveMultipler = ${params.StakingRewards.effectiveMultiplier}`) + console.log(` effectiveMultipler = ${params.StakingRewards.curveEffectiveMultiplier}`) + + // 6. Generate rewards initialization params + console.log("Getting pool backer staking rewards parameters") + const backerStakingRewardsParams = _.fromPairs( + await Promise.all( + BACKER_REWARDS_PARAMS_POOL_ADDRS.map(async (address) => [address, await getRewardsParametersForPool(address)]) + ) + ) - // 6. Add effects to deploy effects + // validate against the manually confirmed values + const expectedRewardsValues: Record = { + [STRATOS_POOL_ADDR]: { + principalDeployedAtDrawdown: "4000000000000", + fiduSharePriceAtDrawdown: "1049335199989661790", + accumulatedRewardsPerToken: "14764838139349853151", + }, + [ALMA_6_POOL_ADDR]: { + principalDeployedAtDrawdown: "2362453454437", + fiduSharePriceAtDrawdown: "1048979727966257806", + accumulatedRewardsPerToken: "14764765626591738655", + }, + [ALMA_7_POOL_ADDR]: { + principalDeployedAtDrawdown: "1999999998834", + fiduSharePriceAtDrawdown: "1055955666945103145", + accumulatedRewardsPerToken: "14774486589523186250", + }, + [CAURIS_2_POOL_ADDR]: { + principalDeployedAtDrawdown: "2000000000001", + fiduSharePriceAtDrawdown: "1049335199989661790", + accumulatedRewardsPerToken: "14765542624996072988", + }, + [LEND_EAST_POOL_ADDR]: { + principalDeployedAtDrawdown: "2030000000000", + fiduSharePriceAtDrawdown: "1052477362957198335", + accumulatedRewardsPerToken: "14770629091940940209", + }, + } + + expect(backerStakingRewardsParams).to.deep.eq(expectedRewardsValues) + console.log("Backer staking rewards params:") + console.log(backerStakingRewardsParams) + + const backerStakingRewardsInitTxs = await Promise.all( + Object.entries(backerStakingRewardsParams).map(async ([address, params]) => { + return backerRewards.populateTransaction.forceInitializeStakingRewardsPoolInfo( + address, + params.fiduSharePriceAtDrawdown, + params.principalDeployedAtDrawdown, + params.accumulatedRewardsPerToken + ) + }) + ) + + // 7. Generate poolToken data fixup transactions + console.log("Getting pool tokens that redeemed before pools were locked") + const poolTokensWithPrincipalWithdrawnBeforeLockById: {[key: string]: string} = _.merge( + {}, + ...(await Promise.all(BACKER_REWARDS_PARAMS_POOL_ADDRS.map(getPoolTokensThatRedeemedBeforeLocking))) + ) + console.log("pool token principal reduction amounts:") + console.log(poolTokensWithPrincipalWithdrawnBeforeLockById) + + const expectedPrincipalReductionAmounts = { + "469": "9000000000", + "477": "6500000000", + "478": "37980320915", + "485": "16556876430", + "487": "25500000000", + "493": "10000000000", + "501": "4000000000", + "507": "1020000000", + "517": "2000000000", + "518": "16870422655", + "525": "1000000000", + "545": "30000810000", + "566": "4000000000", + "689": "129117730000", + "711": "476510000", + } + + expect(poolTokensWithPrincipalWithdrawnBeforeLockById).to.deep.equal(expectedPrincipalReductionAmounts) + + const poolTokenFixupTxs = await Promise.all( + Object.entries(poolTokensWithPrincipalWithdrawnBeforeLockById).map(([id, amount]) => { + return poolTokens.populateTransaction.reducePrincipalAmount(id, amount) + }) + ) + + // 8. Add effects to deploy effects deployEffects.add({ deferred: [ // set Goldfinchconfig key for fidu usdc lp pool @@ -115,9 +276,12 @@ export async function main() { // initialize staking rewards parameters for CurveLP positions await stakingRewards.populateTransaction.setEffectiveMultiplier( - params.StakingRewards.effectiveMultiplier, + params.StakingRewards.curveEffectiveMultiplier, StakedPositionType.CurveLP ), + + ...backerStakingRewardsInitTxs, + ...poolTokenFixupTxs, ], }) @@ -138,3 +302,9 @@ if (require.main === module) { process.exit(1) }) } + +interface StakingRewardsInfoInitValues { + accumulatedRewardsPerToken: string + fiduSharePriceAtDrawdown: string + principalDeployedAtDrawdown: string +} diff --git a/packages/protocol/contracts/protocol/core/PoolTokens.sol b/packages/protocol/contracts/protocol/core/PoolTokens.sol index b5eb0eef4..e69008fae 100644 --- a/packages/protocol/contracts/protocol/core/PoolTokens.sol +++ b/packages/protocol/contracts/protocol/core/PoolTokens.sol @@ -137,6 +137,22 @@ contract PoolTokens is IPoolTokens, ERC721PresetMinterPauserAutoIdUpgradeSafe { emit TokenRedeemed(ownerOf(tokenId), poolAddr, tokenId, principalRedeemed, interestRedeemed, token.tranche); } + /** @notice reduce a given pool token's principalAmount and principalRedeemed by a specified amount + * @dev uses safemath to prevent underflow + * @dev this function is only intended for use as part of the v2.6.0 upgrade + * to rectify a bug that allowed users to create a PoolToken that had a + * larger amount of principal than they actually made available to the + * borrower. This bug is fixed in v2.6.0 but still requires past pool tokens + * to have their principal redeemed and deposited to be rectified. + * @param tokenId id of token to decrease + * @param amount amount to decrease by + */ + function reducePrincipalAmount(uint256 tokenId, uint256 amount) external onlyAdmin { + TokenInfo storage tokenInfo = tokens[tokenId]; + tokenInfo.principalAmount = tokenInfo.principalAmount.sub(amount); + tokenInfo.principalRedeemed = tokenInfo.principalRedeemed.sub(amount); + } + /** * @notice Decrement a token's principal amount. This is different from `redeem`, which captures changes to * principal and/or interest that occur when a loan is in progress. diff --git a/packages/protocol/contracts/rewards/BackerRewards.sol b/packages/protocol/contracts/rewards/BackerRewards.sol index 15edd6112..d51384cdb 100644 --- a/packages/protocol/contracts/rewards/BackerRewards.sol +++ b/packages/protocol/contracts/rewards/BackerRewards.sol @@ -39,6 +39,7 @@ contract BackerRewards is IBackerRewards, BaseUpgradeablePausable, SafeERC20Tran uint256 internal constant GFI_MANTISSA = 10**18; uint256 internal constant FIDU_MANTISSA = 10**18; uint256 internal constant USDC_MANTISSA = 10**6; + uint256 internal constant NUM_TRANCHES_PER_SLICE = 2; struct BackerRewardsInfo { uint256 accRewardsPerPrincipalDollar; // accumulator gfi per interest dollar @@ -104,8 +105,6 @@ contract BackerRewards is IBackerRewards, BaseUpgradeablePausable, SafeERC20Tran uint256 accumulatedRewardsPerTokenAtLastWithdraw; } - uint256 public constant NUM_TRANCHES_PER_SLICE = 2; - /// @notice total amount of GFI rewards available, times 1e18 uint256 public totalRewards; @@ -140,7 +139,7 @@ contract BackerRewards is IBackerRewards, BaseUpgradeablePausable, SafeERC20Tran /// @notice intialize the first slice of a StakingRewardsPoolInfo /// @dev this is _only_ meant to be called on pools that didnt qualify for the backer rewards airdrop /// but were deployed before this contract. - function forceIntializeStakingRewardsPoolInfo( + function forceInitializeStakingRewardsPoolInfo( ITranchedPool pool, uint256 fiduSharePriceAtDrawdown, uint256 principalDeployedAtDrawdown, @@ -845,11 +844,6 @@ contract BackerRewards is IBackerRewards, BaseUpgradeablePausable, SafeERC20Tran poolInfo.lastUpdateTime = block.timestamp; } - function updateGoldfinchConfig() external onlyAdmin { - config = GoldfinchConfig(config.configAddress()); - emit GoldfinchConfigUpdated(_msgSender(), address(config)); - } - /* ======== MODIFIERS ======== */ modifier onlyPool() { @@ -858,7 +852,6 @@ contract BackerRewards is IBackerRewards, BaseUpgradeablePausable, SafeERC20Tran } /* ======== EVENTS ======== */ - event GoldfinchConfigUpdated(address indexed who, address configAddress); event BackerRewardsClaimed( address indexed owner, uint256 indexed tokenId, diff --git a/packages/protocol/hardhat.config.base.ts b/packages/protocol/hardhat.config.base.ts index be86a6f3a..a1f3d74cf 100644 --- a/packages/protocol/hardhat.config.base.ts +++ b/packages/protocol/hardhat.config.base.ts @@ -36,10 +36,11 @@ export default { allowUnlimitedContractSize: true, timeout: 1800000, accounts: {mnemonic: "test test test test test test test test test test test junk"}, + chainId: process.env.HARDHAT_FORK === "mainnet" ? 1 : 31337, forking: process.env.HARDHAT_FORK ? { url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_API_KEY}`, - blockNumber: 14393421, // Mar-15-2022 08:51:50 PM +UTC + blockNumber: 14573711, // Apr-12-2022 11:15:59 PM +UTC } : undefined, }, @@ -125,7 +126,7 @@ export default { }, contractSizer: { runOnCompile: true, - strict: true, + strict: process.env.CI !== undefined, except: [":Test.*", ":MigratedTranchedPool$"], }, } diff --git a/packages/protocol/test/BackerRewards.test.ts b/packages/protocol/test/BackerRewards.test.ts index 88db2b4cb..0958ffe35 100644 --- a/packages/protocol/test/BackerRewards.test.ts +++ b/packages/protocol/test/BackerRewards.test.ts @@ -1833,32 +1833,6 @@ describe("BackerRewards", function () { }).timeout(LONG_TEST_TIMEOUT) }) - describe("updateGoldfinchConfig", async () => { - let otherConfig: GoldfinchConfigInstance - - const updateGoldfinchConfigTestSetup = deployments.createFixture(async ({deployments}) => { - const deployment = await deployments.deploy("GoldfinchConfig", {from: owner}) - const goldfinchConfig = await artifacts.require("GoldfinchConfig").at(deployment.address) - return {goldfinchConfig} - }) - - beforeEach(async () => { - // eslint-disable-next-line @typescript-eslint/no-extra-semi - ;({goldfinchConfig: otherConfig} = await updateGoldfinchConfigTestSetup()) - }) - - describe("setting it", async () => { - it("emits an event", async () => { - await goldfinchConfig.setGoldfinchConfig(otherConfig.address, {from: owner}) - const tx = await backerRewards.updateGoldfinchConfig({from: owner}) - expectEvent(tx, "GoldfinchConfigUpdated", { - who: owner, - configAddress: otherConfig.address, - }) - }) - }) - }) - describe("Staking-rewards-related view functions", () => { const maxInterestDollarsEligible = 1_000_000_000 const totalGFISupply = 100_000_000 diff --git a/packages/protocol/test/PoolTokens.test.ts b/packages/protocol/test/PoolTokens.test.ts index 41680d016..468c492a6 100644 --- a/packages/protocol/test/PoolTokens.test.ts +++ b/packages/protocol/test/PoolTokens.test.ts @@ -12,7 +12,6 @@ import { advanceTime, setupBackerRewards, decodeAndGetFirstLog, - bigVal, Numberish, } from "./testHelpers" import {OWNER_ROLE, interestAprAsBN, GO_LISTER_ROLE} from "../blockchain_scripts/deployHelpers" @@ -23,10 +22,16 @@ const {deployments} = hre const TranchedPool = artifacts.require("TranchedPool") import {expectEvent} from "@openzeppelin/test-helpers" import {mint} from "./uniqueIdentityHelpers" -import {GFIInstance, BackerRewardsInstance, TranchedPoolInstance} from "../typechain/truffle" +import { + GFIInstance, + BackerRewardsInstance, + GoldfinchFactoryInstance, + TestPoolTokensInstance, +} from "../typechain/truffle" import {deployBaseFixture, deployUninitializedTranchedPoolFixture} from "./util/fixtures" import {TokenMinted} from "../typechain/truffle/IPoolTokens" import {TokenPrincipalWithdrawn} from "../typechain/truffle/PoolTokens" +import {PoolCreated} from "../typechain/truffle/GoldfinchFactory" const testSetup = deployments.createFixture(async ({deployments, getNamedAccounts}) => { const [_owner, _person2, _person3] = await web3.eth.getAccounts() @@ -60,7 +65,7 @@ describe("PoolTokens", () => { goldfinchConfig, poolTokens, pool, - goldfinchFactory, + goldfinchFactory: GoldfinchFactoryInstance, usdc, uniqueIdentity, backerRewards: BackerRewardsInstance, @@ -90,7 +95,7 @@ describe("PoolTokens", () => { gfi, } = await testSetup()) - await poolTokens._disablePoolValidation(true) + await (poolTokens as TestPoolTokensInstance)._disablePoolValidation(true) }) async function addToLegacyGoList(target, goLister) { @@ -137,7 +142,7 @@ describe("PoolTokens", () => { [], {from: owner} ) - const event = result.logs[result.logs.length - 1] + const event = decodeAndGetFirstLog(result.receipt.rawLogs, goldfinchFactory, "PoolCreated") pool = await TranchedPool.at(event.args.pool) // grant role so the person can deposit into the senior tranche await pool.grantRole(await pool.SENIOR_ROLE(), person2) @@ -146,7 +151,7 @@ describe("PoolTokens", () => { context("with real pool validation turned on", async () => { beforeEach(async () => { - await poolTokens._disablePoolValidation(false) + await (poolTokens as TestPoolTokensInstance)._disablePoolValidation(false) }) it("should allow validly created pools to call the mint function", async () => { return expect(pool.deposit(new BN(1), usdcVal(5), {from: person2})).to.be.fulfilled @@ -267,7 +272,7 @@ describe("PoolTokens", () => { [], {from: owner} ) - const event = result.logs[result.logs.length - 1] + const event = decodeAndGetFirstLog(result.receipt.rawLogs, goldfinchFactory, "PoolCreated") pool = await TranchedPool.at(event.args.pool) // grant role so the person can deposit into the senior tranche await pool.grantRole(await pool.SENIOR_ROLE(), person2) @@ -370,8 +375,12 @@ describe("PoolTokens", () => { [], {from: owner} ) - let event = result.logs[result.logs.length - 1] - pool = await TranchedPool.at(event.args.pool) + const poolCreatedEvent = decodeAndGetFirstLog( + result.receipt.rawLogs, + goldfinchFactory, + "PoolCreated" + ) + pool = await TranchedPool.at(poolCreatedEvent.args.pool) // grant role so the person can deposit into the senior tranche await pool.grantRole(await pool.SENIOR_ROLE(), person2) @@ -379,13 +388,13 @@ describe("PoolTokens", () => { mintAmountA = usdcVal(5) result = await pool.deposit(new BN(1), mintAmountA, {from: person2}) - event = decodeLogs(result.receipt.rawLogs, poolTokens, "TokenMinted")[0] - tokenIdA = event.args.tokenId + const firstMintEvent = decodeAndGetFirstLog(result.receipt.rawLogs, poolTokens, "TokenMinted") + tokenIdA = firstMintEvent.args.tokenId mintAmountB = usdcVal(50) result = await pool.deposit(new BN(1), mintAmountB, {from: person2}) - event = decodeLogs(result.receipt.rawLogs, poolTokens, "TokenMinted")[0] - tokenIdB = event.args.tokenId + const secondMintEvent = decodeAndGetFirstLog(result.receipt.rawLogs, poolTokens, "TokenMinted") + tokenIdB = secondMintEvent.args.tokenId }) const redeemToken = async (tokenId, principal, interest) => { @@ -493,6 +502,133 @@ describe("PoolTokens", () => { }) }) + describe("reducePrincipalAmount", async () => { + let tokenId, mintAmount + beforeEach(async function () { + let result = await goldfinchFactory.createPool( + person2, + new BN(20), + usdcVal(100), + interestAprAsBN("15.0"), + new BN(30), + new BN(365), + new BN(0), + new BN(185), + new BN(0), + [], + {from: owner} + ) + const poolCreatedEvent = decodeAndGetFirstLog( + result.receipt.rawLogs, + goldfinchFactory, + "PoolCreated" + ) + pool = await TranchedPool.at(poolCreatedEvent.args.pool) + await erc20Approve(usdc, pool.address, usdcVal(100000), [person2]) + + mintAmount = usdcVal(5) + result = await pool.deposit(new BN(2), mintAmount, {from: person2}) + const tokenMintedEvent = decodeAndGetFirstLog(result.receipt.rawLogs, poolTokens, "TokenMinted") + tokenId = tokenMintedEvent.args.tokenId + + await pool.lockJuniorCapital({from: owner}) + await pool.lockPool({from: owner}) + }) + + const redeemToken = async (tokenId, principal, interest) => { + // We need to fake the address so we can bypass the pool + return withPoolSender(() => poolTokens.redeem(tokenId, principal, interest)) + } + + describe("before redeeming", async () => { + describe("as a wallet with OWNER ROLE", () => { + it("it fails because of insufficient principal redeemed", async () => { + await expect(poolTokens.reducePrincipalAmount(tokenId, mintAmount)).to.be.rejectedWith( + /SafeMath: subtraction overflow/i + ) + }) + }) + }) + + describe("after partially redeeming", async () => { + let redemptionAmount + const testSetup = deployments.createFixture(async () => { + await redeemToken(tokenId, redemptionAmount, "0") + }) + + beforeEach(async () => { + redemptionAmount = mintAmount.div(new BN(2)) + await testSetup() + }) + + describe("as a wallet without OWNER_ROLE", () => { + it("it fails", async () => { + await expect(poolTokens.reducePrincipalAmount(tokenId, redemptionAmount, {from: person2})).to.be.rejectedWith( + /Must have admin role to perform this action/i + ) + }) + }) + + describe("as a wallet with OWNER_ROLE", () => { + it("it works", async () => { + const tokenBefore = await poolTokens.getTokenInfo(tokenId) + await expect(poolTokens.reducePrincipalAmount(tokenId, redemptionAmount)).to.not.be.rejected + const tokenAfter = await poolTokens.getTokenInfo(tokenId) + expect(tokenAfter.principalAmount.toString()).to.eq( + new BN(tokenBefore.principalAmount).sub(redemptionAmount).toString() + ) + expect(tokenAfter.principalRedeemed.toString()).to.eq( + new BN(tokenBefore.principalRedeemed).sub(redemptionAmount).toString() + ) + }) + }) + }) + + describe("after fully redeeming", async () => { + let redemptionAmount + + const testSetup = deployments.createFixture(async () => { + await redeemToken(tokenId, redemptionAmount, "0") + }) + + beforeEach(async () => { + redemptionAmount = mintAmount + await testSetup() + }) + + describe("as a wallet with OWNER_ROLE", () => { + it("it works", async () => { + const tokenBefore = await poolTokens.getTokenInfo(tokenId) + await expect(poolTokens.reducePrincipalAmount(tokenId, redemptionAmount)).to.not.be.rejected + const tokenAfter = await poolTokens.getTokenInfo(tokenId) + expect(tokenAfter.principalAmount.toString()).to.eq( + new BN(tokenBefore.principalAmount).sub(redemptionAmount).toString() + ) + expect(tokenAfter.principalRedeemed).to.eq( + new BN(tokenBefore.principalRedeemed).sub(redemptionAmount).toString() + ) + }) + + describe("when a pool has redeemed less than we are reducing by", () => { + it("it fails", async () => { + await expect( + poolTokens.reducePrincipalAmount(tokenId, redemptionAmount.add(new BN(1)), {from: owner}) + ).to.be.rejectedWith(/SafeMath: subtraction overflow/) + }) + }) + }) + + describe("as a wallet without OWNER_ROLE", () => { + it("it fails", async () => { + const notOwner = person2 + await expect( + poolTokens.reducePrincipalAmount(tokenId, redemptionAmount, {from: notOwner}) + ).to.be.rejectedWith(/Must have admin role to perform this action/i) + }) + }) + }) + }) + describe("burning", async () => { let tokenId, mintAmount beforeEach(async function () { @@ -509,8 +645,8 @@ describe("PoolTokens", () => { [], {from: owner} ) - let event = result.logs[result.logs.length - 1] - pool = await TranchedPool.at(event.args.pool) + const poolCreateEvent = decodeAndGetFirstLog(result.receipt.rawLogs, goldfinchFactory, "PoolCreated") + pool = await TranchedPool.at(poolCreateEvent.args.pool) // grant role so the person can deposit into the senior tranche await pool.grantRole(await pool.SENIOR_ROLE(), person2) @@ -518,8 +654,8 @@ describe("PoolTokens", () => { mintAmount = usdcVal(5) result = await pool.deposit(new BN(1), mintAmount, {from: person2}) - event = decodeLogs(result.receipt.rawLogs, poolTokens, "TokenMinted")[0] - tokenId = event.args.tokenId + const mintEvent = decodeAndGetFirstLog(result.receipt.rawLogs, poolTokens, "TokenMinted") + tokenId = mintEvent.args.tokenId }) it("should disallow burning if the token isn't fully redeemed", async () => { @@ -594,7 +730,7 @@ describe("PoolTokens", () => { [], {from: owner} ) - const event = result.logs[result.logs.length - 1] + const event = decodeAndGetFirstLog(result.receipt.rawLogs, goldfinchFactory, "PoolCreated") pool = await TranchedPool.at(event?.args.pool) }) describe("mint", async () => { diff --git a/packages/protocol/test/mainnet_forking/MainnetForking.test.ts b/packages/protocol/test/mainnet_forking/MainnetForking.test.ts index 5d9431645..8970ffd07 100644 --- a/packages/protocol/test/mainnet_forking/MainnetForking.test.ts +++ b/packages/protocol/test/mainnet_forking/MainnetForking.test.ts @@ -14,7 +14,6 @@ import { import {MAINNET_MULTISIG, getExistingContracts} from "../../blockchain_scripts/mainnetForkingHelpers" import {CONFIG_KEYS} from "../../blockchain_scripts/configKeys" import {time} from "@openzeppelin/test-helpers" -import * as migrate250 from "../../blockchain_scripts/migrations/v2.5.0/migrate" import * as migrate260 from "../../blockchain_scripts/migrations/v2.6.0/migrate" const {deployments, ethers, artifacts, web3} = hre @@ -185,12 +184,9 @@ const setupTest = deployments.createFixture(async ({deployments}) => { const signer = ethersUniqueIdentity.signer assertNonNullable(signer.provider, "Signer provider is null") const network = await signer.provider.getNetwork() - - await migrate250.main() await migrate260.main() - // NOTE: Uncomment for the v2.6.0 upgrade. - // const zapper: ZapperInstance = await getDeployedAsTruffleContract(deployments, "Zapper") + const zapper: ZapperInstance = await getDeployedAsTruffleContract(deployments, "Zapper") return { poolTokens, @@ -204,8 +200,7 @@ const setupTest = deployments.createFixture(async ({deployments}) => { go, stakingRewards, backerRewards, - // NOTE: Uncomment for the v2.6.0 upgrade. - // zapper, + zapper, gfi, communityRewards, merkleDistributor, @@ -291,8 +286,7 @@ describe("mainnet forking tests", async function () { go, stakingRewards, backerRewards, - // NOTE: Uncomment for the v2.6.0 upgrade. - // zapper, + zapper, gfi, communityRewards, merkleDistributor, @@ -700,10 +694,8 @@ describe("mainnet forking tests", async function () { }) }) - // NOTE: Skipping these tests for now because they depend on the v2.6.0 upgrade. - // Main should be passing after only running the v2.5.0 upgrade - describe.skip("BackerRewards", () => { - const microTolerance = "100000" // 1e5 + describe("BackerRewards", () => { + const microTolerance = String(1e5) let stakingRewardsEthers: StakingRewards let backerRewardsEthers: BackerRewards let tranchedPoolWithBorrowerConnected: TranchedPool @@ -714,7 +706,7 @@ describe("mainnet forking tests", async function () { const termInDays = new BigNumber("365") const trackedStakedAmount = usdcVal(2500) const untrackedStakedAmount = usdcVal(1000) - const limit = trackedStakedAmount.add(untrackedStakedAmount).mul(new BN("4")) + const limit = trackedStakedAmount.add(untrackedStakedAmount).mul(new BN("5")) const setup = deployments.createFixture(async () => { const result = await goldfinchFactory.createPool( bwr, @@ -883,7 +875,6 @@ describe("mainnet forking tests", async function () { await mineBlock() const blockNumAtTermEnd = await ethers.provider.getBlockNumber() const stakingRewardsAtTermEnd = await getStakingRewardsForToken(stakingRewardsTokenId, blockNumAtTermEnd) - expect(stakingRewardsAtTermEnd).to.bignumber.eq("204719988714582627353") // this section tests the final repayment // the final repayment is different because if the payment happens after the term is over @@ -945,8 +936,6 @@ describe("mainnet forking tests", async function () { const payTx = await payOffTranchedPoolInterest(tranchedPoolWithBorrowerConnected) stakingRewardsEarned = await getStakingRewardsForToken(stakingRewardsTokenId, payTx.blockNumber) backerStakingRewardsEarned = await getBackerRewardsForToken(backerStakingTokenId, payTx.blockNumber) - // NOTE: there's currently a bug in the tranched pool share price calculation - // thats causing imprecision here expect(backerStakingRewardsEarned).to.bignumber.closeTo(stakingRewardsEarned, microTolerance) } @@ -997,8 +986,6 @@ describe("mainnet forking tests", async function () { await mineBlock() const stakingRewardsAtTermEnd = await getStakingRewardsForToken(stakingRewardsTokenId) const secondStakingRewardsAtTermEnd = await getStakingRewardsForToken(secondStakingTokenId) - expect(stakingRewardsAtTermEnd).to.bignumber.eq("102362806340720831518") - expect(secondStakingRewardsAtTermEnd).to.bignumber.eq("25941012414031627309") // this section tests the final repayment // the final repayment is different because if the payment happens after the term is over @@ -1011,10 +998,6 @@ describe("mainnet forking tests", async function () { payTx.blockNumber ) - // NOTE: we need to capture the stakingrewards amount at the final day of the repayment - // and then compare that its within a tolerance. This is asserting that - // the pro rating logic that we're implementing is _close_ to the value - // that should have been given out expect(backerStakingRewardsEarnedAfterFinalRepayment).to.bignumber.closeTo( stakingRewardsAtTermEnd.add(secondStakingRewardsAtTermEnd), microTolerance @@ -1040,133 +1023,6 @@ describe("mainnet forking tests", async function () { }) }) - // describe("forceIntializeStakingRewardsPoolInfo", () => { - // const stratosPoolAddress = "0x00c27fc71b159a346e179b4a1608a0865e8a7470" - // const stratosPoolBackerTokenId = "570" - // const almavest6PoolAddress = "0x418749e294cabce5a714efccc22a8aade6f9db57" - // const almavest6BackerTokenId = "565" - // const testCases = [ - // [stratosPoolAddress, stratosPoolBackerTokenId], - // [almavest6PoolAddress, almavest6BackerTokenId], - // ] - // let seniorPoolEthers: SeniorPool - - // beforeEach(async () => { - // seniorPoolEthers = await getEthersContract("SeniorPool", {at: seniorPool.address}) - // }) - - // NOTE: this test needs to fixed in the follow up v2.5 deploy script - // It's failing with an updated block number because these tests assumed the pools aren't - // funded. - // mochaEach(testCases).it("works correctly on %s", async (address, tokenId) => { - // const pool = await getTruffleContract("TranchedPool", { - // at: address, - // }) - // const creditLine = await getTruffleContract("CreditLine", { - // at: await pool.creditLine(), - // }) - // const borrower = await getTruffleContract("Borrower", { - // at: await creditLine.borrower(), - // }) - // const borrowerEoa = await borrower.getRoleMember(OWNER_ROLE, 0) - // const borrowerEoaSigner = await ethers.getSigner(borrowerEoa) - // const borrowerEthers = ( - // await getEthersContract("Borrower", { - // at: borrower.address, - // }) - // ).connect(borrowerEoaSigner) - // await impersonateAccount(hre, borrowerEoa) - - // await erc20Approve(usdc, borrower.address, MAX_UINT, [borrowerEoa]) - // await erc20Approve(usdc, pool.address, MAX_UINT, [borrowerEoa]) - - // const backerDepositAmount = (await poolTokens.getTokenInfo(tokenId)).principalAmount - - // const limit = await creditLine.maxLimit() - // const juniorTrancheLimit = limit.div(new BN("4")) // junior tranches are 25% - // const currentAmount = await usdc.balanceOf(pool.address) - // const remaining = juniorTrancheLimit.sub(currentAmount) - - // // A generous contribution from circle - // const circleEoa = "0x55fe002aeff02f77364de339a1292923a15844b8" - // await legacyGoldfinchConfig.addToGoList(circleEoa, {from: await getProtocolOwner()}) - // await impersonateAccount(hre, circleEoa) - // await erc20Approve(usdc, pool.address, MAX_UINT, [circleEoa]) - // await pool.deposit(TRANCHES.Junior, remaining, {from: circleEoa}) - // await impersonateAccount(hre, borrowerEoa) - // await borrower.lockJuniorCapital(pool.address, {from: borrowerEoa}) - // await seniorPool.invest(pool.address) - - // const backerRewardsBeforeDrawdownAndInitialization = await getBackerRewardsForToken(tokenId) - // expect(backerRewardsBeforeDrawdownAndInitialization).to.bignumber.eq("0") - - // const amountToDrawdown = limit - // await erc20Approve(usdc, stakingRewards.address, backerDepositAmount, [circleEoa]) - // // const stakeTx = await (await stakingRewardsEthers.depositAndStake(backerDepositAmount.toString())).wait() - // const juniorPortion = await pool.totalJuniorDeposits() - // const [stakeTx] = await mineInSameBlock( - // [ - // // create an equivalent staking rewards position so we can verify that the staking rewards are being accumulated - // // correctly - // await stakingRewardsEthers - // .connect(await ethers.getSigner(circleEoa)) - // .populateTransaction.depositAndStake(backerDepositAmount.toString()), - // await borrowerEthers.populateTransaction.drawdown(pool.address, amountToDrawdown.toString(), borrowerEoa, { - // from: borrowerEoa, - // }), - // ], - // this.timeout() - // ) - // const drawdownBlockNumber = stakeTx?.blockNumber - // const stakingTokenId = getStakingRewardTokenFromTransactionReceipt(stakeTx as ContractReceipt) - // const backerRewardsAfterDrawdownBeforeInitialization = await getBackerRewardsForToken(tokenId) - // expect(backerRewardsAfterDrawdownBeforeInitialization).to.bignumber.eq("0") - - // await advanceTime({days: "30"}) - // await pool.assess() - - // const backerRewardsAfter30DaysBeforeInitialization = await getBackerRewardsForToken(tokenId) - // expect(backerRewardsAfter30DaysBeforeInitialization).to.bignumber.eq("0") - - // const interestOwed = await creditLine.interestOwed() - // await borrower.pay(pool.address, interestOwed, {from: borrowerEoa}) - // const backerRewardsAfter30DaysAndPaymentBeforeInitialization = await getBackerRewardsForToken(tokenId) - // expect(backerRewardsAfter30DaysAndPaymentBeforeInitialization).to.bignumber.eq("0") - - // const stakingRewardsAccumulatorAtDrawdown = await stakingRewardsEthers.accumulatedRewardsPerToken({ - // blockTag: drawdownBlockNumber, - // }) - // const juniorPrincipalDeployedAtDrawdown = juniorPortion - // const sharePriceAtDrawdown = await seniorPoolEthers.sharePrice({blockTag: drawdownBlockNumber}) - - // await backerRewards.forceIntializeStakingRewardsPoolInfo( - // pool.address, - // sharePriceAtDrawdown.toString(), - // juniorPrincipalDeployedAtDrawdown, - // stakingRewardsAccumulatorAtDrawdown.toString() - // ) - - // await advanceTime({days: 30}) - // await pool.assess() - // const secondInterestPayment = await creditLine.interestOwed() - // const secondPaymentTx = await borrower.pay(pool.address, secondInterestPayment, {from: borrowerEoa}) - - // const stakingRewardsAfterPaymentAfterInitialization = await getStakingRewardsForToken( - // stakingTokenId, - // secondPaymentTx.receipt.blockNumber - // ) - // const backerRewardsAfterPaymentAfterInitialization = await getBackerRewardsForToken( - // tokenId, - // secondPaymentTx.receipt.blockNumber - // ) - // expect(backerRewardsAfterPaymentAfterInitialization).to.bignumber.gt(new BN(0)) - // expect(stakingRewardsAfterPaymentAfterInitialization).to.bignumber.gt(new BN(0)) - // expect(stakingRewardsAfterPaymentAfterInitialization).to.bignumber.eq( - // backerRewardsAfterPaymentAfterInitialization - // ) - // }) - // }) - describe("after drawdown but before the first payment", () => { beforeEach(async () => { const limit = await creditLine.maxLimit() @@ -1268,9 +1124,18 @@ describe("mainnet forking tests", async function () { }) }) - // NOTE: Skipping these tests for now because they depend on the v2.6.0 upgrade. - // Main should be passing after only running the v2.5.0 upgrade - describe.skip("when I deposit and stake, and then zap to Curve", async () => { + describe("when I deposit and stake and then exit", async () => { + it("it works", async () => { + const depositAmount = usdcVal(10_000) + const tx = await expect(stakingRewards.depositAndStake(depositAmount, {from: goListedUser})).to.be.fulfilled + const logs = decodeLogs(tx.receipt.rawLogs, stakingRewards, "Staked") + const stakedEvent = asNonNullable(logs[0]) + const tokenId = stakedEvent?.args.tokenId + await expect(stakingRewards.unstake(tokenId, depositAmount, {from: goListedUser})).to.be.fulfilled + }) + }) + + describe("when I deposit and stake, and then zap to Curve", async () => { beforeEach(async () => { await erc20Approve(usdc, seniorPool.address, MAX_UINT, [owner]) await erc20Approve(usdc, stakingRewards.address, MAX_UINT, [goListedUser, owner]) @@ -1711,10 +1576,6 @@ describe("mainnet forking tests", async function () { await usdc.approve(stakingRewards.address, amount, {from: owner}) const receipt = await stakingRewards.depositAndStake(amount, {from: owner}) - - // NOTE: for the v2.5.0 deployment we need to use the existing staking - // rewards deployment because the code differs from what's actually - // deployed. If we used the contract it would use the updated signature const stakedEvent = getFirstLog(decodeLogs(receipt.receipt.rawLogs, stakingRewards, "Staked")) const tokenId = stakedEvent.args.tokenId const depositedAndStakedEvent = getFirstLog( diff --git a/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.5.0/migrate.test.ts b/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.5.0/migrate.test.ts index 32c5a21b3..a9977ad28 100644 --- a/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.5.0/migrate.test.ts +++ b/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.5.0/migrate.test.ts @@ -80,7 +80,7 @@ const almaPool6Info = { }, } -describe("v2.5.0", async function () { +describe.skip("v2.5.0", async function () { this.timeout(TEST_TIMEOUT) let backerRewards: BackerRewardsInstance diff --git a/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.6.0/migrate.test.ts b/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.6.0/migrate.test.ts index 357c86b8c..7d9353b1c 100644 --- a/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.6.0/migrate.test.ts +++ b/packages/protocol/test/mainnet_forking/blockchain_scripts/migrations/v2.6.0/migrate.test.ts @@ -1,15 +1,17 @@ import hre, {deployments, getNamedAccounts} from "hardhat" -import {asNonNullable, assertIsString} from "packages/utils/src/type" +import {asNonNullable, assertIsString, assertNonNullable} from "packages/utils/src/type" import { + getEthersContract, getProtocolOwner, getTruffleContract, getUSDCAddress, MAINNET_CHAIN_ID, MAINNET_FIDU_USDC_CURVE_LP_ADDRESS, + OWNER_ROLE, + TRANCHES, } from "packages/protocol/blockchain_scripts/deployHelpers" import {fundWithWhales} from "@goldfinch-eng/protocol/blockchain_scripts/helpers/fundWithWhales" -import * as migrate250 from "@goldfinch-eng/protocol/blockchain_scripts/migrations/v2.5.0/migrate" import * as migrate260 from "@goldfinch-eng/protocol/blockchain_scripts/migrations/v2.6.0/migrate" import {TEST_TIMEOUT} from "../../../MainnetForking.test" import {impersonateAccount} from "@goldfinch-eng/protocol/blockchain_scripts/helpers/impersonateAccount" @@ -22,6 +24,7 @@ import { GoInstance, GoldfinchConfigInstance, GoldfinchFactoryInstance, + PoolTokensInstance, SeniorPoolInstance, StakingRewardsInstance, TranchedPoolInstance, @@ -30,6 +33,8 @@ import { } from "@goldfinch-eng/protocol/typechain/truffle" import {CONFIG_KEYS} from "@goldfinch-eng/protocol/blockchain_scripts/configKeys" import { + advanceTime, + BN, createPoolWithCreditLine, expectOwnerRole, expectProxyOwner, @@ -38,6 +43,7 @@ import { import {StakedPositionType} from "@goldfinch-eng/protocol/blockchain_scripts/deployHelpers" import {Contract} from "ethers/lib/ethers" import {Migration260Params} from "@goldfinch-eng/protocol/blockchain_scripts/migrations/v2.6.0/migrate" +import {Borrower, CreditLine, SeniorPool, StakingRewards, TranchedPool} from "@goldfinch-eng/protocol/typechain/ethers" const setupTest = deployments.createFixture(async () => { await deployments.fixture("base_deploy", {keepExistingDeployments: true}) @@ -51,6 +57,8 @@ const setupTest = deployments.createFixture(async () => { const stakingRewards = await getTruffleContract("StakingRewards") const uniqueIdentity = await getTruffleContract("UniqueIdentity") const goldfinchFactory = await getTruffleContract("GoldfinchFactory") + const usdc = await getTruffleContract("ERC20", {at: getUSDCAddress(MAINNET_CHAIN_ID)}) + const poolTokens = await getTruffleContract("PoolTokens") const fixedLeverageRatioStrategy = await getTruffleContract( "FixedLeverageRatioStrategy" ) @@ -67,8 +75,10 @@ const setupTest = deployments.createFixture(async () => { communityRewards, backerRewards, seniorPool, + poolTokens, stakingRewards, go, + usdc, uniqueIdentity, goldfinchFactory, fixedLeverageRatioStrategy, @@ -84,35 +94,36 @@ describe("v2.6.0", async function () { let seniorPool: SeniorPoolInstance let go: GoInstance let stakingRewards: StakingRewardsInstance - let uniqueIdentity: UniqueIdentityInstance - let goldfinchFactory: GoldfinchFactoryInstance + let usdc: ERC20Instance + let poolTokens: PoolTokensInstance let tranchedPoolImplAddressBeforeDeploy: string let leverageRatioStrategyAddressBeforeDeploy: string + let goldfinchFactory: GoldfinchFactoryInstance beforeEach(async () => { // eslint-disable-next-line @typescript-eslint/no-extra-semi - ;({gfi, goldfinchConfig, backerRewards, seniorPool, go, stakingRewards, uniqueIdentity, goldfinchFactory} = + ;({gfi, goldfinchConfig, poolTokens, usdc, backerRewards, seniorPool, go, stakingRewards, goldfinchFactory} = await setupTest()) tranchedPoolImplAddressBeforeDeploy = await goldfinchConfig.getAddress(CONFIG_KEYS.TranchedPoolImplementation) leverageRatioStrategyAddressBeforeDeploy = await goldfinchConfig.getAddress(CONFIG_KEYS.LeverageRatio) }) + const setupAfterDeploy = deployments.createFixture(async () => { + const {params, deployedContracts} = await migrate260.main() + const zapper = await getTruffleContract("Zapper", {at: deployedContracts.zapper.address}) + const fixedLeverageRatioStrategy = await getTruffleContract( + "FixedLeverageRatioStrategy" + ) + return {zapper, fixedLeverageRatioStrategy, params, deployedContracts} + }) + describe("after deploy", async () => { let params: Migration260Params let zapper: ZapperInstance let fixedLeverageRatioStrategy: FixedLeverageRatioStrategyInstance let tranchedPoolDeployment: Contract - const setupTest = deployments.createFixture(async () => { - await migrate250.main() - const {params, deployedContracts} = await migrate260.main() - const zapper = await getTruffleContract("Zapper") - const fixedLeverageRatioStrategy = await getTruffleContract( - "FixedLeverageRatioStrategy" - ) - return {zapper, fixedLeverageRatioStrategy, params, deployedContracts} - }) beforeEach(async () => { // eslint-disable-next-line @typescript-eslint/no-extra-semi @@ -121,15 +132,7 @@ describe("v2.6.0", async function () { zapper, fixedLeverageRatioStrategy, deployedContracts: {tranchedPool: tranchedPoolDeployment}, - } = await setupTest()) - }) - - describe("UniqueIdentity", async () => { - describe("supportedUIDType", async () => { - mochaEach([0, 1, 2, 3, 4]).it("is true for type = %d", async (type: number) => { - expect(await uniqueIdentity.supportedUIDTypes(type)).to.equal(true) - }) - }) + } = await setupAfterDeploy()) }) describe("GoldfinchConfig", async () => { @@ -198,19 +201,149 @@ describe("v2.6.0", async function () { }) describe("BackerRewards", async () => { - describe("maxInterestDollarsElligible", async () => { - it("is correct", async () => { - expect(await backerRewards.maxInterestDollarsEligible()).to.bignumber.eq( - params.BackerRewards.maxInterestDollarsEligible - ) - }) + const setupPoolTest = deployments.createFixture(async (hre, options?: {address: string}) => { + assertNonNullable(options) + const {address} = options + let tranchedPool = await getEthersContract("TranchedPool", {at: address}) + const creditLine = await getEthersContract("CreditLine", {at: await tranchedPool.creditLine()}) + let borrowerContract = await getEthersContract("Borrower", {at: await creditLine.borrower()}) + const borrowerEoa = await borrowerContract.getRoleMember(OWNER_ROLE, 0) + const ethersSeniorPool = await getEthersContract("SeniorPool") + const ethersStakingRewards = await getEthersContract("StakingRewards") + await impersonateAccount(hre, borrowerEoa) + await fundWithWhales(["ETH", "USDC"], [borrowerEoa]) + const borrowerSigner = await hre.ethers.provider.getSigner(borrowerEoa) + tranchedPool = tranchedPool.connect(borrowerSigner) + borrowerContract = borrowerContract.connect(borrowerSigner) + + return {tranchedPool, creditLine, borrowerContract, ethersSeniorPool, ethersStakingRewards, borrowerEoa} }) - describe("totalRewardPercentOfTotalGFI", async () => { - it("is correct", async () => { - // This function returns percentage points as the base unit. meaning that 1e18 = 1 percent - const two = "2000000000000000000" - expect((await backerRewards.totalRewardPercentOfTotalGFI()).toString()).to.eq(two) + mochaEach(migrate260.BACKER_REWARDS_PARAMS_POOL_ADDRS).describe("pool at '%s'", (address) => { + let tranchedPool: TranchedPool + let creditLine: CreditLine + let borrowerContract: Borrower + let borrowerEoa: string + let backerTokenIds: string[] + let ethersSeniorPool: SeniorPool + let ethersStakingRewards: StakingRewards + const getBackerTokenIds = async (tranchedPool: TranchedPool): Promise => { + const events = await tranchedPool.queryFilter(tranchedPool.filters.DepositMade(undefined, TRANCHES.Junior)) + return events.map((x) => x.args.tokenId.toString()) + } + + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;({tranchedPool, creditLine, borrowerContract, ethersSeniorPool, ethersStakingRewards, borrowerEoa} = + await setupPoolTest({address})) + backerTokenIds = await getBackerTokenIds(tranchedPool) + }) + + describe("before first repayment", async () => { + it("backers should accrue no staking rewards", async () => { + const stakingRewardsEarned = await Promise.all( + backerTokenIds.map(async (tokenId) => backerRewards.stakingRewardsEarnedSinceLastWithdraw(tokenId)) + ) + expect(stakingRewardsEarned.every((x) => x.toString() === "0")) + }) + }) + + describe("after first repayment", async () => { + let repaymentBlockNumber: number + + const setupTest = deployments.createFixture(async () => { + const dueTime = await creditLine.nextDueTime() + await advanceTime({toSecond: dueTime.toString()}) + await tranchedPool.assess() + const interestOwed = await creditLine.interestOwed() + if (interestOwed.isZero()) { + throw new Error("Expected interest owed > 0.") + } + await usdc.approve(borrowerContract.address, interestOwed.toString(), {from: borrowerEoa}) + await fundWithWhales(["USDC"], [borrowerEoa]) + const tx = await borrowerContract.pay(tranchedPool.address, interestOwed) + const receipt = await tx.wait() + return {repaymentBlockNumber: receipt.blockNumber} + }) + + beforeEach(async () => { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;({repaymentBlockNumber} = await setupTest()) + }) + + const getLatestDrawdownBlockNumber = async (tranchedPool: TranchedPool): Promise => { + const drawdownEvents = await tranchedPool.queryFilter(tranchedPool.filters.DrawdownMade()) + + if (drawdownEvents.length === 0) { + throw new Error("No DrawdownMade events found!") + } + + const lastDrawdownBlockNumber = drawdownEvents.reduce((acc, x) => Math.max(acc, x.blockNumber), 0) + expect(lastDrawdownBlockNumber).to.be.gt(0) + return lastDrawdownBlockNumber + } + + it("backers should earn equivalent staking rewards as LPs", async () => { + const drawdownBlockNum = await getLatestDrawdownBlockNumber(tranchedPool) + const sharePriceAtDrawdown = await ethersSeniorPool.sharePrice({blockTag: drawdownBlockNum}) + const rewardsAccAtDrawdown = await ethersStakingRewards.accumulatedRewardsPerToken({ + blockTag: drawdownBlockNum, + }) + const rewardsAccAtRepayment = await ethersStakingRewards.accumulatedRewardsPerToken({ + blockTag: repaymentBlockNumber, + }) + const rewardsPerTokenSinceDrawdown = rewardsAccAtRepayment.sub(rewardsAccAtDrawdown) + + const trancheInfo = await tranchedPool.getTranche(TRANCHES.Junior, {blockTag: drawdownBlockNum}) + const [, principalDeposited, principalSharePrice] = trancheInfo + + assertNonNullable(principalDeposited) + assertNonNullable(principalSharePrice) + + // we need to know what proportion of the principal was drawdown + // to accurately calculate rewards + const principalDrawdownPercent = principalDeposited + .sub(principalSharePrice.mul(principalDeposited).div(String(1e18))) + .mul(String(1e6)) + .div(principalDeposited) + + const getExpectedRewards = (amount: BN) => { + const fiduDecimals = new BN(String(1e18)) + const usdcDecimals = new BN(String(1e6)) + + return amount + .mul(fiduDecimals) + .div(usdcDecimals) + .mul(fiduDecimals) + .div(new BN(sharePriceAtDrawdown.toString())) + .mul(new BN(rewardsPerTokenSinceDrawdown.toString())) + .div(fiduDecimals) + } + + const tokenIdsWithPrincipal = await Promise.all( + backerTokenIds.map(async (tokenId) => { + const [tokenInfo, stakingRewardsSinceLastWithdraw] = await Promise.all([ + poolTokens.getTokenInfo(tokenId), + backerRewards.stakingRewardsEarnedSinceLastWithdraw(tokenId), + ]) + + return { + tokenId, + principalAmount: tokenInfo.principalAmount.toString(), + stakingRewardsSinceLastWithdraw: stakingRewardsSinceLastWithdraw.toString(), + } + }) + ) + + for (const {principalAmount, stakingRewardsSinceLastWithdraw} of tokenIdsWithPrincipal) { + // adjust principal to the amount that the borrower actually drew down + const adjustedPrincipal = new BN(principalAmount) + .mul(new BN(principalDrawdownPercent.toString())) + .div(new BN(String(1e6))) + const expectedRewards = getExpectedRewards(adjustedPrincipal) + expect(stakingRewardsSinceLastWithdraw).to.bignumber.closeTo(expectedRewards, String(1e11)) + } + }) }) }) }) @@ -221,11 +354,6 @@ describe("v2.6.0", async function () { forContracts: ["Go"], }) - expectOwnerRole({ - toBe: async () => getProtocolOwner(), - forContracts: ["Go"], - }) - describe("hasRole", async () => { describe("ZAPPER_ROLE", async () => { it("Zapper to be true", async () => { @@ -300,10 +428,11 @@ describe("v2.6.0", async function () { describe("effectiveMultiplier", async () => { describe("CurveLp", async () => { it("is correct", async () => { - expect(params.StakingRewards.effectiveMultiplier).to.eq("750000000000000000") + expect(params.StakingRewards.curveEffectiveMultiplier).to.eq("750000000000000000") expect( - (await stakingRewards.getEffectiveMultiplierForPositionType(StakedPositionType.CurveLP)).toString() - ).to.eq(params.StakingRewards.effectiveMultiplier) + (await stakingRewards.getEffectiveMultiplierForPositionType(StakedPositionType.CurveLP)).toString(), + params.StakingRewards.curveEffectiveMultiplier + ) }) }) })